自制温湿度计——STM32-L432KC驱动lcd1602和dht11温湿度显示
L432KC介绍
先给大家甩一张图片哈
NUCLEO-L432KC | Mbed (附上网址)
简单说一下这块板子在这个小项目里的小缺点:一、引脚有点少,lcd1602本来有两种传输数据方式,一种是4bit,另一种是8bit,但是这块板子对于这个项目来说引脚有点少,所以建议用4bit模式。然而,在网上是很难找到设置4bit传输模式的lcd1602的,这也是为什么我写这个blog。二、mbed-os有时候会有大大小小的问题,搞人心态是真的。比如库函数离奇失踪,从官网上下载库函数失败,重装也解决不了然后莫名其妙好了。三、引脚明明就少,D7,D8还有一堆负责模拟输入输出的(A开头的)都用不了,真的很重要,不然会出大问题。
(附上个人心得:做任何项目都得细心,接线问题,代码问题,任何一个小问题都有可能导致最后没有任何结果哦~心态很重要!)
Lcd1602介绍
好了,第一个重头戏来了。
lcd1602是一款简易显示器,能够表示绝大多数非常基础的符号(字母,数字,一些文字)。先看看实物:
依次讲解一下各个针脚:
GND:接地
VDD:Lcd供电源,本应该是5V才可以,但实际上3.3V也行。
VO:对比度调节,接地对比度最大,接高电平对比度最小。(我直接接低省事,但可以用电位计调节,让自己看着更舒服)
RS:数据/指令选择,RS高电平是数据,低电平是指令
RW:读/写选择,RW高电平是读,低电平是写
E:(enable)使能,控制E高低电平变换是通信的关键之一。
D0-D7:数据或指令传输针脚(4bit传输选择D4-D7)
BLA:背光源正极,最好接5V
BLK:背光源负极,接地
针脚清楚之后,建议先不急去了解dht11,可以先测试好lcd1602能不能成功显示。所以我们这里先接lcd1602的线,然后手动在代码中加入想显示的内容以测试lcd1602部分完好。
接线部分
一、GND和BLK还有VO(我自己)都接地,所以可以把板子的GND接出来。
二、D4-D7分别接D6,D5,D3,D2
三、VDD和BLA都需要5V,板子上是有5V的,所以可以把5V接出来。
四、E,RW,RS分别接D9,D10,D11
图片可能不太清楚,但是线接起来,的确比较乱。
下一步是lcd1602的代码部分,在此之前,需要了解一些lcd1602的代码原理,以及下面这些有可能会用到的模块控制指令。
定义变量
BusOut函数
非常好用的函数,可以一次传输四个数据,注意括号里哦,不要填反了。
忙函数
第九条指令,RW设置高电平,RS设置低电平,若此时数据最高位检测到是高电平,则仍然为忙状态。代码的目的就是,先将RW和RS设置好,然后先将E拉高,使单片机能够开始读,E调回低电平,如果这个时候lcd1602最高位是1,那就继续走循环,不是的话就跳出来,此时lcd1602就是空闲状态。为什么要判断忙不忙,就是为防止时序出现问题,因为lcd1602可能反应有点慢,单片机运作太快了,一条一条指令发给lcd1602可能会导致lcd1602反应不过来,所以相当于是等一下lcd1602进行空闲状态。
发送函数
设置好了读写数据模式之后(RS,RW),再用到发送函数,高电平持续2ms,保证读取时间足够
命令函数
判断完忙之后,可以发现指令有关的RW和RS都设置成低电平,然后指令需要4bit一次传输,传两次
显示函数
和命令函数有点像,想要显示的字符对应的ascii码值分两次传输
视觉坐标和地址匹配函数
设置的地址之所以要与0x80,是因为设置地址的命令函数最高位需要是1
字符串显示函数
这里很好看懂,用了指针,将数组里的内容一一传给LCD显示
初始化函数
初始化是必须的,照抄我的即可,每条指令都是根据手册得到的。
想要更通透了解为什么要有这些函数,以及函数中夹杂的等待语句,仍然需要了解lcd1602的工作时序图,可以参考这篇简介LCD1602屏幕简介(全网最详细教程)_LJX的博客-CSDN博客
其中涉及原理的部分讲的也比较清楚,回头再看我的代码应该就比较清楚了。
lcd1602显示
可以先测试一下lcd部分有没有问题,然后进行下一步,dht11
dht11介绍
dht11属于性价比比较高的温湿度传感器,采用单总线通讯,就是包括供电,接地,数据接口在内,一共三个接口。总线一次传输40bit数据,分别是湿度整数部分8bit,湿度小数部分8bit,温度整数部分8bit,温度小数部分8bit,校验位8bit,若校验位等于其他四个8bit的和,那dht11此次采集的数据就可行。
同样,dht11也有对应的工作时序图,可以参考DHT11详细介绍(内含51和STM32代码)_安赫'的博客-CSDN博客
实物如下:
接线部分
具体接法也比较简单,就是需要一个5欧姆的电阻作为上拉电阻,上拉电阻的作用就是保证当数据总线闲置时处于高电平的状态。以下是接线实物图:
上拉电阻的原理图:
图中R1就是上拉电阻,我这里电阻一端接3.3v(板子上自带一个3.3针脚),另一端接到数据总线上。
再看代码
定义变量
针脚D12是作为InOut针脚,可以切换输出或者输入模式,D13作为供电针脚,
定义一个数组用来储存采集的数据,后提供给lcd1602显示
四个unsigned char变量,每一个都是正好8bit大小,便于储存采集数据
启动函数
由单片机向dht11发送高电平,等待2us调为低电平,延时18ms以上再调为高电平,
这个时候等待30us,dht就会开始传输它采集的数据
字节传输函数
一共40bit,每8bit一存储,进入循环,先等待高电平过去,再等待80us低电平过去,这可以理解成dht11的相应信号,也可以写if判断。因为dht11是单总线通讯,所以是通过一根线上的电平变化来表示数据的,50us的低电平加26-28us的高电平表示的就是0,50us的低电平加80us的高电平表示的就是1,这是一位数据。最后数据线拉低回到一开始的状态,等待下一次通讯。
40位数据接收函数
启动dht11之后,dht11会发送低电平给单片机表示准备开始传输数据,然后因为上拉电阻的存在,总线会被拉高,只有拉高之后才能正常开始传输数据。当最后采集的数据校验成功,经过最后的运算,就可以得到湿度的十位和个位,温度的十位和个位,以及温度的小数位。oxDF是符号°。
结尾
如果没有结果的话,检查接线,硬件,代码的问题,一步一步解决,一个问题一个问题地排除,最后一定会成功的。最后,附上本次小项目的全代码。
#include "mbed.h"
DigitalOut D_4(D6);
DigitalOut D_5(D5);
DigitalOut D_6(D3);
DigitalOut D_7(D2);
DigitalOut E(D9);
DigitalOut RW(D10);
DigitalOut RS(D11);
DigitalInOut dht11(D12);
DigitalOut Vcc(D13);
unsigned char rec_dat[11]={0};
unsigned char RH,RL,TH,TL;
BusOut data(D6,D5,D3,D2);//4位数据传输
void DHT11_start()
{
dht11.output();
dht11=1;
wait_us(2);
dht11=0;
wait_us(20000); //延时18ms以上
dht11=1;
wait_us(30);
dht11.input();
}
unsigned char DHT11_rec_byte() //接收一个字节
{
unsigned char dat=0;
for(int i=0;i<8;i++) //从高到低依次接收8位数据
{
while(dht11);
while(!dht11); // 等待80us低电平过去
wait_us(30); //延时60us,如果还为高则数据为1,否则为0
dat<<=1; //移位使正确接收8位数据,数据为0时直接移位
if(dht11==1) //数据为1时,使dat加1来接收数据1
{
dat+=1;
}
while(dht11); //等待数据线拉低
}
return dat;
}
void DHT11_receive() //接收40位的数据
{
unsigned char R_H,R_L,T_H,T_L,RH,RL,TH,TL,revise;
DHT11_start();
if(dht11==0)
{
while(dht11==0); //等待拉高
wait_us(80); //拉高后延时80us
R_H=DHT11_rec_byte(); //接收湿度高八位
R_L=DHT11_rec_byte(); //接收湿度低八位
T_H=DHT11_rec_byte(); //接收温度高八位
T_L=DHT11_rec_byte(); //接收温度低八位
revise=DHT11_rec_byte(); //接收校正位
wait_us(25); //结束
if((R_H+R_L+T_H+T_L==revise))
{
RH=R_H;
RL=R_L;
TH=T_H;
TL=T_L;
}
/*数据处理,方便显示*/
rec_dat[0]='0'+(RH/10);
rec_dat[1]='0'+(RL%10);
rec_dat[2]='R';
rec_dat[3]='H';
rec_dat[4]=' ';
rec_dat[5]=' ';
rec_dat[6]='0'+(TH/10);
rec_dat[7]='0'+(TH%10);
rec_dat[8]='.';
rec_dat[9]='0'+(TL/10);
rec_dat[10]=0xDF;
}
}
void isbusy()
{
unsigned char temp=0x80;
RW=1;
RS=0;
data=0xff;
do {
E=1;
temp=data;//读取端口
E=0;
}while (temp&0x80);
}
void enable()
{
E=1;//拉到高电平
thread_sleep_for(2);
E=0;//拉到低电平
}
void display_to_COM(unsigned char value)
{
isbusy();
RS = 0;// 设置为低电平来写命令
RW = 0;// 设置为写
E=0;
data = value/16;// 数值右移4位,发送高四位到引脚上
thread_sleep_for(2);//延时一小会儿,让1602准备接收
enable();// 发送
data = value%16;// 数值跟0x0F相与,发送低四位
thread_sleep_for(2);
enable();// 发送
}
void display_to_LCD(unsigned char value)
{
isbusy();
RS = 1;// 设置为高电平来写数据
RW = 0;// 设置为写
E=0;
data = value/16;// 数值右移4位,发送高四位
thread_sleep_for(2);
enable();// 发送
data = value%16;// 数值跟0x0F相与,发送低四位
thread_sleep_for(2);
enable();// 发送
}
void LcdSetCursor(unsigned char x,unsigned char y)
{
unsigned char addr;
if(y==0)//由输入的屏幕坐标计算地址
addr=0x00+x;
if(y==1)
addr=0x40+x;
display_to_COM(addr|0x80);//设置RAM地址
}
void lcdShowStr(unsigned char x,unsigned char y, unsigned char *str)
{
LcdSetCursor(x,y);//设置起始地址
while(*str!='\0')
{
display_to_LCD(*str ++);//连续写入字符串数据,直到检测到结束符
}
}
/*区域清除,清除从(x,y)坐标起始的len个字符位*/
void LcdAreaClear(unsigned char x,unsigned char y,unsigned char len)
{
LcdSetCursor(x,y);
while(len--)
{
display_to_LCD(' ');
}
}
//整屏清除
void LcdFullClear()
{
display_to_LCD(0x01);
}
void LCD_init()
{
display_to_COM(0x28);//16*2显示,5*8点阵,4位数据接口
thread_sleep_for(20);
display_to_COM(0x0f);//显示器开,光标开,闪烁开
thread_sleep_for(20);
display_to_COM(0x06);//文字不动,地址自动+1
thread_sleep_for(20);
display_to_COM(0x01);//清屏
thread_sleep_for(20);
}
int main()
{
Vcc=1;
LCD_init();
while(1)
{
thread_sleep_for(15000); //DHT11上电后要等待1S以越过不稳定状态在此期间不能发送任何指令
DHT11_receive();
thread_sleep_for(1500);
lcdShowStr(0,0,rec_dat);
thread_sleep_for(15000);
thread_sleep_for(20);
}
}
结果是这样的
作者:\"五言\"