728x90

728x90

Saturday, February 14, 2026

ATMega644P Serial Peripheral Interface (SPI) Tutorial

Overview

The Serial Peripheral Interface (SPI) is a high speed serial communication that commonly use three wires, serial clock, serial data and enable (master transmit only). Most of modern micro-controller has this serial communication interface. It is useful for board to board or to device communication due to wiring complexity compare to parallel port data transmission.

SPI is popular among graphical display, Flash memory, I/O expansion chip, SRAM, EEPROM, real time clock chip etc. Its transmission and reception speed could reach up to several Mbits/s. These are some example of SPI devices:

  • DS3234 Real Time Clock
  • Nokia 5510 graphical LCD module
  • MCP23S17 I/O Expansion Chip
  • SN74HC595N Serial In Parallel Out Shift Registers
  • ILI9341/ST7785 240x320 TFT Display
  • ST7735 128x160 TFT Display
  • W25Q64JVSSIQ Flash Memory Chip
  • SD MMC Card etc. 
ATMega644P Serial Peripheral Interface (SPI) Tutorial
W25Q64JVSSIQ

 ATMega644P Serial Peripheral Interface (SPI) Tutorial

ATMega644P Serial Peripheral Interface (SPI) Tutorial
ILI9341/ST7785 240x320 TFT Display

 

Some earlier MCU doesn't have an SPI module inside for instance the AT89S52 or PIC16F84A. However the programmer could emulate a software SPI by using the bit-banging technique. This method is popular but it yield a lower speed data transmission and reception. 

Typically a system operation contains a master MCU that transmit and receive data from its connected SPI slave devices.

ATMega644P Serial Peripheral Interface (SPI) Tutorial
Single master to single slave: basic SPI wiring
 

The pin names of the SPI module in the AVR micro-controller are different from other MCU such as PIC. These pins are:

Abbr.NameDescription
SS
Slave Select
Active-low chip select signal from master to
enable communication with a specific slave device
SCLK
Serial Clock
Clock signal from master
MOSI
Master Out Slave In
Serial data output from master
MISO
Master In Slave Out
Serial data output from slave


The master MCU must select any connected SPI slave device via its slave select (SS) pin. One slave device has a unique SS pin. So multiple slave devices have many SS pins.

ATMega644P Serial Peripheral Interface (SPI) Tutorial
A typical hardware setup using two shift registers to form an inter-chip circular buffer
The SS pin is active low or active high upon the specific device.

Its clock polarity is commonly positive (low to high). Somes device use a negative (high to low) clock polarity.

ATMega644P Serial Peripheral Interface (SPI) Tutorial
 

However its clock polarity and phase are configured by user software that requires an understanding of technical detail of an MCU (Dedicated SPI Module).

The master MCU can communicates with many different SPI slave devices (Multidrop SPI bus) on a single bus using additional SS pins.

ATMega644P Serial Peripheral Interface (SPI) Tutorial
Multidrop SPI bus
 

If multiple SPI slave devices with the same type connect to a single SPI bus we can use the Daisy chain configuration. For instance the SN74HC595N or SN74HC164 serial in parallel out shift registers.

ATMega644P Serial Peripheral Interface (SPI) Tutorial
Daisy chain configuration

 It is used for expanding a number of output ports for instance driving a dot matrix display.

 Currently there are a lot of newer method of SPI data transmission that is precise and high speed.

ATMega644P Serial Peripheral Interface

The SPI module of the ATMega644P has the following features:

    Full-duplex, Three-wire Synchronous Data Transfer
    Master or Slave Operation
    LSB First or MSB First Data Transfer
     Seven Programmable Bit Rates
    End of Transmission Interrupt Flag
    Write Collision Flag Protection
    Wake-up from Idle Mode
    Double Speed (CK/2) Master SPI Mode

Its operation mode and speed are configured in user program via its relevant special function registers.

