文章目录
一、I2C是什么?
I2C总线是PHLIPS公司在20世纪80年代推出的一种串行总线。具有引脚少,硬件实现简单,可扩展性强的优点。I2C总线的另一优点是支持多主控,总线上任何能够进行发送/接收数据的设备都可以占领总线。当然,任意时间点上只能存在一个主控。
I2C即是一种总线,也是一种通讯协议。在嵌入式开发中,通讯协议可分为两层:物理层和协议层。物理层是数据在物理媒介传输的保障;协议层主要是规定通讯逻辑,同一收发双方的数据打包、解包标准。打个比方,物理层相当于现实中的公路,而协议层则是交通规则,汽车可以在路上行驶,但是需要交通规则对行驶规则进行约束,不然将出现危险,也就是数据传输紊乱、丢包。
- 在I2C通讯总线上,可连接多个I2C通讯设备,支持多个通讯主机和多个通讯从机
- I2C通讯只需要两条双向总线:串行数据线(SDA),串行时钟线(SCL)。数据线用于传输数据,时钟线用于同步数据收发
- 每个连接到总线的设备都有一个独立的地址,主机正是利用该地址对设备进行访问
- SDA和SCL总线都需要接上上拉电阻,当总线空闲时,两根线均为高电平。连接到总线上的任意器件输出低电平都会将总线信号拉低。即各器件的SDA和SCL都是线与的关系
- 多个主机同时使用总线时,需要用仲裁方式决定哪个设备占用总线,不然数据将会产生冲突
数据传输
(1) 起始信号后,总线上所有的从机开始等待主机紧接下来的从机地址广播。因为总线上每个设备的地址都是唯一的,当主机广播的地址与某个设备地址相同时,该设备就被选中,并向主机发出应答(ACK)或者非应答(NACK),主机只有在接收到应答信号后继续发送/接收数据,没选中的设备将会忽略之后的数据信号。根据I2C协议,从机地址可以是7位或者10位。
(2) I2C总线上传输的数据包括上述的地址信号,又包括真正的数据信号。在起始信号后需传送一个从机地址(7位),第8位是数据的传输方向(接收/发送),“0”表示主机发送数据,“1”表示主机接收数据。每次数据的传输总是由主机产生结束信号以结束传输,但若主机希望继续占用总线进行新的数据传输时,则可以不产生结束信号,而是再次发送起始信号对另一从机地址寻址。
(3) 若配置为写数据方向,主机开始向从机传输数据,数据包大小为8位,主机每发送完1字节数据都有等到从机的应答信号(ACK),多字节数据发送时重复此过程。传输结束后,主机向从机发送一个停止信号表不再传输数据。
(4) 若配置为读方向,从机开始向主机返回数据,数据包大小还是8位。同理,从机每发送完一字节数据都要等到主机的应答信号(ACK),重复此过程可以返回多个数据。当主机希望停止接收数据时就向返回一个非应答信号(NACK),数据传输将结束。
(5) 实际I2C通讯采用的是读写复合的格式。传输过程中主机需要发出2次起始信号:第一次传输主机通过从机地址找到从机设备,发送一段数据,这个数据是从设备内部寄存器或者存储器地址;第二次传输是对该地址进行读/写。主机要读取从机数据时,主机会释放对SDA总线的控制,由从机控制SDA总线,主机负责接收信号;主机要向从机设备写数据时,SDA由主机控制,从机负责接收信号。
I2C总线的特征
●只要求两条总线线路一 条串行数据线SDA -条串行时钟线SCL
●每个连接到总线的器件都可以通过唯一的地址和一 直存在的简单的主机从机关系软件设
定地址主机可以作为主机发送器或主机接收器
●它是一个真正的多主机总线如果两个或更多主机同时初始化数据传输可以通过冲突检测
和仲裁防止数据被破坏
●串行的8位双向数据传输位速率在标准模式下可达100kbit/s 快速模式下可达400kbit/s
高速模式下可达3.4Mbit/s .
●片上的滤波器可以滤去总线数据线上的毛刺波保证数据完整
●连接到相同总线的IC数量只受到总线的最大电容400pF 限制
二、“软件I2C”和“硬件I2C”
I2C协议包括“软件I2C”和“硬件I2C”
所谓硬件I2C对应bai芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的 软件I2C一般是用GPIO管脚,用软件控制管脚状态以模拟I2C通信波形。
区别:
硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。 模拟I2C
是通过GPIO,软件模拟寄存器的工作方式,而硬件(固件)I2C是直接调用内部寄存器进行配置。如果要从具体硬件上来看,可以去看下芯片手册。因为固件I2C的端口是固定的,所以会有所区别。
至于如何区分它们 可以看底层配置,比如IO口配置,如果配置了IO口的功能(IIC功能)那就是固件IIC,否则就是模拟
可以看IIC写函数,看里面有木有调用现成的函数或者给某个寄存器赋值,如果有,则肯定是固件IIC功能,没有的话肯定是数据一个bit一个bit模拟发生送的,肯定用到了循环,则为模拟。
根据代码量判断,模拟的代码量肯定比固件的要大。
- 硬件IIC用法比较复杂,模拟IIC的流程更清楚一些。
- 硬件IIC速度比模拟快,并且可以用DMA 3. 模拟IIC可以在任何管脚上,而硬件只能在固定管脚上。 软件i2c是程序员使用程序控制SCL,SDA线输出高低电平,模拟i2c协议的时序。一般较硬件i2c稳定,但是程序较为繁琐,但不难。 硬件i2c程序员只要调用i2c的控制函数即可,不用直接的去控制SCL,SDA高低电平的输出。但是有些单片机的硬件i2c不太稳定,调试问题较多。
三、AHT20
2020年上市,奥松生产; 3mmx3mmx1mm 超小体积; 经过标定的数字信号,标准I2C输出格式;
由一个电容式湿度传感元件和一个标准的片上温度传感元件组成; 采用SMD封装适于回流焊; 响应迅速、抗干扰能力强; AHT20 的供电范围为
2.0-5.5V, 推荐电压为3.3V。
四、实验
1、
实验环境:
1.奥松AHT20,温湿度传感
2.野火STM32mini开发板
3、MDK Kile5
2、
实验目标:
每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机(win10)
3、
代码实现(在之前一个串口通信的程序上做更改):
main.c
#include "delay.h"
#include "bsp_aht20.h"
#include "bsp_led.h"
int main(void)
{
u32 CT_data[2]={0};
volatile float hum=0,temp=0;
USART_Config(); //USART1初始化
LED_GPIO_Config(); //LED端口初始化
delay_init(); //延时函数初始化
temphum_init(); //初始化温湿度传感器
while(1)
{
AHT20_Read_CTdata(CT_data); //不经过CRC校验,直接读取AHT20的温度和湿度数据
hum = CT_data[0]*100*10/1024/1024; //计算得到湿度值(放大了10倍)
temp = CT_data[1]*200*10/1024/1024-500;//计算得到温度值(放大了10倍)
printf("湿度:%.1f%%\r\n",(hum/10));
printf("温度:%.1f度\r\n",(temp/10));
printf("\r\n");
/*绿灯闪烁提示串口发送状态*/
green_led_on;
delay_ms(1000);
green_led_off;
delay_ms(1000);
}
}
I2C.c
#include "iic.h"
#include "delay.h"
/******************************************************
初始化IIC
*******************************************************/
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC, ENABLE ); //使能GPIOC时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_SetBits(GPIOC,GPIO_Pin_6|GPIO_Pin_7); //PC6,PC7 输出高
}
/******************************************************
产生IIC起始信号
*******************************************************/
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
/******************************************************
产生IIC停止信号
*******************************************************/
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//发送I2C总线结束信号
delay_us(4);
}
/******************************************************
等待应答信号到来
返回值:1,接收应答成功
0,接收应答失败
*******************************************************/
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 0;
}
}
IIC_SCL=0;//时钟输出0
return 1;
}
/******************************************************
产生ACK应答
*******************************************************/
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
/******************************************************
不产生ACK应答
*******************************************************/
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
/******************************************************
IIC发送一个字节
返回从机有无应答
1,有应答
0,无应答
*******************************************************/
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
IIC_SDA=(txd&0x80)>>7;
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
/******************************************************
读1个字节,ack=1时,发送ACK,ack=0,发送nACK
*******************************************************/
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK,表示停止接收
else
IIC_Ack(); //发送ACK,表示继续接收
return receive;
}
bsp_aht20
#include "bsp_aht20.h"
/**************************************************
读取AHT20的状态寄存器
**************************************************/
uint8_t AHT20_Read_Status(void)
{
uint8_t Byte_first,flag;
IIC_Start();
IIC_Send_Byte(0x71);
flag=IIC_Wait_Ack();
Byte_first = IIC_Read_Byte(flag);
IIC_NAck();
IIC_Stop();
return Byte_first;
}
/**************************************************
向AHT20发送AC命令
**************************************************/
void AHT20_SendAC(void)
{
IIC_Start();
IIC_Send_Byte(0x70); //启动传输后发送的01110000 (最后1bit表示读/写 0--写,1--读)
IIC_Wait_Ack();
IIC_Send_Byte(0xac);//0xAC采集命令 命令参数有两个字节,第一个字节为 0x33,第二个字节为0x00。
IIC_Wait_Ack();
IIC_Send_Byte(0x33);
IIC_Wait_Ack();
IIC_Send_Byte(0x00);
IIC_Wait_Ack();
IIC_Stop();
}
/**************************************************
初始化AHT20
**************************************************/
void AHT20_Init(void)
{
IIC_Init();
IIC_Start();
IIC_Send_Byte(0x70);
IIC_Wait_Ack();
IIC_Send_Byte(0xa8);//0xA8进入NOR工作模式
IIC_Wait_Ack();
IIC_Send_Byte(0x00);
IIC_Wait_Ack();
IIC_Send_Byte(0x00);
IIC_Wait_Ack();
IIC_Stop();
delay_ms(10);//延时10ms左右
IIC_Start();
IIC_Send_Byte(0x70);
IIC_Wait_Ack();
IIC_Send_Byte(0xbe);//0xBE初始化命令,AHT20的初始化命令是0xBE, AHT10的初始化命令是0xE1
IIC_Wait_Ack();
IIC_Send_Byte(0x08);//相关寄存器bit[3]置1,为校准输出
IIC_Wait_Ack();
IIC_Send_Byte(0x00);
IIC_Wait_Ack();
IIC_Stop();
delay_ms(10);//延时10ms左右
}
/**************************************************
没有CRC校验,直接读取AHT20的温度和湿度数据
**************************************************/
void AHT20_Read_CTdata(uint32_t *ct)
{
volatile uint8_t Byte_1th=0,Byte_2th=0,Byte_3th=0;
volatile uint8_t Byte_4th=0,Byte_5th=0,Byte_6th=0;
uint32_t RetuData = 0;
u16 cnt = 0,flag;
AHT20_SendAC();//向AHT20发送AC命令
delay_ms(80); //大约延时80ms
while(((AHT20_Read_Status()&0x80)==0x80))//直到状态bit[7]为0,表示为空闲状态,若为1,表示忙状态
{
delay_ms(1);
if(cnt++>=100) break;
}
IIC_Start();
IIC_Send_Byte(0x71);
flag=IIC_Wait_Ack();
Byte_1th = IIC_Read_Byte(flag);//状态字
Byte_2th = IIC_Read_Byte(flag);//湿度,发送ACK(继续发送)
Byte_3th = IIC_Read_Byte(flag);//湿度
Byte_4th = IIC_Read_Byte(flag);//湿度/温度
Byte_5th = IIC_Read_Byte(flag);//温度
Byte_6th = IIC_Read_Byte(!flag);//温度,发送NACK(停止发送)
IIC_Stop();
//保存得到的数据到RetuData中
RetuData = (RetuData|Byte_2th)<<8;
RetuData = (RetuData|Byte_3th)<<8;
RetuData = (RetuData|Byte_4th);
RetuData =RetuData >>4;
ct[0] = RetuData;//湿度
RetuData = 0;
RetuData = (RetuData|Byte_4th)<<8;
RetuData = (RetuData|Byte_5th)<<8;
RetuData = (RetuData|Byte_6th);
RetuData = RetuData&0x0fffff;
ct[1] =RetuData; //温度
}
/**************************************************
初始化温湿度传感器
**************************************************/
void temphum_init()
{
delay_ms(40);//刚上电,延时40ms才可以读取状态
//首先发0x71读取状态字bit[3],如果=1,为校准输出,无须初始化!!!正常情况下读回来的状态是0x1C或者是0x18,读回来是0x80表示忙状态;
if(!((AHT20_Read_Status()&0x08)==0x08))
{
AHT20_Init(); //初始化AHT20
}
}
4、
硬件连接
先把AHT20与排针焊好。vcc接3.3v,GND正常接地,SCLC7,SDA接C6。(并不只是这一种接法,具体应该根据实验环境不同而不一样)
5、
实验效果
烧录好程序,然后打开串口助手
五、总结
硬件比软件部分难,正确接线,甚至焊芯片花了很多时间,所以还是要软硬兼修才行。
六、参考
I2C通讯协议介绍
硬件I2C与模拟I2C
基于I2C通信协议的温湿度采集