RT-Thread Env开发探索——以HC-SR04超声波传感器为例


参考文章:RT Thread Env + CLion环境搭建

0.前言

  对比使用RT Thread Stduio开发程序,使用Env工具的开发流程还是比较繁琐的,但是为了使用最新内核和现代化的IDE工具,不得不做出一些妥协。笔者这里就以开发HC-SR04超声波传感器为例,来介绍一下相关的开发流程。此外笔者在实际的开发过程中,觉得这种开发模式还是有一定的提升空间,所以后续还是暂时使用FreeRTOS吧,RTT的使用会先告一段落。

一、BSP优化

  使用Env工具进行开发时,最重要的就是做好板级支持包BSP文件,在上一篇文章中提到,rt-thread源码目录中的bsp目录下,已经适配好了一些板级支持包,如果没有自己所使用的芯片信号,可以参考RT Thread官方的文档手册进行制作。笔者这篇就主要介绍如何在已有的bsp上制作最小支持包,以及一些简单功能的适配。

1.修改芯片功能配置

  在上一篇文章中,通过scons --dist命令已经生成了现有芯片的板级支持包,生成了project工程模板后,复制一份作为自己的初始工程,在此项目目录中找到CubeMX_Config目录,打开CubeMX工程,即可修改一些现有的芯片配置:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里笔者只开启了基础的SW调试口、UART1串口,以及TIM7定时器供后续超声波传感器使用,然后配置好芯片时钟即可一键生成初始化代码,生成的代码只需要保留以下四个文件即可,其他文件是否保留无关紧要:
注:此处生成的代码,和系统时钟有关的初始化代码会被自动加载到工程中,但如果想自己添加一些其他的外设,则需要自行手动移植对应的配置代码。
在这里插入图片描述

2.修改RTT配置菜单

  修改了芯片配置后,原先的板级配置文件可能就无法生效了,甚至可能造成一些问题,所以需要针对自己已使能的外设,重新修改相关的配置文件。用文本方式打开board目录下的KConfig文件,将其修改成如下内容:

menu "Hardware Drivers Config"

menu "Onboard Peripheral Drivers"

endmenu

menu "On-chip Peripheral Drivers"

    config BSP_USING_GPIO
        bool "Enable GPIO"
        select RT_USING_PIN
        default y

    menuconfig BSP_USING_UART
        bool "Enable UART"
        default y
        select RT_USING_SERIAL
        if BSP_USING_UART
            config BSP_USING_UART1
                bool "Enable UART1"
                default y

            config BSP_UART1_RX_USING_DMA
                bool "Enable UART1 RX DMA"
                depends on BSP_USING_UART1 && RT_SERIAL_USING_DMA
                default n
        endif
	
	menuconfig BSP_USING_TIM
        bool "Enable Hardware TIM"
        default n
        select RT_USING_HWTIMER
        if BSP_USING_TIM
            config BSP_USING_TIM7
                bool "Enable TIM7"
                default n
		endif

    menuconfig BSP_USING_SPI
        bool "Enable SPI BUS"
        default n
        select RT_USING_SPI
        if BSP_USING_SPI
            config BSP_USING_SPI1
                bool "Enable SPI1 BUS"
                default n

            config BSP_SPI1_TX_USING_DMA
                bool "Enable SPI1 TX DMA"
                depends on BSP_USING_SPI1
                default n

            config BSP_SPI1_RX_USING_DMA
                bool "Enable SPI1 RX DMA"
                depends on BSP_USING_SPI1
                select BSP_SPI1_TX_USING_DMA
                default n
        endif

    menuconfig BSP_USING_I2C1
        bool "Enable I2C1 BUS (software simulation)"
        default n
        select RT_USING_I2C
        select RT_USING_I2C_BITOPS
        select RT_USING_PIN
        if BSP_USING_I2C1
            config BSP_I2C1_SCL_PIN
                int "i2c1 scl pin number"
                range 1 216
                default 15
            config BSP_I2C1_SDA_PIN
                int "I2C1 sda pin number"
                range 1 216
                default 16
        endif

    menuconfig BSP_USING_PWM
        bool "Enable PWM"
        default n
        select RT_USING_PWM
        if BSP_USING_PWM
        menuconfig BSP_USING_PWM2
            bool "Enable timer2 output PWM"
            default n
            if BSP_USING_PWM2
                config BSP_USING_PWM2_CH1
                    bool "Enable PWM2 channel 1"
                    default n

                config BSP_USING_PWM2_CH2
                    bool "Enable PWM2 channel 2"
                    default n

                config BSP_USING_PWM2_CH3
                    bool "Enable PWM2 channel 3"
                    default n
            endif
        endif

    source "$BSP_DIR/libraries/HAL_Drivers/drivers/Kconfig"

