关于IIC的一些理解
简单低速通信
简单的双向两线制总线协议标准,支持同步串行半双工通讯。
| 标准模式 | 100kbit/s | 
| 快速模式 | 400kbit/s | 
| 高速模式 | 3.4Mbit/s(大多数 IIC 设备不支持) | 
开漏模式允许总线上多个设备的输出线连接在一起,实现“线与”功能。在 IIC 总线中,多个主设备或从设备会连接到同一组 SDA 线(数据线)和 SCL(时钟线)上。当任意一个设备将信号线拉低时,整个总线的电平就会拉低;只有所有设备都释放总线(输出高阻态)时,总线才会在外部上拉电阻的作用下呈现高电平。这种特性使得多个设备可以共享总线进行通信,避免信号冲突。
物理层

SCL:时钟线,用于数据收发同步;
SDA:串行数据总线,用高低电平表示数据;
支持一主多从,也支持多主多从;
每个设备都有唯一的地址,主机通过这个地址与从设备通信;
总线通过上拉电阻接到电源,设备空闲时,输出高阻态,当所有设备都空闲时,都输出高阻态,由上拉电阻把总线拉成高电平;
协议层
位传输:高位先行;
起始信号:SDA 由高变低,SCL 保持高电平;
停止信号:SDA 由低变高,SCL 保持高电平;

传输地址:主机通过 SDA 线发送设备地址来查找从机;IIC 规定设备地址可以是 7 位或 10 位,实际中 7 位地址应用更为广泛;
数据有效性:SCL 高电平范围内去读取 SDA 线上的数据,只有在 SCL 处于低电平时,SDA 才能改变;SDA 线上的数据必须在时钟的高电平周期保持稳定。

响应:接收方接收到数据后,要给发送方发送响应,有两种响应
1.应答响应:给发送方发送一个低电平;
2.非应答响应:给发送方发送一个高电平;

代码实现


1.软件模拟 IIC
IIC 操作配置
/* 宏定义:应答与非应答 */
#define ACK 0
#define NACK 1
/* 宏定义:时钟线拉高拉低 */
#define SCL_HIGH (GPIOB->ODR |= GPIO_ODR_ODR10)
#define SCL_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR10)
/* 宏定义:数据线拉高拉低 */
#define SDA_HIGH (GPIOB->ODR |= GPIO_ODR_ODR11)
#define SDA_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR11)
/* 宏定义:数据线读取 */
#define SDA_READ (GPIOB->IDR & GPIO_IDR_IDR11)
/* 宏定义:操作延时 */
#define IIC_DELAY Com_Delay_Us(5)IIC 实际操作声明
/**
 * @brief IIC初始化
 *
 */
void Dri_IIC_Init(void);
/**
 * @brief 发出起始信号
 *
 */
void Dri_IIC_Start(void);
/**
 * @brief 发送停止信号
 *
 */
void Dri_IIC_Stop(void);
/**
 * @brief 主机发送应答信号
 *
 */
void Dri_IIC_sendACK(void);
/**
 * @brief 主机发送非应答信号
 *
 */
void Dri_IIC_sendNACK(void);
/**
 * @brief 主机等待从设备发来的应答信号
 *
 * @return uint8_t 0:从机应答 1:从机非应答
 */
uint8_t Dri_IIC_wait4ACK(void);
/**
 * @brief IIC发送一个字节
 *
 * @param byte 要发送的字节
 */
void Dri_IIC_sendByte(uint8_t byte);
/**
 * @brief 主机从从设备接收一个字节
 *
 * @return uint8_t 从机发送的字节
 */
uint8_t Dri_IIC_RceiveByte(void);
IIC 实际操作实现
/**
 * @brief IIC初始化
 *
 */
void Dri_IIC_Init(void)
{
    /* 使能IIC2时钟 */
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    /* GPIO工作模式配置 通用开漏输出 MODE:11 CNF:01 */
    GPIOB->CRH |= (GPIO_CRH_MODE10 | GPIO_CRH_MODE11);
    GPIOB->CRH &= ~(GPIO_CRH_CNF10_1 | GPIO_CRH_CNF11_1);
    GPIOB->CRH |= (GPIO_CRH_CNF10_0 | GPIO_CRH_CNF11_0);
}
/**
 * @brief 发出起始信号
 *
 */
void Dri_IIC_Start(void)
{
    SCL_HIGH;
    SDA_HIGH;
    IIC_DELAY;
    SDA_LOW;
    IIC_DELAY;
    // SCL_LOW;
    // IIC_DELAY;
}
/**
 * @brief 发送停止信号
 *
 */
