3.13) C programs to transfer and receive information using atmega16 via SPI and I2C

posted by Hamid Sayyed • November 12, 2025 0 Comments

In any embedded system, data communication between different modules or external peripherals plays a key role. The ATmega16 microcontroller supports various serial communication protocols like SPI (Serial Peripheral Interface) and I2C (Inter-Integrated Circuit), which help exchange data efficiently between devices. These protocols are widely used in embedded projects involving real-time data exchange such as connecting sensors, memory chips, display modules, or even other microcontrollers. In this article, we will study the practical implementation of SPI and I2C in ATmega16 using C language. We will see how to initialize these modules, transmit and receive data, and understand the internal registers involved. Each section includes example programs that demonstrate how data transfer takes place between master and slave devices using these protocols.

SPI Communication with ATmega16

SPI is a fast, full-duplex, synchronous communication protocol that uses four main signals: MOSI, MISO, SCK, and SS. One device acts as the master that controls the clock, and other devices act as slaves. It is ideal for short-distance, high-speed communication between microcontrollers and devices such as ADCs, DACs, and EEPROMs.

The master sends and receives data simultaneously using MOSI (Master Out Slave In) and MISO (Master In Slave Out) lines. Each bit is shifted on every clock pulse generated by the master.

Registers Used in SPI

RegisterDescription
SPCRSPI Control Register – enables SPI, sets master/slave mode, clock polarity, and phase.
SPSRSPI Status Register – contains status flags like SPIF (SPI Interrupt Flag).
SPDRSPI Data Register – holds data for transmission and reception.

SPI Master Program (ATmega16)


#include <avr/io.h>
#define F_CPU 8000000UL
#include <util/delay.h>

void SPI_MasterInit(void) {
    DDRB = (1<<PB5)|(1<<PB7)|(1<<PB4);  // MOSI, SCK, SS as output
    DDRB &= ~(1<<PB6);                   // MISO as input
    SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0); // Enable SPI, Master mode, Fosc/16
}

void SPI_MasterTransmit(char data) {
    SPDR = data;                           // Load data into buffer
    while(!(SPSR & (1<<SPIF)));           // Wait for transmission complete
}

int main(void) {
    SPI_MasterInit();
    while(1) {
        SPI_MasterTransmit('A');           // Transmit data
        _delay_ms(500);
    }
}
  

SPI Slave Program (ATmega16)


#include <avr/io.h>

void SPI_SlaveInit(void) {
    DDRB = (1<<PB6);  // MISO as output
    SPCR = (1<<SPE);  // Enable SPI in slave mode
}

char SPI_SlaveReceive(void) {
    while(!(SPSR & (1<<SPIF)));  // Wait until data received
    return SPDR;                  // Return received data
}

int main(void) {
    char data;
    SPI_SlaveInit();
    while(1) {
        data = SPI_SlaveReceive(); // Receive from master
        // Further processing or display
    }
}
  

I2C Communication with ATmega16

I2C is a two-wire serial communication protocol that allows multiple devices to share the same bus using SDA (Serial Data) and SCL (Serial Clock) lines. It is a half-duplex, multi-master protocol where every slave is identified by a unique address. This makes it ideal for applications where several sensors or ICs need to communicate using just two wires.

I2C communication begins with a START condition, followed by the slave address and the R/W bit. Each byte transmitted is acknowledged by the receiver, ensuring reliable communication.

Registers Used in I2C (TWI)

RegisterDescription
TWBRBit Rate Register – sets the I2C clock frequency.
TWCRControl Register – manages start, stop, ACK, and enable bits.
TWSRStatus Register – holds status flags for various I2C operations.
TWDRData Register – stores the data to transmit or receive.
TWARAddress Register – holds slave address in slave mode.

I2C Master Program for Data Transmission


#include <avr/io.h>
#define F_CPU 8000000UL
#include <util/delay.h>

void I2C_Init(void) {
    TWSR = 0x00;
    TWBR = 0x47; // Set bit rate for 100kHz SCL
}

void I2C_Start(void) {
    TWCR = (1<<TWSTA)|(1<<TWEN)|(1<<TWINT);
    while (!(TWCR & (1<<TWINT)));
}

void I2C_Stop(void) {
    TWCR = (1<<TWSTO)|(1<<TWEN)|(1<<TWINT);
}

void I2C_Write(unsigned char data) {
    TWDR = data;
    TWCR = (1<<TWEN)|(1<<TWINT);
    while (!(TWCR & (1<<TWINT)));
}

int main(void) {
    I2C_Init();
    while(1) {
        I2C_Start();
        I2C_Write(0xA0); // Slave address + write bit
        I2C_Write(0x55); // Data byte
        I2C_Stop();
        _delay_ms(1000);
    }
}
  
Both SPI and I2C are critical for communication in embedded systems. While SPI offers speed and simplicity, I2C offers flexibility and reduced pin usage for connecting multiple devices.

Conclusion

This article covered the implementation of SPI and I2C data transmission and reception using ATmega16 in C. These protocols are the backbone of most embedded communication systems. Understanding how to initialize, configure, and use these communication modules gives students the ability to build projects that connect multiple devices seamlessly. Mastery of SPI and I2C programming helps in designing reliable, efficient embedded systems for real-world applications.

Comments

Post a Comment

Subscribe to Post Comments [Atom]