超声波小雷达的完整攻略
前言
新的学习项目是超声波雷达,但是与之前的不同的是,这次的学习项目将从超声波雷达的3D打印部件的组装,再到硬件原理,最后才是软件,从项目的起点带领小伙伴领略嵌入式开发设计的魅力。这次项目主要涉及的有CH549单片机、超声波模块、SG90舵机以及LCD屏幕。和往常一样,此次依然作为51的进阶练习,程序方面基础薄弱的同学可以看verimake的51相关教程《以 CH549 为例的 51 教程》。
3D打印件的安装流程
本次的超声波雷达除了主要模块以外,外观部件均由3D打印而成,加由螺钉连接而成,一共用到的部件由下图所示:
其中螺钉分为三种:十字沉头、十字大扁头和十字盘头
首先将PCB板的外缘去掉,本身为圆形,将CH549按下图所示方向放置于PCB板上留好的位置,并取出屏幕座和十字大扁头的螺钉。
然后用十字大扁头的螺钉将屏幕座固定到CH549下方的位置(留意PCB板子上的方框),并将LCD屏幕倒过来,将下方的接口插入PCB板的插槽中,并将屏幕靠到屏幕座上,正好与屏幕座上所留的凹槽吻合。
接着拿出底座、舵机还有十字盘头螺钉,舵机按图示位置嵌在底座用十字盘头螺钉从下方对应舵机两端的螺钉口,由下往上固定,保证舵机的转轴在底座中心。
之后取出超声波雷达的后背,将舵盘放置于原本留好的空位上用螺钉固定,再将后背放在底座上(留有空隙保证可以旋转),保证拿螺钉连接将底座舵机转轴中心和后背的底盘中心,注意朝向(可以把底座留出的屏幕空位作为参考)
然后取出超声波雷达的头部,将超声波模块的引脚连接上杜邦线,这里为了方便查验给的杜邦线颜色具是固定的:
然后将引脚部分朝上,置于头部留好的“眼睛”中,并拧上十字盘头的螺钉固定好;杜邦线另一端穿过后背预先留好的空隙,将头部放置在于后背上,螺钉口对齐。
再然后从底座那一端拧上十字沉头螺钉,这样超声波雷达上部就算完成了。
再接着将之前引过来的杜邦线插在PCB板上留给他的口,接线按之前的表即可,还有就是驱动舵机的线:
之后将PCB板和底座重合,保证PCB板上所留的螺钉口与底座上的螺钉口对齐。
最后合上底盘。用十字沉头的螺钉将底盘、PCB板和底座连接上固定好,超声波雷达就装好啦!
硬件原理图
以前我们调试系统,常常会将硬件电路的连接是怎么方便怎么来,就像我们之前学习项目那样,直接拿杜邦线接一接不就好了。平时学习的时候这样不会说什么,但是进入正式的小制作中,这样一方面不太美观,而且常常手一哆嗦就碰掉好几根线。那还有小伙伴会说,既然直接连线可靠性不好,那我这样将所有原件焊接在洞洞板上呀。可以是可以,但是还是不够极致的美观。作为一个对于设计产品有追求的嵌入式开发者,肯定是为自己的制作进行硬件电路和PCB设计。
言归正传,这次的作品呢,我们绘制一块PCB作为所有元器件连接的底板。本次设计我们将采用开源的Kicad进行原理图与PCB的绘制(注意:因为原本是电路是使用Kicad设计的,后期导入立创EDA,然后再发给嘉立创打样的),PCB的打样是使用的我们的老朋友嘉立创。现在嘉立创有活动,每个月可以免费打样两版PCB哦。
原理图设计
因为这块PCB主要起到各电路模块的连接作用,因此原理图的大多元件都是接插件。系统的供电,将采用CH549系统板的tepy-c接口。需要注意的是超声波模块的Echo引脚是超声波信号的返回信号,需要将其连接在CH549的外部中断引脚,舵机的控制信号与屏幕的调光信号都需要接入单片机具有PWM功能的引脚。屏幕的SPI通信引脚与单片机的SPI引脚连接,需要注意的是以为屏幕的工作电压与单片机不同,因此SPI通行线路上需要串联限流电阻。当小伙伴看到我们的PCB后,会发现为什么一个有三个屏幕接口。因为我们再设计时考虑到了屏幕的一些拓展性,有一个7Pin的排针接口两个20Pin的排线母座接口。如果你觉得20Pin的排母焊接不方便,你就可以购买7Pin接口的屏幕,然后使用杜邦线连接。如果你觉得这么不美观,我们更推荐使用排母座子连接,这样可以让作品更加的美观,也更方便放入我们的外壳中。
PCB设计
一般来说在外壳的设计完成后基本上就可以确定PCB的边框形状以及及重要元件的摆放尺寸与位置。小伙伴们可以看到电路板的有四个特别大的孔,这四个孔位就是用于把电路板的固定。中间的H1与H2孔,可以用于连接铜柱固定舵机。
程序讲解
第三部分就是我们之前教程的老环节,软件部分的讲解,如果之前的算超声波雷达的身体,接下来将说明超声波雷达的“大脑”。
完整程序main.c
#include <CH549_sdcc.h> //ch549的头文件,其中定义了单片机的一些特殊功能寄存器
#include <CH549_LCD.h>
#include <math.h>
#include <CH549_SPI.h>
#include <CH549_DEBUG.h>
#include <CH549_Timer.h>
/**
* 使用 ST7789 240*240 LCD屏,
* LCD 引脚等配置见 CH549_LCD.h
* 也可以更改配置,使用 ILI9225 LCD屏
*/
#define SERVO P2_7 //舵机引脚
#define US_TRIG P2_4 //超声波触发
#define US_ECHO P3_2 //超声波测量
#define SOUNDSPEED 340.0 //声速 340m/s
#define SCALE 0.1 //扫描线与实际距离的比例为 0.1像素点每毫米
#define COLOR_AIR (BLACK) //空域颜色
#define COLOR_SCANLINE (GREEN) //扫描线颜色
#define COLOR_OBSTACLE (RED) //障碍物颜色
//函数声明
void servoInit(UINT16 duty, int freq);
void servoSetDuty(UINT16 duty);
void ultrasonicInit();
UINT16 ultrasonicMeasure();
void servoSetAngle(float angle);
void drawScanLine(float angle, UINT16 dist);
void drawScale(UINT8 x, UINT8 y, int value);
void drawVeriMake(UINT8 x, UINT8 y, UINT8 size);
//主函数
void main()
{
CfgFsys(); //单片机主频配置
LCD_init(); //初始化LCD
SPI_CK_SET(2); //设置 SPI 分频
LCD_setBackground(COLOR_AIR);
LCD_setColor(WHITE);
LCD_clear(0, 0, LCD_parameter.width - 1, LCD_parameter.height - 1);
/* 一般屏幕坐标系以该屏幕左上角为原点,x轴向右,y轴向下 */
/* 以下为在坐标 (80,32) 处显示 "Radar",字体(Font)大小为32 */
LCD_setFont(32);
LCD_setCursor(80, 32);
LCD_printf("Radar");
LCD_setFont(12);
LCD_setCursor(64, 72);
LCD_printf("Power by VeriMake");
//画“比例尺”
drawScale(10, 120, 50);
drawVeriMake(170, 72, 10);
//舵机和超声波模块初始化
servoInit(0, 100);
ultrasonicInit();
//扫描
float angle = 0;
int point = 0;
int dist;
BOOL dir = 0;
int dists[5] = {0};
while (1)
{
servoSetAngle(angle);
dist = ultrasonicMeasure();
drawScanLine(angle, dist);
//滚动显示5条扫描距离
if ((int)(angle) % 10 == 0)
{
LCD_setColor(WHITE);
LCD_setFont(12);
for (UINT8 i = 0; i < 5; i++)
{
if (i == 4)
{
dists[i] = dist / 10;
}
else
{
dists[i] = dists[i + 1];
}
LCD_setCursor(200, 64 + 12 * i);
if (dists[i] > 0)
{
LCD_printf("%4dcm", dists[i]);
}
else
{
LCD_printf(" ");
}
}
}
/* 改变扫描角度(0-180度),
切换扫描方向(从左向右或从右向左) */
if (dir)
{
angle += 1.0f;
}
else
{
angle -= 1.0f;
}
if (angle >= 90.0f)
{
dir = 0;
}
if (angle <= -90.0f)
{
dir = 1;
}
}
}
UINT16 servoPeriod = 0; //舵机 PWM 周期
UINT16 servoDuty = 0; //舵机 PWM 脉宽
/**
* @brief 舵机初始化函数
*
* @param angle : 舵机角度,单位为度,0度为中点,左负右正
* @param freq : 舵机 PWM 频率,单位 Hz,不得小于62
*/
void servoInit(UINT16 angle, int freq)
{
servoPeriod = FREQ_SYS / 12 / freq;
mTimer0Clk12DivFsys(); // T0定时器时钟设置 FREQ_SYS/12
mTimer_x_ModInit(0, 1); // T0 定时器模式设置 模式1 16位定时器
SERVO = 0;
servoSetAngle(angle);
mTimer0RunCTL(1); // T0定时器启动
ET0 = 1; // T0定时器中断开启
EA = 1;
}
/**
* @brief T0 中断服务函数, 作舵机驱动
*/
void servoISR() __interrupt INT_NO_TMR0 __using 1
{
if (SERVO == 0)
{/* 高电平定时 */
SERVO = 1;
UINT16 temp = - servoDuty;
TL0 = temp & 0xff;
TH0 = (temp >> 8) & 0xff;
}
else
{/* 低电平定时 */
SERVO = 0;
UINT16 temp = - (servoPeriod - servoDuty);
TL0 = temp & 0xff;
TH0 = (temp >> 8) & 0xff;
}
}
/**
* @brief 设置舵机角度
*
* @param angle : 角度,单位为度,0度为中点,左负右正
*/
void servoSetAngle(float angle)
{
servoDuty = (UINT16)(-angle * (2000.0 / 180.0) + 1500) * (FREQ_SYS / 12 / 1000000);
}
/**
* @brief 设置舵机PWM信号脉宽
*
* @param duty : 脉宽,单位 us(0.001ms),不得超过周期 (1000000/freq)
*/
void servoSetDuty(UINT16 duty)
{
servoDuty = duty * (FREQ_SYS / 12 / 1000000);
}
/**
* @brief 超声波初始化
*
*/
void ultrasonicInit()
{
mTimer1Clk12DivFsys(); // T1 定时器时钟设置 FREQ_SYS/12
mTimer_x_ModInit(1, 1); // T1 定时器模式设置 模式1 16位定时器
}
/**
* @brief 超声波测距
*
* @return UINT16 :距离,以毫米(千分之一米)为单位。以下返回值为错误代码:
* 0xFFFF(-1) : 超出测量范围
* 0xFFFE(-2) : ECHO无应答。
* 0xFFFD(-3) : ECHO常为高电平。
*/
UINT16 ultrasonicMeasure()
{
//等待之前的测量结束(ECHO 拉低)
UINT16 timeout = 0xFFFF;
while (US_ECHO == 1)
{
timeout--;
if (timeout == 0)
return 0XFFFD;
}
//触发超声波模块
US_TRIG = 1;
mDelayuS(15); //HC-SR04 要求 10us 以上的正脉冲来触发
US_TRIG = 0;
TH1 = TL1 = 0;
//等待 echo 响应
timeout = 0xFFFF;
while (US_ECHO == 0)
{
timeout--;
if (timeout == 0)
return 0XFFFE;
}
//对 Echo 高电平 计时
TR1 = 1;
while ((US_ECHO == 1) && (TH1 < 0xFF));
TR1 = 0;
if (TH1 == 0xFF)
return 0xFFFF; //错误代码
else
//返回距离 单位:毫米
return ((UINT16)TH1 << 8 | (UINT16)TL1) * (SOUNDSPEED / 1000.0 / (FREQ_SYS / 12 / 1000000) / 2);
}
int lastX = 120, lastY = 120;
/**
* @brief 在屏幕上画扫描线
*
* @param angle : 扫描线角度
* @param dist : 扫描线长度
*/
void drawScanLine(float angle, UINT16 dist)
{
int x, y;
int xOrigin = LCD_parameter.width / 2;
int yOrigin = LCD_parameter.height - 1;
UINT8 rangeMax = LCD_parameter.width / 2;
float radian = (angle - 90.0f) * (PI / 180.0f);
float sinA = sinf(radian);
float cosA = cosf(radian);
//空域扫描线
dist *= SCALE;
if (dist > rangeMax)
dist = rangeMax;
x = cosA * dist;
y = sinA * dist;
//坐标变换
x = xOrigin + x;
y = yOrigin + y;
//清除上次的扫描线
LCD_setColor(COLOR_AIR);
LCD_drawLine(xOrigin - 1, yOrigin, lastX - 1, lastY);
LCD_drawLine(xOrigin, yOrigin, lastX, lastY);
LCD_drawLine(xOrigin + 1, yOrigin, lastX + 1, lastY);
LCD_drawLine(xOrigin, yOrigin + 1, lastX, lastY + 1);
//画扫描线
LCD_setColor(COLOR_SCANLINE);
LCD_drawLine(xOrigin - 1, yOrigin, x - 1, y);
LCD_drawLine(xOrigin, yOrigin, x, y);
LCD_drawLine(xOrigin + 1, yOrigin, x + 1, y);
LCD_drawLine(xOrigin, yOrigin + 1, x, y + 1);
lastX = x;
lastY = y;
//障碍线
x = cosA * (rangeMax);
y = sinA * (rangeMax);
//坐标变换
x = xOrigin + x;
y = yOrigin + y;
//画障碍线
LCD_setColor(COLOR_OBSTACLE);
LCD_drawLine(lastX, lastY, x, y);
}
/**
* @brief 在屏幕指定位置画“比例尺”,“比例尺”长度会根据显示数值自动调整
*
* @param x : “比例尺”左上角的x坐标
* @param y : “比例尺”左上角的y坐标
* @param value : “比例尺”上显示的数值,单位厘米
*/
void drawScale(UINT8 x, UINT8 y, int value)
{
UINT8 length = value * 10 * SCALE;
LCD_setFont(16);
LCD_setCursor(x + length / 2 - 16, y);
LCD_printf("%dcm", value);
LCD_drawLine(x, y, x + length, y);
LCD_drawLine(x, y + 3, x, y - 3);
LCD_drawLine(x + length, y + 3, x + length, y - 3);
}
void drawVeriMake(UINT8 x, UINT8 y, UINT8 size)
{
UINT8 middle = size / 2;
UINT8 x1 = x + middle;
UINT8 y1 = y + middle;
UINT8 x2 = x + size;
UINT8 y2 = y + size;
LCD_drawRectangle(x, y, x2, y2);
LCD_drawRectangle(x, y1, x1, y2);
LCD_drawLine(x, y1, x1, y);
LCD_drawLine(x1, y1, x2, y);
LCD_drawLine(x1, y1, x2, y2);
}
该部分程序的头文件在之前的项目里均有涉及,不再赘述。本次雷达主要的几个模块就是超声波模块的驱动、舵机模块的驱动以及LCD屏幕的显示,将这三部分组合起来,便是这次学习的项目。
超声波模块的驱动
之前的教程中也涉及到过这个模块,即HC-SR04。
/**
* @brief 超声波初始化
*
*/
void ultrasonicInit()
{
mTimer1Clk12DivFsys(); // T1 定时器时钟设置 FREQ_SYS/12
mTimer_x_ModInit(1, 1); // T1 定时器模式设置 模式1 16位定时器
}
/**
* @brief 超声波测距
*
* @return UINT16 :距离,以毫米(千分之一米)为单位。以下返回值为错误代码:
* 0xFFFF(-1) : 超出测量范围
* 0xFFFE(-2) : ECHO无应答。
* 0xFFFD(-3) : ECHO常为高电平。
*/
UINT16 ultrasonicMeasure()
{
//等待之前的测量结束(ECHO 拉低)
UINT16 timeout = 0xFFFF;
while (US_ECHO == 1)
{
timeout--;
if (timeout == 0)
return 0XFFFD;
}
//触发超声波模块
US_TRIG = 1;
mDelayuS(15); //HC-SR04 要求 10us 以上的正脉冲来触发
US_TRIG = 0;
TH1 = TL1 = 0;
//等待 echo 响应
timeout = 0xFFFF;
while (US_ECHO == 0)
{
timeout--;
if (timeout == 0)
return 0XFFFE;
}
//对 Echo 高电平 计时
TR1 = 1;
while ((US_ECHO == 1) && (TH1 < 0xFF));
TR1 = 0;
if (TH1 == 0xFF)
return 0xFFFF; //错误代码
else
//返回距离 单位:毫米
return ((UINT16)TH1 << 8 | (UINT16)TL1) * (SOUNDSPEED / 1000.0 / (FREQ_SYS / 12 / 1000000) / 2);
}
程序中可以看出先等待上一次的信号接收完毕之后再进行这次信号的接收,发送一个15us的正脉冲触发超声波模块,再确认超声波模块的Echo端是否初始是低电平,然后拉高到高电平,使用定时器T1来计算收发超声波的时间,再通过之前的公式计算算出距离。
舵机的驱动
SG90舵机,这是一款很常用的舵机,采用PWM信号控制,可以实现0°-180°的旋转角度。而之前的学习教程中也提到了,CH549自带的PWM是unsigned char的数据类型的,即只能在0255之间变化,这就会使每次脉冲变化的时候变化的角度较大,转动幅度大,看上去不连贯。所以我们用定时器来模拟PWM,之前也说到过,定时器本质上是计数器,范围在065535中间,这样可选的变化范围就更大。
UINT16 servoPeriod = 0; //舵机 PWM 周期
UINT16 servoDuty = 0; //舵机 PWM 脉宽
/**
* @brief 舵机初始化函数
*
* @param angle : 舵机角度,单位为度,0度为中点,左负右正
* @param freq : 舵机 PWM 频率,单位 Hz,不得小于62
*/
void servoInit(UINT16 angle, int freq)
{
servoPeriod = FREQ_SYS / 12 / freq;
mTimer0Clk12DivFsys(); // T0定时器时钟设置 FREQ_SYS/12
mTimer_x_ModInit(0, 1); // T0 定时器模式设置 模式1 16位定时器
SERVO = 0;
servoSetAngle(angle);
mTimer0RunCTL(1); // T0定时器启动
ET0 = 1; // T0定时器中断开启
EA = 1;
}
/**
* @brief T0 中断服务函数, 作舵机驱动
*/
void servoISR() __interrupt INT_NO_TMR0 __using 1
{
if (SERVO == 0)
{/* 高电平定时 */
SERVO = 1;
UINT16 temp = - servoDuty;
TL0 = temp & 0xff;
TH0 = (temp >> 8) & 0xff;
}
else
{/* 低电平定时 */
SERVO = 0;
UINT16 temp = - (servoPeriod - servoDuty);
TL0 = temp & 0xff;
TH0 = (temp >> 8) & 0xff;
}
}
/**
* @brief 设置舵机角度
*
* @param angle : 角度,单位为度,0度为中点,左负右正
*/
void servoSetAngle(float angle)
{
servoDuty = (UINT16)(-angle * (2000.0 / 180.0) + 1500) * (FREQ_SYS / 12 / 1000000);
}
/**
* @brief 设置舵机PWM信号脉宽
*
* @param duty : 脉宽,单位 us(0.001ms),不得超过周期 (1000000/freq)
*/
void servoSetDuty(UINT16 duty)
{
servoDuty = duty * (FREQ_SYS / 12 / 1000000);
}
由上可以看出直接用定时器控制PWM的脉宽,从而控制舵机的旋转角度。
PS:这里有个小思考,在中断服务函数里面设定定时器初值的时候为什么直接用-servoDuty?
LCD屏幕的驱动
这次的LCD屏幕的驱动程序与之前的略有区别,但是底层逻辑一样,之前的教程有详解,不再赘述,这里提一下这里封装的两个图像的画法和下方扫描线的画法。
比例尺
void drawScale(UINT8 x, UINT8 y, int value)
{
UINT8 length = value * 10 * SCALE;
LCD_setFont(16);
LCD_setCursor(x + length / 2 - 16, y);
LCD_printf("%dcm", value);
LCD_drawLine(x, y, x + length, y);
LCD_drawLine(x, y + 3, x, y - 3);
LCD_drawLine(x + length, y + 3, x + length, y - 3);
}
这里用的基本上就是划线和写字的函数,先写50cm这个比例尺的值,再画图像上的那个H型的线段,先画中间的横,再画左边的竖,最后画右边的竖。
verimake标志
void drawVeriMake(UINT8 x, UINT8 y, UINT8 size)
{
UINT8 middle = size / 2;
UINT8 x1 = x + middle;
UINT8 y1 = y + middle;
UINT8 x2 = x + size;
UINT8 y2 = y + size;
LCD_drawRectangle(x, y, x2, y2);
LCD_drawRectangle(x, y1, x1, y2);
LCD_drawLine(x, y1, x1, y);
LCD_drawLine(x1, y1, x2, y);
LCD_drawLine(x1, y1, x2, y2);
}
这里仅用到了划线的逻辑,方法基本上同上,一步一步的把verimake的标志画了出来,可以直接通过这个函数调用。
扫描线
void drawScanLine(float angle, UINT16 dist)
{
int x, y;
int xOrigin = LCD_parameter.width / 2;
int yOrigin = LCD_parameter.height - 1;
UINT8 rangeMax = LCD_parameter.width / 2;
float radian = (angle - 90.0f) * (PI / 180.0f);
float sinA = sinf(radian);
float cosA = cosf(radian);
//空域扫描线
dist *= SCALE;
if (dist > rangeMax)
dist = rangeMax;
x = cosA * dist;
y = sinA * dist;
//坐标变换
x = xOrigin + x;
y = yOrigin + y;
//清除上次的扫描线
LCD_setColor(COLOR_AIR);
LCD_drawLine(xOrigin - 1, yOrigin, lastX - 1, lastY);
LCD_drawLine(xOrigin, yOrigin, lastX, lastY);
LCD_drawLine(xOrigin + 1, yOrigin, lastX + 1, lastY);
LCD_drawLine(xOrigin, yOrigin + 1, lastX, lastY + 1);
//画扫描线
LCD_setColor(COLOR_SCANLINE);
LCD_drawLine(xOrigin - 1, yOrigin, x - 1, y);
LCD_drawLine(xOrigin, yOrigin, x, y);
LCD_drawLine(xOrigin + 1, yOrigin, x + 1, y);
LCD_drawLine(xOrigin, yOrigin + 1, x, y + 1);
lastX = x;
lastY = y;
//障碍线
x = cosA * (rangeMax);
y = sinA * (rangeMax);
//坐标变换
x = xOrigin + x;
y = yOrigin + y;
//画障碍线
LCD_setColor(COLOR_OBSTACLE);
LCD_drawLine(lastX, lastY, x, y);
}
这里仅仅是选好扫描线的原点,通过旋转角度和距离的三角函数计算算出扫描线终点,每次清除上一次的扫描线,同时对障碍线进行填充。
main函数
void main()
{
CfgFsys(); //单片机主频配置
LCD_init(); //初始化LCD
SPI_CK_SET(2); //设置 SPI 分频
LCD_setBackground(COLOR_AIR);
LCD_setColor(WHITE);
LCD_clear(0, 0, LCD_parameter.width - 1, LCD_parameter.height - 1);
/* 一般屏幕坐标系以该屏幕左上角为原点,x轴向右,y轴向下 */
/* 以下为在坐标 (80,32) 处显示 "Radar",字体(Font)大小为32 */
LCD_setFont(32);
LCD_setCursor(80, 32);
LCD_printf("Radar");
LCD_setFont(12);
LCD_setCursor(64, 72);
LCD_printf("Power by VeriMake");
//画“比例尺”
drawScale(10, 120, 50);
drawVeriMake(170, 72, 10);
//舵机和超声波模块初始化
servoInit(0, 100);
ultrasonicInit();
//扫描
float angle = 0;
int point = 0;
int dist;
BOOL dir = 0;
int dists[5] = {0};
while (1)
{
servoSetAngle(angle);
dist = ultrasonicMeasure();
drawScanLine(angle, dist);
//滚动显示5条扫描距离
if ((int)(angle) % 10 == 0)
{
LCD_setColor(WHITE);
LCD_setFont(12);
for (UINT8 i = 0; i < 5; i++)
{
if (i == 4)
{
dists[i] = dist / 10;
}
else
{
dists[i] = dists[i + 1];
}
LCD_setCursor(200, 64 + 12 * i);
if (dists[i] > 0)
{
LCD_printf("%4dcm", dists[i]);
}
else
{
LCD_printf(" ");
}
}
}
/* 改变扫描角度(0-180度),
切换扫描方向(从左向右或从右向左) */
if (dir)
{
angle += 1.0f;
}
else
{
angle -= 1.0f;
}
if (angle >= 90.0f)
{
dir = 0;
}
if (angle <= -90.0f)
{
dir = 1;
}
}
}
这里首先初始化时钟和SPI,再设置好LCD屏幕的背景色和字的颜色,并清屏,初始化屏幕。
CfgFsys(); //单片机主频配置
LCD_init(); //初始化LCD
SPI_CK_SET(2); //设置 SPI 分频
LCD_setBackground(COLOR_AIR);
LCD_setColor(WHITE);
LCD_clear(0, 0, LCD_parameter.width - 1, LCD_parameter.height - 1);
之后写上LCD屏幕上的各种字,并初始化超声波模块和舵机模块;
LCD_setFont(32);
LCD_setCursor(80, 32);
LCD_printf("Radar");
LCD_setFont(12);
LCD_setCursor(64, 72);
LCD_printf("Power by VeriMake");
//画“比例尺”
drawScale(10, 120, 50);
drawVeriMake(170, 72, 10);
//舵机和超声波模块初始化
servoInit(0, 100);
ultrasonicInit();
通过之前的函数得到超声波模块检测到的距离和舵机目前旋转的角度,当然这里的旋转角度是由while循环里面的变量改变的;
servoSetAngle(angle);
dist = ultrasonicMeasure();
/* 改变扫描角度(0-180度),
切换扫描方向(从左向右或从右向左) */
if (dir)
{
angle += 1.0f;
}
else
{
angle -= 1.0f;
}
if (angle >= 90.0f)
{
dir = 0;
}
if (angle <= -90.0f)
{
dir = 1;
}
设定距离的数组,储存5条数据分别显示在屏幕上,同时划出指向线。
drawScanLine(angle, dist);
//滚动显示5条扫描距离
if ((int)(angle) % 10 == 0)
{
LCD_setColor(WHITE);
LCD_setFont(12);
for (UINT8 i = 0; i < 5; i++)
{
if (i == 4)
{
dists[i] = dist / 10;
}
else
{
dists[i] = dists[i + 1];
}
LCD_setCursor(200, 64 + 12 * i);
if (dists[i] > 0)
{
LCD_printf("%4dcm", dists[i]);
}
else
{
LCD_printf(" ");
}
}
}
到这里,超声波雷达的软件部分的讲解就结束了。
演示
后话
至此超声波小雷达的完整攻略就结束了,有兴趣的同学可以自己试试。这次的学习项目,从外壳的安装开始,一直到最后的软件部分,希望大家可以从这个流程中感受到嵌入式开发的魅力。这次的例程我将上传至gitee仓库上文件名为:
欢迎大家参考!