树莓派Qt开发入门(二):超声波测距和数码管显示实验 您所在的位置:网站首页 如何用超声波测距显示在led显示屏上 树莓派Qt开发入门(二):超声波测距和数码管显示实验

树莓派Qt开发入门(二):超声波测距和数码管显示实验

2023-12-29 05:56| 来源: 网络整理| 查看: 265

写在前面

因为一些阴差阳错的事情,这个系列鸽在了2019年12月。又因为一些阴差阳错的事情,我重新拿到了这个实验箱,所以准备继续写完这个系列。

一定不鸽。

前言

上一节我们使用8个GPIO控制了一个流水灯模块,本节我们使用GPIO的读写操作进行超声波测距,并将距离值在一个4位LED数码管上显示出来。

基础知识 超声波模块

在这里插入图片描述

超声波模块采用HC-SR04,本模块使用方法简单,控制口发一个10us以上的高电平,就可以在接收的GPIO口等待高电平输出。有输出就可以开定时器计时,当此GPIO口变为低电平时就可以读定时器的值。模块的具体实现原理是这样的:

(1)VCC接5V电源,采用IO口TRIG触发测距,给一个至少10us 的高电平信号; (2)模块自动发送8个40kHz的方波,自动检测是否有信号返回; (3)有信号返回,通过IO口ECHO输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间。

时序如图所示: 在这里插入图片描述

检测距离=(高电平时间×声速(340M/S))/2

本模块使用方法简单,一个控制口发一个10us以上的高电平,在接收口等待高电平输入。一有输入就可以开定时器计时,当此口变为低电平时就可以读定时器的值,此时就为此次测距的时间,代入上式即可算出距离。如此不断的周期测量,即可以达到移动测量的值。

4位LED数码管模块

本实验装置采用1个4位LED模块来显示数据,这个显示模块有4个LED数码管,采用共阳极的接法,由2片74HC595驱动,需要树莓派的3路GPIO口,根据数码管动态扫描原理进行显示。

在这里插入图片描述 从图中可以看到,这个模块有5个引脚,从上到下分别为 VCC(接3.3V),SCLK(串行时钟),RCLK(接收时钟),DIO(数据),GND(接地)。

4位 LED 的位地址从左到右分别是0x08、0x04、0x02、0x01,要显示的字模和位地址按字节依次通过DIO口输出到显示模块,依次检查字模和地址字节的每个位,如果是1,则给DIO一个高电平,如果是0,则给DIO一个低电平,每一位DIO数据输出后,要给串行时钟SCLK一个向上的跳变信号(先低电平、再高电平),当字模字节和地址字节都通过DIO输出后,要给接收时钟 RCLK 一个向上的跳变信号(先低电平、再高电平),那么这个位的数据就输出完成,可以输出下一位的数据。

VCC的3.3V可以从电源模块或者树莓派上获取,如果从电源模块获取,必须将电源模块与树莓派共地。

开发过程

和上一个实验一样,我们首先需要在.pro文件中连接wiringPi的库。

LIBS += -lwiringPi LIBS += -lwiringPiDev

下一步编写读取超声波模块测量数据的部分,使用树莓派的两个GPIO口分别连接超声波模块的TRIG和ECHO引脚,分别设置为输出和输入模式,并将TRIG置0。

wiringPiSetup(); pinMode(Trig, OUTPUT); //设置Trig为输出 pinMode(Echo, INPUT); //设置Echo为输入 digitalWrite(Trig, LOW); //将TRIG置0

计算距离的实现主要使用了sys/time.h的获取系统时间的方法,函数定义为:

gettimeofday (struct timeval * tv, struct timezone * tz)

gettimeofday()会把目前的时间由tv所指的结构返回,当地时区的信息则放到tz所指的结构中。timeval的结构体包含当前的秒和当前的微秒,定义如下:

struct timeval { __time_t tv_sec; /* Seconds. */ __suseconds_t tv_usec; /* Microseconds. */ };

当前的时间即为tv_sec.tv_usec秒。

那么获取到了时间,就可以计算ECHO的回响电平长度,那么也就可以算出超声波模块测量出的距离了。实现方法如下:

