Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing Software-Driven I2C Communication on STM32 Microcontrollers

Tech 1

Core I2C Timing Specifications

All I2C bus operations are built on 6 fundamental timing sequences:

  1. Start Condition: Triggered when SDA transitions from high to low while SCL is held at a high level. This event claims control of the idle I2C bus.
  2. Stop Condition: Triggered when SDA transitions from low to high while SCL is held at a high level. This event releases control of the I2C bus.
  3. Byte Transmission: During SCL low periods, the host places individual bits on the SDA line (most significant bit first), then drives SCL high. The slave samples the SDA value during the SCL high window, so SDA must remain stable while SCL is high. Repeat this process 8 times to transmit a full 8-bit byte.
  4. Byte Reception: The host first releases the SDA line before initiating reecption. During SCL low periods, the slave places individual bits on the SDA line (most significant bit first), then releases SCL to go high. The host samples the SDA value during the SCL high window, with SDA not allowed to change during this period. Repeat 8 times to receive a full 8-bit byte.
  5. Acknowledge Transmission: After receiving a full byte, the host sends a 1-bit response on the next SCL cycle. A value of 0 represents an acknowledge (ACK), while 1 represents a non-acknowledge (NACK).
  6. Acknowledge Reception: After transmitting a full byte, the host releases the SDA line then reads a 1-bit response from the slave on the next SCL cycle. A value of 0 indicates the slave successfully received the transmission, while 1 indicates reception failure or no slave present at the target address.

Software I2C Implementation Code

SoftI2C.c

#include "stm32f10x.h"
#include "SoftI2C.h"
#include "Delay.h"

static void SoftI2C_SetSCL(uint8_t level)
{
    GPIO_WriteBit(SOFT_I2C_PORT, SOFT_I2C_SCL_PIN, (BitAction)level);
    Delay_us(12);
}

static void SoftI2C_SetSDA(uint8_t level)
{
    GPIO_WriteBit(SOFT_I2C_PORT, SOFT_I2C_SDA_PIN, (BitAction)level);
    Delay_us(12);
}

static uint8_t SoftI2C_ReadSDA(void)
{
    uint8_t input_level = GPIO_ReadInputDataBit(SOFT_I2C_PORT, SOFT_I2C_SDA_PIN);
    Delay_us(12);
    return input_level;
}

void SoftI2C_Init(void)
{
    RCC_APB2PeriphClockCmd(SOFT_I2C_GPIO_CLK, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitCfg;
    GPIO_InitCfg.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitCfg.GPIO_Pin = SOFT_I2C_SCL_PIN | SOFT_I2C_SDA_PIN;
    GPIO_InitCfg.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(SOFT_I2C_PORT, &GPIO_InitCfg);
    
    GPIO_SetBits(SOFT_I2C_PORT, SOFT_I2C_SCL_PIN | SOFT_I2C_SDA_PIN);
}

void SoftI2C_GenerateStart(void)
{
    SoftI2C_SetSDA(1);
    SoftI2C_SetSCL(1);
    SoftI2C_SetSDA(0);
    SoftI2C_SetSCL(0);
}

void SoftI2C_GenerateStop(void)
{
    SoftI2C_SetSDA(0);
    SoftI2C_SetSCL(1);
    SoftI2C_SetSDA(1);
}

void SoftI2C_TransmitByte(uint8_t data)
{
    for(uint8_t bit_pos = 7; bit_pos < 8; bit_pos--)
    {
        SoftI2C_SetSDA((data >> bit_pos) & 0x01);
        SoftI2C_SetSCL(1);
        SoftI2C_SetSCL(0);
    }
}

uint8_t SoftI2C_ReceiveByte(void)
{
    uint8_t received_data = 0x00;
    SoftI2C_SetSDA(1);
    for(uint8_t bit_pos = 7; bit_pos < 8; bit_pos--)
    {
        SoftI2C_SetSCL(1);
        if(SoftI2C_ReadSDA() == 1)
        {
            received_data |= (1 << bit_pos);
        }
        SoftI2C_SetSCL(0);
    }
    return received_data;
}

void SoftI2C_SendAck(uint8_t ack_bit)
{
    SoftI2C_SetSDA(ack_bit);
    SoftI2C_SetSCL(1);
    SoftI2C_SetSCL(0);
}

uint8_t SoftI2C_GetAck(void)
{
    uint8_t ack_status;
    SoftI2C_SetSDA(1);
    SoftI2C_SetSCL(1);
    ack_status = SoftI2C_ReadSDA();
    SoftI2C_SetSCL(0);
    return ack_status;
}

SoftI2C.h

#ifndef __SOFT_I2C_H
#define __SOFT_I2C_H

// Configuration Macros - Modify these values to change I2C pin mapping
#define SOFT_I2C_GPIO_CLK    RCC_APB2Periph_GPIOB
#define SOFT_I2C_PORT        GPIOB
#define SOFT_I2C_SCL_PIN     GPIO_Pin_10
#define SOFT_I2C_SDA_PIN     GPIO_Pin_11

void SoftI2C_Init(void);
void SoftI2C_GenerateStart(void);
void SoftI2C_GenerateStop(void);
void SoftI2C_TransmitByte(uint8_t data);
uint8_t SoftI2C_ReceiveByte(void);
void SoftI2C_SendAck(uint8_t ack_bit);
uint8_t SoftI2C_GetAck(void);

#endif

Test Main Function

The test code below verifies basic I2C functionality by checking for the presence of an MPU6050 sensor at address 0x68 (transmitted as 0xD0, the 7-bit address shifted left 1 bit with the write bit set to 0). A returned ACK value of 0 confirms the device is present, while 1 indicates no device detected at the target address.

#include "stm32f10x.h"
#include "OLED.h"
#include "SoftI2C.h"

int main(void)
{
    OLED_Init();
    SoftI2C_Init();
    
    SoftI2C_GenerateStart();
    SoftI2C_TransmitByte(0xD0);
    uint8_t dev_ack = SoftI2C_GetAck();
    SoftI2C_GenerateStop();
    
    OLED_ShowNum(1, 1, dev_ack, 3);
    
    while(1);
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.