Tuesday, October 19, 2021

I2C and TWI (Two Wire Interface) library for AVR microcontrollers

In the last article I talked about How I2C and TWI protocol works and we saw that they are mostly the same so this library works for both I2C and TWI serial interfaces. You don't have to know every detail about how the I2C protocol works but I strongly recommend reading the article to have a general idea about it, and that way it will be easier to use this library.


Using the I2C, TWI library with an AVR microcontroller

Setting the library file

As always, first include the library file:

#include "twi.h"

Most AVR microcontrollers have two TWI modules TWI0 and TWI1 so to choose between the two there is the following line of code:

#define TWI_MODULE_NUMBER	0 // TWI module 0 or 1

The default module is TWI0.

Functions


void TWI_Init(uint32_t frequency)

Used to initialize the TWI module. This will set the TWI bit rate and enable global  interrupts. 400kHz is the maximum TWI speed that regular AVR microcontrollers supports although I have managed to talk to a DAC at 577kHz.

frequency:  can be one of the following constants or any value between 100-400kHz.

#define TWI_400KHZ	400000 // Hz
#define TWI_100KHZ	100000 // Hz

void TWI_StartTransmission(void)

This function sends the START command to begin the transmission and so putting the microcontroller in a Master mode.

 

void TWI_ContactDevice(address, rw, nr_of_bytes_to_read)

Sends the SLA+RW packet.

uint8_t address -  A 7-bit address of the device to communicate with. The first 7 MSB is the address and the first bit must be 0. For example say the 7-bit address is 0b1100001 then address argument would be 0b11000010.

uint8_t rw - Read or write mode. In read mode the TWI interrupt will be enabled and the received data  can be accessed using TWI_ReadByte().
Can be one of the following flags: TWI_READ_MODE, TWI_WRITE_MODE.

uint8_t nr_of_bytes_to_read - How many bytes to read before issuing a STOP condition inside the ISR. Without a STOP, the TWI will clock the SCL and the Slave device will keep re-transmitting the same bytes. In write mode this can be 0.


void TWI_TransmitByte(byte_data)

Transmit a single byte.

uint8_t byte_data - The byte to send.


void TWI_Transmit(const uint8_t *data)

Transmit a string of bytes.

uint8_t *data - Pointer to a string of bytes.


uint8_t TWI_ReadByte(void)

When TWI_ContactDevice() is used in read mode, this function is used to return a received byte. If no byte is available it will return null. The received bytes will be stored by the TWI ISR in a circular buffer and each time this function is executed the next byte will be returned.


void TWI_StopTransmission(void)

Issue a STOP command to end the TWI transmission.


void TWI_Disable(void)

Disables the TWI. Any ongoing transmissions will be stopped immediately. Using TWI_StartTransmission() will re-enable the TWI module.


void TWI_ResetTWIInterface(void)

Resets the TWI interface by sending a START, 9 of 1's another START and a STOP, in case an error appeared on the bus. The explanation for this is a bit complex and can be found is some data sheets for example the data sheet for MCP4706 DAC page 70.

For this function to work, the USE_INTERFACE_RESET define must be set to 1 and then below it set the port and pins that correspond to the TWI module (0 or 1).


Error flag and status codes

After a function is executed, if the returned status code from the TWI module is not ACK or NACK, the TWI_ERROR_FLAG flag will be set to 1 indicating an error. The flag will be cleared by the same functions that can set it on the next run. It's up to the software designer to decide what actions to take in case of a transmission error.

The same functions will also save the status codes from the TWI module, in the TWI_STATUS_CODE variable. A list with TWI status codes and what they represent can be found inside the "twi.h" file in the status codes section.


Code example - Transmit data to an I2C or TWI device:

#include "twi.h"

int main(void){
	uint8_t data_byte;

	TWI_Init();
	
	// Start transmission. This enables the Master mode.
	TWI_StartTransmission();
	
	if(TWI_ERROR_FLAG){
		// Code if the TWI_StartTransmission() had an error
	}
	
	// Select the device using it's address and set the write mode
	TWI_ContactDevice(0b11000010, TWI_WRITE_MODE, 0);
	
	if(TWI_ERROR_FLAG){
		// Code if the TWI_ContactDevice() had an error
	}
	
	// Transmit a byte
	TWI_TransmitByte(data_byte);
	
	if(TWI_ERROR_FLAG){
		// Code if the TWI_TransmitByte() had an error
	}
	
	// ... the transmit function can be used again to transmit 
	// as many bytes is necessary
	
	// Stop the transmission
	TWI_StopTransmission();
	
	while(1){
	
	}
}


Code example - Read data from an I2C or TWI device

#include "twi.h"

int main(void){
	uint8_t i = 0;
	uint8_t received_byte = 0;

	TWI_Init();
	
	// Start transmission. This enables the Master mode.
	TWI_StartTransmission();
	
	if(TWI_ERROR_FLAG){
		// Code if the TWI_StartTransmission() had an error
	}
	
	// Select the device by address with a read command.
	// 4 in this example is the number of bytes it is expected
	// from the Slave device.
	TWI_ContactDevice(0b11000010, TWI_READ_MODE, 4);
	
	if(TWI_ERROR_FLAG){
		// Code if the TWI_ContactDevice() had an error
	}
	
	// Read 4 bytes
	for(i=0; i < 4; i++){
		// The received byte can be processed or saved in an array
		received_byte = TWI_ReadByte();
		
		// Check if the received byte is not null
		if(received_byte){
			// Do something with it
			
		}else{
			// The byte is null. Here the error flag 
			// could be checked or the status codes.
		}
	}
	
	// The STOP command will be issued inside the ISR after all 4 bytes
	// have been received or an error occured
	
	
	while(1){
	
	}
}


Library download

twi.h v1.1

No comments:

Post a Comment