endmenu

menu "Board extended module Drivers"

endmenu

endmenu

  这里是一些常见外设的添加模版,需要开发人员有一些KConfig的编辑基础,或者其实找一些其他的模版文件照着修改也可以。这里笔者使能了PIN设备驱动、UART驱动和TIM驱动,其他的外设驱动设置default n默认关闭,后续有需要再开启。这个配置文件本质上就是通过在menuconfig中选择是否开启相关功能,来设置是否开启代码中的一些相关的宏定义,进而就可以控制是否添加相关的功能模块源码。
  至此,一个最小BSP支持包就制作完毕,不过有一点还需要注意:在这个模板中,笔者使用了TIM7作为超声波传感器的硬件定时器依赖,但是默认的RT Thread内核中,为STM32F1系列声明的定时器名称并没有这么多,所以需要在tim_config.h中手动添加
在CLion中全局搜索一个现有定时器,找到对应的板级配置头文件,然后仿照现有的新建一个即可。除此之外,如果使用一些其他硬件外设,编译时提示找不到对应的设备,也可以看看对应的声明有没有添加。
在这里插入图片描述
在这里插入图片描述

二、软件包加载

  修改完BSP后,就可以在menuconfig中开启对应的功能,加载一些需要的软件包以便后续开发。在项目根目录下打开Env窗口,然后打开menuconfig菜单选项:

1.外设配置

Hardware Drivers Config ---> On-chip Peripheral Drivers栏中,即可看到之前在KConfig中设置的一些外设选项。
在这里插入图片描述

2.驱动框架配置

  一些外设或者软件包都需要与内核的驱动框架进行对接,所以需要在RT-Thread Components ---> Device Drivers目录下,使能对应的驱动框架,这里笔者只使能了超声波传感器需要使用的Sensor框架,需要根据实际的需求自行选择:
在这里插入图片描述

3.软件包配置

  RT-Thread已经集成的软件包较多,手动在RT-Thread online packages目录下搜索会比较麻烦,可以按下键盘上的/键,然后输入关键字进行检索,找到想要的条目后,输入对应的序号就可以直接跳入:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
选择完毕后,保存并退出,在Env窗口中输入pkgs --update即可自动将软件包加载到项目中:
在这里插入图片描述

然后使用scons --targer=cmake重新更新一下工程即可。
在这里插入图片描述

三、编译及运行

在CLion中打开项目,直接编译时会报一些错误:
1.头文件未找到
在这里插入图片描述
将这些地方修改为<drivers/sensor.h>
在这里插入图片描述
2.RT_WEAK识别错误:
在这里插入图片描述
将RT_WEAK修改为rt_weak
在这里插入图片描述
3.找不到pin脚
在这里插入图片描述
添加<drv_gpio.h>头文件
在这里插入图片描述
最后修改sr04_sample.c中的设备相关引脚定义和使用的硬件定时器声明,即可编译下载。
在这里插入图片描述
在这里插入图片描述

四、源码分析

sr04的软件包中,主要内容是这个sensor_hc_sr04.c文件,其中实现了向内核注册sensor设备的流程,以及从设备读取数据的接口:
sensor_hc_sr04.c:

/*
 * Copyright (c) 2006-2020, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2020-05-07     shany       the first version
 */

#include "sensor_hc_sr04.h"
#include "drivers/sensor.h"
#include "board.h"
#include <rtdbg.h>

#define DBG_TAG "sensor.hc.sr04"
#define DBG_LVL DBG_INFO

#define SENSOR_DISTANCE_RANGE_MAX (400)
#define SENSOR_DISTANCE_RANGE_MIN (2)

static struct sr04_device *sr04_dev;

rt_weak void rt_hw_us_delay(rt_uint32_t us)
{
    rt_uint32_t delta;

    us = us * (SysTick->LOAD / (1000000 / RT_TICK_PER_SECOND));
    delta = SysTick->VAL;

    while (delta - SysTick->VAL < us) continue;
}

static rt_err_t _sr04_hwtimer_cb(rt_device_t dev, rt_size_t size)
{
    sr04_dev->err = 0x88;

    return 0;
}

