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

Post by everest »

Don,

UNCLE! This is waay to much for me to bite off. . .hopefully when you get your sensor (which I think you said arrives today) you can make sense of it. I think I have to accept the fact that this is over my head. The main problem is that the MLX90614 is SMBus, and not I2C specifically. I think it's doing some funny things that are making the Zbasic I2C functions choke.

Anyways, that's my theory! Hopefully you can shed some light on the situation. Your insights and support are MUCH appreciated. You've personally made it possible for me to take on the challenge of learning Zbasic. . .THANKS!

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

Post by dkinzer »

everest wrote:hopefully when you get your sensor (which I think you said arrives today) you can make sense of it.
The code below produces sensible results using either the low-level I2C routines or the high-level routine. With this as a starting point it should be fairly easy to extend the code to write the EEPROM value to configure it for PWM mode. If this is done, the code below will need to be modified to pulse SCL low for 1.5mS to get it into I2C mode.

Code: Select all

'
'' GetTemp_MLX90614
'
' This function reads the temperature value of object #1 from the
' MLX90614 and converts it to degrees Centigrade.
'
Function GetTemp_MLX90614() as Single
    ' uncomment this line to use low-level I2C routines
'#define LOW_LEVEL_I2C

    ' define some constants
    Const chan as Byte = 0
    Const slaveAddr as Byte = Shl(&H5a, 1)  ' factory default I2C address
    Const Obj1TempAddr as Byte = 7 ' the RAM address of the object #1 temp
    Const BaseTempVal as UnsignedInteger = &H27ad  ' the reading for -70.01*C
    Const BaseTemp as Single = -70.01
    Const TempRes as Single = 0.02  ' the resolution in *C
    Dim data(1 to 10) as Byte

    ' open the I2C channel for operation
    Call OpenI2C(chan, 0, 0)

#if defined(LOW_LEVEL_I2C)
    ' use low-level I2C commands
    Dim stat as Boolean
    Call I2CStart(chan)
    stat = I2CPutByte(chan, slaveAddr And &Hfe)
    stat = I2CPutByte(chan, Obj1TempAddr)
    Call I2CStart(chan)
    stat = I2CPutByte(chan, slaveAddr Or &H01)
    data(1) = I2CGetByte(chan, True)  ' low byte
    data(2) = I2CGetByte(chan, True)  ' high byte + error bit (MSB)
    data(3) = I2CGetByte(chan, False) ' CRC-8
    Call I2CStop(chan)
#else
    ' use high-level I2C command
    Dim cmd as Byte
    cmd = Obj1TempAddr
    Dim result as Integer
    ' read the low byte, high byte + error bit (MSB), and CRC-8
    result = I2CCmd(chan, slaveAddr, 1, cmd, 3, data)
#endif
    Call CloseI2C(chan)

#if defined(DEBUG_I2C)
    Dim i as Integer
    For i = 1 to 3
        Debug.Print CStrHex(data(i))
    Next i
#endif

    ' convert the result to degrees Centigrade
    Dim tempVal as UnsignedInteger
    tempVal = MakeWord(data(1), data(2) And &H7f)
    GetTemp_MLX90614 = (CSng(tempVal - BaseTempVal) * TempRes) + BaseTemp
End Function
- Don Kinzer
everest
Posts: 96
Joined: 31 May 2010, 9:01 AM

Post by everest »

Hey Don,

Thanks a million for this. . .you are amazing!

I have a couple of questions that are probably trivial, but I can't seem to answer them digging through the system reference. I've seen you use syntax like this several times:

Code: Select all

stat = I2CPutByte(chan, slaveAddr And &Hfe)
What does the AND do? It looks like you are combining those two values into one argument, but I don't understand what exactly is going on there.

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

Post by dkinzer »

everest wrote:What does the AND do?
The And operator in ZBasic is either a bitwise logical operator or a Boolean logic operator depending on how it is used. (Some languages, like C and C++, use two different operators for these distinct operations.) In the example you gave, it is being used as a bitwise logical operator, essentially performing a Boolean And between each of the bits of the operands.

To review, the result of a Boolean And is true if and only if both operands are true. Consequently, if A has the binary value &B00111101 and B has the binary value &B11111110, the result of A And B is &B00111100. Each bit of the result is the And of the corresponding bits of the two operands.

