728x90

728x90

Wednesday, February 11, 2026

ATMega644P Software TWI Example

Overview

For any MCU that doesn't have a dedicated TWI module inside the programmer can emulate a TWI using software bit-banging. It's very common and readable for most of students, programmers and electronics hobbyists. They just need to understand about the overall protocol of TWI data transmission and reception. It's very similar to the SPI but it excepts START, REPEATED START, STOP Condition and one additional ACK/NACK bit. Without an MCU the programmer uses the PC parallel port instead.

However using software TWI it's not robust and fast like the TWI module inside an MCU. The software TWI can fit to any MCU with any additional code modifications without understanding the the detail of the dedicated TWI module of an MCU.

For an AVR micro-controller the user need set a correct fuse and a proper software configuration for the internal TWI module to make a correct operation of the TWI data transmission and reception. 

Unfortunately my ATMega644P was supplied with a over voltage supply of around 10VDC for almost one day. It caused by a malfunction ASM1117-5.0 low drop out voltage regulator chip. But the MCU still works with some defected parts. Then its dedicated TWI module also defected.

PCF8574AP Software TWI GPIO Port Writing 

I decide to learn and write my own software TWI routine to control a TWI OLED display I posses. I worked well. To get started I wrote a simple C routine for writing data to a PCF8574AP I/O expansion chip.

ATMega644P Software TWI Example
A simple software TWI data sending to the PCF8574AP I/O Expansion

This introductory example is a draft of C routine I first wrote. The master MCU send data to the PCF8574AP via its software TWI pins, SCL and SDA. We can select other pins depends on the program settings. The 8-bit write address of PCF8574AP (A2:A0=0x00) is 0x70 and 0x71 for read address.

C Source File "main.c" :

  1. /*
  2. * 11-soft_twi_pcf8574ap.c
  3. *
  4. * Created: 2/9/2026 10:06:25 PM
  5. * Author : Admin
  6. */

  7. #include <avr/io.h>
  8. #include <util/delay.h>
  9. #define F_CPU 16000000UL

  10. #define TWI_PORT PORTC
  11. #define TWI_SDA_IN PINC
  12. #define TWI_DIR DDRC

  13. const char SDA=1;
  14. const char SCL=0;

  15. void delay_counts(unsigned int count){
  16. for(unsigned int i=0;i<count;i++);
  17. }

  18. void twi_start(void){
  19. TWI_DIR=(1<<SDA)|(1<<SCL);
  20. TWI_PORT=(1<<SDA)|(1<<SCL);
  21. delay_counts(20);
  22. TWI_PORT&=~(1<<SDA);
  23. delay_counts(10);
  24. TWI_PORT&=~(1<<SCL);
  25. delay_counts(10);
  26. }

  27. void twi_stop(void){
  28. TWI_PORT&=~(1<<SCL);
  29. TWI_PORT&=~(1<<SDA);
  30. TWI_PORT|=(1<<SCL);
  31. delay_counts(10);
  32. TWI_PORT|=(1<<SDA);
  33. delay_counts(10);
  34. TWI_PORT=(1<<SDA)|(1<<SCL);
  35. delay_counts(20);
  36. }
  37. /*
  38. void twi_write(char data){
  39. char temp=0;
  40. for (unsigned char i=0;i<8;i++)
  41. {
  42. TWI_PORT&=~(1<<SCL);
  43. temp=data&(1<<7);
  44. if(temp==0) TWI_PORT&=~(1<<SDA);
  45. else TWI_PORT|=(1<<SDA);
  46. delay_counts(10);
  47. TWI_PORT|=(1<<SCL);
  48. data<<=1;
  49. delay_counts(10);
  50. }
  51. TWI_PORT&=~(1<<SDA);
  52. TWI_PORT&=~(1<<SCL);
  53. TWI_DIR&=~(1<<SDA);
  54. while(TWI_PORT&(1<<SDA)==0);
  55. delay_counts(10);
  56. TWI_PORT|=(1<<SCL);
  57. delay_counts(10);
  58. TWI_DIR=(1<<SDA)|(1<<SCL);
  59. }
  60. */
  61. void twi_write(char data){
  62. char temp=0;
  63. TWI_DIR|=(1<<SDA)|(1<<SCL);
  64. for (unsigned char i=0;i<9;i++)
  65. {
  66. TWI_PORT&=~(1<<SCL);
  67. if(i<8){
  68. temp=data&0x80;
  69. if(temp==0) TWI_PORT&=~(1<<SDA);
  70. else TWI_PORT|=(1<<SDA);
  71. }
  72. else{
  73. TWI_PORT&=~(1<<SDA);
  74. TWI_DIR&=~(1<<SDA);
  75. while(TWI_SDA_IN&(1<<SDA)==0);
  76. }
  77. delay_counts(100);
  78. TWI_PORT|=(1<<SCL);
  79. data<<=1;
  80. delay_counts(100);
  81. }
  82. TWI_DIR|=(1<<SDA)|(1<<SCL);
  83. }

  84. char twi_read(void){
  85. char temp=0,data=0;
  86. for (unsigned char i=0;i<8;i++)
  87. {
  88. TWI_PORT&=~(1<<SCL);
  89. temp=data&(1<<7);
  90. temp=TWI_PORT&&(1<<SDA);
  91. if(temp==1) data|=1;
  92. else data|=0;
  93. delay_counts(10);
  94. TWI_PORT|=(1<<SCL);
  95. data<<=1;
  96. delay_counts(10);
  97. }
  98. TWI_PORT&=~(1<<SDA);
  99. TWI_PORT&=~(1<<SCL);
  100. //TWI_DIR&=~(1<<SDA);
  101. while(TWI_PORT&(1<<SDA)==0);
  102. delay_counts(10);
  103. TWI_PORT|=(1<<SCL);
  104. delay_counts(10);
  105. TWI_DIR=(1<<SDA)|(1<<SCL);
  106. }

  107. int main(void)
  108. {
  109. /* Replace with your application code */
  110. char i=0;
  111. DDRB=0xFF;
  112. twi_start();
  113. twi_write(0x70);
  114. twi_write(1);
  115. twi_stop();
  116. _delay_ms(5000);
  117. twi_start();
  118. twi_write(0x70);
  119. twi_write(0);
  120. twi_stop();
  121. _delay_ms(5000);
  122. while (1)
  123. {
  124. twi_start();
  125. twi_write(0x70);
  126. twi_write(1<<i);
  127. twi_stop();
  128. _delay_ms(500);
  129. i++;
  130. if(i==8) i=0;
  131. /*
  132. twi_start();
  133. twi_write(0x70);
  134. twi_write(0xF0);
  135. twi_stop();
  136. _delay_ms(500);
  137. */
  138. }
  139. }


