前言
目前利用嵌入式平台进行数据采集,已经习以为常,一方面处于工程本身考虑,嵌入式平台
易于安装;其次嵌入式技术
具有良好的软硬件可裁剪性,功能较强、可靠性高、成本低廉、 体积小、质量轻、功耗低,因而非常适合用于各类复杂现场的数据采集系统。
在此次博客,通过嵌入式系统实现对温度的的采集,并通过 串口将采集到的数据发送出去,上位机是 Windows系统的 PC 机
。
目录
一、I2C介绍
大家自己做项目或者在网上查阅资料的时候就会发现嵌入式很多传感器的实现是通过I2C实现的,下面介绍一下I2C的基本概念及工作通信流程。
Ⅰ.什么是I2C
I2C(集成电路总线)
,由Philips公司(2006年迁移到NXP
)在1980年代初开发的一种简单、双线双向
的同步串行
总线,它利用一根时钟线和一根数据线在连接总线的两个器件之间进行信息的传递,为设备之间数据交换提供了一种简单高效的方法。每个连接到总线上的器件都有唯一的地址
,任何器件既可以作为主机
也可以作为从机
,但同一时刻只允许有一个主机。
I2C 标准是一个具有冲突检测机制和仲裁机制的真正意义上的多主机总线,它能在多个主机同时请求控制总线时利用仲裁机制避免数据冲突并保护数据。在之前也讲过:在嵌入式开发中,使用I2C总线通信
的场景有很多,例如驱动FRAM、传感器等。
I2C结合了SPI和UART的优点。使用I2C,可以将多个从设备连接到单个主设备上(如SPI),并且可以让多个主器件控制单个或多个从器件。
Ⅱ.I2C原理
与UART
通信一样,I2C仅使用两条线在
设备之间传输数据:
SDA(串行数据)
:主站和从站发送和接收数据的线路。
SCL(串行时钟)
:承载时钟信号的线路。
I2C是一种串行通信协议,因此数据沿着单线(SDA线)逐位传输。
与SPI一样,I2C是同步的,因此位输出通过主机和从机之间共享的时钟信号与位采样同步。时钟信号始终由主机控制。
下图为一个单片机(或称MCU)和两个模块通过IIC总线进行通信的简图。
因为IIC总线是支持一个主机和多个从机通信的,即支持“一主多从”通信(也支持“多主多从”通信),故参与通信的器件都需要将自己接口中的数据引脚和时钟引脚分别接入总线中的SDA和SCL。
IIC总线在硬件设计上需要为串行数据线SDA和串行时钟线增加上拉电阻,阻值一般选用10KΩ及以下。
该上拉电阻的主要作用
有:增强总线的驱动能力、以及使总线空闲时其状态自动被拉高。同时阻值的选择也影响当前IIC总线支持的通信速度。
Ⅲ.通信流程
I2C时序图如下:
IIC通信基本流程:主机建立通信
开始条件,数据传输阶段
,主机建立通信停止条件
。
🎈开始条件:主机控制SCL维持高电平状态,控制SDA先拉高一段时间,再拉低一段时间(这也是“重复开始条件” / “再开始条件”)。
🎈停止条件:主机控制SCL维持高电平状态,控制SDA先拉低一段时间,再拉高一段时间。
🎈数据传输阶段的时序:空闲时SCL拉低。在SCL上升沿时传输数据à在SCL拉低期间准备数据(调整SDA状态),在SCL拉高期间,保持数据(SDA状态不变)。
注:若在数据传输阶段,SCL拉高期间,更改了数据线状态,可能导致从机误认为接收到“开始条件”或“停止条件”信号,导致通信流程混乱。
Ⅳ.I2C优缺点
1.优点
🎈只使用两根电线
🎈支持多个主服务器和多个从服务器
🎈ACK / NACK位确认每个帧都已成功传输
🎈硬件没有UART那么复杂
2.缺点
🎐数据传输速率比SPI慢
🎐数据帧的大小限制为8位
🎐实现比SPI更复杂的硬件
Ⅴ.软件I2C与硬件I2C
硬件I2C
对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的;
软件I2C
一般是用GPIO管脚,用软件控制管脚状态以模拟I2C通信波形。
硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。
模拟I2C
是通过GPIO
,软件模拟寄存器的工作方式,而硬件(固件)I2C
是直接调用内部寄存器
进行配置。如果要从具体硬件上来看,可以去看下芯片手册。
二、小试牛刀——采集温湿度数据
Ⅰ.实验内容
阅读AHT20数据手册,编程实现:每隔2秒钟
采集一次温湿度数据
,并通过串口
发送到上位机(win10)
。
Ⅱ.实验分析
1.了解温湿度传感器模块——AHT20
AHT20芯片的相关信息可到官方下载对应产品介绍文档,资料链接如下:http://www.aosong.com/class-36.html
这里要强调一下:一定要注意管脚号,不要接反
STM32F103系列,接引脚说明
2. 理解官方资料后,编写代码
看懂官方给的资料,明白传感器的具体用法,根据实验要求进行代码编写。
3.编译,上板测试
编译成功后,将程序烧录到单片机中,通过串口助手观察输出!
Ⅲ.实验步骤
1编写main()函数
代码如下:
#include "System.h"
#include "Delay.h"
#include "UART.h"
#include "AHT20.h"
struct AHT20_INF AHT20;
int main(void)
{
IIC_Init();
Delay_Init();
My_Uart_Init(115200);
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_Init(GPIOC,&GPIO_InitStructure);
GPIO_ResetBits(GPIOC,GPIO_Pin_13);
AHT20.alive = !AHT20_Init();
while(1)
{
if(AHT20.alive)
{
AHT20.flag = AHT20_ReadHT(AHT20.ht);
StandardUnitCon(&AHT20);
}
TEST_MainPage();
printf("温度:%.2f°c 湿度:%.2f%%\n",AHT20.te,AHT20.rh);
Delay_ms(1000);
}
}
2.部分代码——读取传感器的关键代码及注解
#include "AHT20.h"
//读AHT20的状态字
static uint8_t AHT20_ReadStatusCmd(void)
{
uint8_t tmp[1];
Soft_I2C_Read(AHT20_SLAVE_ADDRESS,AHT20_STATUS_REG,1,tmp);
return tmp[0];
}
//读AHT20标准使能位
static uint8_t AHT20_ReadCalEnableCmd(void)
{
uint8_t tmp;
tmp = AHT20_ReadStatusCmd();
return (tmp>>3)&0x01;
}
//读取AHT20 忙标志位
static uint8_t AHT20_ReadBusyCmd(void)
{
uint8_t tmp;
tmp = AHT20_ReadStatusCmd();
return (tmp>>7)&0x01;
}
//AHT20芯片初始化函数
static void AHT20_IcInitCmd(void)
{
uint8_t tmp[2];
tmp[0] = 0x08;
tmp[1] = 0x00;
Soft_I2C_Write(AHT20_SLAVE_ADDRESS, AHT20_INIT_REG, 2, tmp);
}
//触发AHT20测量
static void AHT20_TrigMeasureCmd(void)
{
uint8_t tmp[2];
tmp[0] = 0x33;
tmp[1] = 0x00;
Soft_I2C_Write(AHT20_SLAVE_ADDRESS, AHT20_Measure, 2, tmp);
}
//AHT20软复位函数
static void AHT20_SoftResetCmd(void)
{
uint8_t tmp[1];
Soft_I2C_Write(AHT20_SLAVE_ADDRESS, AHT20_Reset, 0, tmp);
}
//AHT20 设备初始化
uint8_t AHT20_Init(void)
{
uint8_t rcnt = 2+1;//软复位命令 重试次数,2次
uint8_t icnt = 2+1;//初始化命令 重试次数,2次
while(--rcnt)
{
icnt = 2+1;
Delay_ms(40);//上电后要等待40ms
// 读取温湿度之前,首先检查[校准使能位]是否为1
while((!AHT20_ReadCalEnableCmd()) && (--icnt))// 2次重试机会
{
Delay_ms(10);
// 如果不为1,要发送初始化命令
AHT20_IcInitCmd();
Delay_ms(200);//这个时间不确定,手册没讲
}
if(icnt)//[校准使能位]为1,校准正常
{
break;//退出rcnt循环
}
else//[校准使能位]为0,校准错误
{
AHT20_SoftResetCmd();//软复位AHT20器件,重试
Delay_ms(200);//这个时间不确定,手册没讲
}
}
if(rcnt)
{
Delay_ms(200);//这个时间不确定,手册没讲
return 0;// AHT20设备初始化正常
}
else
{
return 1;// AHT20设备初始化失败
}
}
//读取AHT20的数据
uint8_t AHT20_ReadHT(uint32_t *HT)
{
uint8_t cnt=3+1;//忙标志 重试次数,3次
uint8_t tmp[6];
uint32_t RetuData = 0;
// 发送触发测量命令
AHT20_TrigMeasureCmd();
do{
Delay_ms(75);//等待75ms待测量完成,忙标志Bit7为0
}while(AHT20_ReadBusyCmd() && (--cnt));//重试3次
if(cnt)//设备闲,可以读温湿度数据
{
Delay_ms(5);
// 读温湿度数据
Soft_I2C_Read(AHT20_SLAVE_ADDRESS, AHT20_STATUS_REG, 6, tmp);
// 计算相对湿度RH。原始值,未计算为标准单位%。
RetuData = 0;
RetuData = (RetuData|tmp[1]) << 8;
RetuData = (RetuData|tmp[2]) << 8;
RetuData = (RetuData|tmp[3]);
RetuData = RetuData >> 4;
HT[0] = RetuData;
// 计算温度T。原始值,未计算为标准单位°C。
RetuData = 0;
RetuData = (RetuData|tmp[3]) << 8;
RetuData = (RetuData|tmp[4]) << 8;
RetuData = (RetuData|tmp[5]);
RetuData = RetuData&0xfffff;
HT[1] = RetuData;
return 0;
}
else//设备忙,返回读取失败
{
return 1;
}
}
//原始数据转换函数
uint8_t StandardUnitCon(struct AHT20_INF* aht)
{
aht->rh = (double)aht->ht[0] *100 / 1048576;//2^20=1048576 //原式:(double)aht->HT[0] / 1048576 *100,为了浮点精度改为现在的
aht->te = (double)aht->ht[1] *200 / 1048576 -50;
//限幅,RH=0~100%; Temp=-40~85°C
if((aht->rh >=0)&&(aht->rh <=100) && (aht->te >=-40)&&(aht->te <=85))
{
aht->flag = 0;
return 0;//测量数据正常
}
else
{
aht->flag = 1;
return 1;//测量数据超出范围,错误
}
}
uint8_t Soft_I2C_Write(uint8_t addr,uint8_t reg_addr,uint8_t len,uint8_t *data_buf)
{
uint8_t i;
I2C_Start();
I2C_Send_Byte(addr << 1 | I2C_Direction_Transmitter);//发送器件地址+写命令
if(I2C_Wait_ACK())//等待应答
{
I2C_Stop();
return 1;
}
I2C_Send_Byte(reg_addr);//写寄存器地址
I2C_Wait_ACK();//等待应答
for(i=0;i<len;i++)
{
I2C_Send_Byte(data_buf[i]);//发送数据
if(I2C_Wait_ACK())//等待ACK
{
I2C_Stop();
return 1;
}
}
I2C_Stop();
return 0;
}
uint8_t Soft_I2C_Read(uint8_t addr,uint8_t reg_addr,uint8_t len,uint8_t *data_buf)
{
uint8_t result;
I2C_Start();
I2C_Send_Byte(addr << 1 | I2C_Direction_Transmitter);//发送器件地址+写命令
if(I2C_Wait_ACK())//等待应答
{
I2C_Stop();
return 1;
}
I2C_Send_Byte(reg_addr);//写寄存器地址
I2C_Wait_ACK();//等待应答
I2C_Start();
I2C_Send_Byte(addr << 1 | I2C_Direction_Receiver);//发送器件地址+读命令
I2C_Wait_ACK();//等待应答
while(len)
{
if(len==1)*data_buf=I2C_Read_Byte(0);//读数据,发送nACK
else *data_buf=I2C_Read_Byte(1);//读数据,发送ACK
len--;
data_buf++;
}
I2C_Stop();//产生一个停止条件
return 0;
}
获取全部代码请点这里👉参考代码
3编译并烧录。
编译成功之后,使用Flymcu
软件将代码烧录到程序中,利用串口助手
观察结果如下所示:
动图演示如下:
三、总结
在此次实验中,碰到很多问题,一开始由于接线不稳导致采集不到数据,后续可以采集到数据,可能是因为供电原因期间会出现”错误数据“,后续改到5V供电,输出稳定。
四、参考文献
[1] https://blog.csdn.net/hhhhhh277523/article/details/111397514
[2] https://blog.csdn.net/qq_43279579/article/details/111597278