第34章_瑞萨MCU零基础入门系列教程之SR04超声波测距实验

2023-09-13 23:12:34

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写,需要的同学可以在这里获取: https://item.taobao.com/item.htm?id=728461040949

配套资料获取:https://renesas-docs.100ask.net

瑞萨MCU零基础入门系列教程汇总https://blog.csdn.net/qq_35181236/article/details/132779862


第34章 SR04超声波测距实验

34.1 SR04超声波模块简介

34.1.1 产品概述

超声波测距模块是利用超声波来测距。模块先发送超声波,然后接收反射回来的超声波,由反射经历的时间和声音的传播速度340m/s,计算得出距离。

SR04是一款常见的超声波传感器,模块自动发送8个40KHz的方波,自动检测是否有信号返回,用户只需提供一个触发信号,随后检测回响信号的时间长短即可。

SR04采用5V电压,静态电流小于2mA,感应角度最大约15度,探测距离约2cm-450cm。

34.1.2 硬件连接

主机通过两条数据线与SR04连接:主机通过Trig引脚发脉冲给SR04,主机检测Echo引脚的高电平时长。

SR04模块上面有四个引脚,分别为:VCC、Trig、Echo、GND。

Trig是脉冲触发引脚,即控制该脚让SR04模块开始发送超声波。

Echo是回响接收引脚,即SR04模块一旦接收到超声波的返回信号则输出回响信号,回响信号的脉冲宽度与所测距离成正比。

34.1.3 测距时序

SR04超声波测距模块时序图所下图所示:

要测距,需如下操作:

① 触发:

向Trig(脉冲触发引脚)发出一个大约10us的高电平。

② 发出超声波,接收反射信号:

模块就自动发出8个40Khz的超声波,超声波遇到障碍物后反射回来,模块接收返回来的超声波。

③ 回响:

模块接收到反射回来的超声波后,Echo引脚输出一个与检测距离成比例的高电平。

我们只要在该引脚为高时,开启定时器计数,在该引脚变为低时,结束定时器计数。根据定时器的计数和定时器频率就可以算出经历时间,根据时间即可推导出距离。

计算公式为:测试距离=(高电平时间*声速(340M/S))/2;

34.2 模块配置

34.2.1 GPIO配置

本次实验使用的超声波模块会使用到2个IO:Trig和Echo。P003连接到Trig,将其配置为输出模式:

P503引脚连接到Echo,使用它的GPT触发功能,触发GPT的计数开启或者停止。

34.2.2 定时器配置

本次实验获取回响时间采用的方法是,使用P503触发GPT开始计数和结束计数。

已知Echo默认情况下是低电平的,当Trig发出信号后,Echo会产生高脉冲。

所以,使用P503的上升沿触发GPT的计数开启,下降沿触发GPT的计数停止,在Trig引脚发出开始脉冲后,就轮询GPT的状态和计数变化。

如果GPT处于停止状态,且计数不为0,那就代表着Echo触发了一次GPT计数,将计数值读取出来,就可以算出时间、举例。

RA6M5的P503引脚具有的外设复用功能如下图所示:

本书利用其GPY_POEG2:GTETRGC功能触发GPT,本次实验使用的是GPT0。对于GPT的配置如下图所示:

这里配置的计数周期值是32位GPT最大计数,这是为了尽可能让测量时间在GPT的一次计数周期内完成,不产生溢出以免增加计算的复杂度。

34.2.3 GPT_POEG配置

因为使用到了外部引脚触发GPT的POEG功能,因而还需要添加POEG Stack模块:

另外P503是PORG2,所以添加的POEG模块通道要修改为2,如下图所示:

34.3 驱动程序

34.3.1 IO驱动

和DS18B20的IO驱动一模一样,参考《32.4.1 IO驱动》。

34.3.2 定时器驱动

  1. 初始化定时器

在初始化的时候除了要打开GPT以外,还需要打开POEG设备:

static int GPTDrvInit(struct TimerDev *ptdev)
{
    if(NULL==ptdev) return -EINVAL;
    switch(ptdev->channel)
    {
        case 0:
        {
            /* 打开GPT设备完成初始化 */
            fsp_err_t err = g_timer0.p_api->open(g_timer0.p_ctrl, g_timer0.p_cfg);
            assert(FSP_SUCCESS == err);
            err = g_poeg0.p_api->open(g_poeg0.p_ctrl, g_poeg0.p_cfg);
            assert(FSP_SUCCESS == err);
            err = g_timer0.p_api->enable(g_timer0.p_ctrl);
            assert(FSP_SUCCESS == err);
            break;
        }
        default:break;
    }
    
    return ESUCCESS;
}
  1. 轮询GPT状态以及读取计数值