void Dri_IIC_Stop(void)
{
    SCL_HIGH;
    SDA_LOW;
    IIC_DELAY;
    SDA_HIGH;
    IIC_DELAY;
}
/**
 * @brief 主机发送应答信号
 *
 */
void Dri_IIC_sendACK(void)
{
    SCL_LOW;
    SDA_HIGH;
    IIC_DELAY;
    SDA_LOW;
    IIC_DELAY;
    SCL_HIGH;
    IIC_DELAY;
    SCL_LOW;
    IIC_DELAY;
    SDA_HIGH;
    IIC_DELAY;
}
/**
 * @brief 主机发送非应答信号
 *
 */
void Dri_IIC_sendNACK(void)
{
    SCL_LOW;
    SDA_HIGH;
    IIC_DELAY;
    SCL_HIGH;
    IIC_DELAY;
    SCL_LOW;
    IIC_DELAY;
}
/**
 * @brief 主机等待从设备发来的应答信号
 *
 * @return uint8_t 0:从机应答 1:从机非应答
 */
uint8_t Dri_IIC_wait4ACK(void)
{
    //  ack;
    SCL_LOW;
    SDA_HIGH;
    IIC_DELAY;
    SCL_HIGH;
    IIC_DELAY;
    uint16_t ack = SDA_READ;
    SCL_LOW;
    IIC_DELAY;
    return ack ? NACK : ACK;
}
/**
 * @brief IIC发送一个字节
 *
 * @param byte 要发送的字节
 */
void Dri_IIC_sendByte(uint8_t byte)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        SCL_LOW;
        SDA_LOW;
        IIC_DELAY;
        if (byte & 0x80) // 判断最高位是否为1
        {
            SDA_HIGH;
        }
        else
        {
            SDA_LOW;
        }
        IIC_DELAY;
        SCL_HIGH;
        IIC_DELAY;
        SCL_LOW;
        IIC_DELAY;
        /* byte左移 */
        byte <<= 1;
    }
}
/**
 * @brief 主机从EEPROM接收一个字节
 *
 * @return uint8_t 从机发送的字节
 */
uint8_t Dri_IIC_RceiveByte(void)
{
   
    uint8_t data = 0;
    for (uint8_t i = 0; i < 8; i++)
    {
        SCL_LOW;
        IIC_DELAY;
        SCL_HIGH;
        IIC_DELAY;
        data <<= 1;   // 先左移一位,防止左移溢出
        if (SDA_READ) // 主机发送应答信号
        {
            data |= 0x01;
        }
        
        SCL_LOW;
        IIC_DELAY;
    }
    return data;
}
M24C02 操作声明
#include "Dri_IIC.h"
#include "Dri_UART.h"
/* 宏定义:地址读写 */
#define W_ADDR 0xA0
#define R_ADDR 0xA1
/**
 * @brief EEPROM初始化
 *
 */
void Int_EEPROM_Init(void);
/**
 * @brief 向EEPROM中写入一个字节
 *
 * @param byte 待写入字节
 * @param inner_addr 写入字节的位置
 */
void Int_EEPROM_WriteByte(uint8_t byte, uint8_t inner_addr);
/**
 * @brief 从EEPROM中读取一个字节
 *
 * @param inner_addr 读取字节的内部地址
 * @return uint8_t 返回值
 */
uint8_t Int_EEPROM_ReadByte(uint8_t inner_addr);
/**
 * @brief 页写
 *
 * @param inner_addr 起始地址
 * @param bytes 待写入数据
 * @param size 待写入数据长度
 */
void Int_EEPROM_PageWrite(uint8_t inner_addr, uint8_t *bytes, uint8_t size);
/**
 * @brief 页读
 *
 * @param inner_addr 起始地址
 * @param buffer 缓冲区
 * @param size 缓冲区长度
 */
void Int_EEPROM_PageRead(uint8_t inner_addr, uint8_t *buffer, uint8_t size);M24C02 操作实现
/**
 * @brief EEPROM初始化
 *
 */
void Int_EEPROM_Init(void)
{
    Dri_IIC_Init();
}
/**
 * @brief 向EEPROM中写入一个字节
 *
 * @param byte 待写入字节
 * @param inner_addr 写入字节的位置
 */
