关于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号