static int GPTDrvRead(struct TimerDev *ptdev, unsigned char *buf, unsigned int length)
{
    if(NULL == ptdev)   return -EINVAL;
    if(NULL == buf)     return -EINVAL;
    if(0 == length)     return -EINVAL;

    switch(ptdev->channel)
    {
        case 0:
        {
            timer_status_t status = {.state = TIMER_STATE_STOPPED};
            fsp_err_t err = g_timer0.p_api->statusGet(g_timer0.p_ctrl, &status);
            assert(FSP_SUCCESS == err);
            if(TIMER_STATE_STOPPED == status.state && status.counter == 0)
            {
                return -EIO;
            }
            else if(TIMER_STATE_STOPPED == status.state && status.counter != 0)
            {
                unsigned int *pbuf = (unsigned int *)buf;
                *pbuf = status.counter*10;    /* ns */
                err = g_timer0.p_api->reset(g_timer0.p_ctrl);
                assert(FSP_SUCCESS == err);
                return (int)length;
            }
            else
                return -EIO;
            break;
        }
        default:break;
    }
    return (int)length;
}
  • 第12行:获取定时器状态;
  • 第14行:判断状态是否停止和计数值是否为零,如果是这种情况那么就代表没有回响信号;
  • 第18行:如果定时器是停滞状态,且GPT计数值不为0,就表示成功测量了一次回响信号,下面就取出计数值存入buffer。

34.4 SR04模块

34.4.1 SR04设备对象

要操作SR04,只需要对它进行初始化、然后读取数值。抽象出如下结构体(dev_ultra.h):

typedef struct UltraDev{
    float distance;
    unsigned int humidity;
    int (*Init)(struct UltraDev *ptdev);
int (*Read)(struct UltraDev *ptdev);
} UltraDevice;

在drv_ ultra.c中实现了一个UltraDev结构体,代码如下:

static struct UltraDev gDevice = {
.distance = 0,
    .Init = UltraDevInit,
    .Read = UltraDevRead
};

最后需要向上层应用提供获取SR04设备的接口:

struct UltraDev *UltraGetDevice(void)
{
    return &gDevice;
}

34.4.2 初始化设备

SR04的初始化函数就是初始化IO以及定时器:

static int UltraDevInit(struct UltraDev *ptdev)
{
    if(NULL == ptdev)   return -EINVAL;
    gTrigDevice = IODeviceFind("Ultra Trig");
    if(NULL == gTrigDevice)
    {
        printf("Failed to Find Ultra Trig IO!\r\n");
        return -ENXIO;
    }
    if(ESUCCESS != gTrigDevice->Init(gTrigDevice))
    {
        printf("Failed to init GPIO!\r\n");
        return -EIO;
    }
    gTrigDevice->Write(gTrigDevice, 0);

    gEchoTimerDevice = TimerDeviceFind("Ultra Echo Timer");
    if(NULL == gEchoTimerDevice)
    {
        printf("Failed to Find Ultra Echo Timer!\r\n");
        return -ENXIO;
    }
    if(ESUCCESS != gEchoTimerDevice->Init(gEchoTimerDevice))
    {
        printf("Failed to init Ultra Echo Time!\r\n");
        return -EIO;
    }

    return ESUCCESS;
}

34.4.3 测量距离

当使用Trig发出开始脉冲后,只需要等待读取定时器的状态和计算出来的时间值即可:

static int UltraDevRead(struct UltraDev *ptdev)
{
    if(NULL == ptdev)   return -EINVAL;
    gTrigDevice->Write(gTrigDevice, 1);
    udelay(20);
    gTrigDevice->Write(gTrigDevice, 0);
    unsigned int time = 0;
    while(gEchoTimerDevice->Read(gEchoTimerDevice, (unsigned char*)&time, 4) != 4);
    ptdev->distance = (float)(time*34.0/2/1000000.0);
    return ESUCCESS;
}
  • 第09行:因为计算出来的时间是ns单位,因而通过声速和时间的公式计算出探测距离。

34.5 测试程序

本次实验每隔10ms探测一次距离:

void DeviceTest(void)
{
    UartDevicesRegister();
    TimerDevicesRegister();
    IODevicesRegister();
    
    UltraDevice *pDevice = UltraGetDevice();
    if(NULL == pDevice)
    {
        printf("Error. There is no SR04 Ultra device!\r\n");
        return;
    }
    pDevice->Init(pDevice);
    while(1)
    {
        if(ESUCCESS == pDevice->Read(pDevice))
        {
            printf("测试距离:%.4fcm\r\n", pDevice->distance);
        }
        mdelay(10);
    }
}