I didn't test this firmware in real hardware. But I think it work like the one's in software simulator. 

PCF8574AP Software TWI GPIO Port Reading and Writing 

Now I added and test a software TWI write routine. The master ATMega644P MCU periodically send data between PORTB and I/O port of the PCF8574AP.

ATMega644P Software TWI Example
PCF8574AP GPIO Port Reading and Writing

The lower nibble of PORTB is an output port while its higher nibble is an input port. The lower nibble of PCF8574AP is an input port while its higher nibble is an output port. The data is exchanged between these two chip periodically.

Source File "main.c" :

  1. /*
  2. * 11-soft_twi_pcf8574ap_ex.c
  3. *
  4. * Created: 2/12/2026 9:02:47 AM
  5. * Author : Admin
  6. */

  7. #include <avr/io.h>
  8. #include <util/delay.h>
  9. #define F_CPU 16000000UL

  10. #define TWI_PORT PORTC
  11. #define TWI_SDA_IN PINC
  12. #define TWI_DIR DDRC

  13. const char SDA=1;
  14. const char SCL=0;
  15. const char PULSE=20;

  16. void delay_counts(unsigned int count){
  17. for(unsigned int i=0;i<count;i++);
  18. }

  19. void twi_start(void){
  20. TWI_DIR|=(1<<SDA)|(1<<SCL);
  21. TWI_PORT|=(1<<SDA)|(1<<SCL);
  22. delay_counts(PULSE);
  23. TWI_PORT&=~(1<<SDA);
  24. delay_counts(PULSE);
  25. TWI_PORT&=~(1<<SCL);
  26. delay_counts(PULSE);
  27. }

  28. void twi_stop(void){
  29. TWI_PORT&=~(1<<SCL);
  30. TWI_PORT&=~(1<<SDA);
  31. TWI_PORT|=(1<<SCL);
  32. delay_counts(PULSE);
  33. TWI_PORT|=(1<<SDA);
  34. delay_counts(PULSE);
  35. TWI_PORT|=(1<<SDA)|(1<<SCL);
  36. delay_counts(PULSE);
  37. }

  38. void twi_write(char data){
  39. char temp=0;
  40. TWI_DIR|=(1<<SDA)|(1<<SCL);
  41. for (unsigned char i=0;i<9;i++)
  42. {
  43. TWI_PORT&=~(1<<SCL);
  44. delay_counts(PULSE);
  45. if(i<8){
  46. temp=data&0x80;
  47. if(temp==0) TWI_PORT&=~(1<<SDA);
  48. else TWI_PORT|=(1<<SDA);
  49. }
  50. else{
  51. TWI_PORT&=~(1<<SDA);
  52. TWI_DIR&=~(1<<SDA);
  53. while(TWI_SDA_IN&(1<<SDA)==0);
  54. }
  55. TWI_PORT|=(1<<SCL);
  56. delay_counts(PULSE);
  57. data<<=1;
  58. }
  59. TWI_DIR|=(1<<SDA)|(1<<SCL);
  60. }

  61. char twi_read(void){
  62. char temp=0,data=0;
  63. TWI_DIR&=~(1<<SDA);
  64. for (unsigned char i=0;i<9;i++)
  65. {
  66. TWI_PORT&=~(1<<SCL);
  67. delay_counts(PULSE);
  68. if(i<8){
  69. /*
  70. data<<=1;
  71. temp=TWI_SDA_IN&(1<<SDA);
  72. if(temp==(1<<SDA)) data|=1;
  73. else data|=0;
  74. */
  75. temp=TWI_SDA_IN&(1<<SDA);
  76. temp>>=1;
  77. data|=(temp<<(7-i));
  78. }
  79. else{
  80. while((TWI_SDA_IN&(1<<SDA))==0);
  81. }
  82. TWI_PORT|=(1<<SCL);
  83. delay_counts(PULSE);
  84. }
  85. return data;
  86. }

  87. int main(void)
  88. {
  89. /* Replace with your application code */
  90. char data;
  91. DDRB=0x0F;
  92. PORTB=0xF0;
  93. twi_start();
  94. twi_write(0x70);
  95. twi_write(0x0F);
  96. twi_stop();
  97. _delay_ms(100);
  98. while (1)
  99. {
  100. twi_start();
  101. twi_write(0x71);
  102. data=twi_read();
  103. twi_stop();
  104. _delay_ms(100);
  105. //Send Data To PORTB and Turn On Pull Up For PINC4:PINC7
  106. PORTB=data|0xF0;
  107. //Read PINB Turn On Pull Up For P3:P0
  108. data=PINB|0x0F;
  109. twi_start();
  110. twi_write(0x70);
  111. twi_write(data);
  112. twi_stop();
  113. _delay_ms(100);
  114. }
  115. }