static rt_device_t _sr04_hwtimer_init(void)
{
    rt_device_t dev;
    rt_err_t ret = RT_EOK;

    dev = rt_device_find(sr04_dev->hwtimer);
    if (dev == RT_NULL) {
        rt_kprintf("can't find %s device!\n", sr04_dev->hwtimer);
        return RT_NULL;
    }

    ret = rt_device_open(dev, RT_DEVICE_OFLAG_RDWR);
    if (ret != RT_EOK) {
        rt_kprintf("open hwtimer device failed!\n");
        return RT_NULL;
    }

    rt_device_set_rx_indicate(dev, _sr04_hwtimer_cb);

    static rt_hwtimer_mode_t hw_mode = HWTIMER_MODE_PERIOD;
    ret = rt_device_control(dev, HWTIMER_CTRL_MODE_SET, &hw_mode);
    if (ret != RT_EOK) {
        rt_kprintf("set hwtimer mode failed!\n");
        return RT_NULL;
    }

    return dev;
}

static rt_err_t _sr04_hwtimer_start(rt_device_t dev)
{
    rt_hwtimerval_t hw_val;

    hw_val.sec = 1;
    hw_val.usec = 0;
    if (rt_device_write(dev, 0, &hw_val, sizeof(hw_val)) != sizeof(hw_val)) {
        rt_kprintf("set value failed\n");
        return RT_ERROR;
    }

    return RT_EOK;
}

static int32_t _sr04_hwtimer_stop(rt_device_t dev)
{
    rt_hwtimerval_t hw_val;

    rt_device_read(dev, 0, &hw_val, sizeof(hw_val));
    // rt_kprintf("read: sec = %d, usec = %d\n", hw_val.sec, hw_val.usec);

    rt_device_close(dev);

    return (int32_t)(hw_val.sec * 1000000 + hw_val.usec);
}

int32_t sr04_get_distance(void)
{
    int32_t duration = 0, distance = 0;
    rt_device_t dev;

    dev = _sr04_hwtimer_init();
    rt_pin_write(sr04_dev->trig_pin, PIN_LOW);
    rt_hw_us_delay(2);
    rt_pin_write(sr04_dev->trig_pin, PIN_HIGH);
    rt_hw_us_delay(10);
    rt_pin_write(sr04_dev->trig_pin, PIN_LOW);
    while ((rt_pin_read(sr04_dev->echo_pin) == PIN_LOW));
    _sr04_hwtimer_start(dev);
    while ((rt_pin_read(sr04_dev->echo_pin) == PIN_HIGH) && (sr04_dev->err != 0x88));
    duration = _sr04_hwtimer_stop(dev);

    distance = (int32_t)(duration * 340.0 / 2000.0 + 0.5);

    return distance;
}

static rt_size_t _sr04_polling_get_data(rt_sensor_t sensor, struct rt_sensor_data *data)
{
    rt_int32_t distance_x10;

    if (sensor->info.type == RT_SENSOR_CLASS_PROXIMITY) {
        distance_x10 = sr04_get_distance();
        data->data.proximity = distance_x10;
        data->timestamp = rt_sensor_get_ts();
    }

    return 1;
}

static rt_size_t sr04_fetch_data(struct rt_sensor_device *sensor, void *buf, rt_size_t len)
{
    RT_ASSERT(buf);

    if (sensor->config.mode == RT_SENSOR_MODE_POLLING) {
        return _sr04_polling_get_data(sensor, buf);
    }
    else {
        return 0;
    }
}

static rt_err_t sr04_control(struct rt_sensor_device *sensor, int cmd, void *args)
{
    rt_err_t ret = RT_EOK;

    return ret;
}

static struct rt_sensor_ops sensor_ops =
{
    sr04_fetch_data,
    sr04_control
};

static sr04_device_t _sr04_init(struct rt_sensor_config *cfg)
{
    rt_base_t *pins;
    sr04_device_t dev;

    dev = rt_calloc(1, sizeof(struct sr04_device));
    if (dev == RT_NULL) {
        LOG_E("Can't allocate memory for sr04 device on '%s'.\n", cfg->intf.dev_name);
        return RT_NULL;
    }

    dev->hwtimer = cfg->intf.dev_name;
    rt_kprintf("hwtimer: %s\n", dev->hwtimer);

    pins = (rt_base_t *)cfg->intf.user_data;
    // rt_kprintf("trig: %d, echo: %d\n", pins[0], pins[1]);
    dev->trig_pin = pins[0];
    dev->echo_pin = pins[1];
    // rt_kprintf("trig: %d, echo: %d\n", dev->trig_pin, dev->echo_pin);
    rt_pin_mode(dev->trig_pin, PIN_MODE_OUTPUT);
    rt_pin_mode(dev->echo_pin, PIN_MODE_INPUT);

    return dev;
}

