RT-Thread Env开发探索——以HC-SR04超声波传感器为例
- 0.前言
- 一、BSP优化
- 1.修改芯片功能配置
- 2.修改RTT配置菜单
- 二、软件包加载
- 1.外设配置
- 2.驱动框架配置
- 3.软件包配置
- 三、编译及运行
- 四、源码分析
- 五、总结
参考文章: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"endmenumenu "On-chip Peripheral Drivers"config BSP_USING_GPIObool "Enable GPIO"select RT_USING_PINdefault ymenuconfig BSP_USING_UARTbool "Enable UART"default yselect RT_USING_SERIALif BSP_USING_UARTconfig BSP_USING_UART1bool "Enable UART1"default yconfig BSP_UART1_RX_USING_DMAbool "Enable UART1 RX DMA"depends on BSP_USING_UART1 && RT_SERIAL_USING_DMAdefault nendifmenuconfig BSP_USING_TIMbool "Enable Hardware TIM"default nselect RT_USING_HWTIMERif BSP_USING_TIMconfig BSP_USING_TIM7bool "Enable TIM7"default nendifmenuconfig BSP_USING_SPIbool "Enable SPI BUS"default nselect RT_USING_SPIif BSP_USING_SPIconfig BSP_USING_SPI1bool "Enable SPI1 BUS"default nconfig BSP_SPI1_TX_USING_DMAbool "Enable SPI1 TX DMA"depends on BSP_USING_SPI1default nconfig BSP_SPI1_RX_USING_DMAbool "Enable SPI1 RX DMA"depends on BSP_USING_SPI1select BSP_SPI1_TX_USING_DMAdefault nendifmenuconfig BSP_USING_I2C1bool "Enable I2C1 BUS (software simulation)"default nselect RT_USING_I2Cselect RT_USING_I2C_BITOPSselect RT_USING_PINif BSP_USING_I2C1config BSP_I2C1_SCL_PINint "i2c1 scl pin number"range 1 216default 15config BSP_I2C1_SDA_PINint "I2C1 sda pin number"range 1 216default 16endifmenuconfig BSP_USING_PWMbool "Enable PWM"default nselect RT_USING_PWMif BSP_USING_PWMmenuconfig BSP_USING_PWM2bool "Enable timer2 output PWM"default nif BSP_USING_PWM2config BSP_USING_PWM2_CH1bool "Enable PWM2 channel 1"default nconfig BSP_USING_PWM2_CH2bool "Enable PWM2 channel 2"default nconfig BSP_USING_PWM2_CH3bool "Enable PWM2 channel 3"default nendifendifsource "$BSP_DIR/libraries/HAL_Drivers/drivers/Kconfig"endmenumenu "Board extended module Drivers"endmenuendmenu
这里是一些常见外设的添加模版,需要开发人员有一些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的发展。