As used in the I2C code, the And &Hfe ensures that the least significant bit of the result is zero and all the remaining bits retain their original values.

ZBasic has three other logical operators: Or, Xor and Not. For the first two, the idea is the same as described above but the result is the Boolean Or (true if either of the operands are true) or Boolean Exclusive-Or (true if either but not both of the operands are true). The Not operator is different in that it is a unary operator, i.e. it only takes one operand. The result of a Not operation is true if and only if the operand is false.

These are concepts that are very useful in programming and should thus be mastered.
- Don Kinzer
everest
Posts: 96
Joined: 31 May 2010, 9:01 AM

Post by everest »

Don,

Wow, thanks for the information. I had no idea one could use an AND that way, I use them in loops all the time, but never like that!!

I'm deeply. . .DEEPLY embarrassed to admit that all of my testing up to this point has been with my MLX90614 plugged in to P11 and P12 on my Parallax BOE, failing to account for the fact that 11, 12 are true pin references. I know this but for some reason just wired this up wrong. *smack*

Thanks again!

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

Post by dkinzer »

everest wrote:I had no idea one could use an AND that way
See the table of logical operators. Note that the permitted operand type for logical And is Boolean only (they way that you've been using it) but for the bitwise And the permitted operand type is any integral, the way that I used it.
- Don Kinzer
everest
Posts: 96
Joined: 31 May 2010, 9:01 AM

Post by everest »

Don,

Amazing! Thanks so much for helping me learn something new!! And thanks again for the help with the MLX90614 sensor, I can't tell you how much I appreciate it. That was the last piece in the puzzle for my application!!

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

Post by dkinzer »

The code below can be used to set the I2C slave address for an MLX90614. This is necessary if you want to use multiple MLX90614 devices connected on the same bus since they must each have a unique address.

Note, also, that this code contains a function to assist in the calculation of the CRC-8 value of an SMBus datastream. Because all of the bytes sent, including the slave address byte, contribute to the CRC-8 value, the low-level I2C routines are used. The I2CCmd() function will still work but it hides some of the data bytes sent over the bus that must be factored into the CRC value.

Code: Select all

'
' This program can be used to set the SMBus slave address of an MLX90614.
' Note, particularly, that the device being modified must be the only
' device on the bus since "all call" addressing is used.

Private Const chan as Byte = 0
Private Const slaveAddr as Byte = 0      ' slave address 0 is used
Private Const SlaveIdAddr as Byte = &H2e ' the EEPROM address of the I2C slave address

' set this constant to the desired address
Private Const newAddr as Byte = &H6a

Sub Main()
    Call OpenI2C(chan, 0, 0)
    Call Sleep(0.25)
    Dim curAddr as Byte
    curAddr = GetAddr_MLX90614()
    If (curAddr = newAddr) Then
        Debug.Print "The slave address is already set to &H"; CStrHex(newAddr)
    Else
        Debug.Print "The slave address is currently &H"; CStrHex(curAddr)
        ' set the address to zero first
        Call SetAddr_MLX90614(&H00)
        Call Sleep(0.5)
        ' then set to the desired address
        Call SetAddr_MLX90614(newAddr)
        Call Sleep(0.5)
        Debug.Print "The slave address is now set to &H"; CStrHex(newAddr)
        Debug.Print "Cycle the power to have the new address take effect"
    End If
    Call CloseI2C(chan)
End Sub

'
'' GetAddr_MLX90614
'
' Determine the current slave address setting.
'
Private Function GetAddr_MLX90614() as Byte
    Dim data(1 to 10) as Byte

    Dim stat as Boolean
    Call I2CStart(chan)
    stat = I2CPutByte(chan, slaveAddr And &Hfe)
    stat = I2CPutByte(chan, SlaveIdAddr)
    Call I2CStart(chan)
    stat = I2CPutByte(chan, slaveAddr Or &H01)
    data(1) = I2CGetByte(chan, True)  ' low byte
    data(2) = I2CGetByte(chan, True)  ' high byte (meaningless)
    data(3) = I2CGetByte(chan, False) ' CRC-8
    Call I2CStop(chan)
    GetAddr_MLX90614 = data(1)
End Function

'
'' SetAddr_MLX90614
'
' Set the slave address value.
'
Private Sub SetAddr_MLX90614(ByVal addr as Byte)
    Dim crc as Byte = 0
    Call I2CStart(chan)
    Call sendByte(chan, slaveAddr And &Hfe, crc)
    Call sendByte(chan, SlaveIdAddr, crc)
    Call sendByte(chan, addr, crc) ' low address byte
    Call sendByte(chan, 0, crc)    ' high address byte (meaningless)
    Call sendByte(chan, crc, crc)  ' the computed CRC-8 value
    Call I2CStop(chan)
End Sub

Private Sub sendByte(ByVal i2cChan as Byte, ByVal data as Byte, ByRef crc as Byte)
    Dim stat as Boolean
    stat = I2CPutByte(i2cChan, data)
    crc = CRC8(crc, data)
End Sub

'
'' CRC8
'
' Permute the CRC-8 accumulator with a given data byte.  The initial
' CRC-8 accumulator value should be zero.
'
Private Function CRC8(ByVal CRC as byte, ByVal data as byte) as byte
    CRC8 = crcTable((CRC xor data) + 1)
End Function

' Polynominal lookup table for CRC calculation
Private crcTable as ByteVectorData({ 
    &H00, &H07, &H0E, &H09, &H1C, &H1B, &H12, &H15, &H38, &H3F, &H36, &H31, &H24, &H23, &H2A, &H2D,
    &H70, &H77, &H7E, &H79, &H6C, &H6B, &H62, &H65, &H48, &H4F, &H46, &H41, &H54, &H53, &H5A, &H5D,
    &HE0, &HE7, &HEE, &HE9, &HFC, &HFB, &HF2, &HF5, &HD8, &HDF, &HD6, &HD1, &HC4, &HC3, &HCA, &HCD,
    &H90, &H97, &H9E, &H99, &H8C, &H8B, &H82, &H85, &HA8, &HAF, &HA6, &HA1, &HB4, &HB3, &HBA, &HBD,
    &HC7, &HC0, &HC9, &HCE, &HDB, &HDC, &HD5, &HD2, &HFF, &HF8, &HF1, &HF6, &HE3, &HE4, &HED, &HEA,
    &HB7, &HB0, &HB9, &HBE, &HAB, &HAC, &HA5, &HA2, &H8F, &H88, &H81, &H86, &H93, &H94, &H9D, &H9A,
    &H27, &H20, &H29, &H2E, &H3B, &H3C, &H35, &H32, &H1F, &H18, &H11, &H16, &H03, &H04, &H0D, &H0A,
    &H57, &H50, &H59, &H5E, &H4B, &H4C, &H45, &H42, &H6F, &H68, &H61, &H66, &H73, &H74, &H7D, &H7A,
    &H89, &H8E, &H87, &H80, &H95, &H92, &H9B, &H9C, &HB1, &HB6, &HBF, &HB8, &HAD, &HAA, &HA3, &HA4,
    &HF9, &HFE, &HF7, &HF0, &HE5, &HE2, &HEB, &HEC, &HC1, &HC6, &HCF, &HC8, &HDD, &HDA, &HD3, &HD4,
    &H69, &H6E, &H67, &H60, &H75, &H72, &H7B, &H7C, &H51, &H56, &H5F, &H58, &H4D, &H4A, &H43, &H44,
    &H19, &H1E, &H17, &H10, &H05, &H02, &H0B, &H0C, &H21, &H26, &H2F, &H28, &H3D, &H3A, &H33, &H34,
    &H4E, &H49, &H40, &H47, &H52, &H55, &H5C, &H5B, &H76, &H71, &H78, &H7F, &H6A, &H6D, &H64, &H63,
    &H3E, &H39, &H30, &H37, &H22, &H25, &H2C, &H2B, &H06, &H01, &H08, &H0F, &H1A, &H1D, &H14, &H13,
    &HAE, &HA9, &HA0, &HA7, &HB2, &HB5, &HBC, &HBB, &H96, &H91, &H98, &H9F, &H8A, &H8D, &H84, &H83,
    &HDE, &HD9, &HD0, &HD7, &HC2, &HC5, &HCC, &HCB, &HE6, &HE1, &HE8, &HEF, &HFA, &HFD, &HF4, &HF3
})
- Don Kinzer
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

The code that I originally posted had an error in the CRC lookup table. It has now been corrected.
- Don Kinzer
Post Reply