ATMega644P Serial Peripheral Interface (SPI) Tutorial
SPI Block Diagram

 Its clock generator is divided form the MCU clock up to CK/2. Data is shifted in and out using its internal 8-bit shift register at each clock cycle of the SPI clock generator. It also generate interrupt flag whenever the shift registers is empty.

ATMega644P Serial Peripheral Interface (SPI) Tutorial
SPI Master-slave Interconnection

 These are its relevant registers:

  •   SPCR – SPI Control Register
ATMega644P Serial Peripheral Interface (SPI) Tutorial
SPCR – SPI Control Register
  •  SPSR – SPI Status Register 
ATMega644P Serial Peripheral Interface (SPI) Tutorial
SPSR – SPI Status Register
  • SPDR – SPI Data Register 
ATMega644P Serial Peripheral Interface (SPI) Tutorial
SPDR – SPI Data Register

The ATMega644P may operate in master transmit only, slave receive only or even full duplex (synchronous data transfer) depending on these registers.  For more detail please see the device datasheet. 

ATMega644P SPI Interfacing and Programming

We can configure the SPI module of the ATMega644P to operates in a specific mode depends on the worked application.

SPI Master Transmit Mode 

An SPI master transmits only is common. There are many SPI slave device that only need data reception such as the SN74HC595N or 74HC164 shift register chips. 

ATMega644P Serial Peripheral Interface (SPI) Tutorial
SN74HC595N and SN74HC164N Shift Registers

These chips is very popular due to its availability, low cost, ease of control etc. The SN74HC595N is commonly found in output expanding application such as relays driving, LED driving especially the dot matrix display driver.

ATMega644P Serial Peripheral Interface (SPI) Tutorial
SN74HC595N Pin Diagram

In this example the ATMega644P operate in master mode to transmit data to the SN74HC595N shifter register chip. The output port of this chip can connects to LED, 7-Segment displays or even a character LCD.

There are sample code of using the SPI transmit in device data sheet in both Assembly and C program.

ATMega644P Serial Peripheral Interface (SPI) Tutorial
Sample Code

 

LED Driving

I have a DIY single chip SN74HC595N LED module that is very easy to make. However we can install them together on a single breadboard.

ATMega644P Serial Peripheral Interface (SPI) Tutorial
A completed Soldering Board
ATMega644P Serial Peripheral Interface (SPI) Tutorial
Copper Side

 For more information about this DIY PCB see this post.

 The program is very simple.

  1. /*
  2. * 12-spi_74hc595_led.c
  3. *
  4. * Created: 2/14/2026 6:57:01 PM
  5. * Author : Admin
  6. */

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

  10. #define DDR_SPI DDRB
  11. #define PRT_SPI PORTB
  12. #define DD_MOSI 5
  13. #define DD_SCK 7
  14. #define DD_SS 4

  15. void SPI_MasterInit(void)
  16. {
  17. /* Set MOSI and SCK output, all others input */
  18. DDR_SPI = (1<<DD_MOSI)|(1<<DD_SCK)|(1<<DD_SS);
  19. /* Enable SPI, Master, set clock rate fck/16 */
  20. SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
  21. }

  22. void SPI_MasterTransmit(char cData)
  23. {
  24. /* Start transmission */
  25. SPDR = cData;
  26. /* Wait for transmission complete */
  27. while(!(SPSR & (1<<SPIF)))
  28. ;
  29. }

  30. int main(void)
  31. {
  32. /* Replace with your application code */
  33. SPI_MasterInit();
  34. char i=0;
  35. while (1)
  36. {
  37. SPI_MasterTransmit(1<<i);
  38. PRT_SPI&=~(1<<DD_SS);
  39. _delay_us(100);
  40. PRT_SPI=(1<<DD_SS);
  41. _delay_us(100);
  42. i++;
  43. if(i>8) i=0;
  44. _delay_ms(100);
  45. }
  46. }


Schematic:

ATMega644P Serial Peripheral Interface (SPI) Tutorial
SN74HC595N LED Driving

 AVR Experiment Board:

ATMega644P Serial Peripheral Interface (SPI) Tutorial
SN74HC595N LED Driving

 

