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"
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的发展。