I2C学习笔记-软件模拟I2C
- 其他
- 2025-08-27 06:12:02

I2C学习笔记(软件模拟) 介绍GPIO的配置信号的展示起始信号 与 停止信号应答信号(非应答信号)检测应答信号发送一个字节数据接收一个字节数据 硬件配置实物测试 介绍
I2C的信号大概有 起始信号、应答信号、停止信号、写数据、读数据、无应答信号等,每个信号都有其不同的特点时序要求。
参考视频思路: .youtube /watch?v=6IAkYpmA1DQ
参考资料:正点原子HAL库介绍
GPIO的配置 /** * @brief 初始化IIC * @param 无 * @retval 无 */ void iic_init(void) { GPIO_InitTypeDef gpio_init_struct; IIC_SCL_GPIO_CLK_ENABLE(); /* SCL引脚时钟使能 */ IIC_SDA_GPIO_CLK_ENABLE(); /* SDA引脚时钟使能 */ gpio_init_struct.Pin = IIC_SCL_GPIO_PIN; gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */ gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */ gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */ HAL_GPIO_Init(IIC_SCL_GPIO_PORT, &gpio_init_struct);/* SCL */ gpio_init_struct.Pin = IIC_SDA_GPIO_PIN; gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD; /* 开漏输出 */ HAL_GPIO_Init(IIC_SDA_GPIO_PORT, &gpio_init_struct);/* SDA */ /* SDA引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */ iic_stop(); /* 停止总线上所有设备 */ } /* 引脚 定义 */ #define IIC_SCL_GPIO_PORT GPIOB #define IIC_SCL_GPIO_PIN GPIO_PIN_6 #define IIC_SCL_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */ #define IIC_SDA_GPIO_PORT GPIOB #define IIC_SDA_GPIO_PIN GPIO_PIN_7 #define IIC_SDA_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */ /******************************************************************************************/ /* IO操作 */ #define IIC_SCL(x) do{ x ? \ HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_SET) : \ HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_RESET); \ }while(0) /* SCL */ #define IIC_SDA(x) do{ x ? \ HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_SET) : \ HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_RESET); \ }while(0) /* SDA */ #define IIC_READ_SDA HAL_GPIO_ReadPin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN) /* 读取SDA */ 信号的展示 起始信号 与 停止信号起始信号:当SCL为高电平时,SDA由高电平向低电平跳变
停止信号:当SCL位高电平时,SDA由低电平向高电平跳变
代码实现
/*SCL SAD都由1跳变到0*/ void I2CStart(void) { /* SCL为高电平期间, SDA从高电平往低电平跳变*/ IIC_SDA ( 1 ); IIC_SCL ( 1 ); iic_delay( ); IIC_SDA ( 0 ); iic_delay( ); IIC_SCL ( 0 ); iic_delay( ); /* 钳住总线, 准备发送/接收数据 */ }抓包数据
/*SCL SDA 都由0跳变到1*/ void I2CStop(void) { /* SCL为高电平期间, SDA从低电平往高电平跳变*/ IIC_SDA ( 0 ); iic_delay( ); IIC_SCL ( 1 ); iic_delay( ); IIC_SDA ( 1 ); /* 发送总线停止信号*/ iic_delay( ); } 应答信号(非应答信号)在发送完数据后,SCL为高电平,如果SDA为低电平则为应答信号。因为I2C外部默认上拉,如果为低电平时就说明从机在响应了,如果还是高电平就说明从机没有动作。
数据线为低位时,表示应答
void iic_ack(void) { IIC_SCL (0); iic_delay( ); IIC_SDA (0); /* 数据线为低电平,表示应答 */ iic_delay( ); IIC_SCL (1); iic_delay( ); }数据线为高位时,说明从机没动作,被上拉至高电平,说明没有应答。
void iic_nack(void) { IIC_SCL (0); iic_delay( ); IIC_SDA (1); /* 数据线为高电平,表示非应答 */ iic_delay( ); IIC_SCL (1); iic_delay( ); } 检测应答信号 检测应答信号,是在SCL为高电平的时候检测的,所以首先SDA为高电平,上拉电阻的存在,表示是释放了SDA,然后将SCL拉高,延时等待SDA是否为低电平,如果SDA为低电平,表示是从机发来的应答信号,为高电平则说明从机没有应答。 uint8_t iic_wait_ack (void) /* return 1:fail 0:succeed*/ { IIC_SDA (1); /* 主机释放SDA线 */ iic_delay( ); IIC_SCL (1); /* 从机返回ACK*/ iic_delay( ); if ( IIC_READ_SDA ) /* SCL高电平读取SDA状态*/ { iic_stop(); /* SDA高电平表示从机nack */ return 1; } IIC_SCL(0); /* SCL低电平表示结束ACK检查 */ iic_delay( ); return 0; } 发送一个字节数据一个字节数据默认为8位,首先先发高位,
0x80就是 1000 0000,所以经过8次循环,每一次取得最高位然后左移7 发送出去,发送出去后数据位左移动一位,用于下一次循环的发送。
当SCL为高电平时,数据位有效,所以
void iic_send_byte(uint8_t data) { for (uint8_t t = 0; t < 8; t++) { /* 高位先发 */ IIC_SDA((data & 0x80) >> 7); iic_delay( ); IIC_SCL ( 1 ); iic_delay( ); IIC_SCL ( 0 ); data <<= 1; /* 左移1位, 用于下一次发送 */ } IIC_SDA ( 1 ); /* 发送完成,主机释放SDA线 */ }波形图分析:
接收一个字节数据接收数据,当数据在SCL为高电平时,说明数据有效。也会是先把SCL拉高,延时等待,让后去读取SDA的电平作为接收的数据,每次在SDL为高电平的时候接收,每接收一位数据就左移一位,最后组成8位数据。
如果是0 的话,就+0 如果是1的话就+1 让后接收完左移1
如 0xaa 1010 1010
第0次 rec = 0
第一次 rec=1 0000 0001 <<1 0000 0010
第二次 rec=0 0000 0010 <<1 0000 0100
第三次 rec=1 0000 0101 <<1 0000 1010
第四次 rec=0 0000 1010 <<1 0001 0100
第五次 rec=1 0001 0101 <<1 0010 1010
第六次 rec=0 0010 1010 <<1 0101 0100
第七次 rec=1 0101 0101 <<1 1010 1010
uint8_t iic_read_byte (uint8_t ack) /* 1:ack 0:nack*/ { uint8_t receive = 0 ; for (uint8_t t = 0; t < 8; t++) { /* 高位先输出,先收到的数据位要左移 */ receive <<= 1; IIC_SCL ( 1 ); iic_delay( ); if ( IIC_READ_SDA ) receive++; IIC_SCL ( 0 ); iic_delay( ); } if ( !ack ) iic_nack(); else iic_ack(); return receive; } 硬件配置为什么IIC总线SDA建议用开漏模式?
IIC的SDA脚即要作为输出,又要作为输入,用开漏输出模式,很好实现输出输入共用,避免IO模式频繁切换带来的麻烦。
**输出时:**主机(MCU)输出0,可以拉低信号,来实现低电平发送,主机输出1(实际不起作用),由外部上拉电阻上拉,实现高电平发送。
**输入时:**主机(MCU)设置输出1状态,此时因为MCU无法输出1,相当于释放了SDA脚,此时外部器件可以主动拉低SDA脚/释放SDA脚(同样由上拉电阻提供“输出1的功能”),实现SDA脚的高低电平变化。
由于开漏输出模式下,MCU还是可以读取IDR状态寄存器,来获取引脚高低电平,因此MCU读取IDR,即可获得SDA脚的高低电平状态,从而实现输入检测。
实物测试
实物采用USB转I2C调试器和调试工具,接逻辑分析仪和I2C从机AT24C02来读取和写入EEPROM中地址0的数据。
开始抓包 读取AT24C02地址为0xA0的寄存器0的数据
写入数据
I2C学习笔记-软件模拟I2C由讯客互联其他栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“I2C学习笔记-软件模拟I2C”