ATMega644P Serial Peripheral Interface (SPI) Tutorial
SN74HC595N LED Driving

 

 

ATMega644P Serial Peripheral Interface (SPI) Tutorial
SN74HC595N LED Driving

 The program just shift each bits of the SN74HC595N output port.

 

Seven Segments Display Driving

I made a single common cathode 7-Segment display that is driven by an SN74HC595N chip. So it's easy for the MCU to connect to this display using a little wires. 

Making A Single 74HC595 Seven Segments Driver For Arduino PIC And AVR
Fully Solder Board

 

Making A Single 74HC595 Seven Segments Driver For Arduino PIC And AVR
Copper Side After I Soldered.

 Source Code:

  1. /*
  2. * 12-spi_74hc595_7.c
  3. *
  4. * Created: 2/14/2026 9:42:04 PM
  5. * Author : Admin
  6. */

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

  10. //Common Anode
  11. const char ca_7[16]={0xC0, 0xF9, 0xA4, 0xB0,0x99, 0x92,
  12. 0x82, 0xF8, 0x80, 0x90, 0x88, 0x83, 0xA7, 0xA1, 0x86, 0x8E};
  13. //Common Cathode
  14. const char cc_7[16]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,
  15. 0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};

  16. #define DDR_SPI DDRB
  17. #define PRT_SPI PORTB
  18. #define DD_MOSI 5
  19. #define DD_SCK 7
  20. #define DD_SS 4

  21. void SPI_MasterInit(void)
  22. {
  23. /* Set MOSI and SCK output, all others input */
  24. DDR_SPI = (1<<DD_MOSI)|(1<<DD_SCK)|(1<<DD_SS);
  25. /* Enable SPI, Master, set clock rate fck/16 */
  26. SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
  27. }

  28. void SPI_MasterTransmit(char cData)
  29. {
  30. /* Start transmission */
  31. SPDR = cData;
  32. /* Wait for transmission complete */
  33. while(!(SPSR & (1<<SPIF)))
  34. ;
  35. }

  36. int main(void)
  37. {
  38. /* Replace with your application code */
  39. SPI_MasterInit();
  40. char i=0;
  41. while (1)
  42. {
  43. SPI_MasterTransmit(ca_7[i]);
  44. PRT_SPI&=~(1<<DD_SS);
  45. _delay_us(100);
  46. PRT_SPI=(1<<DD_SS);
  47. _delay_us(100);
  48. i++;
  49. if(i>15) i=0;
  50. _delay_ms(500);
  51. }
  52. }


Schematic:

ATMega644P Serial Peripheral Interface (SPI) Tutorial
Seven Segments Display Driving

 

 

 
 

 

 

 

 

 

 

Friday, February 13, 2026

ATMega644P Software TWI SH1106 OLED and DS1307 RTC

In previous example the ATMega644P emulate the TWI via software bit-banging that able to communicate to various slave TWI devices on a single bus. In this example the ATMega644P master MCU send display data to the SH1106 128x64 OLED display and read calendar data from the DS1307 RTC. For more information about using the SH1106 128x64 OLED display please visit this post.

ATMega644P Software TWI SH1106 OLED and DS1307 RTC
ATMega644P Software TWI SH1106 OLED 

 

ATMega644P Software TWI SH1106 OLED 

For an introductory example the master MCU send display data to the SH1106 128x64 OLED module. 

