最近需要拿超声波写一个避障的部分,打算拿arduino来写但是板子还在路上,用51单片机来写就需要拿液晶来显示,于是就在网上找了一大堆资料来学,也参考了很多前人的代码,最后把在这过程中查阅读一部分资料整理出来,作为记录。
图片来自网络,侵删。
HC-SR04模块
超声波模块使用原理
- IO口和TRIG两个接口触发测距,给10μs高电平信号
- 模块自动发送8个4kHz方波,自动检测有无信号返回
- 有信号返回,通过IO口给ECHO口输出一个高电平,高电平持续时间为超声波发出到返回时间,测距距离=(高电平持续时间*声速(340M/S))/2
超声波模块时序图
HC-SR04模块引脚
超声波模块共有四个引脚:
- VCC 提供5v电源
- GND 地线
- TRIG 触发控制,信号输入
- ECHO 回响信号输入
HC-SR04模块参数
- 额定电压:5V
- 静态工作电流:2mA
- 感应角度:小于15°
- 探测距离:2cm-400cm
- 精度:0.3cm
- 盲区:<2cm
误差分析
- 盲区:当被测物体与超声波模块探头距离足够近的时候,发射出去的声波会在超声波探头与被测物体之间来回反射,此时串口监视器返回负值.
- 被测物体的材质:当被测物体表面积小于0.5m²或者是表面过于粗糙,都会影响到超声波测距的返回值.
- 反射:(1)三角误差:大部分时候超声波和被测物体之间都存在一个夹角,所测得的距离不是一个直线距离而是带有三角误差(2)多次反射:存在于墙角,所测得距离可能是被多次反射之后才被接收到.
- 噪音:轮子与地面摩擦产生的高频噪音,不同超声波之间的干扰.
- 余震:也可以理解为抖动现象,与探头,外壳甚至电路干扰都有关系.表现为在串口监视器返回的数据中突然出现不是与其他数据产生极大偏差的数据.
解决方案
查阅资料之后网络上提供的大部分方案是算术平均滤波或者是求方差之后取方差较小区域作为返回值。
我在此使用的是算术平均滤波,通俗地讲就是多次测量取平均值。返回N个测量值之后平均,优点是能够在一定程度上让测量结果更准确,但是缺点是占用大量RAM值。
LCD1602模块
LCD1602引脚
编号 | 符号 | 说明 | 编号 | 符号 | 说明 |
---|---|---|---|---|---|
1 | VSS | 电源地 | 9 | D2 | 数据 |
2 | VDD | 电源正极 | 10 | D3 | 数据 |
3 | VL | 液晶显示偏压 | 11 | D4 | 数据 |
4 | RS | 数据/命令选择 | 12 | D5 | 数据 |
5 | RW | 读/写选择 | 13 | D6 | 数据 |
6 | E | 使能信号 | 14 | D7 | 数据 |
7 | D0 | 数据 | 15 | BLA | 背光源正极 |
8 | D1 | 数据 | 16 | BLK | 背光源负极 |
- VDD接5V正电源
- VL是对比度调整端,接正电源对比度最弱,接地对比度最高,对比度过高会产生“白影”,在链接一个10K电位器来调节对比度
- RS寄存器选择,高电平选择数据寄存器,低电平选择指令寄存器
- RW读写选择;高电平进行读操作,低电平进行写操作。(在这里我们只写不读,所以RW=0即可)
- E为使能端,当E端从高电平变成低电平,液晶模块执行命令
- D0-D7为双向数据线。
- BLA/BLK为背光源正负极
LCD1602参数
显示容量:162个字符(1602)
芯片工作电压:4.5-5.5V
模块最佳工作电压:5.0V
工作电流:2.0mA
字符尺寸:2.954.35(W*H)mm
LCD1602指令集
编号 | 指令 | RS | RW | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 清屏 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
2 | 光标返回 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | * |
3 | 置输入模式 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | I/D | S |
4 | 显示开关控制 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | D | C | B |
5 | 光标或字符位移 | 0 | 0 | 0 | 0 | 0 | 1 | S/C | R/L | * | * |
6 | 置功能 | 0 | 0 | 0 | 0 | 1 | DL | N | F | * | |
7 | 置字符发生存储地址 | 0 | 0 | 0 | 1 | ||||||
8 | 置数据存储器地址 | 0 | 0 | 1 | |||||||
9 | 读忙标志或地址 | 0 | 1 | BF | |||||||
10 | 写数据到CGRAM | 1 | 0 | ||||||||
- 清显示屏,指令码01H,光标复位到00H位置
- 光标复位,光标返回地址00H
- 光标和显示模式设置:I/D:高电平右移,低电平左移;S:高电平屏幕上所有文字左移或右移,低电平无效
- 显示开关控制:D:高电平整体显示开,低电平整体显示关;C:高电平显示光标,低电平关闭光标;B:高电平光标闪烁,低电平光标不闪烁
- 光标显示与位移:S/C:高电平移动显示文字,低电平移动光标
- 功能设置命令 DL:高电平时为 4 位总线,低电平时为 8 位总线 N:低电平时为单行显示,高电平时双行显示 F: 低电平时显示 5x7 的点阵字符,高电平时显示 5x10 的点阵字符
- 设置字符发生器RAM地址
- DDRAM地址设置
- 读忙信号与光标地址:BF:高电平忙,不能接受命令和数据,低电平为不忙
- 写数据
LCD1602时序图
- 左侧字母对应IO口
- 线交叉表示电平变化,在上为高电平,在下为低电平
- Valid Date 有效数据
LCD1602映射关系
80H为第一行第一位的地址,那么第一行第二位就是0x80+1;第二行第一位就是0x80+40.
LCD1602字库
代码部分
#include"reg52.h"
#include <intrins.h>
#define uint unsigned int //偷懒
#define uchar unsigned char
sbit rs=P2^6; //数据指令控制口
sbit rw=P2^5; //读写控制口
sbit en=P2^7; //使能端
sbit trig=P1^0; //超声波触发引脚
sbit echo=P1^1; //超声波接收引脚
bit flag1;
uchar count;
long int distance;
unsigned char code table[]={"0123456789"};
void delay(uint n) //延时函数
{
uint x,y;
for(x=n;x>0;x--)
for(y=110;y>0;y--);
}
void delayt(uint x) //延时函数
{
uchar j;
while(x-- > 0)
{
for(j = 0;j < 125;j++)
{
;
}
}
}
void T0_init() //T0中断初始化
{
TMOD=0x01; //定义计时器0工作方式为1
TL0=0x66; //装入初始值
TH0=0xfc;
ET0=1; //开定时器0中断
EA=1; //开总中断
}
void comwrite(uchar com) //写指令函数
{
rs=0; //选择指令寄存器
rw=0; //选择写
P0=com; //输入指令
delay(5);
en=1; //使能端打开
en=0; //使能端关闭
}
void datwrite(uchar dat) //写数据函数
{
rs=1; //选择数据寄存器
rw=0; //选择写
P0=dat; //输入数据
delay(5);
en=1; //使能端打开
en=0; //使能端关闭
}
void lcd_init() //LCD1602初始化
{
comwrite(0x38); //设置16*2显示,8位数据端口
comwrite(0x0c); //打开显示屏不打开光标
comwrite(0x06); //显示地址递增,写入一个数据之后,显示位置右移
comwrite(0x01); //清屏
}
void dis() //显示固定字符
{
comwrite(0x80+0x40); //设定输入位置
datwrite('d'); //输入用'',不可以用""
datwrite('i');
datwrite('s');
datwrite('t');
datwrite('a');
datwrite('n');
datwrite('c');
datwrite('e');
datwrite(':');
comwrite(0x80+0x4c); //设定输入位置
datwrite('.');
comwrite(0x80+0x4e); //设定输入位置
datwrite('c');
datwrite('m');
}
void trigger() //超声波模块触发函数
{
trig=1; //打开超声波触发端口
delay(1);
trig=0; //关闭超声波触发端口
}
void measure_init() //测量函数初始化
{
trig=0; //触发端口低电平
echo=0; //接收端口低电平
count=0;
}
void measure() //测距函数
{
uchar a;
uint b,c;
TR0=1; //打开定时器
while(echo) //当超声波接收打开时计时
{
;
}
TR0=0; //关闭定时器
a=TL0; //a,b分别赋予中断函数高八位和第八位计时后的数值
b=TH0;
c=(b<<8)+a; //高八位左移八位之后与低八位做加法运算
c=c-0xfc66; //减去初始值高八位和低八位
distance=c+1000*count; //总的时间,单位是μs
TL0=0x66; //重新装入初始值
TH0=0xfc;
delayt(30);
distance=3453*distance/20000; //计算距离,单位是cm
}
/*
距离计算原理:所求t μs,distance=t*0.34(声速340m/s=0.34cm/μs)/2
*/
void echodisplay(uint x) //显示距离函数
{
uchar q,b,s,g;
q=x/1000; //将测量函数测得的distance分为个位,十位,百位,千位
b=(x/100)%10;
s=(x/10)%10;
g=x%10;
comwrite(0x80+0x49); //设定输出位置
datwrite(table[q]); //写入数据
datwrite(table[b]);
datwrite(table[s]);
comwrite(0x80+0x4d); //设定输入位置
datwrite(table[g]); //写入数据
}
void main() //主函数
{
lcd_init(); //初始化LCD
T0_init(); //初始化T0计时器
measure_init();//初始化超声波测量模块
while(1) //循环
{
dis(); //显示固定字符
trigger(); //触发超声波测距
while(echo==0)//当接收端没有打开的时候在这里循环
{
;
}
measure();//测量距离distance
echodisplay(distance);//显示距离distance
measure_init();//再次初始化测距模块
delayt(500);//测量间隔500ms
}
}
void T_0() interrupt 1 //计时器中断函数T0
{
TF0=0; //计时器溢出中断
TL0=0x66; //填入初始数据
TH0=0xfc;
count++; //产生一次中断就加一
if(count==18) //当计数到达18的时候,约1s
{
TR0=0; //关闭计时器中断0
TL0=0x66; //重新装入初始值
TH0=0xfc;
count=0; //把count清零
}
}