34.6 测试结果

将程序烧写到开发板上运行可以得到如下图所示的探测结果:


本章完
更多推荐

什么是JavaScript中的IIFE(Immediately Invoked Function Expression)?它的作用是什么?

聚沙成塔·每天进步一点点⭐专栏简介⭐JavaScript中的IIFE⭐示例⭐写在最后⭐专栏简介前端入门之旅:探索Web开发的奇妙世界欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发者,这里都将为你提供一个系统而

【Linux从入门到精通】多线程 | 线程介绍&线程控制

本篇文章主要对线程的概念和线程的控制进行了讲解。其中我们再次对进程概念理解。同时对比了进程和线程的区别。希望本篇文章会对你有所帮助。文章目录一、线程概念1、1什么是线程1、2再次理解进程概念1、3轻量级进程二、进程控制2、1创建线程pthread_create2、2线程与进程资源2、3线程id2、4获得线程idpthr

小米华为,化干戈为玉帛!

近日来,手机圈又掀起了各大厂家推出新品的高潮。首先是华为Mate60的推出,其自研的麒麟9000S芯片瞬间点燃了国内手机市场,得到了国内甚至国外业界人士的认可和好评。而近日网上盛传的小米创始人雷军的“愿意加入华为技术生态圈”的邀请,引起了网友们的高度关注。截图自今日头条@刘哥抖料大家都知道,小米采用的是高通芯片和谷歌操

代码签名证书品牌哪家好?选微软推荐机构

代码签名证书是保护软件代码完整性及来源可信的重要方式,软件程序要在操作系统中运行,就需要使用权威合规的代码签名证书,对软件代码进行数字签名,确保软件来源可信、未被非法篡改,消除操作系统“未知发布者”警告,让软件能够顺畅运行。众多代码签名证书厂商中,哪些厂商提供的代码签名证书才是获得操作系统信任的呢?本文将为大家介绍,如

【JVM】类加载器

类与类加载器类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远超类加载阶段。对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加

springboot实战(七)之jackson配置前后端交互下划线转驼峰&对象序列化与反序列化

目录环境:1.驼峰转下划线配置1.1单个字段命名转化使用@JsonProperty注解1.2单个类进行命名转化使用@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)注解3.全局命名策略配置2.序列化以及反序列化2.1序列化2.2反序列化3.自定义序

ref和reactive区别

使用区别reactive定义引用数据类型,ref定义基本类型reactive定义的变量直接使用,ref定义的变量使用时需要.value模板中均可直接使用,vue帮我们判断了是reactive还是ref定义的(通过__v_isRef属性),从而自动添加了.value。//定义letcount=ref(0);letobj=

好用的记笔记app选哪个?

当你在日常生活中突然获得了一个灵感,或者需要记录会议的重要内容,或者是学校课堂上的笔记,你通常会拿出手机,因为它总是在你身边,随时可用。这时候,一款好的记笔记App可以让你事半功倍。敬业签是一款全面的云端备忘记事软件,支持在Windows/Web/Android/iOS/Mac/HarmonyOS等端口同步和编辑记事内

机器学习技术(十)——决策树算法实操,基于运营商过往数据对用户离网情况进行预测

机器学习技术(十)——决策树算法实操文章目录机器学习技术(十)——决策树算法实操一、引言二、数据集介绍三、导入相关依赖库四、读取并查看数据1、读取数据2、查看数据五、数据预处理1、选择数据2、数据转码六、建模与参数优化1、训练模型2、评估模型3、调参优化七、模型可视化八、决策树实操总结一、引言决策树部分主要包含基于py

django_model_一对一映射

settings相关配置#settings.py...DATABASES={'default':{'ENGINE':'django.db.backends.mysql','NAME':'djangos','USER':'root','PASSWORD':'990212','HOST':'localhost','PORT

【TCP】滑动窗口、流量控制 以及拥塞控制

滑动窗口、流量控制以及拥塞控制1.滑动窗口(效率机制)2.流量控制(安全机制)3.拥塞控制(安全机制)1.滑动窗口(效率机制)TCP使用确认应答策略,对每一个发送的数据段,都要给一个ACK确认应答。收到ACK后再发送下一个数据段。这样做有一个比较大的缺点,就是性能较差。尤其是数据往返的时间较长的时候。既然这样一发一收的

热文推荐