Source Code "main.c":

  1. /*
  2. * 11-soft_twi_sh1106.c
  3. *
  4. * Created: 2/10/2026 12:03:22 AM
  5. * Author : Admin
  6. */

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

  10. int main(void)
  11. {
  12. /* Replace with your application code */
  13. _delay_ms(1000);
  14. display_init();
  15. display_clear(0);
  16. display_text_8x16(0,0,"SH1106 I2C OLED");
  17. display_text_8x16(0,1,"And ATMega644P");
  18. display_text_8x16(0,2,"Programming With");
  19. display_text_8x16(0,3,"Microchip Studio");
  20. _delay_ms(10000);
  21. display_clear(0x00);
  22. while (1)
  23. {
  24. display_text_8x16(0,0,"Software TWI");
  25. _delay_ms(2500);
  26. uint8_t h=15;
  27. for(uint8_t i=0;i<10;i++) {
  28. display_char_8x16(h,1,'0'+i);
  29. h+=10;
  30. }
  31. _delay_ms(2500);
  32. h=0;
  33. for(uint8_t i=0;i<14;i++) {
  34. display_char_8x16(h,2,'A'+i);
  35. h+=10;
  36. }
  37. _delay_ms(2500);
  38. h=0;
  39. for(uint8_t i=0;i<14;i++) {
  40. display_char_8x16(h,3,'N'+i);
  41. h+=10;
  42. }
  43. _delay_ms(10000);
  44. display_clear(255);
  45. _delay_ms(10000);
  46. display_clear(0);
  47. _delay_ms(1000);
  48. }
  49. }

Schematic:

Schematic
Schematic

Proteus only has a model for the SSD1306. Simulating this part is very slow.

AVR Prototype Board:

ATMega644P Software TWI SH1106 OLED and DS1307 RTC
ATMega644P AVR Prototype Board

 

ATMega644P Software TWI SH1106 OLED and DS1307 RTC 

 

ATMega644P Software TWI SH1106 OLED and DS1307 RTC
ATMega644P AVR Prototype Board

 

Click here to download this example. 

ATMega644P Software TWI SH1106 OLED 

Here the ATMega644P read the calendar from DS1307 RTC that will display on the SH1106 128x64 OLED display.

Source Code "main.c":

  1. /*
  2. * 11-soft_twi_ds1307_sh1106.c
  3. *
  4. * Created: 2/9/2026 7:34:19 PM
  5. * Author : Admin
  6. */


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

  11. #include "twi_device.h"

  12. void rtc_init(void){
  13. char rtc[8]={0x30,0x35,0x13,0x07,0x31,0x01,0x26,1<<4};
  14. for (char i=0;i<8;i++)
  15. {
  16. twi_start();
  17. //D0 is DS1307 Write Address
  18. twi_write(DS1307_W);
  19. //Select Control Register
  20. twi_write(i);
  21. //Enable SQWE bit blinks at 1 Hz
  22. twi_write(rtc[i]);
  23. twi_stop();
  24. _delay_ms(10);
  25. }
  26. }

  27. unsigned char rtc[50], msg[20];
  28. float voltage;
  29. unsigned int temp;

  30. void rtc_read(void){
  31. for(char i=0;i<50;i++){
  32. /*Second Register*/
  33. twi_start();
  34. twi_write(DS1307_W);
  35. /*Select Second register*/
  36. twi_write(i);
  37. twi_stop();
  38. _delay_us(100);
  39. twi_start();
  40. twi_write(DS1307_R);
  41. rtc[i]=twi_read();
  42. twi_stop();
  43. _delay_us(100);
  44. }
  45. }
  46. int main(void)
  47. {
  48. /* Replace with your application code */
  49. _delay_ms(1000);
  50. display_init();
  51. adc_init();
  52. //rtc_init();
  53. display_clear(0);
  54. display_text_8x16(0,0,"Software TWI");
  55. display_text_8x16(0,1,"ATMega644P OLED");
  56. display_text_8x16(0,2,"SH1106 DS1307");
  57. display_text_8x16(0,3,"RTC Example");
  58. _delay_ms(10000);
  59. display_clear(0);
  60. while (1)
  61. {
  62. rtc_read();
  63. sprintf(msg,"Time: %02X:%02X:%02X",rtc[2],rtc[1],rtc[0]);
  64. display_text_8x16(0,0,msg);
  65. sprintf(msg,"Date: %02X/%02X/20%02X",rtc[4],rtc[5],rtc[6]);
  66. display_text_8x16(0,1,msg);
  67. //Read POT
  68. temp=read_adc(0);
  69. //Convert To Voltage
  70. voltage=temp*5.0/1023;
  71. sprintf(msg,"ADC0: %4d %.2fV",temp,voltage);
  72. display_text_8x16(0,2,msg);
  73. //Read LM35
  74. temp=read_adc(1);
  75. voltage=temp*5.0/1023;
  76. voltage*=100;
  77. sprintf(msg,"Temp: %.2f C",voltage);
  78. display_text_8x16(0,3,msg);
  79. _delay_us(500);
  80. }
  81. }