struct timeval time_1; //timeval是time.h中的预定义结构体 其中包含秒和微秒 struct timeval time_2; long t1, t2; float dist = 0.; digitalWrite(Trig, HIGH); delayMicroseconds(10); //发送一个10us的脉冲 digitalWrite(Trig, LOW); while(digitalRead(Echo) == 0); //捕获上升沿 gettimeofday(&time_1, nullptr); while(digitalRead(Echo) == 1); //捕获下降沿 gettimeofday(&time_2, nullptr); t1 = time_1.tv_sec * 1000000 + time_1.tv_usec; //换算成us t2 = time_2.tv_sec * 1000000 + time_2.tv_usec; dist = static_cast(t2 - t1) / 1000000 * 34000 / 2; //计算距离

下一步就是把测量得到的数据显示在LED模块上。

在使用LED模块时,我们通常会定义数码管的段码表,来将数字转换为LED段选的控制信号。例如使用LED显示数字0时,给数码管的段选信号就是0xC0。

static unsigned char LED_f[] = {//0 1 2 3 4 5 6 7 8 9 A B C D E F - 0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0x8c, 0xbf, 0xc6, 0xa1, 0x8e, 0xbf, 0xff, 0x7f, 0xbf };

然后将3个数码管的IO口设置为输出模式,分别控制数码管的SCLK、RCLK和DIO引脚。

wiringPiSetup(); pinMode(SCLK,OUTPUT); pinMode(RCLK,OUTPUT); pinMode(DIO,OUTPUT);

定义输出一字节数据的函数,每写一个比特,在SCLK上输出一个脉冲信号:

void LED_out(char data) { int i; for(i=8; i>=1 ;i--) //循环8次为1字节 { if(data&0x80) digitalWrite(DIO,HIGH); else digitalWrite(DIO,LOW); delayMicroseconds(1); data LED_display(0x08,data/1000%10); delayMicroseconds(10); LED_display(0x04,data/100%10); delayMicroseconds(10); LED_display(0x02,data/10%10); delayMicroseconds(10); LED_display(0x01,data%10); delayMicroseconds(10); }

通过循环这样一段代码,我们就可以在0x08、0x04、0x02、0x01这4个地址上分别将data的每一位显示出来。

如果要实现连续采集并稳定显示,我们可以开启一个线程来无休止地循环上面这段LED显示的函数。

这是由于 LED 数码管在显示的时候,要求没有任何延时的快速刷新,才不会闪烁。而 超声波模块在采集数据时需要时间,不能快速采集。因此,不能在一个线程里面又采集数据,又通过 LED 显示数据,否则要么无法读取数据(无延时),要么 LED 显示很闪烁(有延时)。为了解决这个矛盾,可采用多线程的模式,LED 显示和数据采集,分别用两个线程来完成。

这里我们用到的方法是继承QThread类的run函数。首先新建了一个thread.h文件,在其中声明Thread类:

#ifndef THREAD_H #define THREAD_H #include "QThread" class Thread : public QThread { Q_OBJECT public: Thread(); protected: void run(); }; #endif // THREAD_H

然后写它的cpp文件,这个类中重写的run函数就是多线程运行时的函数,我们把主循环写在这里面:

#include "thread.h" #include "led.h" #include "wiringPi.h" Thread::Thread() { } void Thread::run() { LED_init(); //初始化LED while(1) { LED_loop(data); //调用LED循环刷新函数 } }

同样,在mainwindow.h中,我们也要include “thread.h”。然后要在MianWindow类里面添加我们刚才写的Thread类,并实例化:

class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); Thread *thread_led; //实例化Thread类 private slots: void measure(); //定时测量的槽函数 private: Ui::MainWindow *ui; };

这样,我们就可以在MainWindow.cpp中使用如下的代码开启这个线程了,start()中便会运行我们刚才重写的run函数:

thread_led = new Thread(); thread_led->start();

写好LED显示的线程后,若要实现另一个定时读取数据的线程,则可以通过#include "QTimer"使用Qt中的QTimer类实现。

在上面的mainwindow.h可以看到我声明了一个槽函数,用来定时读取数据,并传递给LED显示的线程:

void MainWindow::measure() { thread_led->value = SR04_measure(); }

QTimer类为定时器提供了一个高级别的编程接口,它很容易使用:首先,创建一个QTimer,连接timeout()信号到我们读取超声波测量值的槽函数,并调用start(),然后在恒定的时间间隔会发射timeout()信号。例如定时100ms:

timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(measure())); timer->start(100);

start()之后,每100ms都会调用measure()。这样定时采集并稳定显示的程序开发过程就讲述完毕了。

code

最后,我把我的工程上传到了平台上,可供大家参考。 树莓派Qt开发入门(二):超声波测距实验——GPIO的读写操作+多线程编程



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有