文章目录
一、题目要求
学习I2C总线通信协议,使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口和OLED显示屏输出。
1)阅读AHT20数据手册,编程实现:每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机(win10)。
2)理解OLED屏显和汉字点阵编码原理,使用STM32F103的SPI或IIC接口实现以下功能:
显示AHT20的温度和湿度;
二、“软件I2C”和“硬件I2C”
(一)I2C 通讯协议
I2C 通讯协议 (Inter - Integrated Circuit) 是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单, 可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路 (IC) 间的通讯。
STM32 的 I2C 特性及架构
如果我们直接控制 STM32 的两个 GPIO 引脚,分别用作 SCL 及 SDA,按照上述信号的时序要求,直接像控制 LED 灯那样控制引脚的输出 (若是接收数据时则读取 SDA 电平), 就可以实现 I2C 通讯。同样,假如我们按照 USART 的要求去控制引脚,也能实现 USART 通讯。所以只要遵守协议,就是标准的通讯,不管您如何实现它, 不管是 ST 生产的控制器还是 ATMEL 生产的存储器,都能按通讯标准交互。
由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。相对地,还有“硬件协议”方式,STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号, 收发数据并缓存起来,CPU 只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理 I2C 协议的方式减轻了CPU 的工作,且使软件设计更加简单。
下图为STM32 的 I2C 架构剖析:
(二)硬件I2C
所谓硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的;软件I2C一般是用GPIO管脚,用软件控制管脚状态以模拟I2C通信波形。
硬件I2C, 32芯片上有片上外设可以实现I2C通信协议,片上外设可不是开发板的硬件哈,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。硬件I2C的GPIO引脚是固定的
(三)软件I2C
软件I2C,顾名思义,就是编写程序来模拟I2C通信,直接控制GPIO引脚电平产生通信时序,通俗点就是配置GPIO引脚”有序“地输入输出0或1。软件I2C控制的GPIO引脚是任意的。
(四)硬件I2C和软件I2C
- 硬件I2C用法比较复杂,模拟IIC的流程更清楚一些。
- 硬件I2C速度比模拟快,并且可以用DMA
- 模拟I2C可以在任何管脚上,而硬件只能在固定管脚上。
一般较硬件i2c稳定,但是程序较为繁琐,但不难。硬件i2c程序员只要调用i2c的控制函数即可,不用直接的去控制SCL,SDA高低电平的输出。但是有些单片机的硬件i2c不太稳定,调试问题较多。
三、AHT20采集温湿度串口显示
(一)工程建立
1.准备
温湿度传感器AHT20
串口调试助手
了解AHT20芯片的相关信息,具体信息请到官方下载对应产品介绍文档
资料链接:http://www.aosong.com/class-36.html
2.核心代码
已在文章末端上传整个工程代码,可供参考。
- main.c主函数
> #include "delay.h"
#include "usart.h"
#include "bsp_i2c.h"
int main(void)
{
delay_init(); //?óê±oˉêy3?ê??ˉ
uart_init(115200); //′??ú3?ê??ˉ?a115200
IIC_Init();
while(1)
{
printf("温度湿度显示");
read_AHT20_once();
delay_ms(1500);
}
}
- AHT20芯片使用过程read_AHT20_once函数
> void read_AHT20_once(void)
> {
> delay_ms(10);
> reset_AHT20();
> delay_ms(10);
> init_AHT20();
> delay_ms(10);
> startMeasure_AHT20();
> delay_ms(80);
> read_AHT20();
> delay_ms(5);
> }
- AHT20芯片读取数据read_AHT20函数
void read_AHT20(void)
{
uint8_t i;
for(i=0; i<6; i++)
{
readByte[i]=0;
}
//-------------
I2C_Start();
I2C_WriteByte(0x71);
ack_status = Receive_ACK();
readByte[0]= I2C_ReadByte();
Send_ACK();
readByte[1]= I2C_ReadByte();
Send_ACK();
readByte[2]= I2C_ReadByte();
Send_ACK();
readByte[3]= I2C_ReadByte();
Send_ACK();
readByte[4]= I2C_ReadByte();
Send_ACK();
readByte[5]= I2C_ReadByte();
SendNot_Ack();
//Send_ACK();
I2C_Stop();
//--------------
if( (readByte[0] & 0x68) == 0x08 )
{
H1 = readByte[1];
H1 = (H1<<8) | readByte[2];
H1 = (H1<<8) | readByte[3];
H1 = H1>>4;
H1 = (H1*1000)/1024/1024;
T1 = readByte[3];
T1 = T1 & 0x0000000F;
T1 = (T1<<8) | readByte[4];
T1 = (T1<<8) | readByte[5];
T1 = (T1*2000)/1024/1024 - 500;
AHT20_OutData[0] = (H1>>8) & 0x000000FF;
AHT20_OutData[1] = H1 & 0x000000FF;
AHT20_OutData[2] = (T1>>8) & 0x000000FF;
AHT20_OutData[3] = T1 & 0x000000FF;
}
else
{
AHT20_OutData[0] = 0xFF;
AHT20_OutData[1] = 0xFF;
AHT20_OutData[2] = 0xFF;
AHT20_OutData[3] = 0xFF;
printf("lyy");
}
printf("\r\n");
printf("温度:%d%d.%d",T1/100,(T1/10)%10,T1%10);
printf("湿度:%d%d.%d",H1/100,(H1/10)%10,H1%10);
printf("\r\n");
}
整体布局:
(二)电路连接
AHT20对应引脚及描述如下所示:
AHT20与STM32F103C8T6的对应
AHT20 | STM32F103C8T6 |
---|---|
VDD | 3V3 |
GND | GND |
SDA | PB7 |
SCL | PB6 |
USB to TTL与STM32F103C8T6的连接
USB to TTL | STM32F103C8T6 |
---|---|
3V3 | 3V3 |
GND | GND |
RXD | A9 |
TXD | A10 |
(三)编译烧录
1.编译结果
2.烧录
(四)效果展示
串口温度显示
四、AHT20采集温度OLED显示
(一)代码撰写
1.取字模
打开字模软件提取“温湿度显示”的字模
- 在文字输入区输入需要的文字
- 按下电脑上Ctrl和Enter
- 点击“取模方式”中的C51模式
在点阵生成区就可以得到对应的字模。
2.代码编写
在OLED的工程文件中直接进行修改。
将上述中温湿度显示的bsp_i2c.h、bsp_i2c.c、sys.h(修改名称为AHT20_sys.h)、sys.c(修改名称为AHT20_sys.c)进行移植。
- 将对应文件粘贴至USER目录下
-
添加文件(如果出现对usart头文件的报错,说明可能原工程下没有对应文件,需要进行同步骤添加)
-
对于AHT20_sys.c和AHT20_sys.h文件的重命名,对应文件中的头文件也需要进行修改
- 对文件进行编译会出现 NVIC_Configuration重定义的报错,原因是AHT20_sys.c和sys.c的内容均是这个函数,因此需要进行修改。
#include "sys.h"
//THUMB指令不支持汇编内联
//采用如下方法实现执行汇编指令WFI
void WFI_SET(void)
{
__ASM volatile("wfi");
}
//关闭所有中断
void INTX_DISABLE(void)
{
__ASM volatile("cpsid i");
}
//开启所有中断
void INTX_ENABLE(void)
{
__ASM volatile("cpsie i");
}
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
- 在bsp_i2c.c文件首部添加以下定义内容
uint32_t H1=0; //Humility
uint32_t T1=0; //Temperature
uint8_t t;
u8 *strTemp1;
u8 *strTemp2;
u8 *strTemp3;
u8 *strHumi1;
u8 *strHumi2;
u8 *strHumi3;
- 在bsp_i2c.c文件中void read_AHT20(void)函数末尾添加
Show_OLED();
如下图所示
- 在bsp_i2c.c文件中void read_AHT20(void)函数下方添加void Show_OLED(void)函数
void Show_OLED(void)
{
t = T1/100;
switch(t)
{
case 0:break;
case 1:strTemp1 = "1";break;
case 2:strTemp1 = "2";break;
case 3:strTemp1 = "3";break;
case 4:strTemp1 = "4";break;
case 5:strTemp1 = "5";break;
case 6:strTemp1 = "6";break;
case 7:strTemp1 = "7";break;
case 8:strTemp1 = "8";break;
case 9:strTemp1 = "9";break;
}
t = (T1/10)%10;
switch(t)
{
case 0:strTemp2 = "0";break;
case 1:strTemp2 = "1";break;
case 2:strTemp2 = "2";break;
case 3:strTemp2 = "3";break;
case 4:strTemp2 = "4";break;
case 5:strTemp2 = "5";break;
case 6:strTemp2 = "6";break;
case 7:strTemp2 = "7";break;
case 8:strTemp2 = "8";break;
case 9:strTemp2 = "9";break;
}
t = T1%10;
switch(t)
{
case 0:strTemp3 = "0";break;
case 1:strTemp3 = "1";break;
case 2:strTemp3 = "2";break;
case 3:strTemp3 = "3";break;
case 4:strTemp3 = "4";break;
case 5:strTemp3 = "5";break;
case 6:strTemp3 = "6";break;
case 7:strTemp3 = "7";break;
case 8:strTemp3 = "8";break;
case 9:strTemp3 = "9";break;
}
t = H1/100;
switch(t)
{
case 0:break;
case 1:strHumi1 = "1";break;
case 2:strHumi1 = "2";break;
case 3:strHumi1 = "3";break;
case 4:strHumi1 = "4";break;
case 5:strHumi1 = "5";break;
case 6:strHumi1 = "6";break;
case 7:strHumi1 = "7";break;
case 8:strHumi1 = "8";break;
case 9:strHumi1 = "9";break;
}
t = H1/100;
switch(t)
{
case 0:strHumi2 = "0";break;
case 1:strHumi2 = "1";break;
case 2:strHumi2 = "2";break;
case 3:strHumi2 = "3";break;
case 4:strHumi2 = "4";break;
case 5:strHumi2 = "5";break;
case 6:strHumi2 = "6";break;
case 7:strHumi2 = "7";break;
case 8:strHumi2 = "8";break;
case 9:strHumi2 = "9";break;
}
t = H1/100;
switch(t)
{
case 0:strHumi3 = "0";break;
case 1:strHumi3 = "1";break;
case 2:strHumi3 = "2";break;
case 3:strHumi3 = "3";break;
case 4:strHumi3 = "4";break;
case 5:strHumi3 = "5";break;
case 6:strHumi3 = "6";break;
case 7:strHumi3 = "7";break;
case 8:strHumi3 = "8";break;
case 9:strHumi3 = "9";break;
}
GUI_ShowString(40,32," ",16,1);
GUI_ShowString(40,48," ",16,1);
GUI_ShowCHinese(0,32,16,"温度:",1);
GUI_ShowString(40,32,strTemp1,16,1);
GUI_ShowString(48,32,strTemp2,16,1);
GUI_ShowString(56,32,".",16,1);
GUI_ShowString(64,32,strTemp3,16,1);
GUI_ShowCHinese(72,32,16,"℃",1);
GUI_ShowCHinese(0,48,16,"湿度:",1);
GUI_ShowString(40,48,strHumi1,16,1);
GUI_ShowString(48,48,strHumi2,16,1);
GUI_ShowString(56,48,".",16,1);
GUI_ShowString(64,48,strHumi3,16,1);
GUI_ShowCHinese(72,48,16,"%",1);
}
- 在oledfont.h文件中的const typFNT_GB16 cfont16[]中添加字模内容,如下图所示
- main.c中的代码如下(注意添加头文件)
#include "bsp_i2c.h"
#include "AHT20_sys.h"
int main(void)
{
delay_init(); //延时函数初始化
NVIC_Configuration(); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
OLED_Init();
IIC_Init(); //初始化OLED
OLED_Clear(0); //清屏(全黑)
while(1)
{
read_AHT20_once(); //延时函数初始化
delay_ms(5000); //显示5s
OLED_Clear(0); //清屏刷新温湿度数据
}
}
(二)电路连接
各板块线路连接如下图所示:
OLED模块和STM32F103C8T6开发板:
(三)编译烧录
编译结果如下所示
烧录可参考“AHT20采集温湿度串口显示”的烧录步骤。
(四)效果展示
OLED和AHT20温湿度
五、总结
刚开始做实验的时候,出现了温湿度读取为00的情况,原因是没有读取到温湿度数据而不是传感器的问题,这个是需要注意的。在OLED显示温湿度进行文件移植的时候,在编译过程中出现了函数NVIC_Configuration重定义的问题,原因是因为移植的AHT20_sys.c的文件和原本的sys.c的文件中均包含了这个函数,后面是根据其他成功的示例工程文件对sys.c的文件进行了修改才能够编译成功。还需要注意一下,如果下载了以下提供的工程文件再编译会出现报错,这里需要自行启动文件的路径修修改。
以上两个工程文件如下:
链接:https://pan.baidu.com/s/1ELHGZWbQTZoopZJPLUz1Gg
提取码:uj5o
六、参考文献
https://blog.csdn.net/m0_58892312/article/details/121410862
https://blog.csdn.net/qq_43279579/article/details/111597278
https://blog.csdn.net/qq_46467126/article/details/121439142
https://blog.csdn.net/qq_31860135/article/details/88658136
https://doc.embedfire.com/module/module_tutorial/zh/latest/Module_Manual/iic_class/iic.html