打印
[应用相关]

STM32 软件I2C读写

[复制链接]
20|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Jiangxiaopi|  楼主 | 2025-2-8 21:15 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一、引言
I2C(Inter - Integrated Circuit)是一种串行通信协议,常用于连接微控制器和各种外围设备,如传感器、EEPROM 等。在 STM32 开发中,除了使用硬件 I2C 接口,还可以通过软件模拟的方式实现 I2C 通信。软件 I2C 具有灵活性高、不受硬件资源限制等优点,适用于各种需要自定义通信时序的场景。本文将详细介绍 STM32 软件 I2C 读写的实现方法,并给出相应的代码示例。

二、I2C 协议基础
2.1 物理层
I2C 总线由两根线组成:串行数据线(SDA)和串行时钟线(SCL)。所有连接到 I2C 总线上的设备都通过这两根线进行通信,并且每个设备都有一个唯一的 7 位或 10 位地址。

2.2 协议层
I2C 通信基于主从模式,主设备负责发起通信并控制时钟信号,从设备根据主设备的指令进行响应。通信过程主要包括起始条件、地址帧、数据帧、应答信号和停止条件。

起始条件:SCL 为高电平时,SDA 由高电平变为低电平,表示通信开始。
地址帧:主设备发送从设备的地址,其中最低位表示读写方向(0 为写,1 为读)。
数据帧:主设备和从设备之间传输数据,每次传输一个字节。
应答信号:每传输一个字节后,接收方需要发送一个应答信号(ACK)或非应答信号(NACK)。
停止条件:SCL 为高电平时,SDA 由低电平变为高电平,表示通信结束。
三、STM32 软件 I2C 实现步骤
3.1 引脚初始化
选择两个 GPIO 引脚分别作为 SDA 和 SCL,并将它们初始化为开漏输出模式。

3.2 起始条件和停止条件的实现
通过控制 SDA 和 SCL 引脚的电平变化来产生起始条件和停止条件。

3.3 数据传输和应答信号处理
逐位发送和接收数据,并处理应答信号。

3.4 读写操作的实现
根据 I2C 协议,实现主设备对从设备的读写操作。

四、代码实现
4.1 引脚定义和初始化
#include "stm32f10x.h"

// SDA 和 SCL 引脚定义
#define I2C_SCL_PIN GPIO_Pin_6
#define I2C_SDA_PIN GPIO_Pin_7
#define I2C_GPIO_PORT GPIOB

// 引脚初始化函数
void I2C_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    // SCL 引脚初始化
    GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure);

    // SDA 引脚初始化
    GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;
    GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure);

    // 初始状态 SCL 和 SDA 都为高电平
    GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN | I2C_SDA_PIN);
}



4.2 起始条件和停止条件的实现
// 产生起始条件
void I2C_Start(void) {
    GPIO_SetBits(I2C_GPIO_PORT, I2C_SDA_PIN);
    GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN);
    delay_us(5);
    GPIO_ResetBits(I2C_GPIO_PORT, I2C_SDA_PIN);
    delay_us(5);
    GPIO_ResetBits(I2C_GPIO_PORT, I2C_SCL_PIN);
}

// 产生停止条件
void I2C_Stop(void) {
    GPIO_ResetBits(I2C_GPIO_PORT, I2C_SDA_PIN);
    GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN);
    delay_us(5);
    GPIO_SetBits(I2C_GPIO_PORT, I2C_SDA_PIN);
    delay_us(5);
}



4.3 数据传输和应答信号处理
// 发送一个字节数据
void I2C_SendByte(uint8_t byte) {
    uint8_t i;
    for (i = 0; i < 8; i++) {
        if (byte & 0x80) {
            GPIO_SetBits(I2C_GPIO_PORT, I2C_SDA_PIN);
        } else {
            GPIO_ResetBits(I2C_GPIO_PORT, I2C_SDA_PIN);
        }
        byte <<= 1;
        delay_us(2);
        GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN);
        delay_us(5);
        GPIO_ResetBits(I2C_GPIO_PORT, I2C_SCL_PIN);
        delay_us(2);
    }
}

// 接收一个字节数据
uint8_t I2C_ReceiveByte(void) {
    uint8_t i, byte = 0;
    GPIO_InitTypeDef GPIO_InitStructure;

    // 将 SDA 引脚设置为输入模式
    GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure);

    for (i = 0; i < 8; i++) {
        GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN);
        delay_us(5);
        byte <<= 1;
        if (GPIO_ReadInputDataBit(I2C_GPIO_PORT, I2C_SDA_PIN)) {
            byte |= 0x01;
        }
        GPIO_ResetBits(I2C_GPIO_PORT, I2C_SCL_PIN);
        delay_us(2);
    }

    // 将 SDA 引脚恢复为输出模式
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure);

    return byte;
}

