STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集
一、什么是I2C
I²C(Inter-Integrated Circuit)是内部整合电路的称呼,是一种串行通讯总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边装置而发展。I²C(读作"I-squared-C" ),还有可选的拼写方式是I2C(读作I-two-C)以及IIC(读作I-I-C),在中国则多以"I方C"称之。
I2C(Inter-Integrated Circuit)总线是由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,器件封装形式小,通信速率较高等优点。I2C 总线支持任何IC 生产过程(CMOS、双极性)。通过串行数据(SDA)线和串行时钟 (SCL)线在连接到总线的器件间传递信息。每个器件都有一个唯一的地址识别(无论是微控制器——MCU、LCD 驱动器、存储器或键盘接口),而且都可以作为一个发送器或接收器(由器件的功能决定)。LCD 驱动器只能作为接收器,而存储器则既可以接收又可以发送数据。除了发送器和接收器外,器件在执行数据传输时也可以被看作是主机或从机(见表1)。主机是初始化总线的数据传输并产生允许传输的时钟信号的器件。此时,任何被寻址的器件都被认为是从机。
特征:
- 只要求两条总线线路:一条串行数据线SDA,一条串行时钟线SCL;
- 每个连接到总线的器件都可以通过唯一的地址和一直存在的简单的主机/从机关系软件设定地址,主机可以作为主机发送器或主机接收器;
- 它是一个真正的多主机总线,如果两个或更多主机同时初始化,数据传输可以通过冲突检测和仲裁防止数据被破坏;
- 串行的8 位双向数据传输位速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s;
- 连接到相同总线的IC 数量只受到总线的最大电容400pF 限制。
1.2 软件I2C和硬件I2C的区别
所谓硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的;软件I2C一般是用GPIO管脚,用软件控制管脚状态以模拟I2C通信波形。
硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。
模拟I2C 是通过GPIO,软件模拟寄存器的工作方式,而硬件(固件)I2C是直接调用内部寄存器进行配置。如果要从具体硬件上来看,可以去看下芯片手册。因为固件I2C的端口是固定的,所以会有所区别。
至于如何区分它们
可以看底层配置,比如IO口配置,如果配置了IO口的功能(IIC功能)那就是固件IIC,否则就是模拟
可以看IIC写函数,看里面有木有调用现成的函数或者给某个寄存器赋值,如果有,则肯定是固件IIC功能,没有的话肯定是数据一个bit一个bit模拟发生送的,肯定用到了循环,则为模拟。
根据代码量判断,模拟的代码量肯定比固件的要大。
- 硬件IIC用法比较复杂,模拟IIC的流程更清楚一些。
- 硬件IIC速度比模拟快,并且可以用DMA
- 模拟IIC可以在任何管脚上,而硬件只能在固定管脚上。
软件i2c是程序员使用程序控制SCL,SDA线输出高低电平,模拟i2c协议的时序。一般较硬件i2c稳定,但是程序较为繁琐,但不难。
硬件i2c程序员只要调用i2c的控制函数即可,不用直接的去控制SCL,SDA高低电平的输出。但是有些单片机的硬件i2c不太稳定,调试问题较多。
软硬I2C的比较
通信速度
相对来说,即使两者的通信速率设置成相等时,硬件I2C的通信速度要比软件I2C的速度要快。因为硬件I2C是通过片上外设实现的通信,CPU只需要去读写寄存器数据就可以进行I2C通信,程序较为简单并且占用的资源少。而软件I2C则需要程序模拟I2C时序,程序较为复杂且占用资源较多。
可移植性
对于硬件I2C来说,由于芯片I2C外设的IO口已经确定,无法随意更改其他IO口,因而可移植性较差;但是由于软件I2C是通过IO口模拟I2C通信时序实现的通信,因而可移植性比较好,在任何单片机上都可以使用,只需要修改一下通信时间以及配置好IO口就可以实现I2C通信。
稳定性
一般来说,软件模拟I2C稳定性要比硬件I2C更加稳定,硬件I2C不稳定,容易卡死,想要写得稳定程序就非常复杂;但是软件I2C可能会因为中断的影响造成数据读取不准确。
二、AHT20传输数据
2.1基本要求
- 每隔2秒钟采集一次温湿度数据
- 通过串口发送到上位机(windows10)
2.2主要代码
#include "led.h" #include "delay.h" #include "temhum.h" #include "sys.h" #include "usart.h" int main(void) { u32 CT_data[2]={0}; volatile float hum=0,tem=0; delay_init(); //延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级 uart_init(115200); //串口初始化为115200 LED_Init(); //LED端口初始化 temphum_init(); //ATH20初始化 while(1) { AHT20_Read_CTdata(CT_data); //不经过CRC校验,直接读取AHT20的温度和湿度数据 hum = CT_data[0]*100*10/1024/1024; //计算得到湿度值(放大了10倍) tem = CT_data[1]*200*10/1024/1024-500;//计算得到温度值(放大了10倍) printf("湿度:%.1f%%\r\n",(hum/10)); printf("温度:%.1f度\r\n",(tem/10)); printf("\r\n"); //延时2s,LED闪烁提示串口发送状态 LED=0; delay_ms(1000); LED=1; delay_ms(1000); } }
引脚连接的一些细节:
完整项目代码:https://github.com/LinZJ0423/STM32
完整项目代码放在STM32_AHT20.zip内。
结果展示:
可以在图中看出 芯片正在采集数据,在当用手捂住芯片的时候温度和湿度都在上升,说明芯片在正常工作且没有出现异常。
三、 基于SPI的OLED通信
3.1SPI的简介
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是由 Motorola 公司提出的一种高速的,全双工,同步的通信总线,被广泛地使用在 ADC、LCD 等设备与 MCU 间要求通讯速率较高的场合。SPI总线系统可直接与各个厂家生产的多种标准外围器件连接,该接口一般使用4条线:串行时钟线(SCK)、主机输入/从机输出数据线MISO、主机输出/从机输入数据线MOST和低电平有效的从机选择线C/S(有的SPI接口芯片带有中断信号线INT或INT、有的SPI接口芯片没有主机输出/从机输入数据线MOSI)。
3.2SPI时序
上图中的时序只是 SPI 其中一种通讯模式,SPI 一共有四种通讯模式,它们的主要区别是总线空闲时 SCK 的时钟状态以及数据采样时刻。为方便说明,在此引入“时钟极性 CPOL”和“时钟相位 CPHA”的概念。
时钟极性 CPOL 是指 SPI 通讯设备处于空闲状态时,SCK 信号线的电平信号(即 SPI 通讯开始前、 NSS 线为高电平时 SCK 的状态)。CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。
时钟相位 CPHA 是指数据的采样的时刻,当 CPHA=0 时,MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的“奇数边沿”被采样。当 CPHA=1 时,数据线在 SCK 的“偶数边沿”采样。
3.3OLED简介
OLED(OrganicLight-Emitting Diode),又称为有机电激光显示、有机发光半导体(OrganicElectroluminesence Display,OLED)。OLED属于一种电流型的有机发光器件,是通过载流子的注入和复合而致发光的现象,发光强度与注入的电流成正比。OLED在电场的作用下,阳极产生的空穴和阴极产生的电子就会发生移动,分别向空穴传输层和电子传输层注入,迁移到发光层。当二者在发光层相遇时,产生能量激子,从而激发发光分子最终产生可见光。
3.4实际操作
基本要求:
- 显示自己的学号和姓名;
- 显示AHT20的温度和湿度;
- 上下或左右的滑动显示(使用硬件刷屏模式)
主要程序
#include "delay.h" #include "sys.h" #include "oled.h" #include "gui.h" #include "test.h" #include "temhum.h" int main(void) { u8 i; delay_init(); //延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); AHT20_Init(); OLED_Init(); //初始化OLED OLED_Clear(0); //清屏(全黑) GUI_ShowCHinese(0,20,32,"通信二班",1); OLED_Display_scroll(); delay_ms(1000); delay_ms(1000); delay_ms(1000); delay_ms(1000); delay_ms(1000); delay_ms(1000); delay_ms(1000); while(1) { TEST_Chinese(); //学号、姓名显示 OLED_Clear(0); for(i=0;i<5;i++) { TEST_Menu2(); //AHT20温湿度显示 delay_ms(500); } OLED_Clear(0); } }
OLED主要函数
//OLED控制用函数 void OLED_WR_Byte(unsigned dat,unsigned cmd); void OLED_Display_On(void); void OLED_Display_Off(void); void OLED_Set_Pos(unsigned char x, unsigned char y); void OLED_Reset(void); void OLED_Init_GPIO(void); void OLED_Init(void); void OLED_Set_Pixel(unsigned char x, unsigned char y,unsigned char color); void OLED_Display(void); void OLED_Clear(unsigned dat); //OLED滚动显示 void OLED_Display_scroll(void);
接线说明:
void OLED_Display_scroll(void) { OLED_WR_Byte(0x2e,OLED_CMD);//关滚动 OLED_WR_Byte(0x2a,OLED_CMD);//29向右,2a向左(垂直水平滚动) OLED_WR_Byte(0x00,OLED_CMD);//A:空字节 OLED_WR_Byte(0x00,OLED_CMD);//B:水平起始页 OLED_WR_Byte(0x00,OLED_CMD);//C:水平滚动速度 OLED_WR_Byte(0x07,OLED_CMD);//D:水平结束页 OLED_WR_Byte(0x01,OLED_CMD);//E:每次垂直滚动位移 OLED_WR_Byte(0x2f,OLED_CMD);//开滚动 }
void TEST_Chinese(void) { OLED_Clear(0); GUI_ShowString(50,50,"***",8,1); //此处显示学号 GUI_ShowCHinese(10,10,32,"***",1); //此处显示姓名 delay_ms(1000); OLED_Clear(0); }
显示温湿度:
void TEST_Menu2(void) { u32 CT_data[2]; volatile int c1=0,t1=0; srand(123456); delay_ms(40); AHT20_Read_CTdata(CT_data); //不经过CRC校验,直接读取AHT20的温度和湿度数据 推荐每隔大于1S读一次 c1 = CT_data[0]*100*10/1024/1024; //湿度 t1 = CT_data[1]*200*10/1024/1024-500; //温度 GUI_DrawLine(0, 10, WIDTH-1, 10,1); GUI_DrawLine(WIDTH/2-1,11,WIDTH/2-1,HEIGHT-1,1); GUI_DrawLine(WIDTH/2-1,10+(HEIGHT-10)/2-1,WIDTH-1,10+(HEIGHT-10)/2-1,1); GUI_ShowString(0,1,"2020-12-23",8,1); GUI_ShowString(74,1,"Wednesday",8,1); GUI_ShowString(14,HEIGHT-1-10,"Cloudy",8,1); GUI_ShowString(WIDTH/2+1,13,"TEMP",8,1); //显示温度 GUI_DrawCircle(WIDTH-20, 25, 1,2); GUI_ShowString(WIDTH-15,20,"C",16,1); GUI_ShowNum(WIDTH/2+8,20,t1/10,2,16,1); GUI_ShowString(WIDTH-41,26,".",8,1); GUI_ShowNum(WIDTH-35,20,t1%10,1,16,1); GUI_ShowString(WIDTH/2+1,39,"HUM",8,1); //显示湿度 GUI_ShowNum(WIDTH/2+8,46,c1/10,2,16,1); GUI_ShowString(WIDTH-41,52,".",8,1); GUI_ShowNum(WIDTH-35,46,c1%10,1,16,1); GUI_ShowString(WIDTH-21,46,"%",16,1); GUI_DrawBMP(6,16,51,32, BMP5, 1); delay_ms(1000); }
滑动显示字符:
void OLED_Display_scroll(void) { OLED_WR_Byte(0x2e,OLED_CMD);//关滚动 OLED_WR_Byte(0x2a,OLED_CMD);//29向右,2a向左(垂直水平滚动) OLED_WR_Byte(0x00,OLED_CMD);//A:空字节 OLED_WR_Byte(0x00,OLED_CMD);//B:水平起始页 OLED_WR_Byte(0x00,OLED_CMD);//C:水平滚动速度 OLED_WR_Byte(0x07,OLED_CMD);//D:水平结束页 OLED_WR_Byte(0x01,OLED_CMD);//E:每次垂直滚动位移 OLED_WR_Byte(0x2f,OLED_CMD);//开滚动 }
字库取模
由于OLED显示中使用了中文字符,因此需要将中文进行取模得到中文的点阵编码并存到oledfont.h中,方便程序调用并显示到OLED上去。
下面演示如何使用汉字取模软件进行汉字取模:
我们利用一些工具生成字模,他会在下方显示字形码,同时需要注意一些设置
与图中相同即可。
在gui.c中找到oledfont.h的头文件,如图
在上述函数中放入你需要显示的中文的字形码,我们的准备工作就完成了。
同时完整的项目代码在前面的github地址中,名为ISP.zip的压缩包中。
四、结果展示
显示了我们需要的温湿度,滚动屏,以及姓名和学号。
五、心得体会
这是一次非常不容易的实验,在实验室做实验时就做了很多的调试,最后磕磕碰碰还是显示出来了。课后做这个题目的时候一开始不懂得如何将两个项目合并,参考了许多的博客,关于代码的部分还是一知半解,虽然做完了作业但是还是会继续学习代码的流程,虽然图中展示的结果或许十分简单,但是要做出来还是十分困难。
六、参考地址
https://blog.csdn.net/qq_45237293/article/details/111712565
https://blog.csdn.net/qq_43279579/article/details/111414037