SMBus Sample Code

Discussion about the ZBasic language including the System Library. If you're not sure where to post your message, do it here. However, do not make test posts here; that's the purpose of the Sandbox.
everest
Posts: 96
Joined: 31 May 2010, 9:01 AM

SMBus Sample Code

Post by everest »

I'm toying around with an MLX90615 sensor that I managed to rip out of a cheap IR thermometer. I'm 95% sure I've got it wired up properly to my development board, and I have the SDA line going to Pin11 on and the SCL line going to Pin12 on my Z24 (along with 4.7k pullups).

So in effect I'm ready to try experimenting with SMBus communications with this device.

What I really need is some example of where someone has implemented SMBus communications with Zbasic. I can't tell if it's like a regular serial protocol, or what functions/subs I would use to send/receive bits/bytes.

Any examples/samples would be extremely helpful. Thanks!

-Jeff
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Re: SMBus Sample Code

Post by dkinzer »

everest wrote:I can't tell if it's like a regular serial protocol, or what functions/subs I would use to send/receive bits/bytes.
SMBus is derived from I2C and, I gather, is mostly compatible with I2C. I was planning to initially try the OpenI2C()/I2CCmd()/CloseI2C() commands to see what I could get it to do. If that doesn't work, I was planning to use the low-level I2C commands - I2CStart(), I2CPutByte(), I2CGetByte(), I2CStop() - instead of I2CCmd().

The documentation suggests that the MLX90615 should respond to the "all call" address of zero but I would probably use the documented slave address of &H5A. I suspect, however, that this is a 7-bit value so the 8-bit slave address would be &HB4, with the LSB being the read/write bit.

If you haven't used I2C before it would be a good idea to get the basic concepts. These links may help:
http://www.best-microcontroller-project ... orial.html
http://en.wikipedia.org/wiki/I2c
http://www.robot-electronics.co.uk/htm/ ... 2c_bus.htm
http://extremeelectronics.co.in/avr-tut ... c-and-spi/

There are several posts of example code for I2C devices in the Files section:
http://www.zbasic.net/forum/about56.html
http://www.zbasic.net/forum/about743.html
http://www.zbasic.net/forum/about1238.html
- Don Kinzer
everest
Posts: 96
Joined: 31 May 2010, 9:01 AM

Post by everest »

Thanks Don,

I'm actually putting together some simple I2CCmd statements now just to see if I can interrogate this MLX90615. I ripped apart a harbo freight IR thermometer and sure enough, it's got one in there, and it's very similar to the MLX90614 (just 3v and some pin differences).

This is very helpful information, thanks!

-Jefff
everest
Posts: 96
Joined: 31 May 2010, 9:01 AM

Post by everest »

Hey Don,

I'm looking at Page 12 of the MLX90615 datasheet (defines the SMBus protocol), which I believe is the same as the MLX90614 datasheet. . .I pretty much understand the basic protocol, but what's throwing me for a loop is that it LOOKS like the protocol wants me to transmit 9 bits as part of the "Read Word" command.

1********7*******1 - Doesn't this show a 9 bit package??
S - Slave Address - Wr * ACK

Would you think I'd need to write individual bits?? The I2CCmd looks like it can only talk in bytes. . .

Again, sorry for the deluge of questions and your insight and knowledge is truly appreciated.

-Jeff
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

everest wrote:1********7*******1 - Doesn't this show a 9 bit package??
S - Slave Address - Wr * ACK
No. The S represents the "Start Condition", Slave Address is the 7-bit slave address, Wr is the read/write bit, and ACK is the acknowledgement value returned by the slave. This may be more clear after studying the image below (excerpted from the first tutorial in my previous post).

everest wrote:The I2CCmd looks like it can only talk in bytes. . .
That is correct; even the low-level commands are byte-oriented (other than I2CStart() and I2CStop(), of course).