void Int_EEPROM_WriteByte(uint8_t byte, uint8_t inner_addr)
{
    Dri_IIC_Start();
    /* 发送写地址 */
    Dri_IIC_sendByte(W_ADDR);
    uint8_t ack = Dri_IIC_wait4ACK();
    if (ack == ACK)
    {
        Dri_IIC_sendByte(inner_addr);
        Dri_IIC_wait4ACK();
        /* 发送具体数据 */
        Dri_IIC_sendByte(byte);
        Dri_IIC_wait4ACK();
        printf("写入成功\r\n");
        Dri_IIC_Stop();
    }
    /* 延时5ms:等待写入周期结束 */
    Com_Delay_Ms(5);
}
/**
 * @brief 从EEPROM中读取一个字节
 *
 * @param inner_addr 读取字节的内部地址
 * @return uint8_t 返回值
 */
uint8_t Int_EEPROM_ReadByte(uint8_t inner_addr)
{
    uint8_t data = 0;
    Dri_IIC_Start();
    Dri_IIC_sendByte(W_ADDR); // 假写
    Dri_IIC_wait4ACK();
    Dri_IIC_sendByte(inner_addr);
    Dri_IIC_wait4ACK();
    Dri_IIC_Start();
    Dri_IIC_sendByte(R_ADDR); // 真读
    Dri_IIC_wait4ACK();
    data = Dri_IIC_RceiveByte();
    printf("读取成功\r\n");
    Dri_IIC_sendNACK(); // 发送一个非应答
    Dri_IIC_Stop();
    return data;
}
/**
 * @brief 页写
 *
 * @param inner_addr 起始地址
 * @param bytes 待写入数据
 * @param size 待写入数据长度
 */
void Int_EEPROM_PageWrite(uint8_t inner_addr, uint8_t *bytes, uint8_t size)
{
    Dri_IIC_Start();
    Dri_IIC_sendByte(W_ADDR);
    uint8_t ack = Dri_IIC_wait4ACK();
    if (ack == ACK)
    {
        Dri_IIC_sendByte(inner_addr);
        Dri_IIC_wait4ACK();
        for (uint8_t i = 0; i < size; i++)
        {
            Dri_IIC_sendByte(bytes[i]);
            Dri_IIC_wait4ACK();
        }
        Dri_IIC_Stop();
    }
    Com_Delay_Ms(5);
}
/**
 * @brief 页读
 *
 * @param inner_addr 起始地址
 * @param buffer 缓冲区
 * @param size 缓冲区长度
 */
void Int_EEPROM_PageRead(uint8_t inner_addr, uint8_t *buffer, uint8_t size)
{
    Dri_IIC_Start();
    Dri_IIC_sendByte(W_ADDR); // 假写
    Dri_IIC_wait4ACK();
    Dri_IIC_sendByte(inner_addr);
    Dri_IIC_wait4ACK();
    Dri_IIC_Start();
    Dri_IIC_sendByte(R_ADDR);
    Dri_IIC_wait4ACK();
    for (uint8_t i = 0; i < size; i++)
    {
        buffer[i] = Dri_IIC_RceiveByte();
        if (i < size - 1)
        {
            Dri_IIC_sendACK();
        }
        else
        {
            Dri_IIC_sendNACK();
        }
    }
    Dri_IIC_Stop();
}主函数测试
int main()
{
	Dri_UART_Init();
	Int_EEPROM_Init();
	printf("STM32‘s EEPROM exampl...\r\n");
	// 向EEPROM依次写入数据
	Int_EEPROM_WriteByte('g', 0x00);
	Int_EEPROM_WriteByte('h', 0x01);
	Int_EEPROM_WriteByte('r', 0x02);
	// 读取字符
	uint8_t byte1 = Int_EEPROM_ReadByte(0x00);
	uint8_t byte2 = Int_EEPROM_ReadByte(0x01);
	uint8_t byte3 = Int_EEPROM_ReadByte(0x02);
	printf("byte1=%c\r\n", byte1);
	printf("byte2=%c\r\n", byte2);
	printf("byte3=%c\r\n", byte3);
	/* 写入多个字符 */
	Int_EEPROM_PageWrite(0x000, "高浩然", 9);
	/* 读取多个字符 */
	uint8_t buffer[100] = {0};
	Int_EEPROM_PageRead(0x00, buffer, 9);
	printf("buffer=%s\r\n", buffer);
	/* 验证写入和读取的字符串是否一致 */
	char *str = "高浩然";
	if (memcmp(str, buffer, 9) == 0)
	{
		printf("写入和读取的字符串一致\r\n");
	}
	else
	{
		printf("写入和读取的字符串不一致\r\n");
	}
	while (1)
	{
	};
}
结果
https://p.sda1.dev/24/fd6873fdc9d45d38696f2300c74198a4/Snipaste_2025-06-16_00-22-52.png

硬件模拟
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号