4. 软件模拟IIC通信(stm32为例)
2025-09-19 来源:cnblogs
1. 硬件连接
1.1 从设备端引脚连接

1.2 mcu端引脚连接

2. iic初始化
1 #include 'delay.h'
2
3 #define IIC_SCL PBout(8) //SCL(输出)
4 #define IIC_SDA PBout(9) //SDA(输出)
5 #define IIC_SDA_R PBin(9) //SDA(输入)
6
7 void AT24C0X_IIC_Init(void)
8 {
9 GPIO_InitTypeDef GPIO_InitStructure;
10
11 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能gpiob时钟
12 //GPIOB8,B9初始化
13 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
14 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //普通输出模式
15 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出,增加输出电流能力
16 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz,高速响应
17 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //使能上拉电阻
18 GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化gpio
19 PBout(8)=1; // iic_scl,时序图初始为高电平
20 PBout(9)=1; // iic_sda,时序图初始为高电平
21 }
22 void sda_pin_mode(GPIOMode_TypeDef mode) // GPIO_Mode_IN,GPIO_Mode_OUT
23 {
24 GPIO_InitTypeDef GPIO_InitStructure;
25
26 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能gpiob时钟
27 //GPIOB8,B9初始化
28 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
29 GPIO_InitStructure.GPIO_Mode = mode; //普通输入或出模式
30 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出,增加输出电流能力
31 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz,高速响应
32 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //使能上拉电阻
33 GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化gpio
34 }
3.时序图
3.1 开始和终止的定义(Start and Stop Definition)

3.1.1 开始信号
1 void iic_start(void)
2 {
3 //保证sda引脚为输出模式
4 sda_pin_mode(GPIO_Mode_OUT);
5
6 IIC_SCL = 1; // 1.初始电平都为高电平
7 IIC_SDA = 1;
8 delay_us(5); // 延时5us,保证电平稳定有效
9
10 IIC_SDA = 0; // 2.scl为高期间,sda:1->0
11 delay_us(5); // 产生start信号
12
13 IIC_SCL = 0; // 3.钳住iic总线,准备开始发送数据
14 delay_us(5); //
15 }
3.1.2 终止信号(停止信号)
void iic_stop(void)
{
//保证sda引脚为输出模式
sda_pin_mode(GPIO_Mode_OUT);
IIC_SCL = 1; // 1.初始电平为高电平
IIC_SDA = 0; // 初始电平为低电平
delay_us(5);
IIC_SDA = 1; // 2.scl为高期间,sda:0->1
delay_us(5);
}
3.2 输出应答(Data Validity)

3.2.1 从机应答(主机等待从机应答)
1 uint8_t iic_wait_ack(void)
2 {
3 uint8_t ack = 0;
4
5 //保证sda引脚为输出模式
6 sda_pin_mode(GPIO_Mode_IN);
7 IIC_SCL = 1; // 在scl=1期间,读取sda
8 delay_us(5);
9
10 if(IIC_SDA_R)
11 {
12 ack = 1; // 若sda_r为1,则无应答
13 }
14 else
15 {
16 ack = 0; // 若sda_r为0,则有应答
17 }
18 return ack;
19 }
3.2.2 主机应答
void iic_ack(void)
{
//保证sda引脚为输出模式
sda_pin_mode(GPIO_Mode_OUT);
IIC_SCL = 0; // scl初始电平
IIC_SDA = 0; // sda初始电平
delay_us(5);
IIC_SCL = 1; // scl=1期间,
delay_us(5);
IIC_SDA = 0; // sda=0,主机应答
}
3.2.3 主机不应答
1 void iic_no_ack(void)
2 {
3 //保证sda引脚为输出模式
4 sda_pin_mode(GPIO_Mode_OUT);
5
6 IIC_SCL = 0; // scl初始电平
7 IIC_SDA = 1; // sda初始电平
8 delay_us(5);
9
10 IIC_SCL = 1; // scl=1期间,
11 delay_us(5);
12
13 IIC_SDA = 1; // sda=1,主机不应答
14 }
3.3 读写时序
3.3.1 数据有效性(Data Validity)