The "slaveID" parameter used in the I2CCmd() function must contain the 7-bit slave address in the most significant 7 bits; the LSB should be zero. Beyond that, the I2CCmd() function takes care of creating the start condition, sending the slave address and read/write bit, sending the additional data (if any). If data is to be read, it then creates the "repeated start" condition, sends the slave address with the READ bit and reads the bytes sent by the slave.
Attachments
I2C.jpg
I2C.jpg (36 KiB) Viewed 6001 times
- Don Kinzer
everest
Posts: 96
Joined: 31 May 2010, 9:01 AM

Post by everest »

I figured I could save some time and just see if I can even get this thing to respond to ANY byte value (it should to something I would think), so I wrote this program:

Code: Select all

Const SDA as Byte = 11
Const SCL as Byte = 12
Dim WriteByte as Byte = 0
Dim Success as Boolean

Sub Main()

Dim Count as Integer = 0

Do While Success = False

Call OpenI2C (0, SDA, SCL)
Call I2CStart (0)
Success = I2CPutByte(0, WriteByte)
Call I2CStop (0)
Call CloseI2C (0)

If (Success = True) Then
	Debug.Print "Received ACK with: " & WriteByte
	Exit Sub
Else
	Debug.Print "No ACK Received with: " & WriteByte
End If

WriteByte = WriteByte + 1
	
Loop

End Sub
As far as I can tell, this program SHOULD be sending pretty much every possible byte to this MLX90615 and just looking for any ACK coming back. The code runs, but never stops (it never receives an ACK). Any suggestions? Am I at least on the right track here?

-Jeff
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

everest wrote:Any suggestions?
Look at page 11 of the MLX90615 datasheet. It shows the procedure that one must use to put the device in SMBus mode if it is configured for PWM output mode. You should be able to generate this pulse thusly:

Code: Select all

Call PutPin(SCL, 0)
Call Sleep(0.50)
Call PutPin(SCL, zxInputTriState)
Also, if you're going to try to scan for the slave address like this you should start with 2 and increment by 2 on each pass, breaking out of the loop when you get back to zero.
- Don Kinzer
everest
Posts: 96
Joined: 31 May 2010, 9:01 AM

Post by everest »

Yea, I had tried something similar earlier to no avail. I tried both a full byte count and counting by 2 and I still get no ACKs back.

Maybe the MLX90614 doesn't like my voltage divider as a power supply? I had to step down the 5v to 3 so I just put together a 220 and 330 ohm voltage divider that puts out 2.99 volts according to my volt meter. I've got 4.7k pullups on the SDA and SCL pins. . .

I dunno what else to do. It doesn't seem to be responding to anything!

Here's the code as of now. Any other suggestions? I may pull another MLX90615 tomorrow and just send you that, I believe the protocols are similar enough that any solution would work with the MLX90614s I ordered.

Code: Select all

Const SDA as Byte = 11
Const SCL as Byte = 12
Dim WriteByte as Byte = 2
Dim Success as Boolean

Sub Main()

	Dim Count as Integer = 0

	Call PutPin(SCL, 0)
	Call Sleep(0.50)
	Call PutPin(SCL, zxInputTriState)

	Do While &#40; WriteByte <> 0 &#41;

		Call OpenI2C &#40;0, SDA, SCL&#41;
		Call I2CStart &#40;0&#41;
		Success = I2CPutByte&#40;0, WriteByte&#41;
		Call I2CStop &#40;0&#41;
		Call CloseI2C &#40;0&#41;

		If &#40;Success = True&#41; Then
			Debug.Print "Received ACK with&#58; " & WriteByte
			Exit Sub
		Else
			Debug.Print "No ACK Received with&#58; " & WriteByte
		End If

		WriteByte = WriteByte + 2
	
	Loop

End Sub
-Jeff
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

everest wrote:Maybe the MLX90614 doesn't like my voltage divider as a power supply?
A technique to keep in mind is to use small signal silicon diodes (or even larger rectifier diodes) to drop voltage. Three diodes like the 1N914 will give you about 1.6V of drop, yielding a 3.4V level - well within the device's maximum operating voltage.
- Don Kinzer
everest
Posts: 96
Joined: 31 May 2010, 9:01 AM

Post by everest »

