文章目录
前言
今天主要讲解IIC协议是什么,以及如何通过IIC对AHT20传感器进行通信和信息获取。
一、IIC是什么
以下内容参考正点原子资料
IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。
在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。
I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。
CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。IIC 总线时序图如下图所示:
1.硬件IIC介绍
STM32的硬件IIC过于复杂,在此我们只做简单了解不做深究。
(1)功能概述
I2C模块接收和发送数据,并将数据从串行转换成并行,或并行转换成串行。可以开启或禁止中断。接口通过数据引脚(SDA)和时钟引脚(SCL)连接到I2C总线。允许连接到标准(高达100kHz)或快速(高达400kHz)的I
2C总线。
(2)模式选择
接口可以下述4种模式中的一种运行:
● 从发送器模式
● 从接收器模式
● 主发送器模式
● 主接收器模式
该模块默认地工作于从模式。接口在生成起始条件后自动地从从模式切换到主模式;当仲裁丢
失或产生停止信号时,则从主模式切换到从模式。允许多主机功能.
主模式时,I2C接口启动数据传输并产生时钟信号。串行数据传输总是以起始条件开始并以停止条件结束。起始条件和停止条件都是在主模式下由软件控制产生。
从模式时,I2C接口能识别它自己的地址(7位或10位)和广播呼叫地址。软件能够控制开启或禁止广播呼叫地址的识别。
数据和地址按8位/字节进行传输,高位在前。跟在起始条件后的1或2个字节是地址(7位模式为1个字节,10位模式为2个字节)。地址只在主模式发送。
(3)IIC主模式
在主模式时,I2C接口启动数据传输并产生时钟信号。串行数据传输总是以起始条件开始并以停止条件结束。当通过START位在总线上产生了起始条件,设备就进入了主模式。
以下是主模式所要求的操作顺序:
● 在I2C_CR2寄存器中设定该模块的输入时钟以产生正确的时序
● 配置时钟控制寄存器
● 配置上升时间寄存器
● 编程I2C_CR1寄存器启动外设
● 置I2C_CR1寄存器中的START位为1,产生起始条件
I2C模块的输入时钟频率必须至少是:
● 标准模式下为:2MHz
● 快速模式下为:4MHz
起始条件
当BUSY=0时,设置START=1,I2C接口将产生一个开始条件并切换至主模式(M/SL位置位)。注: 在主模式下,设置START位将在当前字节传输完后由硬件产生一个重开始条件。
一旦发出开始条件:
● SB位被硬件置位,如果设置了ITEVFEN位,则会产生一个中断。
然后主设备等待读SR1寄存器,紧跟着将从地址写入DR寄存器。
从地址的发送
从地址通过内部移位寄存器被送到SDA线上。
● 在10位地址模式时,发送一个头段序列产生以下事件:
─ ADD10位被硬件置位,如果设置了ITEVFEN位,则产生一个中断。
然后主设备等待读SR1寄存器,再将第二个地址字节写入DR寄存器。
─ ADDR位被硬件置位,如果设置了ITEVFEN位,则产生一个中断。
随后主设备等待一次读SR1寄存器,跟着读SR2寄存器。
● 在7位地址模式时,只需送出一个地址字节。
一旦该地址字节被送出,
─ ADDR位被硬件置位,如果设置了ITEVFEN位,则产生一个中断。
随后主设备等待一次读SR1寄存器,跟着读SR2寄存器。
根据送出从地址的最低位,主设备决定进入发送器模式还是进入接收器模式。
● 在7位地址模式时,
─ 要进入发送器模式,主设备发送从地址时置最低位为’0’。
─ 要进入接收器模式,主设备发送从地址时置最低位为’1’。
● 在10位地址模式时
─ 要进入发送器模式,主设备先送头字节(11110xx0),然后送最低位为’0’的从地址。(这里xx代表10位地址中的最高2位。)
─ 要进入接收器模式,主设备先送头字节(11110xx0),然后送最低为’1’的从地址。然后再重新发送一个开始条件,后面跟着头字节(11110xx1)(这里xx代表10位地址中的最高2位。) TRA位指示主设备是在接收器模式还是发送器模式。
(一)主发送器
主发送器 在发送了地址和清除了ADDR位后, 主设备通过内部移位寄存器将字节从DR寄存器发送到SDA线上。 主设备等待,直到TxE被清除。
当收到应答脉冲时: ● TxE位被硬件置位,如果设置了INEVFEN和ITBUFEN位,则产生一个中断。
如果TxE被置位并且在上一次数据发送结束之前没有写新的数据字节到DR寄存器,则BTF被硬件置位,在清除BTF之前I2C接口将保持SCL为低电平;读出I2C_SR1之后再写入I2C_DR寄存器将清除BTF位。
关闭通信 在DR寄存器中写入最后一个字节后,通过设置STOP位产生一个停止条件,然后I2C接口将自动回到从模式(M/S位清除)。 注:
当TxE或BTF位置位时,停止条件应安排在出现EV8_2事件时。
如下图为主发送传输序列图:
(二)主接受器
主接收器 在发送地址和清除ADDR之后,I2C接口进入主接收器模式。在此模式下,I2C接口从SDA线接收
数据字节,并通过内部移位寄存器送至DR寄存器。在每个字节后,I2 C接口依次执行以下操作:
● 如果ACK位被置位,发出一个应答脉冲。
●硬件设置RxNE=1,如果设置了INEVFEN和ITBUFEN位,则会产生一个中断。
如果RxNE位被置位,并且在接收新数据结束前,DR寄存器中的数据没有被读走,硬件将设置BTF=1,在清除BTF之前I2C接口将保持SCL为低电平;读出I2C_SR1之后再读出I2C_DR寄存器将清除BTF位。
关闭通信
主设备在从从设备接收到最后一个字节后发送一个NACK。接收到NACK后,从设备释放对SCL和SDA线的控制;主设备就可以发送一个停止/重起始条件。
● 为了在收到最后一个字节后产生一个NACK脉冲,在读倒数第二个数据字节之后(在倒数第二个RxNE事件之后)必须清除ACK位。
●为了产生一个停止/重起始条件,软件必须在读倒数第二个数据字节之后(在倒数第二个RxNE事件之后)设置STOP/START位。
●只接收一个字节时,刚好在EV6之后(EV6_1时,清除ADDR之后)要关闭应答和停止条件的产生位。在产生了停止条件后,I2C接口自动回到从模式(M/SL位被清除)
如下图为主接收机的传输序列图:
2.软件IIC介绍
在这里我们将通过代码的形式介绍软件IIC,下面代码主要的意思是通过初始化引脚PC11和PC12来模拟IIC的两个引脚,在通过模拟IIC的时序来模拟IIC。
该部分为 IIC 驱动代码,实现包括 IIC 的初始化(IO 口)、IIC 开始、IIC 结束、ACK、IIC读写等功能,在其他函数里面,只需要调用相关的 IIC 函数就可以和外部 IIC 器件通信了
#include "myiic.h"
#include "delay.h"
//IIC 初始化
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOC_CLK_ENABLE(); //使能 GPIOC 时钟
//PC11,12 初始化设置
GPIO_Initure.Pin=GPIO_PIN_11|GPIO_PIN_12;
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //高速
HAL_GPIO_Init(GPIOC,&GPIO_Initure);
IIC_SDA=1;
IIC_SCL=1;
}
//产生 IIC 起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda 线输出
IIC_SDA=1;
IIC_SCL=1; delay_us(4);
IIC_SDA=0; delay_us(4);//IIC START: when CLK is high,DATA change form high to low
IIC_SCL=0;//钳住 I2C 总线,准备发送或接收数据
}
//产生 IIC 停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda 线输出
IIC_SCL=0; IIC_SDA=0;
delay_us(4);
IIC_SCL=1;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SDA=1;//发送 I2C 总线结束信号
}
//等待应答信号到来
//返回值: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 1; }
}
IIC_SCL=0;//时钟输出 0
return 0;
}
//产生 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);
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;
}
二、AHT20温湿度传感器介绍
1.上电后要等待40ms,读取温湿度值之前, 首先要看状态字的校准使能位Bit[3]是否为 1(通过发送0x71可以获取一个字节的状态字),如果不为1,要发送0xBE命令(初始化),此命令参数有两个字节, 第一个字节为0x08,第二个字节为0x00,然后等待10ms。
2.直接发送 0xAC命令(触发测量),此命令参数有两个字节,第一个字节为 0x33,第二个字节为0x00。
3.等待80ms待测量完成,如果读取状态字Bit[7]为0,表示测量完成,然后可以连续读取六个节;否则继续等待。
4.当接收完六个字节后,紧接着下一个字节是CRC校验数据,用户可以根据需要读出,如果接收端需要CRC校验,则在接收完第六个字节后ACK应答,否则发NACK结束,CRC初始值为0XFF,CRC8校验多项式为:
5.计算温湿度值。
三、通过IIC获取温湿度传感器数据
1.IIC配置
IIC初始化代码如下:
#include "i2c.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
I2C_HandleTypeDef hi2c1;
/* I2C1 init function */
void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
}
void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(i2cHandle->Instance==I2C1)
{
/* USER CODE BEGIN I2C1_MspInit 0 */
/* USER CODE END I2C1_MspInit 0 */
__HAL_RCC_GPIOB_CLK_ENABLE();
/**I2C1 GPIO Configuration
PB6 ------> I2C1_SCL
PB7 ------> I2C1_SDA
*/
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* I2C1 clock enable */
__HAL_RCC_I2C1_CLK_ENABLE();
/* USER CODE BEGIN I2C1_MspInit 1 */
/* USER CODE END I2C1_MspInit 1 */
}
}
void HAL_I2C_MspDeInit(I2C_HandleTypeDef* i2cHandle)
{
if(i2cHandle->Instance==I2C1)
{
/* USER CODE BEGIN I2C1_MspDeInit 0 */
/* USER CODE END I2C1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_I2C1_CLK_DISABLE();
/**I2C1 GPIO Configuration
PB6 ------> I2C1_SCL
PB7 ------> I2C1_SDA
*/
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_6|GPIO_PIN_7);
/* USER CODE BEGIN I2C1_MspDeInit 1 */
/* USER CODE END I2C1_MspDeInit 1 */
}
}
2.AHT20初始化
代码如下:
void AHT20_Init(void)
{
uint8_t receive=0;
uint8_t trans[3]={0xBE,0x08,0x00};
uint8_t trans_1[3]={0xAC,0x33,0x00};
uint32_t out_time=0;
uint8_t receive_data[6]={0};
uint8_t tan=0x71;
HAL_Delay(40);
HAL_I2C_Master_Transmit(&hi2c1,0x70,NULL,1,0xffff);
HAL_I2C_Master_Receive(&hi2c1,0x70,&receive,1,0xffff);
printf("%x",receive);
if(!receive&0x08)
{
printf("1");
HAL_I2C_Master_Transmit(&hi2c1,0x70,trans,1,0xffff);
HAL_Delay(10);
}
HAL_I2C_Master_Transmit(&hi2c1,0x70,trans_1,1,0xffff);
HAL_Delay(80);
receive=0xff;
HAL_I2C_Master_Receive(&hi2c1,0x70,&receive,1,0xffff);
printf("%x",receive);
while(receive&0x80&&out_time<0xffff)
{
out_time++;
HAL_I2C_Master_Receive(&hi2c1,0x70,&receive,1,0xffff);
}
if(out_time==0xffff)
{
printf("Init error");
return;
}
HAL_I2C_Master_Receive(&hi2c1,0x70,receive_data,6,0xffff);
}
3.AHT20数据获取及解析
代码如下:
void AHT20_GET(void)
{
uint8_t receive=0xff;
float RH,T;
uint32_t ll;
uint8_t tan[]={0xAC,0x33,0x00,0x71};
uint32_t out_time=0;
HAL_I2C_Master_Transmit(&hi2c1,0x70,tan,3,0xffff);
HAL_Delay(80);
HAL_I2C_Master_Transmit(&hi2c1,0x70,&tan[3],1,0xffff);
HAL_I2C_Master_Receive(&hi2c1,0x70,receive_,7,0xffff);
while(receive_[0]&0x80&&out_time<0xffff)
{
out_time++;
HAL_I2C_Master_Receive(&hi2c1,0x70,&receive,1,0xffff);
}
if(out_time==0xffff)
{
printf("Get error");
return;
}
RH=(((uint32_t)receive_[3]>>4)+((uint32_t)receive_[2]<<4)+((uint32_t)receive_[1]<<12));
RH=RH/(1<<20)*100;
T=(((uint32_t)receive_[3]&0x0f<<16)+((uint32_t)receive_[4]<<8)+((uint32_t)receive_[5]));
T=T/(1<<20)*200.0f-50;
printf("水汽度:%f,温度:%f",RH,T);
}
最终结果将通过串口回传。