The schematic is the one's above.

 

AVR Experiment Board: 

ATMega644P Software TWI SH1106 OLED and DS1307 RTC
ATMega644P Software TWI SH1106 OLED 
ATMega644P Software TWI SH1106 OLED and DS1307 RTC
ATMega644P Software TWI SH1106 OLED

 Click here to download this example.

 

 

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.

ATMega644P Software TWI Example 

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. We can use this chip to make a 4x4 matrix keypad. In this post the dedicated TWI module of this chip make a key pad scanning via a PCF8574AP.

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. 

 

PCF8574T HD44780 Character LCD Controller

Driving an HD4470 based character LCD using the PCF8574T is very easy. A character LCD with a PCF8574T is widely available at very low cost. It uses only four wires connects to a master MCU, GND, VCC, SDA and SCL. 

ATMega644P Software TWI Example
PCF8574T HD44780 Character LCD Controller

We don't need to use its full 8-bit data transfer mode. Sending the 8-bit data or command twice using its 4-bit data transfer mode is suitable for most of applications.

Source Code: 

  1. /*
  2. * 11-soft_twi_pcf8574T_1602.c
  3. *
  4. * Created: 2/12/2026 4:04:24 PM
  5. * Author : Admin
  6. */

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

  10. /*TWI LCD Driver*/
  11. #define RS 0
  12. #define RW 1
  13. #define EN 2
  14. #define BL 3

  15. #define twi_lcd_cursor_off() twi_lcd_command(0x0C)
  16. #define twi_lcd_line_1() twi_lcd_command(0x80)
  17. #define twi_lcd_line_2() twi_lcd_command(0xC0)

  18. char backLight=1;

  19. void twi_lcd_command(uint8_t command){
  20. uint8_t data;
  21. data=command&0xF0;
  22. pcf8574_write(data|(backLight<<BL)|(1<<EN));
  23. //_delay_us(10);
  24. pcf8574_write(data|(backLight<<BL));
  25. //_delay_us(50);
  26. data=command<<4;
  27. pcf8574_write(data|(backLight<<BL)|(1<<EN));
  28. //_delay_us(10);
  29. pcf8574_write(data|(backLight<<BL));
  30. //_delay_us(50);
  31. }

  32. void twi_lcd_data(uint8_t command){
  33. uint8_t data;
  34. data=command&0xF0;
  35. pcf8574_write(data|(backLight<<BL)|(1<<EN)|(1<<RS));
  36. //_delay_us(10);
  37. pcf8574_write(data|(backLight<<BL)|(1<<RS));
  38. _delay_us(50);
  39. data=command<<4;
  40. pcf8574_write(data|(backLight<<BL)|(1<<EN)|(1<<RS));
  41. //_delay_us(10);
  42. pcf8574_write(data|(backLight<<BL)|(1<<RS));
  43. //_delay_us(50);
  44. }

  45. void twi_lcd_xy(int8_t x, int8_t y){
  46. int8_t addr[]={0x80,0xC0};
  47. twi_lcd_command(addr[y-1]+x-1);
  48. }

  49. void twi_lcd_text(int8_t *txt){
  50. while(*txt) twi_lcd_data(*txt++);
  51. }

  52. void twi_lcd_clear(void){
  53. twi_lcd_command(0x01);
  54. _delay_ms(5);
  55. }

  56. void twi_lcd_init(void){
  57. pcf8574_write(0);
  58. //_delay_ms(10);
  59. twi_lcd_command(0x33);
  60. //_delay_us(10);
  61. twi_lcd_command(0x32);
  62. //_delay_us(10);
  63. twi_lcd_command(0x28);
  64. //_delay_us(10);
  65. twi_lcd_command(0x0F);
  66. //_delay_us(10);
  67. twi_lcd_command(0x01);
  68. //_delay_ms(5);
  69. twi_lcd_command(0x06);
  70. //_delay_us(10);
  71. }

  72. int main(void)
  73. {
  74. /* Replace with your application code */
  75. twi_lcd_init();
  76. twi_lcd_text("Software TWI");
  77. twi_lcd_line_2();
  78. twi_lcd_text("PCF8574T LCD");
  79. twi_lcd_cursor_off();
  80. while (1)
  81. {
  82. }
  83. }