Yea, good idea. . .that said, I don't think that's the problem here. I have a very well regulated 5v and I measured it again, and it's putting out a very nice 2.99v even with the MLX90615 connected.

I'm stumped. By my estimation this thing should respond with an ACK to at least one of those bytes and probably at least 2 of them.

I'll ship one out to you tomorrow and will send along the tracking number. If you can help with this that would be GREAT.

-Jeff
everest
Posts: 96
Joined: 31 May 2010, 9:01 AM

Post by everest »

My currently theory is that this little unit is NOT an MLX90615 as indicated in that other thread. . .my friend put his on a Oscilloscope and he's not seeing any digital I/O or even PWM output from this IR sensor when it's operating in the unit from Harbor Freight. That might explain why this IR thermometer is $9, whereas an MLX90615 is over $20 just for the module.

-Jeff
everest
Posts: 96
Joined: 31 May 2010, 9:01 AM

Post by everest »

Hey Don,

My MLX90614 arrived today. . .and I can't get this unit to do anything. In fact, the I2C routines seem to actually lock up the microcontroller. . .here's my code:

Code: Select all

Dim odata&#40;1 to 2&#41; as Byte, idata&#40;1 to 10&#41; as Byte
Dim ival as Integer
Dim PutByte as Boolean = False

Sub Main&#40;&#41;

Call PutPin&#40;12, 0&#41;
Call Delay&#40;0.25&#41;
Call PutPin&#40;12, zxInputTriState&#41;

Debug.Print "Opening I2C Channel"
Call OpenI2C &#40;0, 11, 12&#41;
odata&#40;1&#41; = &B00000001
odata&#40;2&#41; = &B00000011
Debug.Print "Writing Address Byte"
PutByte = i2cputbyte &#40;0, odata&#40;1&#41;&#41;
Debug.Print "Acknowledgement = " & PutByte
PutByte = i2cputbyte &#40;0, odata&#40;2&#41;&#41;
Debug.Print "Acknowledgement = " & PutByte
idata&#40;1&#41; = i2cgetbyte &#40;0, False&#41;
idata&#40;2&#41; = i2cgetbyte &#40;0, False&#41;
idata&#40;3&#41; = i2cgetbyte &#40;0, False&#41;
'ival = I2CCmd&#40;1, &H00, 1, odata&#40;2&#41;, 3, idata&#40;1&#41;&#41;
Debug.Print "First Received Byte = " & idata&#40;1&#41;
Debug.Print "Second Received Byte = " & idata&#40;2&#41;
Debug.Print "Third Received Byte = " & idata&#40;3&#41;
Debug.Print "Done"

End Sub
The programs stops at the first PutByte. . .it never progressed beyond that point. I've had similar problems with I2CStart. Any ideas?

-Jeff
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

everest wrote:The programs stops at the first PutByte. . .it never progressed beyond that point. I've had similar problems with I2CStart. Any ideas?
You must use I2CStart(), I2CPutByte(), I2CGetByte() and I2CStop() in the correct sequence in order for the I2C transactions to work. Moreover, there must be pullup resistors on the SDA and SCL lines. The I2CPutByte() command waits for the slave to acknowledge the data and if it doesn't occur the code will hang. You can replicate this behavior by calling I2CPutByte() with nothing connected to the SDA/SCL lines.
- Don Kinzer
everest
Posts: 96
Joined: 31 May 2010, 9:01 AM

Post by everest »

Hey Don,

Yea, sorry that wasn't a great example. . .I've tried quite a few difference things including this:

Code: Select all

Const SDA as Byte = 11
Const SCL as Byte = 12
Dim WriteByte1 as Byte = &B00000001
Dim WriteByte2 as Byte = &B00000011
Dim ReadByte&#40;3&#41; as Byte
Dim Success as Boolean = False

