一、AHT20实现温湿度的数据采集
1、I2C协议层简述 (1)I2C协议的通讯过程 这些图表示的是主机和从机通讯时,SDA 线的数据包序列。 其中 S 表示由主机的 I2C 接口产生的传输起始信号(S),这时连接到 I2C 总线上的所有 从机都会接收到这个信号。 起始信号产生后,所有从机就开始等待主机紧接下来 广播 的从机地址信号 (SLAVE_ADDRESS)。在 I2C 总线上,每个设备的地址都是唯一的,当主机广播的地址与 某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。 根据 I2C 协议,这个从机地址可以是 7 位或 10 位。 在地址位之后,是传输方向的选择位,该位为 0 时,表示后面的数据传输方向是由主 机传输至从机,即主机向从机写数据。该位为 1 时,则相反,即主机由从机读数据。 从机接收到匹配的地址后,主机或从机会返回一个应答(ACK)或非应答(NACK)信号, 只有接收到应答信号后,主机才能继续发送或接收数据。 (2)通讯的起始和停止信号 起始(S)和停止§信号是两种特殊的状态,见图 24-5。当 SCL 线是高电 平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA 线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。 (3)数据有效性 I2C 使用 SDA 信号线来传输数据,使用 SCL信号线进行数据同步。见图 24-6。SDA 数 据线在 SCL的每个时钟周期传输一位数据。传输时,SCL为高电平的时候 SDA表示的数据 有效,即此时的 SDA为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL为低 电平时,SDA的数据无效,一般在这个时候 SDA进行电平切换,为下一次表示数据做好准 备。 每次数据传输都以字节为单位,每次传输的字节数不受限制。 (4)地址及数据方向 I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W——),第 8 位或第 11 位。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。 读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接 收信号,写数据方向时,SDA 由主机控制,从机接收信号。 (5)响应 2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种 信号。作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后, 若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下 一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接 收到该信号后会产生一个停止信号,结束信号传输。 传输时主机产生时钟,在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接 收端控制 SDA,若 SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。
2、什么是软件I2C和硬件I2C 如果我们直接控制 STM32的两个 GPIO 引脚,分别用作 SCL及 SDA,按照上述信号的 时序要求,直接像控制 LED 灯那样控制引脚的输出(若是接收数据时则读取 SDA 电平),就 可以实现 I2C 通讯。同样,假如我们按照 USART 的要求去控制引脚,也能实现 USART 通 讯。所以只要遵守协议,就是标准的通讯,不管您如何实现它,不管是 ST 生产的控制器还 是 ATMEL 生产的存储器, 都能按通讯标准交互。
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);
}
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();
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("读取失败!!!");
}
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");
}
(3)编译生成hex文件,烧录
(4)打开串口调试助手,查看温湿度数据采集结果
二、 基于SPI总线的OLED显示
1、SPI协议简介 与 I2C 的类似,SPI 协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环节。这里简单介绍它的通讯时序以及模式。 (1)SPI通讯时序 这是一个主机的通讯时序。NSS、SCK、MOSI 信号都由主机控制产生,而 MISO 的信 号由从机产生,主机通过该信号线读取从机的数据。MOSI 与 MISO 的信号只在 NSS 为低 电平的时候才有效,在 SCK 的每个时钟周期 MOSI 和 MISO 传输一位数据 (2)CPOL/CPHA及通讯模式 上面讲述的图 25-2 中的时序只是 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)SPI四种模式 由 CPOL 及 CPHA 的不同状态,SPI 分成了四种模式(见下表),主机与从机需要工作在相同的模式下才可以正常通讯,实际中采用较多的是“模式 0”与“模式 3”。 (4)SYN32的SPI架构 STM32 的 SPI 外设可用作通讯的主机及从机,支持最高的 SCK 时钟频率为 fpclk/2 (STM32F103 型号的芯片默认 fpclk1为 72MHz,fpclk2为 36MHz),完全支持 SPI 协议的 4 种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。它还支持双线全双工(前面小节说明的都是这种模式)、双线单向以及单线模式。其中双线单向模式可以同时使用 MOSI 及 MISO 数据线向一个方向传输数据,可以加快一倍的传输速度。而单线模式则可以减少硬件接线,当然这样速率会受到影响。我们只讲解双线全双工模式。 STM32的SPI架构剖析 SPI通讯引脚 2、OLED屏显 (1)简介 LED 点阵彩色显示器的单个像素点内 包含红绿蓝三色 LED灯,显示原理类似我们实验板上的 LED彩灯,通过控制红绿蓝颜色的强度进行混色,实现全彩颜色输出,多个像素点构成一个屏幕。OLED 显示器与 LED 点阵彩色显示器的原理类似,但由于它采用的像素单元是“有机发光二极管”(Organic Light Emitting Diode),所以像素密度比普通 LED 点阵显示器高得多。 OLED的显存情况我们可以理解为:水平方向分布了128个像素点,垂直方向分布了64个像素点(如图一所示)。而驱动芯片在点亮像素点的时候,是以8个像素点为单位的。官方的例程推荐的是垂直扫描的方式,也就是先画垂直方向的8个像素点(如下图二所示),所以我们在画点的时候Y的取值为0-7,X的取值为0-127.
(2)OLED汉字取模显示 生成汉字字模后进行画点,以列行式字模为例,先画Y方向的八个点,再画X方向的128个点。
{0x10,0x10,0xF0,0x1F,0x10,0xF0,0x00,0x80,0x82,0x82,0xE2,0x92,0x8A,0x86,0x80,0x00},
{0x40,0x22,0x15,0x08,0x16,0x61,0x00,0x00,0x40,0x80,0x7F,0x00,0x00,0x00,0x00,0x00},
****************************************************************/
void tt_Oled_ShowChinese(unsigned char x,unsigned char y,unsigned char no)
{
UINT8 t;
tt_Oled_Set_Pos(x,y);
for(t=0;t<16;t++){
tt_Oled_WR_Byte(dis_word[2*no][t],OLED_DATA);
}
tt_Oled_Set_Pos(x,y+1);
for(t=0;t<16;t++){
tt_Oled_WR_Byte(dis_word[2*no+1][t],OLED_DATA);
}
}
汉字大小是1616的,所以会占两页,按照上面的分析,先画的是第一页(168个点),对应tt_Oled_WR_Byte(dis_word[2no][t],OLED_DATA);,接着画第二页(168个点))tt_Oled_WR_Byte(dis_word[2*no][t],OLED_DATA);
3、STM32的SPI或IIC接口显示AHT20的温度和湿度 (1)下载显示工程代码 OLED显示代码链接
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();
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");
}
t=T1/10;
t1=T1%10;
a=(float)(t+t1*0.1);
h=H1/10;
h1=H1%10;
b=(float)(h+h1*0.1);
sprintf(strTemp,"%.1f",a);
sprintf(strHumi,"%.1f",b);
GUI_ShowCHinese(16,00,16,"温湿度显示",1);
GUI_ShowCHinese(16,20,16,"温度",1);
GUI_ShowString(53,20,strTemp,16,1);
GUI_ShowCHinese(16,38,16,"湿度",1);
GUI_ShowString(53,38,strHumi,16,1);
delay_ms(1500);
delay_ms(1500);
}
#include "delay.h"
#include "usart.h"
#include "bsp_i2c.h"
#include "sys.h"
#include "oled.h"
#include "gui.h"
#include "test.h"
int main(void)
{
delay_init();
uart_init(115200);
IIC_Init();
NVIC_Configuration();
OLED_Init();
OLED_Clear(0);
while(1)
{
read_AHT20_once();
OLED_Clear(0);
delay_ms(1500);
}
}
(2)硬件接线
4、STM32的SPI或IIC接口显示学号和姓名 (1)打开汉字取模软件生成字模 网盘下载链接(提取码jdzg)
- 相关设置
- 输入文字生成字模
(2)将字模编码复制添加到代码oledfont.h中的16*16部分
void TEST_Chinese(void)
{
GUI_ShowString(16,0,"631907030706",8,1);
GUI_ShowCHinese(16,20,16,"卡布布卡",1);
delay_ms(1000);
OLED_Clear(0);
}
int main(void)
{
delay_init();
uart_init(115200);
IIC_Init();
NVIC_Configuration();
OLED_Init();
OLED_Clear(0);
while(1)
{
TEST_Chinese();
OLED_Clear(0);
delay_ms(1500);
}
}
(3)编译生成hex文件,烧录并观察结果
5、上下或左右的滑动显示长字符 (1)打开字模软件,生成字模
(2)添加字模编码到代码
void TEST_MainPage(void)
{
GUI_ShowCHinese(10,20,16,"卡卡欢迎您!",1);
delay_ms(1500);
delay_ms(1500);
}
int main(void)
{
delay_init();
uart_init(115200);
IIC_Init();
NVIC_Configuration();
OLED_Init();
OLED_Clear(0);
while(1)
{
OLED_WR_Byte(0x2e,OLED_CMD);
OLED_WR_Byte(0x2a,OLED_CMD);
OLED_WR_Byte(0x00,OLED_CMD);
OLED_WR_Byte(0x00,OLED_CMD);
OLED_WR_Byte(0x00,OLED_CMD);
OLED_WR_Byte(0x07,OLED_CMD);
OLED_WR_Byte(0x01,OLED_CMD);
OLED_WR_Byte(0x2f,OLED_CMD);
TEST_MainPage();
OLED_Clear(0);
delay_ms(1500);
}
}
(3)编译烧录,观察结果
三、总结
万事开头难,一开始还不能理解如何让字符显示在OLED显示屏上,后面参考了几篇学长学姐和其他博主的博客明白了一系列步骤的过程,在探究过程中分析代码的实现性,理解中文点阵字库的编码取模原理。在完成OLED显示的步骤中,逐步解决字符显现不完全、数字部分不显示的问题。
|