int rt_hw_sr04_init(const char *name, struct rt_sensor_config *cfg)
{
    rt_int8_t result;
    rt_sensor_t sensor_sr04 = RT_NULL;

    sr04_dev = _sr04_init(cfg);
    /* sr04 sensor register */
    sensor_sr04 = rt_calloc(1, sizeof(struct rt_sensor_device));
    if (sensor_sr04 == RT_NULL) {
        return -1;
    }

    sensor_sr04->info.type       = RT_SENSOR_CLASS_PROXIMITY;
    sensor_sr04->info.vendor     = RT_SENSOR_VENDOR_UNKNOWN;
    sensor_sr04->info.model      = "sr04";
    sensor_sr04->info.unit       = RT_SENSOR_UNIT_CM;
    sensor_sr04->info.intf_type  = RT_SENSOR_INTF_ONEWIRE;
    sensor_sr04->info.range_max  = SENSOR_DISTANCE_RANGE_MAX;
    sensor_sr04->info.range_min  = SENSOR_DISTANCE_RANGE_MIN;
    sensor_sr04->info.period_min = 5;

    rt_memcpy(&sensor_sr04->config, cfg, sizeof(struct rt_sensor_config));
    sensor_sr04->ops = &sensor_ops;

    result = rt_hw_sensor_register(sensor_sr04, name, RT_DEVICE_FLAG_RDONLY, RT_NULL);
    if (result != RT_EOK) {
        LOG_E("device register err code: %d", result);
        goto __exit;
    }

    return RT_EOK;

__exit:
    if (sensor_sr04)
        rt_free(sensor_sr04);
    return -RT_ERROR;
}

测距的主要流程:
首先TRIG引脚产生一个10us的高电平信号,即代表开启一次测距,读取到ECHO引脚高电平后,在_sr04_hwtimer_start函数中将定时器的超时时间设置成了1s,如果超时就会触发_sr04_hwtimer_cb回调函数,将标志位置成0x88,在读取ECHO引脚的死循环中,如果高电平结束或者超时标志位被置0x88,则结束本次测距。
问题1:SR04的最大测距量程为4米,所以超时时间可以调小一些,按照一个来回最大量程,预留30ms左右的超时时间即可。(影响不大)
问题2:没有对测距超时的结果做出舍弃,无论本次测距成功或者超时,都会将结果输出。(影响较大)

五、总结

  RT Thread的亮点就是实现了不少驱动框架封装,使得应用层代码可以与底层分离开来,这样如果更换了底层硬件平台,上层的应用代码改动就不是很大。但在笔者的实际使用中发现,目前实现的框架大多只有一些基本功能,比如想要实现STM32的多个ADC外设交替采样,那么还是只能通过手动移植HAL库来实现。
  软件层面的软件包以及驱动框架,目前看起来确实有一些独到之处,毕竟参考了linux内核的实现方式来做的,对于有一定的linux基础的开发人员来说还是比较容易上手。不过软件包的质量良莠不齐,此外在资源有限的单片机上,这种框架所带来的开销也确实需要仔细考量。所以笔者目前对RT Thread的探索就先到这,再观望一阵RTT的发展。

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-05-26 05:38:32       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-05-26 05:38:32       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-05-26 05:38:32       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-05-26 05:38:32       20 阅读

热门阅读

  1. JVM之回收策略的详细解析

    2024-05-26 05:38:32       11 阅读
  2. pillow学习4

    2024-05-26 05:38:32       11 阅读
  3. PostgreSQL查看数据库的创建时间

    2024-05-26 05:38:32       14 阅读
  4. 解决 Scrapy 下载图片时的 Pillow 依赖问题

    2024-05-26 05:38:32       13 阅读
  5. Hive 变量定义与引用

    2024-05-26 05:38:32       15 阅读
  6. 【QT】包含中文的QString转换为std::string乱码问题

    2024-05-26 05:38:32       14 阅读
  7. 【vue嵌套iframe】实现项目重构

    2024-05-26 05:38:32       15 阅读
  8. glances的安装方式

    2024-05-26 05:38:32       10 阅读
  9. netstat命令检查端口是否监听

    2024-05-26 05:38:32       11 阅读
  10. C语言基础-如何避免内存泄漏

    2024-05-26 05:38:32       14 阅读
  11. 泛型中K T V E ? Object等分别代表的含义

    2024-05-26 05:38:32       12 阅读