Sub Main&#40;&#41;

		'Call PutPin&#40;SCL, 0&#41;
		'Call Delay&#40;0.25&#41;
		'Call PutPin&#40;SCL, zxInputTriState&#41;

		'Debug.Print "Calling OpenI2c"
		'Call OpenI2C &#40;0, SDA, SCL&#41;
		Debug.Print "Calling I2CStart"
		Call I2CStart &#40;0&#41;
		Debug.Print "Calling I2CPutByte"
		Success = I2CPutByte&#40;0, WriteByte1&#41;
		Debug.Print "Success&#58; " & Success
		Success = I2CPutByte&#40;0, WriteByte2&#41;
		Debug.Print "Success&#58; " & Success
		ReadByte&#40;1&#41; = I2CGetByte&#40;0, Success&#41;
		Debug.Print "Success&#58; " & Success
		ReadByte&#40;2&#41; = I2CGetByte&#40;0, Success&#41;
		Debug.Print "Success&#58; " & Success
		ReadByte&#40;3&#41; = I2CGetByte&#40;0, Success&#41;
		Debug.Print "Success&#58; " & Success
		Call I2CStop &#40;0&#41;
		'Call CloseI2C &#40;0&#41;

		Debug.Print "Byte1 Received&#58; " & ReadByte&#40;1&#41;
		Debug.Print "Byte1 Received&#58; " & ReadByte&#40;2&#41;
		Debug.Print "Byte1 Received&#58; " & ReadByte&#40;3&#41;


End Sub
It also returns nothing either. . .I really went over the protocol carefully and I swear that &B0000001 (&H01) and &B00000011 (&H03) should return the ambient sensor data

-Jeff
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

everest wrote:I really went over the protocol carefully and I swear that &B0000001 (&H01) and &B00000011 (&H03) should return the ambient sensor dataf
Those commands may well do that but, again, you have to do things in a precise order and according to the I2C protocol. A valid low-level I2C sequence will look like this:

Code: Select all

Const chan as Byte = 0
Call OpenI2C &#40;chan, 0, 0&#41;

' this is the write phase &#40;skip if there is no data to write&#41;
Call I2CStart&#40;chan&#41;  ' this generates a "start condition"
Success = I2CPutByte&#40;chan, slaveAddr And &Hfe&#41; ' LSB must be zero
Success = I2CPutByte&#40;chan, cmd&#41;
<write other command/data bytes as necessary>

' this is the read phase &#40;skip if there is no data to be read&#41;
Call I2CStart&#40;chan&#41;  ' this generates a "repeated start condition"
Success = I2CPutByte&#40;chan, slaveAddr Or &H01&#41; ' LSB must be one
ReadByte&#40;1&#41; = I2CGetByte&#40;chan, True&#41;   ' ACK on all but the last byte
<read additional data bytes as necessary>
ReadByte&#40;N&#41; = I2CGetByte&#40;chan, False&#41;  ' NAK on last byte

' this is the termination phase
Call I2CStop&#40;chan&#41;  ' this generates a "stop condition"

Call CloseI2C&#40;chan&#41;
There are several things to note here.
  • On the call to OpenI2C() the second and third parameters are ignored if using channel zero but they must be valid pin numbers if using channel 1-4.
  • The slave address is generally set by the manufacturer although on some devices you can control part of the address using address pins on the device. The manufacturer may specify the slave address as a 7-bit value or as an 8-bit value. If it is given as a 7-bit value you'll have to multiply it by two to use it as slaveAddr. If it is given as an 8-bit value the LSB will always be zero and you can use it directly as slaveAddr.
  • For the write phase, the LSB of the second parameter on the first I2CPutByte() call following the I2CStart() call must be zero and for the read phase it must be 1.
  • Either the write phase or the read phase may be omitted but, of course, it makes no sense to omit both.
  • The second parameter on the I2CGetByte() calls essentially tells the slave whether or not to send more data. Consequently, if there is more than one I2CGetByte() call, it must be True for the first through the penultimate calls and it must always be False on the last call.
  • The sequence of calls from the first I2CStart() through the I2CStop() constitutes an I2C bus transaction. The code shown above represents a single transaction. You may have as many complete I2C transactions as you wish between the OpenI2C() and the CloseI2C().
In many/most cases, it is much simpler to just use I2CCmd() since it takes care of issuing the calls represented above.
- Don Kinzer
Post Reply