Schematic:

ATMega644P Software TWI Example
PCF8574T HD44780 Character LCD Controller

 

AVR Prototype Board:

ATMega644P Software TWI Example
PCF8574T HD44780 Character LCD Controller

Click here to download its source file. 

PCF8574T HD44780 Character LCD and DS1307 RTC

At this point the ATMega644P master MCU read the RTC data from the on-board DS1307 Real Time Clock (RTC). The date and time data will show on an HD44780 character LCD via its driver PCF8574T. The software TWI work fine without reading or writing error.

ATMega644P Software TWI Example
PCF8574T HD44780 Character LCD and DS1307 RTC

 

Source Code:

  1. /*
  2. * 11-soft_twi_ds1307_pcf8574T_1602.c
  3. *
  4. * Created: 2/12/2026 5:21:17 PM
  5. * Author : Admin
  6. */

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

  10. char rtc[7], msg[16];
  11. void rtc_read(void){
  12. for(char i=0;i<7;i++){
  13. /*Second Register*/
  14. twi_start();
  15. twi_write(0xD0);
  16. /*Select Second register*/
  17. twi_write(i);
  18. twi_stop();
  19. _delay_ms(10);
  20. twi_start();
  21. twi_write(0xD1);
  22. rtc[i]=twi_read();
  23. twi_stop();
  24. _delay_ms(10);
  25. }
  26. }

  27. void rtc_init(void){
  28. char rtc[8]={0x30,0x10,0x21,0x04,0x11,0x02,0x26,1<<4};
  29. for (char i=0;i<8;i++)
  30. {
  31. twi_start();
  32. //D0 is DS1307 Write Address
  33. twi_write(0xD0);
  34. //Select Control Register
  35. twi_write(i);
  36. //Enable SQWE bit blinks at 1 Hz
  37. twi_write(rtc[i]);
  38. twi_stop();
  39. _delay_ms(10);
  40. }
  41. }

  42. int main(void)
  43. {
  44. /* Replace with your application code */
  45. twi_lcd_init();
  46. twi_lcd_text("Software TWI");
  47. twi_lcd_line_2();
  48. twi_lcd_text("DS1307 PCF8574T");
  49. _delay_ms(10000);
  50. twi_lcd_clear();
  51. twi_lcd_cursor_off();
  52. while (1)
  53. {
  54. rtc_read();
  55. twi_lcd_line_1();
  56. sprintf(msg,"Time: %02X:%02X:%02X",rtc[2],rtc[1],rtc[0]);
  57. twi_lcd_text(msg);
  58. twi_lcd_line_2();
  59. sprintf(msg,"Date: %02X/%02X/20%02X",rtc[4],rtc[5],rtc[6]);
  60. twi_lcd_text(msg);
  61. _delay_ms(500);
  62. }
  63. }


Schematic:

ATMega644P Software TWI Example
PCF8574T HD44780 Character LCD and DS1307 RTC

AVR Prototype Board:

ATMega644P Software TWI Example

 

ATMega644P Software TWI Example 

Click here to download its source file.

PCF8574T HD44780 Character LCD and DS3231 RTC

There are a lot of newer Real Time Clock chip that consumes lower energy, rich of functions and more precise. The DS3231 or DS3232 is a TWI Real Time Clock(RTC) that has some addional functions for example alarm setting and alarm triggering. Its timing data is very precise due to its internal clock and programmable capacitors.