I don't need to test this example on a physical hardware because it work nearly identical.  

PCF8574AP Software TWI HD44780 LCD and DS1307 RTC

I tested the on-board DS13017 RTC and the HD44780 based 16x2 characters LCD. This 8-bit parallel port LCD operates in its 4-bit data transfer mode at PORTC. The software TWI uses PORTC PC0(SCL) and PC1(SDA) in the same port with the LCD. It doesn't cause any problem since I use masking in C program.

ATMega644P Software TWI Example
AVR Prototype Board

Source Code "main.c" : 

  1. /*
  2. * 11-soft_twi_ds1307_lcd.c
  3. *
  4. * Created: 2/10/2026 3:03:24 PM
  5. * Author : Admin
  6. */

  7. #include <avr/io.h>
  8. #include <util/delay.h>
  9. #define F_CPU 16000000UL

  10. #define TWI_PORT PORTC
  11. #define TWI_SDA_IN PINC
  12. #define TWI_DIR DDRC

  13. const char SDA=1;
  14. const char SCL=0;
  15. const unsigned int PULSE=100;

  16. //DS1307 and DS3231 RTC Chip
  17. const char DS1307_W=0xD0;
  18. const char DS1307_R=0xD1;
  19. //AT24C16B 16kB EEPROM
  20. const char AT24C16B_W=0xA0;
  21. const char AT24C16B_R=0xA1;
  22. //PCF8574 and PCF8574A Series
  23. const char PCF8574_W=0x40;
  24. const char PCF8574_R=0x41;
  25. const char PCF8574A_W=0x70;
  26. const char PCF8574A_R=0x71;
  27. //DIY Arduino PCF8574T LCD Module
  28. const char PCF8574T_LCD_W=0x4E;
  29. const char PCF8574T_LCD_R=0x4F;
  30. //SH1106 OLED Module
  31. const char SH1106_W=0x78;
  32. const char SH1106_R=0x79;
  33. //MCP23017 GPIO Expansion
  34. const char MCP23017_W=0x40;
  35. const char MCP23017_R=0x41;

  36. void delay_counts(unsigned int count){
  37. for(unsigned int i=0;i<count;i++);
  38. }

  39. void twi_start(void){
  40. TWI_DIR|=(1<<SDA)|(1<<SCL);
  41. TWI_PORT|=(1<<SDA)|(1<<SCL);
  42. delay_counts(PULSE);
  43. TWI_PORT&=~(1<<SDA);
  44. delay_counts(PULSE);
  45. TWI_PORT&=~(1<<SCL);
  46. delay_counts(PULSE);
  47. }

  48. void twi_stop(void){
  49. TWI_DIR|=(1<<SDA)|(1<<SCL);
  50. TWI_PORT&=~(1<<SCL);
  51. TWI_PORT&=~(1<<SDA);
  52. TWI_PORT|=(1<<SCL);
  53. delay_counts(PULSE);
  54. TWI_PORT|=(1<<SDA);
  55. delay_counts(PULSE);
  56. TWI_PORT|=(1<<SDA)|(1<<SCL);
  57. delay_counts(PULSE);
  58. }

  59. void twi_write(char data){
  60. char temp=0;
  61. TWI_DIR|=(1<<SDA)|(1<<SCL);
  62. for (unsigned char i=0;i<9;i++)
  63. {
  64. TWI_PORT&=~(1<<SCL);
  65. delay_counts(PULSE);
  66. if(i<8){
  67. temp=data&0x80;
  68. if(temp==0) TWI_PORT&=~(1<<SDA);
  69. else TWI_PORT|=(1<<SDA);
  70. }
  71. else{
  72. TWI_PORT&=~(1<<SDA);
  73. TWI_DIR&=~(1<<SDA);
  74. while(TWI_SDA_IN&(1<<SDA)==0);
  75. }
  76. TWI_PORT|=(1<<SCL);
  77. delay_counts(PULSE);
  78. data<<=1;
  79. }
  80. TWI_DIR|=(1<<SDA)|(1<<SCL);
  81. }

  82. char twi_read(void){
  83. char temp=0,data=0;
  84. TWI_DIR&=~(1<<SDA);
  85. for (unsigned char i=0;i<9;i++)
  86. {
  87. TWI_PORT&=~(1<<SCL);
  88. delay_counts(PULSE);
  89. TWI_PORT|=(1<<SCL);
  90. delay_counts(PULSE);
  91. if(i<8){
  92. /*
  93. data<<=1;
  94. temp=TWI_SDA_IN&(1<<SDA);
  95. if(temp==(1<<SDA)) data|=1;
  96. else data|=0;
  97. */
  98. /*
  99. temp=TWI_SDA_IN&(1<<SDA);
  100. temp>>=1;
  101. data|=temp<<(7-i);
  102. */
  103. data<<=1;
  104. if (TWI_SDA_IN&(2))
  105. {
  106. data|=1;
  107. }
  108. }
  109. else{
  110. while((TWI_SDA_IN&(1<<SDA))==0);
  111. }
  112. }
  113. return data;
  114. }

  115. void rtc_init(void){
  116. char rtc[8]={0x30,0x10,0x21,0x04,0x11,0x02,0x26,1<<4};
  117. for (char i=0;i<8;i++)
  118. {
  119. twi_start();
  120. //D0 is DS1307 Write Address
  121. twi_write(DS1307_W);
  122. //Select Control Register
  123. twi_write(i);
  124. //Enable SQWE bit blinks at 1 Hz
  125. twi_write(rtc[i]);
  126. twi_stop();
  127. _delay_ms(10);
  128. }
  129. }

  130. char rtc[7], msg[16];
  131. void rtc_read(void){
  132. for(char i=0;i<7;i++){
  133. /*Second Register*/
  134. twi_start();
  135. twi_write(DS1307_W);
  136. /*Select Second register*/
  137. twi_write(i);
  138. twi_stop();
  139. _delay_ms(10);
  140. twi_start();
  141. twi_write(DS1307_R);
  142. rtc[i]=twi_read();
  143. twi_stop();
  144. _delay_ms(10);
  145. }
  146. }

  147. int main(void)
  148. {
  149. /* Replace with your application code */
  150. lcd_init();
  151. lcd_text("ATMega644P RTC");
  152. lcd_xy(1,2);
  153. lcd_text("Software TWI");
  154. _delay_ms(10000);
  155. lcd_clear();
  156. char count;
  157. lcd_command(0x0C);
  158. //rtc_init();
  159. while (1)
  160. {
  161. rtc_read();
  162. lcd_xy(1,1);
  163. sprintf(msg,"Time: %02X:%02X:%02X",rtc[2],rtc[1],rtc[0]);
  164. lcd_text(msg);
  165. lcd_xy(1,2);
  166. sprintf(msg,"Date: %02X/%02X/20%02X",rtc[4],rtc[5],rtc[6]);
  167. lcd_text(msg);
  168. _delay_ms(500);
  169. }
  170. }


This package contain the HD44780 LCD driver.

ATMega644P Software TWI Example
Simulation Program

 In simulation program it run without error.

 ATMega644P Software TWI Example

 

ATMega644P Software TWI Example 

Click here to download this example. 

 

 

 

  

 

 

No comments:

Post a Comment

320x50

Search This Blog

tyro-728x90