The last article was 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.
Contents
- Structure objects
- TWI initialization
- Set address
- Start transmission
- Send TWI address
- Transmit a byte
- Transmit a string of bytes
- Read byte
- Byte ready
- Stop transmission
- Set Slave mode
- Slave transmitter mode
- Disable TWI
- Read error flag
- Read status code
- Reset TWI interface
- Error flag and status codes
- Code example - Master Transmitter (MT) mode - Transmit data to an I2C or TWI device
- Code example - Master Receiver (MR) mode - Read data from an I2C or TWI device
- Slave Receiver (SR) mode
- Slave Transmitter (ST) mode
- Download
Two-wire serial Interface
The TWI protocol allows the systems designer to interconnect up to 128 individually addressable devices using only two bi-directional bus lines - one for clock (SCL) and one for data (SDA). The only external hardware required to implement the bus is a single pull-up resistor for each of the TWI bus lines. All devices connected to the bus have individual addresses, and mechanisms for resolving bus contention are inherent in the TWI protocol.
Both TWI lines (SDA and SCL) are bi-directional, therefore outputs connected to the TWI bus must be of an open-drain or an open-collector type. Each line must be connected to the supply voltage via a pull-up resistor. A line is then logic high when none of the connected devices drives the line, and logic low if one or more drives the line low.
The pull-up resistor is typically chosen between 1 kΩ and 10 kΩ ranges for Standard and Fast modes, and less than 1 kΩ for High-Speed mode. Note that the internal pull-ups in the AVR pads can be enabled by setting the PORT bits corresponding to the SCL and SDA pins, as explained in the I/O Port section. In some systems, the internal pull-ups can eliminate the need for external resistors.
API
Structure objects
Since some micro-controllers have two TWI modules, every function takes a pointer to a structure object as an argument. This way, both TWI modules can be used at the same time.
Example:
TWI_Init(&twi0, TWI_400KHZ)
Notice the ampersand used to pass the memory address as an argument.
There are two structure objects defined that can be used: twi0 and
twi1. By default only twi0 module is included. If you need the twi1
module, the TWI_ENABLE_TWI1 define must be set to 1 in the header
file.
TWI initialization
void TWI_Init(TWI_t* twi, uint32_t frequency)
Only for Master modes. 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 (TWI_100KHZ, TWI_400KHZ) or any value between 100-400kHz.
According to AVR311:
Slave operation does not depend on Bit Rate or Prescaler settings, but the CPU clock frequency in the Slave must be at least 16 times higher than the SCL frequency. The following table shows minimum CPU clock speeds for normal and high speed TWI transmission.
![]() |
AVR311 |
Set address
void TWI_SetAddress(TWI_t* twi, uint8_t addr, bool general_call)
Set device address. The address '0000 000' is reserved for a general call. The Address Match unit is able to compare addresses even when the AVR MCU is in sleep mode, enabling the MCU to wake up if addressed by a Master.
addr:
A 7 bit address starting from 1.
general_call:
If true, the first bit of TWAR register will be set to 1 and the TWI module will respond to the general call address 0x00.
Start transmission
void TWI_StartTransmission(TWI_t* twi)
This function sends the START command to begin the transmission and so putting the microcontroller in a Master mode. If an error occurs the TWI_CHECK_STATUS is set. The returned status code from TWI hardware is saved in the TWI_STATUS_CODE variable. After this function TWI_ContactDevice() should be used.
After a repeated START condition (status code 0x10), the 2-wire Serial Interface can access the same Slave again, or a new Slave without transmitting a STOP condition. Repeated START enables the Master to switch between Slaves, Master Transmitter mode and Master Receiver mode without losing control of the bus.
The START/STOP controller is able to detect START and STOP conditions even when the AVR MCU is in one of the sleep modes, enabling the MCU to wake up if addressed by a Master.
Send TWI address
void TWI_ContactDevice(TWI_t* twi, uint8_t address, uint8_t rw)
Sends the SLA+RW packet (7-bit address and read or write bit).
address:
The address of the device to communicate with.
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 constants: TWI_READ_MODE,
TWI_WRITE_MODE.
Transmit a byte
void TWI_TransmitByte(TWI_t* twi, uint8_t byte_data)
Transmit a single byte.
byte_data:
The byte to send.
Transmit a string of bytes
void TWI_Transmit(TWI_t* twi, const uint8_t *data)
Transmit a string of bytes. If the receiver sends a NACK the rest of the bytes, if any, will not be transmitted. If status code is not ACK the error flag will be set and rest of bytes will not be transmitted. The last array element must be a NULL.
data:
Pointer to a null terminated array.
Read byte
uint8_t TWI_ReadByte(TWI_t* twi)
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.
Byte ready
bool TWI_ByteReady(TWI_t* twi)
Returns true if new bytes are available. When this returns true, the read byte
function can be used.
Stop transmission
void TWI_StopTransmission(TWI_t* twi)
Only for Master modes. Issue a STOP command to end the TWI transmission.
Set Slave mode
void TWI_SlaveMode(TWI_t* twi)
Enable TWI, address acknowledgment and interrupt to receive data. The TWI waits until it is addressed by its own slave address (or the general call address if enabled) followed by the data direction bit. If the direction bit is 1 (read), the TWI will operate in ST mode, otherwise SR mode is entered. The ST mode may also be entered if arbitration is lost while the TWI is in the Master mode (see state 0xB0).
Slave transmitter mode
bool TWI_DataRequest(TWI_t* twi)
Returns true if a master asks for data using SLA+R. Set by interrupt on TWI_CODE_ST_SLA_ACK.
Disable TWI
void TWI_Disable(TWI_t* twi)
Disables the TWI. Any ongoing transmissions will be stopped immediately. Using
TWI_StartTransmission() will re-enable the TWI module.
Read error flag
uint8_t TWI_StatusNotACK(TWI_t* twi)
Returns the error flag [0:1] in case the TWI status code is other than ACK.
Read status code
uint8_t TWI_ReadStatusCode(TWI_t* twi)
Returns the last TWI status code. Can be used when the error flag is 1. The TWI status codes are defined in the header file.
Reset TWI interface
void TWI_ResetTWIInterface(TWI_t* twi)
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 TWI_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, the TWI_CHECK_STATUS flag will be set to 1. The flag will be cleared automatically. It's up to the software designer to decide what actions to take based on the status code.
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 header file in the status codes section.
Code example - Master Transmitter (MT) mode - Transmit data to an I2C or TWI device
uint8_t data_array[] = {123, 67, '\0'}; // last byte must be a NULL uint8_t status_code; uint8_t device_address = 0x03; // Initialize TWI 0 at 100kHz TWI_Init(&twi0, TWI_100KHZ); // Start transmission. This enables the Master mode. TWI_StartTransmission(&twi0); // Optional. Do something based on TWI status code. if(TWI_StatusNotACK(&twi0)){ status_code = TWI_ReadStatusCode(&twi0); } // Select the device using it's address and set the write mode TWI_ContactDevice(&twi0, device_address, TWI_WRITE_MODE); // Optional. Do something based on TWI status code. // If device doesn't respond with ACK, send a STOP. if(TWI_StatusNotACK(&twi0)){ status_code = TWI_ReadStatusCode(&twi0); } // Transmit an array of bytes TWI_Transmit(&twi0, data_array); // ... the transmit function can be used again to transmit // as many bytes is necessary // Optional. Do something based on TWI status code. // If device doesn't respond with ACK after every byte, send a STOP. if(TWI_StatusNotACK(&twi0)){ status_code = TWI_ReadStatusCode(&twi0); } // Stop the transmission TWI_StopTransmission(&twi0);
Code example - Master Receiver (MR) mode - Read data from an I2C or TWI device
int main(void){ #define array_size 5 uint8_t received_data[array_size] = {0}; uint8_t idx = 0; uint8_t status_code; uint8_t device_address = 0x03; // Initialize TWI 0 at 100kHz TWI_Init(&twi0, TWI_100KHZ); // Start transmission. This enables the Master mode. TWI_StartTransmission(&twi0); // Select the device using it's address and set the write mode // to instruct the device about the type of data. TWI_ContactDevice(&twi0, device_address, TWI_WRITE_MODE); // Transmit a code just as an example. TWI_TransmitByte(&twi0, 0x21); TWI_StartTransmission(&twi0); TWI_ContactDevice(&twi0, device_address, TWI_READ_MODE); while(1){ if(TWI_ByteReady(&twi0)){ received_data[idx] = TWI_ReadByte(&twi0); idx++; if(idx >= array_size) idx = 0; } status_code = TWI_ReadStatusCode(&twi0); // Issue a STOP if 2 bytes were received. if((status_code == TWI_CODE_MR_DATA_IN_NACK) || (idx > 1)){ // Stop the transmission TWI_StopTransmission(&twi0); } } return(EXIT_SUCCESS); }
Slave Receiver (SR) mode
int main(void){ #define array_size 5 uint8_t received_data[array_size] = {0}; uint8_t idx = 0; const uint8_t device_addr = 0x03; // Initialize TWI0 in Slave mode and set the address TWI_SetAddress(&twi0, device_addr, 0); TWI_SlaveMode(&twi0); while(1){ if(TWI_ByteReady(&twi0)){ received_data[idx] = TWI_ReadByte(&twi0); idx++; if(idx >= array_size) idx = 0; } } return 0; }
Slave Transmitter (ST) mode
When the device is addressed by a Master using SLA+R mode, the interrupt will trigger and the software application will be informed using the DataRequest() function. In the interrupt at that point, the interrupt will be deactivated otherwise the same byte in the TWDR register will be sent continuously. The data is not transmitted using the interrupt, but by using the transmit function. Even disabling the interrupt when the address is acknowledged, a byte is still transmitted by the TWI hardware expecting the TWDR to be written in the interrupt. When the TWDR is not written the byte that will be transmitted is always 7. Don't know why. This makes that the first byte in the array is not transmitted and for this reason the first array value is duplicated. Rest of the bytes are sent correctly.
Having the data prepared when is requested is only feasible if only one type of data is needed otherwise there is no time to prepare the data between the address acknowledgement and first data byte.
int main(void){ uint8_t arr[] = {123, 123, 67, 99, 88, '\0'}; // Initialize TWI0 in Slave mode and set the address TWI_SetAddress(&twi0, device_addr, 0); TWI_SlaveMode(&twi0); while(1){ if(TWI_DataRequest(&twi0)){ // The STOP must be issued by the Master. TWI_Transmit(&twi0, arr); } } return 0; }
Download
v3.0 | |
twi.h | |
twi.c | |
v2.0 | |
twi.h | |
twi.c | |
Changelog | |
v3.0 (9-04-2025) |
- Added support for Slave mode. - Fixed some issues with status codes. |
v2.0 (1-01-2025) |
- Now devices with multiple TWI modules can use them simultaneously by
using object pointers as function parameters. - Some code optimization. |
v1.2 | - Fixed a bug on ATmega328P and similar devices with only one I2C module where registers TWxRn are not defined. |
No comments:
Post a Comment