Harri Sapto WIJAYA

22 March 2008

Build Your Own I2C Header File

Filed under: microcontroller, tutorial - Administrator @ 1:08 am

In order to keep my blog useful, I create the microcontroller category. So we* can share our knowledge and experience.emoticon

* : I means, myself and readers

Let’s go.

———-

I learn about I2C protocol when I did my academic project of Embedded System lecture, I built a digital phone book. It only device that capable to store and manage some data, that all. (Apologize I can’t include my device photo, imagine it as a device with keypad, LCD display, and some additional buttons).

 

I²C is a multi-master serial computer bus invented by Philips that is used to attach low-speed peripherals to a motherboard, embedded system, or cellphone. The name stands for Inter-Integrated Circuit and is pronounced I-squared-C and also, incorrectly, I-two-C. As of October 1, 2006, no licensing fees are required to implement the I²C protocol. However, fees are still required in order to obtain I²C slave addresses.[1] SMBus is a subset of I²C that defines stricter electrical and protocol conventions. One purpose of SMBus is to promote robustness and interoperability. Accordingly, modern I²C systems incorporate policies and rules from SMBus, and the line between these two standards is often blurred in practice. (ref.: wiki/I2c)

Hardware 

The I2C bus physically consists of 2 active wires and a ground connection. The active wires, called SDA and SCL, are both bi-directional. SDA is the Serial DAta line, and SCL is the Serial CLock line. Every device hooked up to the bus has its own unique address, no matter whether it is an MCU, LCD driver, memory, or ASIC. Each of these chips can act as a receiver and/or transmitter, depending on the functionality. Obviously, an LCD driver is only a receiver, while a memory or I/O chip can be both transmitter and receiver. (ref.: esacademy.com)

I2C bus [figure 1 I2C bus design]

If you wonder why the default state is in high logic, one of the reasons is to prevent voltage drop due to long wire. If the default state is low logic, when device transmit the high logic bit, it’s no guarantee that it will be kept high logic along wire.

Communication 

As you can see in Figure 2, the master begins the communication by issuing the start condition (S). The master continues by sending a unique 7-bit slave device address, with the most significant bit (MSB) first. The eighth bit after the start, read/not-write (), specifies whether the slave is now to receive (0) or to transmit (1). This is followed by an ACK bit issued by the receiver, acknowledging receipt of the previous byte. Then the transmitter (slave or master, as indicated by the bit) transmits a byte of data starting with the MSB. At the end of the byte, the receiver (whether master or slave) issues a new ACK bit. This 9-bit pattern is repeated if more bytes need to be transmitted. In a write transaction (slave receiving), when the master is done transmitting all of the data bytes it wants to send, it monitors the last ACK and then issues the stop condition (P). In a read transaction (slave transmitting), the master does not acknowledge the final byte it receives. This tells the slave that its transmission is done. The master then issues the stop condition. (ref.: embedded.com)

I2C protocol  [figure 2 Structur of I2C data protocol]

Let’s Code It 

After introduced how I2C bus is configured and how data protocol is arranged, let’s code it! In my implementation, I use AT89S52 microcontroller and SDCC as C compiler.

From above explanation, so we need to built at least 4 elements, i.e. START bit, ADDRESS byte, ACKNOWLEGDE bit, DATA byte (similar to ADDRESS byte), and STOP bit.

Preparation

Before go to code to produce i2c signal, let’s make some initializations. 

#define    SCL    P3_6
#define    SDA    P3_7
#define    I2C_DELAY    0x0F

That is we allocate two pin for SCL pin and SDA pin, and a delay constant. We will need a delay to tuning the speed, I just use looping method. Notice that another compiler use different way to access bit, e.g. Franklin use P1^0 instead of P1_0 to access bit-0 of port P1.

/* memberi delay pada sinyal I2C */
void i2c_delay(void)
{
    unsigned char i;
    for(i=0;i<I2C_DELAY;i++);
}

START and STOP bits

START bit [figure 3 START bit] STOP bit [figure 4 STOP bit]

Figure 3 show how to geneate a START bit, that is drop the SDA low while SCL is high, then drop the SCL low too. Figure 4 show how to generate a STOP bit, that is rise up SCL while SDA low then rise up SDA too.

So the code to generate such signal is like below.

/* START bit */ 

// SDA = H-to-L saat SCL = H, kemudian LOW-kan keduanya
void i2c_start(void)
{
    if(SCL) SCL = 0; // pastikan LOW
   
    // high-kan dulu keduanya
    SDA = 1;
    SCL = 1;
   
    i2c_delay();
    SDA = 0;
    i2c_delay();
    SCL = 0;
}

/* STOP bit */
// SDA = L-to-H saat SCL = H, kemudian HIGH-kan keduanya
void i2c_stop(void)
{
    if(SCL) SCL = 0;
   
    // low-kan dulu keduanya
    SDA = 0;
   
    i2c_delay();
    SCL = 1;
    i2c_delay();
    SDA = 1;
}

