51单片机基础难点知识理解后,直接通过下面3个小项目可以更好理解51单片机。中断 定时器 串口甚至是基础的IO输入输出都有结合,再加上一些外设模块的学习(如1602显示屏,L298N电机驱动等)
项目一:秒表设计
项目二:LCD1602显示器显示内容
项目三:循迹+蓝牙小车
LCD1602显示屏
介绍
1602,顾名思义就是16(列) x 02(行)显示内容,RAM地址映射,控制器内部有80X8位的RAM缓冲区,但是一行只有16个(实际上一行有40个内容,有一部分存到缓冲区没显示出来罢了)
实物图以及外形尺寸图如图所示,下面是具体的引脚定义(后面的编程要用到)
可以主要理解为由3个控制信号 RS R/W E 和8个数据位D0-D7组成
工作原理
1.基本读写时序图如下:
读操作的时候
读状态: 输入:RS = L , RW = H , E = H;
读数据: 输入:RS = H, RW = H , E = H;
>写状态的时候
写指令 : 输入: RS = L , RW = L , D0~D7 = 指令码, E = 高脉冲(H)
写数据 : 输入 : RS = H . RW = L ,D0~D7 = s数据, E = 高脉冲(H)关于里面的一些时序参数
这里面的单位都是ns级别,而单片机的指令周期为1us,具体看清时序里面的时间,方便延时以符合要求,具体的后面代码中可以参考
2.指令代码如下,表格为8位数据位
功能3: 置输入模式
DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 1 | N | S |
N =1,读或写一个字符后地址指针+1,光标+1;
N =0,读或写一个字符后地址指针-1,光标-1;
S =1,写一个字符,整屏显示左移(N=1)或者右移(N=0)
S =0,写一个字符,没有移动功能
功能4: 显示开/关设置
DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 1 | D | C | B |
D =1,开显示;D =0,关显示
C =1,显示光标;C =0,不显示光标
B =1,光标闪烁;B =0,不闪烁
功能5:光标或字符移位
DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 1 | S/C | R/L | X | X |
S/C | R/L | 设定情况 |
---|---|---|
0 | 0 | 光标左移1格,AC值-1 |
0 | 1 | 光标右移1格,AC值+1 |
1 | 0 | 全部左移1格,光标不动 |
1 | 1 | 全部右移1格,光标不动 |
功能6:置功能
DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 |
---|---|---|---|---|---|---|---|
0 | 0 | 1 | DL | N | F | 0 | 0 |
DL: 0 = 数据线4位; 1 = 数据线8位
N: 0 = 显示1行; 1 = 显示两行
F: 0 = 5x7点阵; 1 = 5x10点阵
代码驱动
大部分解释完之后附上初始化的过程
LCD1602.c文件
#include <REG52.H>
#include "LCD1602.h"
#include <intrins.h> //头文件,用到nop()函数
//这里的LCD三个控制信号口根据51开发板定的,可以直接将1602显示屏插51开发板上面
sbit LCD_RS = P2^6;
sbit LCD_RW = P2^5;
sbit LCD_E = P2^7; //定义三个控制信号引脚的端口
#define LCD_DatePort P0 //宏定义 数据口
void LCD_Delay1ms() //延时1ms函数
{
unsigned char i, j;
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
//写指令
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS = 0;
LCD_RW = 0; //写指令时 输入: RS = L , RW = L
LCD_DatePort = Command; //数据口传入指令
LCD_E = 1;
LCD_Delay1ms();
LCD_E = 0;
LCD_Delay1ms(); //这部分根据时序图时序写
}
//写数据
void LCD_WriteDate(unsigned char Date)
{
LCD_RS = 1;
LCD_RW = 0; //写数据时 输入: RS = H , RW = L
LCD_DatePort = Date; //数据口传入数据
LCD_E = 1;
LCD_Delay1ms();
LCD_E = 0;
LCD_Delay1ms(); //这部分根据时序图时序写
}
//LCD初始化
void LCD_Init()
{
LCD_WriteCommand(0x38);//数据8位,显示2行,5x7点阵 具体参考指令6
LCD_WriteCommand(0x0c);//开显示,光标关 具体参考指令4
LCD_WriteCommand(0x06);//地址,光标移动 具体参考指令3
LCD_WriteCommand(0x01);//清屏
}
//LCD显示字符函数
void LCD_Showchar(unsigned char Row,unsigned char Column,unsigned char FuHao)
{
//参数: 行 列 符号
SetMouse(Row,Column);
LCD_WriteDate(FuHao);
}
//LCD显示字符串函数
void LCD_ShowString(unsigned char Row,unsigned char Column,unsigned char *String)
{
//参数: 行 列 字符串
unsigned char i;
SetMouse(Row,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteDate(String[i]);
}
}
//计算m^n函数
int LCD_Power(int x,int y) //x^y
{
unsigned char i;
int ret = 1;
for(i=0;i<y;i++)
{
ret = ret*x;
}
return ret;
}
//显示数字函数
void LCD_ShowNum(unsigned char Row,unsigned char Column,int num,int length)
{
SetMouse(Row,Column);
for(;length>0;length--)
{
LCD_WriteDate('0'+num/LCD_Power(10,(length-1))%10);
//从高位开始逐渐显示数字的每一位,前面多一个'0'+ 代表从数字0所在的RAM存储地址算起,即让其显示数字字符
}
}
DHT11温湿度模块
介绍
由三根线组成,VCC GND供电供地,Data为数据端口,实现数据的收发输出为单总线数字信号,温度测量范围0-50度(精度正负2度,分辨率1度),湿度测量范围为20-90%RH(精度为正负5%,分辨率1%)
工作原理
采用单总线双向串行通信协议,每次采集都要由单片机发起开始信号,然后DHT11会向单片机发送响应并开始传输40位数据帧,高位在前。
数据格式
数据格式40帧(40bit)
湿度整数部分+湿度小数部分 8bit + 8bit
温度整数部分+温度小数部分 8bit + 8bit
校验位部分 8bit
数据校验,湿度和温度的整数小数部分全部加起来判断是否与校验位部分相等,若相等,则取出数据;否之,数据重新获取
湿度小数部分和温度小数部分单片机都默认为0,即校验位只会判断两者的整数部分和
时序图
总线空闲状态高电平.
主机 将总线拉低,等待DHT11响应, (主机发送起始信号)
从机(DHT11)接收主机起始信号后,等待主机起始信号结束, 这个过程要求18ms以上,保证DHT11检测到起始信号
从机(DHT11)发送80us的低电平响应信号
主机延时20-40us后读取DHT11的响应信号
主机发送完开始信号,可以切换到输入模式或者接上拉电阻置高电平,
代码驱动
DHT11.c文件
#include <REG52.H>
#include "DHT11.h"
sbit DHT11_Data = P3^6; //数据端口和IO口的连接
unsigned int rec_dat [4]; //存储温湿度高四位和低四位
unsigned char rec_dat_lcd0 [6];//湿度高四位
unsigned char rec_dat_lcd1 [6];//湿度低4位
unsigned char rec_dat_lcd2 [6];//温度高4位
unsigned char rec_dat_lcd3 [6]; //温度低4位
//微妙延时函数
void DHT11_Delayus(unsigned char us)
{
while(--us);
}
//毫秒延时函数
void DHT11_Delayms(unsigned int ms)
{
unsigned int i;
for(;ms>0;ms--)
for(i=110;i>0;i--);
}
//主机发送起始信号
void DHT11_START()
{
DHT11_Data = 1;
DHT11_Delayus(2);
DHT11_Data = 0;
DHT11_Delayms(20);
DHT11_Data = 1;
DHT11_Delayus(13);
//延时都根据时序来的
}
//DHT11接收字节函数
unsigned char DHT11_REC_bit()
{
unsigned char i,dat;
for(i=0;i<8;i++) //一个字节8位,循环8次
{
while(!DHT11_Data); //等待高电平
DHT11_Delayus(8); //等高电平稳定,根据时序来
dat<<=1; //数据左移
if(DHT11_Data == 1)
{
dat += 1; //如果数据端口为高电平,直接将该位数据置1
}
while(DHT11_Data == 1); //等待电平拉低
}
return dat; //返回读取的字节
}
//DHT11接收信号函数
void DHT11_REC()
{
unsigned int RH,RL,TH,TL,receive;
unsigned char R_H,R_L,T_H,T_L;
DHT11_START();
DHT11_Data = 1; //保持闲置状态
if(DHT11_Data == 0) //判断是否进入响应信号
{
while(DHT11_Data == 0); //等待高电平
DHT11_Delayus(40);//主机40us读取温湿度信号
R_H = DHT11_REC_bit();//接收湿度高8
R_L = DHT11_REC_bit();//接收湿度低8
T_H = DHT11_REC_bit();//接收温度高8
T_L = DHT11_REC_bit();//接收温度低8
receive = DHT11_REC_bit();//接收校验位
DHT11_Delayus(25);//延时一下,开始后面校验
if((R_H+R_L+T_H+T_L)== receive)
{
//如果校验通过,直接将读取的值存到存储数组中去,后面就只管用数组里面的数据
RH = R_H;
RL = R_L;
TH = T_H;
TL = T_L;
}
rec_dat [0] = RH;
rec_dat [1] = RL;
rec_dat [2] = TH;
rec_dat [3] = TL;
}
}
主函数实现
.h文件
LCD1602.h
#ifndef _LCD1602_H_
#define _LCD1602_H_
void LCD_Delay1ms();
void LCD_WriteCommand(unsigned char Command);
void LCD_WriteDate(unsigned char Date);
void LCD_Init();
void SetMouse(unsigned char Row,unsigned char Column);
void LCD_Showchar(unsigned char Row,unsigned char Column,unsigned char FuHao);
void LCD_ShowString(unsigned char Row,unsigned char Column,unsigned char *String);
int LCD_Power(int x,int y);
void LCD_ShowNum(unsigned char Row,unsigned char Column,int num,int length);
#endif
DHT11.h
#ifndef _DHT11_H_
#define _DHT11_H_
void DHT11_Delayus(unsigned char us);
void DHT11_Delayms(unsigned int ms);
void DHT11_START();
unsigned char DHT11_REC_bit();
void DHT11_REC();
#endif
主函数
#include <REG52.H>
#include "LCD1602.h"
#include "DHT11.h"
#include <stdio.h>
//DHT11文件中定义的存储数据数组
extern unsigned int rec_dat [4];
extern unsigned char rec_dat_lcd0 [6];
extern unsigned char rec_dat_lcd1 [6];
extern unsigned char rec_dat_lcd2 [6];
extern unsigned char rec_dat_lcd3 [6];
int main()
{
LCD_Init();
/*在写显示字符串 显示数字函数之前,自己也可以写随便显示几个来验证函数正确性
LCD_Showchar( 1, 1, 'Q');
LCD_Showchar( 1, 2, 'A');
LCD_ShowNum( 2, 7, 666,3);*/
LCD_ShowString(1,0,"Humi:"); //在1行 0列显示字符串"Humi:"
LCD_ShowString(2,0,"Temp:"); //在2行 0列显示字符串"Temp:"
while(1)
{
DHT11_Delayms(150);
DHT11_REC(); //不断接收数据
sprintf(rec_dat_lcd0,"%d",rec_dat[0]);
sprintf(rec_dat_lcd1,"%d",rec_dat[1]);
sprintf(rec_dat_lcd2,"%d",rec_dat[2]);
sprintf(rec_dat_lcd3,"%d",rec_dat[3]);
//包含在stdio.h文件中,将数据格式强制转化成字符串形式
LCD_ShowString(1,6,rec_dat_lcd0);
LCD_ShowString(1,8,".");
LCD_ShowString(1,9,rec_dat_lcd1);
LCD_ShowString(1,10," %");
//实时显示DHT11采集的温湿度,然后在LCD1602上显示了
LCD_ShowString(2,6,rec_dat_lcd2);
LCD_ShowString(2,8,".");
LCD_ShowString(2,9,rec_dat_lcd3);
LCD_ShowString(2,10," C");
}
}
总结
项目总结
与前面一个51单片机秒表项目相比,前面一个偏向于51单片机本身的内容(IO 中断 定时器等)。这个项目更多是偏向单片机外设的一些学习
基于51单片机温湿度显示的项目,主要是学习外设-LCD1602模块 DHT11模块。学会看外设的数据手册(里面的截图基本上都来自数据手册) 看懂时序图,会根据时序图写代码,具体可以根据时序图来验证代码,了解整个外设的工作过程
拓展应用
1.学会LCD1602之后,驱动代码基本上每次可以照搬下来,如果要显示其它内容,可以根据数据手册或者指令在.c文件中添加相关的函数
2.DHT11模块只是众多传感器的一个,基本上大部分外设模块重点就是了解大致原理,看懂时序,直接移植相关代码进行引脚修改,这部分重点仅仅是学会看时序图,根据时序图写代码,为后面学习更多外设做准备
3.也还可以添加串口初始化以及串口中断函数,在电脑串口上显示单片机从外设接收过来的内容