// 发送应答信号
void I2C_SendACK(void) {
    GPIO_ResetBits(I2C_GPIO_PORT, I2C_SDA_PIN);
    delay_us(2);
    GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN);
    delay_us(5);
    GPIO_ResetBits(I2C_GPIO_PORT, I2C_SCL_PIN);
    delay_us(2);
    GPIO_SetBits(I2C_GPIO_PORT, I2C_SDA_PIN);
}

// 发送非应答信号
void I2C_SendNACK(void) {
    GPIO_SetBits(I2C_GPIO_PORT, I2C_SDA_PIN);
    delay_us(2);
    GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN);
    delay_us(5);
    GPIO_ResetBits(I2C_GPIO_PORT, I2C_SCL_PIN);
    delay_us(2);
}

// 等待应答信号
uint8_t I2C_WaitACK(void) {
    uint8_t ack;
    GPIO_InitTypeDef GPIO_InitStructure;

    // 将 SDA 引脚设置为输入模式
    GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure);

    GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN);
    delay_us(5);
    ack = GPIO_ReadInputDataBit(I2C_GPIO_PORT, I2C_SDA_PIN);
    GPIO_ResetBits(I2C_GPIO_PORT, I2C_SCL_PIN);
    delay_us(2);

    // 将 SDA 引脚恢复为输出模式
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure);

    return ack;
}



4.4 读写操作的实现
// 写数据到从设备
uint8_t I2C_WriteData(uint8_t slaveAddr, uint8_t regAddr, uint8_t data) {
    I2C_Start();
    I2C_SendByte(slaveAddr << 1); // 发送从设备地址(写模式)
    if (I2C_WaitACK()) {
        I2C_Stop();
        return 1;
    }
    I2C_SendByte(regAddr); // 发送寄存器地址
    if (I2C_WaitACK()) {
        I2C_Stop();
        return 1;
    }
    I2C_SendByte(data); // 发送数据
    if (I2C_WaitACK()) {
        I2C_Stop();
        return 1;
    }
    I2C_Stop();
    return 0;
}

// 从从设备读取数据
uint8_t I2C_ReadData(uint8_t slaveAddr, uint8_t regAddr, uint8_t *data) {
    I2C_Start();
    I2C_SendByte(slaveAddr << 1); // 发送从设备地址(写模式)
    if (I2C_WaitACK()) {
        I2C_Stop();
        return 1;
    }
    I2C_SendByte(regAddr); // 发送寄存器地址
    if (I2C_WaitACK()) {
        I2C_Stop();
        return 1;
    }

    I2C_Start();
    I2C_SendByte((slaveAddr << 1) | 0x01); // 发送从设备地址(读模式)
    if (I2C_WaitACK()) {
        I2C_Stop();
        return 1;
    }
    *data = I2C_ReceiveByte();
    I2C_SendNACK();
    I2C_Stop();
    return 0;
}



4.5 主函数示例
int main(void) {
    uint8_t data;

    I2C_GPIO_Init();

    // 写数据到从设备
    if (I2C_WriteData(0x50, 0x00, 0xAA) == 0) {
        // 写成功
    }

    // 从从设备读取数据
    if (I2C_ReadData(0x50, 0x00, &data) == 0) {
        // 读成功
    }

    while (1) {
        // 主循环
    }
}



五、代码解释
引脚初始化:将 SDA 和 SCL 引脚初始化为开漏输出模式,确保在通信过程中可以正确控制引脚电平。
起始条件和停止条件:通过控制 SDA 和 SCL 引脚的电平变化,产生起始条件和停止条件,标志着通信的开始和结束。
数据传输和应答信号处理:I2C_SendByte 和 I2C_ReceiveByte 函数用于逐位发送和接收数据,I2C_SendACK、I2C_SendNACK 和 I2C_WaitACK 函数用于处理应答信号。
读写操作:I2C_WriteData 函数用于向从设备的指定寄存器写入数据,I2C_ReadData 函数用于从从设备的指定寄存器读取数据。
主函数:在主函数中调用 I2C_WriteData 和 I2C_ReadData 函数进行读写操作。
六、注意事项
延时函数:代码中使用了 delay_us 函数进行延时,需要根据实际情况实现该函数,以确保通信时序的正确性。
从设备地址:在进行读写操作时,需要根据从设备的实际地址进行设置。
引脚电平:在读取 SDA 引脚状态时,需要将其设置为输入模式;在发送数据时,需要将其恢复为输出模式。
通过以上代码和解释,我们可以实现 STM32 软件 I2C 的读写功能,满足不同应用场景的需求。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/duierrorshuobu/article/details/145445441

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

11

主题

54

帖子

0

粉丝