ADDRESS and DATA byte

Once START bit has been sent, then we ready to send ADDRESS byte. ADDRESS and DATA are similar, the different only in purpose, write mode or read mode, relative to slave. In write mode (slave write data into bus), the bit-0 of ADDRESS byte is set to ‘1′; in read mode (slave read data from bus), the bit-0 of ADDRESS byte is set to ‘0′.

So there will be two function, write and read. Figure 5 show how SCL signal in every bits in SDA.

I2C Data byte  [figure 5 Wave form in data transmission]

That is when try to write/red ‘1′ (or ‘0′) bit, rise up (or pull down) SDA while SCL is low, rise up SCL for a while depend on speed then fall it down. When SCL bit is low, SDA changing is allowed. So the code is like below.

/* menulis data I2C */
// kirim dat bit-per-bit, RETURN : bit ACK
bit i2c_write(unsigned char dat)
{
    bit data_bit;
    unsigned char i;
   
    for(i=0;i<8;i++)
    {
        data_bit = dat & 0x80; // ambil MSB
        SDA = data_bit; // kirim
        i2c_clock(); // beri clock pada SCL
        dat = dat<<1; // geser 1 bit ke kiri (arah MSB)
    }
   
    // high-kan kembali kedua wire
    SDA = 1;
    i2c_delay();
    SCL = 1;
    i2c_delay();
   
    data_bit = SDA; // baca sinyal ACK, 0 jika OK
    SCL = 0;
   
    i2c_delay();
   
    return data_bit; // return ACK, 0 jika valid
}

 /* membaca data I2C */
// membaca bit-per-bit, RETURN : byte data
unsigned char i2c_read(void)
{
    bit rd_bit;
    unsigned char i, dat;
    
    dat = 0x00;
    
    for(i=0;i<8;i++)
    {
        i2c_delay();
        SCL = 1; // high-kan, agar SCL = H-to-L
        i2c_delay();
        rd_bit = SDA; // ambil bit
        dat = dat<<1; // geser data
        dat = dat | rd_bit; // ubah bit yg bersesuaian
        SCL = 0; // baca bit selesai, SDA boleh berubah
    }
    
    return dat;    
}

Oops! I skip something, i.e. ACK. It’s ok, now on it’s turn.

ACKnowledge bit

When an address or data byte has been transmitted onto the bus then this must be ACKNOWLEDGED by the slave(s). In case of an address: If the address matches its own then that slave and only that slave will respond to the address with an ACK. In case of a byte transmitted to an already addressed slave then that slave will respond with an ACK as well. The slave that is going to give an ACK pulls the SDA line low immediately after reception of the 8th bit transmitted, or, in case of an address byte, immediately after evaluation of its address. In practical applications this will not be noticeable*. (ref.: esacademy.com)

* : In my project too ;)

Figure 6 show how ACK bit take an action.

ACK bit  [figure 6 ACK bit]

If you need, here below can generate such condition.

/* memberi sinyal ACKnowlegdment*/
// sinyal SDA L-to-H
void i2c_ack()
{
    SDA = 0;
    i2c_delay();
    i2c_clock; // tanda send data
    SDA = 1;
}

void i2c_noack()
{
    SDA = 1;
    i2c_delay();
    i2c_clock();
    SCL = 1;
}

 

Bliss Them 

That’s all! Put them all together in i2c.h for instance, then include it in your application. Here below is a chop of my code to accessing a byte of memory in EEPROM.

#include <i2c.h>
#define    EEPROM_ID    0xA0    // ID eeprom Atmel24C02 + address = 000

unsigned char eeprom_get(unsigned char addr)
{
    unsigned char dat;
   
    i2c_start();        // start bit, mulai I2C
    i2c_write(EEPROM_ID);    // alamati EEPROM AT24Cxx untuk Write
    i2c_write(addr);
   
    i2c_start();
    i2c_write(EEPROM_ID+1);    // alamati EEPROM untuk Read
    dat = i2c_read();    // ambil data
   
    i2c_noack();
    i2c_stop();        // stop i2c bus
   
    return dat;
}

 

Have a nice coding ;) !

15 February 2008

Basic of Microcontroller (part 1)

Filed under: microcontroller, tutorial - Administrator @ 2:49 pm

Microcontroller (or MCU, microcontroller unit), is a computer-on-a-chip [1]. A system could be called a computer if it has at least processor, memory, and input/output (I/O) device; lack one of the three, it never be a computer. Lack of processor, it can not do any logical or arithmetic in order to process the information. Lack of memory, so where the program would take a place. Lack of I/O device, it can’t interact with "outside world", and will be useless, can’t take any advantages from it.

… [will be updated later]

Get free blog up and running in minutes with Blogsome
Theme designed by Alex King