Ron Kreymborg
The LM75 is similar to a large range of peripheral devices now available that use the two-wire I2C serial protocol for input/output. It measures local temperature in degrees Celsius to within half a degree over the range from -25° to +100°. This example demonstrates both a generalised I2C interface and how this can be tailored for a specific device like the LM75.
The I2C protocol is not directly supported by the AVR chips, and must be implemented via bit banging. The avrgcc versions of sbi/cbi compile to their assembler equivelents and can be used to great effect in a protocol like I2C, as they provide direct bit level control with no C overhead. All the work in I2C is done on the clock transitions, with the only requirement being a minimum setup time of a few microseconds. In reality you could have a clock period of several seconds and I2C would still work, albeit very slowly of course. The point here is that the clock period can vary immensely with no effect, and so an I2C transaction can withstand multiple interruptions.
First a little discussion of the National LM75. It includes a silicon band-gap temperature sensor along with a 9-bit delta-sigma analog to digital converter and an I2C interface. The interface provides for setting temperature alarms and hystersis as well as reading the current temperature. I refer you to the LM75 data sheet for details of the required I2C transactions. In this example I will only deal with reading temperature, and the LM75 reliably starts up in this mode.
The I2C protocol uses two active connections and a ground return. The SCL or clock line is always driven by the master. The SDA or data line must be bi-directional. There are several ways this can be implemented on chips such as the '103. To begin with I will demonstrate the simplest. This requires three lines from the cpu, two configured as outputs and one as input. The input line I will call SDAin is connected directly to SDA. The output line is called SDAout and is connected to SDA via a diode. Because of the diode, the SDA line will need to be pulled high with an external resistor. The value will depend on the clock frequency, and need only be low enough to ensure the low to high transition (which because of the diode now has no active pullup) is fast enough. Unless you are pushing the limits a value of 3.3K should be ore than adequate. The SCL line is connected directly to the other output line. The idea is that when the master needs to drive the SDA line, it uses the SDAout line. When it needs to listen to the slave it leaves the SDAout line high and, now isolated by the diode, the slave can control the state of the SDAin line. This technique simplifies the code at the expense of an external diode. We will investigate the requirements for driving a true bi-directional SDA line later. The initialisation sequence is:
static void InitLM75(void)
{
sbi(DDR, SCL); // output
sbi(DDR, SDAout); // output
cbi(DDR, SDAin); // input
sbi(PORT, SCL); // clock high
cbi(PORT, SDAout); // data low
sbi(PORT, SDAin); // pullup on
}
The DDR and PORT variables are defined for the particular port you are using. Addressing the chip consists of sending a 0x90 byte ored with the 3-bit chip address and the read/write bit. Bits 7-4 are hard wired by National as 9, the chip address is in bits 3-1 and the read/write bit in bit 0. The read/write bit is low for a write.The address is set by how the three address pins on the chip are configured. Here I will assume they are all tied to ground to give an address of zero. The second byte selects the source/destination pointer for subseqent data transfers. The temperature register is address zero, so the two bytes to send to the chip are 0x90 and 0x00.
We can now look at the I2C requirements for sending these two bytes. The start sequence is always the same, the SDA line goes low followed by the SCL line, so the start function looks like:
static void Start(void)
{
cbi(PORT, SDAout);
cbi(PORT, SCL);
}
Of course with an AVR and a 4 Mhz clock there will be only 250 nSec between these instructions, whereas the LM75 spec requires a minimum SCL clock period of 2.5 uSec, or 1.25 uSec between transitions. In the download version of this program I interleave transitions with a Tick function that introduces a delay you can change for your application. If you are using a Mega103 or one of the processors with a cpu clock divider, you can specifically slow the clock while processing an I2C transaction. I will show an example of this later. But in the following discussions just pretend the cpu clock is around 500 Khz and thus a clock transition period of 2 uSec.
The stop sequence is similarly always the same, SDA is driven low, then the clock goes high followed by SDA going high, and this can also be implemented as a function.
static void Stop(void)
{
cbi(PORT, SDAout);
sbi(PORT, SCL);
sbi(PORT, SDAout);
}
Now it gets a little more complicated. The function to send a byte is:
static int SendByte(BYTE theByte)
{
register BYTE mask = 0x80;
register BYTE i = 8;
while (i--)
{
cbi(PORT, SCL); // clock low
if (theByte & mask) // is bit a 1?
sbi(PORT, SDAout); // ..yes
else
cbi(PORT, SDAout); // ..no
mask >>= 1;
sbi(PORT, SCL); // clock high
}
// The byte has been sent. Now manage the ack from slave
//
cbi(PORT, SCL); // clock low
sbi(PORT, SDAout); // data line free
sbi(PORT, SCL); // clock high
i = inp(PIN); // get ack
cbi(PORT, SCL); // clock low
return i & 1<<SDAin;
}
On entry both the clock and data lines are low. For the following eight bits, the respective data bit must be clocked into the slave by the rising edge of the clock. Thus the initial while loop starts by ensuing the clock is low, examines and then sets or clears the data bit, then takes the clock high. Once the eight bits have been sent the master must wait for the slave to acknowledge. To do this it must take the clock line low, and then tri-state the SDA line. Because of the diode mentioned earlier, we can do this by simply taking the SDAout line high. The SDAin line is then free to follow the SDA line as the slave takes over control. So we take the clock high, read the SDAin level, take the clock low again, and then exit with whatever the Ack level was. Note that the SDA and SCL exit levels are set for either sending additional bytes or sending a stop.
Receiving a byte is somewhat simpler as whether there is a master (us) Ack or not depends on the data stream and is not this function's responsibilty.
static BYTE GetByte(void)
{
register BYTE value = 0;
register BYTE i = 8;
while (i--)
{
sbi(PORT, SCL); // clock high
value <<= 1; // shift result left
if (inp(PIN) & (1<<SDAin))
value += 1; // insert a 1
cbi(PORT, SCL); // clock low
}
return value;
}
Bits arrive most significant bit first, so this function shifts the result left and adds in a 1 if the SDAin line is high.
That takes care of the basic IO requirements for I2C. Subsequent functions deal with using these tools to handle the LM75, the first of which is actually selecting the chip. This must be done regardless of whether it is the only device on the I2C bus or one of many.
Before the temperature can be read, the internal pointer register must be set to the temperature data register address. As discussed earlier, with the three address lines pulled low, the address byte for an LM75 is 0x90, and the internal pointer address for the temperature register is 0x00. We can therefore select the temperature register with a function to send the address:
static void SendAddress(void)
{
Start();
SendByte(0x90); // send address select
SendByte(0x00); // set pointer at 0
Stop();
}
This neatly demonstrates the use of our previously defined basic I2C functions. After sending this command the LM75 is ready to be read. This can be encapsulated in a function.
static void ReadTemperature(void)
{
int temperature;
// Send the get temperature command
//
Start();
SendByte(0x91); // read at current pointer command
// Read back the temperature
//
temperature = GetWord();
Stop();
}
This introduces the last function that manages the getting of two consecutive bytes from the LM75 for a 16-bit value reading. This is simply two GetBytes with an master Acknowledge in the middle, and a master Ack means keeping the SDA line low while supplying the usual clock.
static int GetWord(void)
{
WORD temperature;
sbi(PORT, SDAout); // ensure SDA high
temperature = (WORD)GetByte()<<8; // get MSB
cbi(PORT, SDAout); // SDA low for ack
sbi(PORT, SCL); // clock high
cbi(PORT, SCL); // clock low
sbi(PORT, SDAout); // SDA high again
temperature += GetByte(); // get LSB
sbi(PORT, SCL); // clock high
cbi(PORT, SCL); // clock low
cbi(PORT, SDAout); // SDA low for exit
return temperature>>7;
}
The final right shift moves the 9-bit result into the correct position.
Having described all the various tools, we can now put together a program that will read the LM75 temperature. To keep it simple I will display the temperature in hex on the stk300 leds. The main program is:
#define PORT PORTA // allow simply move to other port
#define DDR DDRA
#define PIN PINA
int main(void)
{
// PORTB
outp(0xff, PORTB); // all low
outp(0xff, DDRB); // all output
InitLM75(); // prepare the LM75
Delay();
// The following assumes a 4 Mhz cpu clock. It will slow this to 800 Khz.
//
outp(0xfc, XDIV); // slow the cpu clock
SendAddress(); // select pointer register zero (temperature)
outp(0x00, XDIV); // restore clock
while (1)
{
Delay(); // must be at least 100 mSec
outp(0xfc, XDIV); // slow the cpu clock
temp = ReadTemperature(); // get the current temperature reading
if (temp & 1) // round it up to an integer
temp += 2;
temp = temp>>1;
temp = ~temp; // display on stk300 leds
outp(temp, PORTB);
}
}
Note that the typical conversion time for an LM75 is 100 mSec. If a read instruction is received before the conversion is complete, the conversion will be aborted and started again after the read concludes. Thus if you send read requests faster than the conversion time you will never get a new reading. Note also that the two functions that call I2C rountines are bracketed by output instructions that change the divisor value in the '103 XDIV register. Bit 7 is the XDIVEN bit, and setting this will simultaneously write to the cpu clock divider the 7-bit value in bits 6-0. This value subtracted from 129 gives the crystal divider. In this example I load the XDIV register with 0cfc which, after taking out the XDIVEN bit, is 0x7c or 124. So the 4 Mhz crystal is divided by 129 - 124 to give a frequency while doing I2C stuff of 800 Khz.
However, if you are using a '103 it is likely a few other things are going on apart from the I2C transactions. The version you can download inserts a function call between every output instruction so you can set the I2C clock to whatever you want.
The following implements the same as above only now using a true 2-wire I2C interface. The SDA line is bit 1 of PORTA, and the SCL line is bit 3 of PORTA. The only difference is in the GetWord and SendByte functions. You can cut and paste this program directly into your favourite editor.
#include <stdlib.h>
#include <interrupt.h>
#include <signal.h>
#define BYTE unsigned char
#define WORD unsigned int
#define PORT PORTA
#define DDR DDRA
#define PIN PINA
#define SDA 1
#define SCL 3
int main(void);
static void InitLM75(void);
static void Start(void);
static void Stop(void);
static int SendByte(BYTE theByte);
static BYTE GetByte(void);
static void SendAddress(void);
static WORD ReadTemperature(void);
static int GetWord(void);
void Delay(void);
void Dummy(void);
int main(void)
{
WORD temp;
// PORTB
outp(0xff, PORTB); // all low
outp(0xff, DDRB); // all output
// PORTC
outp(0xff, PORTD); // all low
outp(0xff, DDRD); // all output
InitLM75();
Delay();
outp(0x81, XDIV); // clock now about 31 Khz
SendAddress();
outp(0x00, XDIV);
Delay();
while(1)
{
outp(0x81, XDIV); // clock now about 31 Khz
temp = ReadTemperature(); // get new temperature
outp(0x00, XDIV); // 4 Mhz clock again
if (temp & 1) // round up to an integer
temp += 2;
temp = temp>>1;
temp = ~temp; // display on stk300 leds
outp(temp, PORTB);
Delay(); // delay for over 100 mSec
}
return 0;
}
//-----------------------------------------------------------------------------
static void InitLM75(void)
{
sbi(DDR, SCL); // output
sbi(DDR, SDA); // initially output
sbi(PORT, SCL); // clock high
}
//-----------------------------------------------------------------------------
// Return the current 9-bit temperature reading. Bit 0 represents 0.5 degrees
// centigrade.
//
static WORD ReadTemperature(void)
{
WORD temperature;
// Send the get temperature command
//
Start();
SendByte(0x91); // read at current pointer command
// Read back the temperature
//
temperature = GetWord();
Stop();
return temperature;
}
//-----------------------------------------------------------------------------
// Select an LM75 and send the zero pointer register select.
//
static void SendAddress(void)
{
Start();
SendByte(0x90); // send address select
SendByte(0x00); // set pointer at 0
Stop();
}
//-----------------------------------------------------------------------------
// Returns a two byte word. Manage the master Ack between bytes.
//
static int GetWord(void)
{
WORD temperature;
cbi(DDR, SDA); // SDA now input
sbi(PORT, SDA); // with pullup
temperature = (WORD)GetByte()<<8; // get MSB
sbi(DDR, SDA); // SDA now output
cbi(PORT, SDA); // SDA low for ack
sbi(PORT, SCL); // clock high
cbi(PORT, SCL); // clock low
sbi(PORT, SDA); // SDA high again
cbi(DDR, SDA); // SDA now input
temperature += GetByte(); // get LSB
sbi(DDR, SDA); // SDA now output
sbi(PORT, SDA); // ensure high
sbi(PORT, SCL); // clock high
cbi(PORT, SCL); // clock low
cbi(PORT, SDA); // SDA low for exit
return temperature>>7;
}
//-----------------------------------------------------------------------------
// Send an I2C start sequence.
//
static void Start(void)
{
cbi(PORT, SDA);
cbi(PORT, SCL);
}
//-----------------------------------------------------------------------------
// Send an I2C stop sequence.
//
static void Stop(void)
{
cbi(PORT, SDA); // ensure SDA low
sbi(PORT, SCL); // SCL high
sbi(PORT, SDA); // SDA high
}
//-----------------------------------------------------------------------------
// Send a byte - returns the slave Ack level.
//
static int SendByte(BYTE theByte)
{
register BYTE mask = 0x80;
register BYTE i = 8;
while (i--)
{
cbi(PORT, SCL); // clock low
if (theByte & mask) // is bit a 1?
sbi(PORT, SDA); // ..yes
else
cbi(PORT, SDA); // ..no
mask >>= 1;
sbi(PORT, SCL); // clock high
}
// The byte has been sent. Now manage the ack from slave
//
cbi(DDR, SDA); // SDA now input
sbi(PORT, SDA); // with pullup
cbi(PORT, SCL); // clock low
sbi(PORT, SCL); // clock high
i = inp(PIN); // get ack
cbi(PORT, SCL); // clock low
// Exit with SDA an input
return i & 1<<SDA;
}
//-----------------------------------------------------------------------------
// Return a byte.
//
static BYTE GetByte(void)
{
register BYTE value = 0;
register BYTE i = 8;
while (i--)
{
sbi(PORT, SCL); // clock high
value <<= 1; // shift result left
if (inp(PIN) & (1<<SDA))
value += 1; // insert a 1
cbi(PORT, SCL); // clock low
}
return value;
}
//-----------------------------------------------------------------------------
// About a 140 mSec delay at 4 Mhz cpu clock.
//
void Delay(void)
{
int i, j, k;
for (j=0; j<3; j++)
for (i=0; i<20000; i++)
k = i;
}