ATMega644P Software TWI Example 

I bought a few Arduino DS3231 RTC AT2432N EEPROM module around 2USD. Its slave address and data/time data are quivalent to the DS1307. So I don't need to write additional firmware for this example. It work the same way as above. I just modify some lines of code.

Schematic:

ATMega644P Software TWI Example
Schematic

The DS3231 and PCF8574T connected on a single TWI bus.

AVR Experiment Board:

ATMega644P Software TWI Example


 

MCP23017 16-bit GPIO Example

The Microchip MCP23017 is a TWI 16-bit GPIO expansion chip. It I/O port and internal structure is well organized. However it's more expensive than the PCF8574. The PCF8574 is more popular and widely available at very low cost.

ATMega644P TWI MCP23017 GPIO Example
MCP23017 Chip and Module

 

ATMega644P TWI MCP23017 GPIO Example
Package Types

For more example about using this chip please see this post. 

In this example the ATMega644P implements its software TWI to read data from GPB and send it back to GPA of the MCP23017. 

ATMega644P Software TWI Example
MCP23017 16-bit GPIO Example

 

Source Code "main.c":

  1. /*
  2. * 11-soft_twi_mcp23017_gpio.c
  3. *
  4. * Created: 2/12/2026 9:40:26 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 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. // MCP2017 Routine
  88. //MCP23017 TWI Slave Address
  89. const char MCP23017_W=0x40;
  90. const char MCP23017_R=0x41;

  91. //IOCON.BANK=0
  92. enum BANK0{
  93. IODIRA=0,IODIRB,IPOLA,IPOLB,GPINTENA,GPINTENB,DEFVALA,
  94. DEFVALB,INTCONA,INTCONB,IOCON1,IOCON2,GPPUA,GPPUB,
  95. INTFA,INTFB,INTCAPA,INTCAPB,GPIOA,GPIOB,OLATA,OLATB
  96. };


  97. void mcp23017_write(char address,char data){
  98. twi_start();
  99. /*Select the write address*/
  100. twi_write(MCP23017_W);
  101. /*Select a register address*/
  102. twi_write(address);
  103. /*Send configuration data*/
  104. twi_write(data);
  105. twi_stop();
  106. }

  107. unsigned char mcp23017_read(char address){
  108. /*Select a specific address*/
  109. twi_start();
  110. twi_write(MCP23017_W);
  111. twi_write(address);
  112. twi_stop();
  113. /*Read data from the given address*/
  114. twi_start();
  115. twi_write(MCP23017_R);
  116. unsigned char i2cData=twi_read();
  117. twi_stop();
  118. return i2cData;
  119. }

  120. int main(void)
  121. {
  122. /* Replace with your application code */
  123. char data;
  124. /*Set GPIOA To Output*/
  125. mcp23017_write(IODIRA,0x00);
  126. /*Set GPIOB To Input With Pull Up Enable*/
  127. mcp23017_write(IODIRB,0xFF);
  128. mcp23017_write(GPPUB,0xFF);
  129. while (1)
  130. {
  131. /*Read GPIOB*/
  132. data=mcp23017_read(GPIOB);
  133. _delay_ms(10);
  134. /*Send Data to OLATA*/
  135. mcp23017_write(OLATA,data);
  136. _delay_ms(10);
  137. }
  138. }


I use C enum to keep all registers of the MCP23017 chip. 

Schematic and Simulation: 

ATMega644P Software TWI Example
Schematic and Simulation

I tested this example on my AVR Prototype Board.

ATMega644P Software TWI Example
AVR Prototype Board

 

ATMega644P Software TWI Example
AVR Prototype Board

 

ATMega644P Software TWI Example
AVR Prototype Board

We can use this chip to make a 4x4 matrix keypad and character LCD controlling etc. For more examples about using this chip please visit these posts.

For a full list of ATMega644P AVR C programming tutorials see this page.

 

 

 

 

 

  

 

 

320x50

Search This Blog

tyro-728x90