STM32:IIC
一、IIC总线原理
IIC是一种同步半双工串行(MSB)高位先行通信总线,由飞利浦公司开发,广泛用于短距离低速通信(如传感器、EEPROM、OLED 等外设)。
1.物理层
两根信号线:SDA数据线和SCL时钟线。IIC的接线方式SCL接SCL,SDA接SDA,IIC总线上必须外接两个上拉电阻(通常 4.7kΩ),用于在总线空闲时将总线拉高,实现 "线与" 逻辑(多主设备低电平优先)。IO需要设置为开漏输出。
时钟线的作用:用于控制数据线上的数据的有效性。时钟线为低电平时,数据无效。时钟线为高电平时,数据有效。
支持多主多从,通过从设备地址区分通信对象。内置硬件应答(ACK)处理,无需手动控制。
通信速率:主流实用速率是100kHz(标准模式)和 400kHz(快速模式)。
2.协议层
核心信号与操作:起始信号、停止信号、应答信号、非应答信号、等待信号、发送数据、接受数据。
核心信号(起始、停止、重复起始)
起始信号时序:SCL 为高电平时,SDA 从高电平跳变为低电平(高→低)。
停止信号时序:SCL 为高电平时,SDA 从低电平跳变为高电平(低→高)。
重复起始信号时序:与起始信号完全相同(SCL 高时 SDA 高→低),但它出现在一次通信过程中。
重复起始信号作用:主设备在不释放总线的情况下切换通信方向,如从 “写” 切换到 “读”,避免其他主设备抢占总线。
数据传输规则
(1)数据有效性
SCL 为高电平时,SDA 的电平必须稳定为高或者低,此时数据有效;SCL 为低电平时,SDA 可以跳变,准备下一位数据。
(2)应答机制(ACK/NACK)
每传输8位后,接收方必须在第9个时钟周期回应 “应答”,确认数据已接收:
ACK(应答):接收方拉低 SDA,低电平,表示已正确接收字节。
NACK(非应答):接收方释放 SDA,高电平(由上拉电阻维持),表示未接收、接收错误或无需继续发送。
(3)一句话总结
高时钟看状态(起始 / 停止 / 应答),低时钟换数据。
主设备与从设备的对话
1.主设备写从设备
向从设备的某个寄存器写入数据
① 主设备发送起始信号(S),启动通信。
② 主设备发送从设备写地址(7 位地址 + 0),等待从设备应答。
③ 从设备地址匹配,回应ACK。
④ 主设备发送寄存器地址(如 0x05),等待从设备应答。
⑤ 从设备回应ACK。
⑥ 主设备发送要写入的数据(如 0xAB),等待从设备应答。
⑦ 从设备回应ACK(表示数据接收成功)。
⑧ 主设备发送停止信号(P),结束通信。
2.主设备读从设备
读取从设备某个寄存器的数据
① 主设备发送起始信号(S),启动通信。
② 主设备发送从设备写地址(先告诉从设备 “要读哪个寄存器”),等待 ACK。
③ 从设备回应ACK。
④ 主设备发送目标寄存器地址(如 0x05),等待 ACK。
⑤ 从设备回应ACK。
⑥ 主设备发送重复起始信号(Sr)(不释放总线,切换到读模式)。
⑦ 主设备发送从设备读地址(7 位地址 + 1),等待 ACK。
⑧ 从设备回应ACK,开始向主设备发送数据。
⑨ 主设备接收数据后,发送NACK(表示 “这是最后一个字节,无需继续发送”)。
⑩ 主设备发送停止信号(P),结束通信。
二、编写代码实战
基于STM32F4标准库、软件模拟I2C总线驱动SHT20温湿度传感器的完整实现,适配了 FreeRTOS 实时操作系统。
1.底层IIC驱动
#include "drv_iic.h"
#include "drv_systick.h"
/*
函数名:iic_init()
函数参数:无
函数功能:初始化软件模拟iic的gpio引脚
函数返回值:无
其他:
*/
void iic_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;//
GPIO_InitStruct.GPIO_OType=GPIO_OType_OD;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void sda_in(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void sda_out(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_OType=GPIO_OType_OD;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void iic_start(void)
{
SCL_L;
sda_out();
SDA_H;
SCL_H;
delay_us(5);
SDA_L;
delay_us(5);
SCL_L;
}
void iic_stop(void)
{
SCL_L;
sda_out();
SDA_L;
SCL_H;
delay_us(5);
SDA_H;
delay_us(5);
}
void send_ACK(void)
{
SCL_L;
sda_out();
SDA_L;
SCL_H;
delay_us(5);
SCL_L;
}
void send_NOACK(void)
{
SCL_L;
sda_out();
SDA_H;
SCL_H;
delay_us(5);
SCL_L;
}
int wait_ACK(void)
{
uint16_t time_out=0;
SCL_L;
sda_in();
SCL_H;
while(SDA_READ)
{
time_out++;
if(time_out>300)
{
iic_stop();
return NOACK;
}
}
SCL_L;
return ACK;
}
void send_data(uint8_t data)
{
uint8_t i=0;
SCL_L;
sda_out();
for(i;i<8;i++)
{
if(data & 0x80)
{
SDA_H;
}
else
{
SDA_L;
}
SCL_H;
delay_us(1);
SCL_L;
delay_us(1);
data<<=1;
}
}
uint8_t read_data(uint8_t ack_flag)
{
uint8_t r_data=0,i=0;
SCL_L;
sda_in();
for(i;i<8;i++)
{
r_data<<=1;
SCL_H;
if(SDA_READ)
{
r_data|=0x01;
}
delay_us(1);
SCL_L;
delay_us(1);
}
if(ack_flag==ACK)
{
send_ACK();
}
else
{
send_NOACK();
}
SCL_L;
return r_data;
}
2.SHT20应用驱动
#include "FreeRTOS.h"
#include "drv_sht20.h"
#include "task.h"
sht20_val sht20_data;
void sht20_init(void)
{
iic_init();
}
uint8_t sht20_reset(void)
{
iic_start();
send_data(0x80);
if(wait_ACK() != ACK)
{
iic_stop();
return NOACK;
}
send_data(0xfe);
if(wait_ACK() != ACK)
{
iic_stop();
return NOACK;
}
iic_stop();
return ACK;
}
uint16_t sht20_read(uint8_t cmd)
{
uint16_t r_data=0;
uint8_t temp[2];
iic_start();
send_data(0x80);//写地址
if(wait_ACK() != ACK)
{
iic_stop();
return NOACK;
}
send_data(cmd);
if(wait_ACK() != ACK)
{
iic_stop();
return NOACK;
}
iic_start();
send_data(0x81);//读地址
if(wait_ACK() != ACK)
{
iic_stop();
return NOACK;
}
vTaskDelay(120 / portTICK_PERIOD_MS);
temp[0] = read_data(ACK);
temp[1] = read_data(NOACK);
iic_stop();
r_data =(temp[0]<<8|temp[1]);
return r_data;
}
void sht20_getdata(void)
{
uint16_t tmp=0;
tmp = sht20_read(0xe5);//选择湿度
sht20_data.hum = -6+125*((float)tmp/65536);
//printf("hum=%.2f\r\n",sht20_data.hum);
uint16_t temperature_raw = sht20_read(0xe3);
sht20_data.temperature = -46.85 + 175.72 * ((float)temperature_raw / 65536);
//printf("Temperature: %.2f\r\n", sht20_data.temperature);
}
更多推荐



所有评论(0)