本次实验内容:
1.学习I2C总线协议,完成基于I2C协议的ATH20温湿度的数据采集,并串口输出。
2.理解OLED屏显和汉字点阵编码原理,完成对应的练习。
使用硬件器材及软件:
STM32F103C8T6、若干杜邦线飞线、USB转TTL、AHT20温湿度传感器、0.96‘’OLED屏、面包板、
Keil 5 MDK、flymuc烧录软件、串口调试助手
文章目录
一、I2C总线协议及温湿度读取实验
1、I2C总线协议
1.1 什么是I2C总线协议
I2C总线是一个半双工通信协议。由两根线组成,时钟线(SCL)和数据线(SDA)。I2C可以分为主机和从机,同一总线上只能有一个主机、可以有多个从机。需要注意的是从机不能发出传输请求,并且从机的数据传输也是由主机控制的。本次实验内容便是将STM32作为主机,控制AHT20完成温湿度数据采集并传输数据。
1.2 I2C总线的物理层和协议层
- 以下为物理层接线方式
- I2C总线的协议层则是定义了通信的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等。接下来简单介绍以下各个定义:
1.起始信号和停止信号
起始信号:在SCL为高电平时,SDA从高电平变为低电平。`
停止信号:在SCL为高电平时,SDA从低电平变为高电平。
起始信号和停止信号是一种电平跳变时序信号,而不是一个电平信号。以下为演示视图:
起始信号
停止信号
2.数据有效性
如下图,当SCL处于低电平时,SDA数据无效,此时SDA数据互换。
3.响应
当SDA传输数据后,接收方对接受到的数据进行一个应答。如果希望继续进行传输数据,则回应应答信号(低电平),否则回应非应答信号(高电平)。
2、实践练习
实验要求:
使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。
- 以下为功能实现主要函数,详细代码请跳转
void read_AHT20_once(void)//主函数调用,读取一次温湿度数据
{
delay_ms(1);
reset_AHT20();//重置
delay_ms(1);
init_AHT20();//初始化
delay_ms(1);
startMeasure_AHT20();//开始测量数据
delay_ms(8);
read_AHT20();//读取数据
delay_ms(2);
}
- 下面是上方调用的各个函数具体实现代码。
void reset_AHT20(void)
{
I2C_Start();
I2C_WriteByte(0x70);
ack_status = Receive_ACK();
if(ack_status) printf("1");
else printf("1-n-");
I2C_WriteByte(0xBA);
ack_status = Receive_ACK();
if(ack_status) printf("2");
else printf("2-n-");
I2C_Stop();
}
void init_AHT20(void)
{
I2C_Start();
I2C_WriteByte(0x70);
ack_status = Receive_ACK();
if(ack_status) printf("3");
else printf("3-n-");
I2C_WriteByte(0xE1);
ack_status = Receive_ACK();
if(ack_status) printf("4");
else printf("4-n-");
I2C_WriteByte(0x08);
ack_status = Receive_ACK();
if(ack_status) printf("5");
else printf("5-n-");
I2C_WriteByte(0x00);
ack_status = Receive_ACK();
if(ack_status) printf("6");
else printf("6-n-");
I2C_Stop();
}
void startMeasure_AHT20(void)
{
I2C_Start();
I2C_WriteByte(0x70);
ack_status = Receive_ACK();
if(ack_status) printf("7");
else printf("7-n-");
I2C_WriteByte(0xAC);
ack_status = Receive_ACK();
if(ack_status) printf("8");
else printf("8-n-");
I2C_WriteByte(0x33);
ack_status = Receive_ACK();
if(ack_status) printf("9");
else printf("9-n-");
I2C_WriteByte(0x00);
ack_status = Receive_ACK();
if(ack_status) printf("10");
else printf("10-n-");
I2C_Stop();
}
void read_AHT20(void)
{
uint8_t i;
for(i=0; i<6; i++)
{
readByte[i]=0;
}
I2C_Start();//I2C启动
I2C_WriteByte(0x71);//I2C写数据
ack_status = Receive_ACK();//收到的应答信息
readByte[0]= I2C_ReadByte();//I2C读取数据
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();//I2C停止函数
//判断读取到的第一个字节是不是0x08,0x08是该芯片读取流程中规定的,如果读取过程没有问题,就对读到的数据进行相应的处理
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");
//根据AHT20芯片中,温度和湿度的计算公式,得到最终的结果,通过串口显示
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");
}
- 演示视图
二、OLED屏显原理及实验
1、OLED屏显和汉字点阵编码原理
1.1 SPI接口
SPI(Serial Peripheral interface)是串行外围设备接口。是一种高速、全双工、同步的通信总线,在芯片上只占用四根线。以下为物理层结构
四条线分别为:
1.MISO 主设备数据输入,从设备数据输出。
2.MOSI 主设备数据输出,从设备数据输入。
3.SCLK 时钟信号,由主设备产生。
4.CS 从设备片选信号,由主设备控制。
SPI的主要特点:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。
下为SPI通信过程
MOSI与MISO的信号只在NSS为低电平的时候才有效,在SCK的每个时钟周期MOSI和MISO传输一位数据。
1.2 OLED介绍
本次实验使用的是0.96‘’OLED
- 实物视图:
- 模块原理图:
- 与STM32C8T6接线说明:
1.3 OLED字模显示
OLED显示字符就是点亮对应的点,实现目标字符的显示。在Keil中,我们只能控制点亮对应的点亮,而不能直接去显示目标字符,因此需要使用字模转换工具,安装地址
安装完成后,打开程序,如下图。字符集选择用户定义,编辑码表中填入想要显示的字符,最后文件格式选为C文件即可保存导出,此处以“你好”作为示例。
-
保存后的文件用记事本打开,后面的一串16进制数就是需要的显示点。
-
在文件中找到oledfont.h中的cfont16数组存储进去即可。
2、OLED屏显实验
实验要求:
使用STM32F103的SPI或IIC接口实现以下功能:
- 显示自己的学号和姓名;
- 显示AHT20的温度和湿度;
- 上下或左右的滑动显示长字符,比如“Hello,欢迎来到重庆交通大学物联网205实训室!”或者一段歌词或诗词(最好使用硬件刷屏模式)。
2.1 姓名学号显示
- 主要实现函数
void TEST_MainPage(void)
{
GUI_ShowCHinese(40,20,16,"郑",1);//40、20、16、1分别代表x轴、y轴、字符大小、个数
GUI_ShowString(8,40,"63200",16,1);
delay_ms(1500);
delay_ms(1500);
}
int main(void)
{
delay_init(); //延时函数初始化
NVIC_Configuration(); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
OLED_Init(); //初始化OLED
OLED_Clear(0); //清屏(全黑)
while(1)
{
TEST_MainPage();
}
}
- 演示视图
2.2读取温度湿度显示
- 思路:在OLED屏显的基础上,将上一温度湿度的读取数据存储到一字符数组内打印出来
- 主要实现函数
int main(void)
{
delay_init(); //延时函数初始化
uart_init(115200);
NVIC_Configuration(); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
OLED_Init(); //初始化OLED
OLED_Clear(0); //清屏(全黑)
IIC_Init();
while(1)
{
TEST_MainPage();
printf("温度湿度显示");
read_AHT20_once();
delay_ms(1500);
}
}
void TEST_MainPage(void)
{
*int i;
u8 str1[4];
u8 str2[4];
u8* c=celsius(str1);
u8* h=humidity(str2);
for(i=0;i<=3;i++)//循环将数字变为数字字符
{
if(i!=2)
{
c[i]=c[i]+0x30;
h[i]=h[i]+0x30;
}
}
GUI_ShowCHinese(8,20,16,"现在温度",1);
GUI_ShowString(72,20,":",16,1);
GUI_ShowString(96,20,c,16,1);
GUI_ShowCHinese(8,48,16,"现在湿度",1);
GUI_ShowString(72,48,":",16,1);
GUI_ShowString(96,48,h,16,1);
delay_ms(15);
delay_ms(15);
}
void *celsius(u8 *str1)//存储返回温度
{
str1[0]=T1/100;
str1[1]=(T1/10)%10;
str1[2]='.';
str1[3]=T1%10;
return str1;
}
void *humidity(u8 *str2)存储返回湿度
{
str2[0]=H1/100;
str2[1]=(H1/10)%10;
str2[2]='.';
str2[3]=H1%10;
return str2;
}
- 演示视图
2.3 滚动字幕显示
- 主要实现函数
int main(void)
{
delay_init(); //延时函数初始化
uart_init(115200);
NVIC_Configuration(); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
OLED_Init(); //初始化OLED
OLED_Clear(0); //清屏(全黑)
IIC_Init();
//滚动
OLED_WR_Byte(0x2E,OLED_CMD); //关闭滚动
OLED_WR_Byte(0x27,OLED_CMD); //水平向左或者右滚动 26/27
OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
OLED_WR_Byte(0x00,OLED_CMD); //起始页 0
OLED_WR_Byte(0x07,OLED_CMD); //滚动时间间隔
OLED_WR_Byte(0x07,OLED_CMD); //终止页 7
OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
OLED_WR_Byte(0xFF,OLED_CMD); //虚拟字节
TEST_MainPage();
OLED_WR_Byte(0x2F,OLED_CMD); //开启滚动
}
void TEST_MainPage(void)
{
GUI_ShowCHinese(10,24,16,"我是重交校霸",1);
delay_ms(1500);
delay_ms(1500);
}
- 演示示例
三、总结
以上就是本文的全部内容,简单学习了I2C和SPI总线的架构,并完成了对应的实验练习。其中也存在着许多问题,例如温度湿度读取失败,最后发现使启动文件有所纰漏,更换了之后便成功了但对具体原因仍一头雾水;还有就是OLED显示字符时,显示字符串用字符数组替代时会出现尾部打印出错,推测是应为数组未定义正确而导致的结束字符也打印上了。总的来说,本次实验受益匪浅,期待下次的实验学习。
四、参考文献
https://blog.csdn.net/qq_19810697/article/details/123569360
https://blog.csdn.net/qq_43279579/article/details/111597278
https://blog.csdn.net/qq_43279579/article/details/111414037
https://blog.csdn.net/qq_46467126/article/details/121439142