3.3.2 设备地址(Device Address)

3.3.3 写操作
1 void iic_write_byte(uint8_t byte)
2 {
3 int32_t i;
4 //保证sda引脚为输出模式
5 sda_pin_mode(GPIO_Mode_OUT);
6
7 IIC_SCL = 0;
8 delay_us(5);
9
10 for (i=7; i>=0; i--)
11 {
12 if (byte & (1<13 {
14 IIC_SDA = 1;
15 }
16 else
17 {
18 IIC_SDA = 0;
19 }
20 delay_us(5);
21
22 IIC_SCL = 1; //
23 delay_us(5);
24
25 IIC_SCL = 0; // 为下一次写数据做准备
26 delay_us(5);
27 }
28 }
3.3.4 读操作
1 uint8_t iic_read_byte(void)
2 {
3 uint8_t byte;
4 int32_t i;
5
6 //保证sda引脚为输出模式
7 sda_pin_mode(GPIO_Mode_IN);
8
9 IIC_SCL = 0; // 初始化为低电平
10 delay_us(5);
11
12 for (i=7; i>=0; i--)
13 {
14 IIC_SCL = 1; // 只有在scl=1时,才可以读数据
15 delay_us(5);
16
17 if (IIC_SDA_R)
18 {
19 byte |= 1<20 }
21 else
22 {
23 byte |= 0<24 }
25 IIC_SCL = 0; // 为读下一个字节的数据做准备
26 delay_us(5);
27 }
28 return byte;
29 }
3.4 向AT24CXX写数据
3.4.1 写一字节数据( Byte Write)

1 uint32_t AT24CXX_Byte_Write(uint8_t addr,uint8_t byte)
2 {
3 uint8_t ack;
4
5 // 1.发送开始信号
6 iic_start();
7
8 // 2.写设备地址,选择iic总线的设备,bit流由高到低(从最高位开始写)
9 iic_write_byte(0xA0);
10
11 // 3.等待从机应答(从机位EEPROM设备,主机位mcu设备)
12 ack = iic_wait_ack();
13 if (1 == ack)
14 {
15 printf('dev addr is error!rn');
16 return -1;
17 }
18
19 // 4.写入数据地址(数据在eeprom中要存放的位置)
20 iic_write_byte(addr);
21
22 // 5.等待从机应答
23 ack = iic_wait_ack();
24 if (1 == ack)
25 {
26 printf('data addr is error!rn');
27 return -2;
28 }
29
30 // 6.写入要存入EEPROM中的数据
31 iic_write_byte(byte);
32
33 // 7.等到从机应答
34 ack = iic_wait_ack();
35 if (1 == ack)
36 {
37 printf('data addr is error!rn');
38 return -3;
39 }
40
41 // 8.发送终止信号
42 iic_stop();
43
44 return 0;
45 }
3.4.2 写一页的数据(Page Write)

1 int32_t AT24C0X_Page_Write(uint8_t addr,uint8_t *pbuf,uint8_t len)
2 {
3 uint8_t ack;
4 uint8_t i;
5
6 // 1.发送起始信号
7 iic_start();
8
9 // 2.发送设备地址,iic总线寻址
10 iic_write_byte(0xA0); // 写设备地址(0xA0)
11
12 // 3.等待应答信号
13 ack = iic_wait_ack();
14 if (ack)
15 {
16 printf('dev addr is err!rn');
17 return -1;
18 }
19
20 // 4.发送要写入的数据地址
21 iic_write_byte(addr);
22
23 // 5.等待应答信号
24 ack = iic_wait_ack();
25 if (ack)
26 {
27 printf('data addr is err!rn');
28 return -2;
29 }
30
31 // 6.发送要写入的数据(多字节)
32 for (i=0; i 34 // 发送要写入的数据 35 iic_write_byte(pbuf[i]); 36 // 等待应答 37 ack = iic_wait_ack(); 38 if (ack) 39 { 40 printf('data is err!rn'); 41 return -3; 42 } 43 } 44 // 7.发送停止信号 45 iic_stop(); 46 } 3.4.3 写入多字节的数据(写入数据流) 1 int32_t AT24CXX_Write_Data(uint8_t Addr,uint8_t *pBuf,uint16_t Len) 2 { 3 while(Len--) 4 { 5 AT24CXX_Byte_Write(Addr,pBuf); 6 Addr++; // 目的地址(eeprom中存储数据的地址+1) 7 pBuf++; // 源地址(源数据的地址) 8 } 9 return 0; 10 } 3.5 从AT24CXX读数据 3.5.1 任意地址读一个byte(Random Read) 1 uint8_t AT24CXX_Read_Byte_Random(uint8_t addr) 2 { 3 uint8_t byte; 4 uint8_t ack; 5 6 // 1.发送开始信号 7 iic_start(); 8 9 // 2.写入要读的设备地址(硬件设备id),为写方向 10 iic_write_byte(0xA0); 11 12 // 3.等待从机应答信号 13 ack = iic_wait_ack(); 14 if (ack) 15 { 16 printf('When Master read randomly,what write slave device address is error! rn'); 17 return -1; 18 } 19 20 // 4.写入从机设备(eeprom)中,要读的位置 21 iic_write_byte(addr); 22 23 // 5.等待从机应答信号 24 ack = iic_wait_ack(); 25 if (ack) 26 { 27 printf('When Master read randomly,what write data address in slave is error! rn'); 28 return -2; 29 } 30 31 // 6.发送开始信号 32 iic_start(); 33 34 // 7.写入要读的设备地址(硬件设备id),为读方向 35 iic_write_byte(0xA1); 36 37 // 8.等待从机应答信号 38 ack = iic_wait_ack(); 39 if (ack) 40 { 41 printf('When Master read randomly,what write data address in slave is error! rn'); 42 return -2; 43 } 44 45 // 9.读取从机数据(eeprom) 46 byte = iic_read_byte(); 47 48 // 10.主机不应答 49 iic_no_ack(); 50 51 // 11.发送停止信号 52 iic_stop(); 53 54 return byte; 55 } 3.5.2 读出short或int型的数据 1 /** 2 * @brief 在AT24XX里面的指定地址开始读出长度为Len的数据,该函数用于读出 3 * 读出16bit或者32bit的数据 4 * @param ReadAddr:开始读出的地址 5 * @param Len: 读出数据的长度2,4 6 * 7 * @retval 数据 8 */ 9 uint32_t AT24CXX_ReadLenByte(uint16_t ReadAddr,uint8_t Len) 10 { 11 uint8_t t; 12 uint32_t temp=0; 13 for(t=0;t 15 temp<<=8; 16 temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1); // 17 } 18 return temp; 19 } 3.5.3 读出epprom中一定长度的数据 1 /** 2 * @brief 在AT24XX里面的指定地址开始读出指定长度为Len的数据 3 * @param ReadAddr:开始读出的地址,对24c02为0-255 4 * @param pData: 输出数据数组首地址 5 * @param Len: 要读数据的个数 6 7 */ 8 void AT24CXX_Read_Data(uint16_t addr,uint8_t *pData, uint16_t DataLen) 9 { 10 while (DataLen--) 11 { 12 *pData++ = AT24CXX_Read_Byte_Random(addr++); 13 } 14 }
- 六大全新产品系列推出,MCX A微控制器家族迎来创新
- 意法半导体全新STM32C5系列,重新定义入门级微控制器性能与价值,赋能万千智能设备
- 从控制到系统:TI利用边缘AI重塑嵌入式MCU的边界
- 模组复用与整机重测在SRRC、CCC、CTA/NAL认证中的实践操作指南
- 有源晶振与无源晶振的六大区别详解
- 英飞凌持续巩固全球微控制器市场领导地位
- 使用 Keil Studio for Visual Studio Code开发 STM32 设备
- 蓝牙信道探测技术原理与开发套件实践
- LoRa、LoRaWAN、NB-IoT与4G DTU技术对比及工业无线方案选型分析
- Microchip 推出生产就绪型全栈边缘 AI 解决方案,赋能MCU和MPU实现 智能实时决策




