模块来源
采购链接:
HC-SR04 超声波测距模块 宽电压3-5.5V 工业级 传感器
资料下载链接:
https://pan.baidu.com/s/1sSah9PvLBrmbA7So-6YcSw
资料提取码:qq35
规格参数
工作电压:3-5.5V
工作电流:5.3MA
感应角度:小于15度
探测距离:2CM-600CM
探测精度:0.1CM+1%
输出方式: GPIO
管脚数量:4 Pin
以上信息见厂家资料文件
移植过程
我们的目标是将例程移植至开发板上【能够判断前方障碍物距离的功能】。首先要获取资料,查看数据手册应如何实现读取数据,再移植至我们的工程。
查看资料
只需要在 Trig 管脚(触发信号)输入一个 10US 以上的高电平,系统便可发出 8 个 40KHZ 的超声波脉冲,然后检测回波信号。当检测到回波信号后,通过 Echo 管脚输出。根据 Echo 管脚输出高电平的持续时间可以计算距离值。即距离值为:(高电平时间*340m/s)/2。
当测量距离超过 HC-SR04 的测量范围时,仍会通过 Echo管脚输出高电平的信号,高电平的宽度约为 66ms。如图所示:
测量周期:当接收到 HC-SR04 通过 Echo 管脚输出的高电平脉冲后,便可进行下一次测量,所以测量周期取决于测量距离,当距离被测物体很近时,Echo 返回的脉冲宽度较窄,测量周期 就很短;当距离被测物体比较远时,Echo 返回的脉冲宽度较宽,测量周期也就相应的变长。最坏情况下,被测物体超出超声波模块的测量范围,此时 返回的脉冲宽度最长,约为 66ms,所以最坏情况下的测量周期稍大于 66ms 即可(取 70ms 足够)。
引脚选择
这里选择的引脚见引脚接线表
代码移植
下载为大家准备的驱动代码文件夹,复制到自己工程中\luban-lite\application\rt-thread\helloworld\user-bsp
文件夹下
提示
如果未找到 user-bsp
这个文件夹,说明你未进行模块移植的前置操作。请转移到手册使用必要操作(点击跳转)中进行必要的配置操作!!!
接下来打开自己的工程,开始修改Kconfig文件。
1、在 VSCode 中打开 application\rt-thread\helloworld\Kconfig 文件
2、在该文件的 #endif
前面添加该模块的 Kconfig路径语句
# SR04超声波测距传感器
source "application/rt-thread/helloworld/user-bsp/sr04-ultrasonic-ranging-sensor/Kconfig"
2
添加完成之后:
menuconfig操作
1、我们 双击 luban-lite
文件夹下的 win_env.bat
脚本打开env工具:
2、输入以下命令列出所有可用的默认配置:
scons --list-def
3、选择 d13x_JLC_rt-thread_helloworld
这个配置!这个是我们衡山派开发板的默认配置!输入以下命令即可:
scons --apply-def=7
或者
scons --apply-def=d13x_JLC_rt-thread_helloworld_defconfig
这两个命令作用是一样的,一个是 文件名 ,一个是 编号 !!!
4、输入以下命令进入menuconfig菜单
scons --menuconfig
进入以下界面:
5、选中 Porting code using the LCKFB module
按
Y
选中按
N
取消选中方向键
左右
调整 最下面菜单的选项方向键
上下
调整 列表的选项
回车
执行最下面菜单的选项
6、回车进入 Porting code using the LCKFB module
菜单
7、按方向键 上下
选中 Use SR04 ultrasonic ranging sensor
后按 Y
键,看到前面括号中出现一个 *
号,就可以下一步了。
8、按方向键 左右
选中 <Save>
然后一路回车
,然后 退出
即可
编译
我们 保存并退出menuconfig菜单 之后,输入以下命令进行编译:
scons
或
scons -j16
-j 用来选择参与编译的核心数: 我这里是选择16
大家可以根据自己的电脑来选择
核心越多编译越快
如果写的数量高于电脑本身,那么就自动按照最高可用的来运行!
镜像烧录
编译完成之后会在 \luban-lite\output\d13x_JLC_rt-thread_helloworld\images
文件夹下生成一个 d13x_JLC_v1.0.0.img
镜像文件!
然后我们烧录镜像,具体的教程请查看:镜像烧录(点击跳转🚀)
到这里完成了,请移步到 最后一节 进行移植验证。
工程代码解析
bsp_sr04.c
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 文档网站:wiki.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 嘉立创社区问答:https://www.jlc-bbs.com/lckfb
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*/
#include <stdio.h>
#include <getopt.h>
#include <sys/time.h>
#include <rtthread.h>
#include "rtdevice.h"
#include "aic_core.h"
#include "aic_hal_gpio.h"
#include "aic_common.h"
#include <stdlib.h>
#include <posix/string.h>
#include "drv_hrtimer.h"
#include "boot_param.h"
#include "bsp_sr04.h"
#define SR04_TIMER_NAME "hrtimer1" // 硬件定时器
#define SR04_TRIG_PIN "PE.14"
#define SR04_ECHO_PIN "PE.12"
#define SR04_TRIG rt_pin_get(SR04_TRIG_PIN)
#define SR04_ECHO rt_pin_get(SR04_ECHO_PIN)
static rt_device_t g_hrtimer_dev = RT_NULL;
static rt_base_t echo_pin;
/* 开始us计数 */
static unsigned long usHcCount_start = 0;
/* 结束us计数 */
static unsigned long usHcCount_end = 0;
/* ms计数 */
static unsigned long msHcCount = 0;
int Get_SR04_Echo(void)
{
unsigned int g = GPIO_GROUP(echo_pin);
unsigned int p = GPIO_GROUP_PIN(echo_pin);
unsigned int value = 0;
hal_gpio_get_value(g, p, &value);
return value;
}
/******************************************************************
* 函 数 名 称:OpenTimer
* 函 数 说 明:打开定时器
* 函 数 形 参:无
* 函 数 返 回:-RT_ERROR:错误 RT_EOK:成功
* 作 者:LC
* 备 注:无
******************************************************************/
static int OpenTimer(void)
{
rt_hwtimerval_t tm = {0}; // 定时器超时值
msHcCount = 0; // 重置ms计数
usHcCount_start = 0;
usHcCount_end = 0;
/* 微秒 */
tm.usec = 1000; // 1ms超时时间
/* 设置定时器超时时间 */
if(!rt_device_write(g_hrtimer_dev, 0, &tm, sizeof(tm)))
{
LOG_E("set timeout value failed");
return -RT_ERROR;
}
return RT_EOK;
}
/******************************************************************
* 函 数 名 称:CloseTimer
* 函 数 说 明:关闭定时器
* 函 数 形 参:无
* 函 数 返 回:-RT_ERROR:错误 RT_EOK:成功
* 作 者:LC
* 备 注:无
******************************************************************/
static int CloseTimer(void)
{
int ret = 0;
/* 设置定时器停止计数*/
ret = rt_device_control(g_hrtimer_dev, HWTIMER_CTRL_STOP, NULL);
if(ret != RT_EOK)
{
LOG_E("Failed to STOP HW Timer");
return -RT_ERROR;
}
return RT_EOK;
}
/******************************************************************
* 函 数 名 称:GetEchoTimer
* 函 数 说 明:获取定时器时间
* 函 数 形 参:无
* 函 数 返 回:-RT_ERROR:错误 其他:返回获取到的时间
* 作 者:LC
* 备 注:无
******************************************************************/
static int GetEchoTimer(void)
{
uint32_t time_us = 0;
rt_hwtimerval_t timer_dat;
int ret = 0;
/*当回响信号很长是,计数值溢出后重复计数,msHcCount用来保存溢出次数*/
time_us = msHcCount * 1000; //定时器每次超时msHcCount都会自增
// 不要使用rt_device_read读取定时器中未超时的计数,底层的read函数未实现!!
// ret = rt_device_read(g_hrtimer_dev, 0, &timer_dat, sizeof(timer_dat));
// if(ret == 0)
// {
// LOG_E("Read HW Timer data failed !!");
// return -RT_ERROR;
// }
// /* 将定时器中的数据合入 */
// time_us += timer_dat.usec;
// rt_kprintf("aic_timer_get_us = %ld\n",aic_timer_get_us());
/* 计算时间差 */
uint32_t temp_count_us = usHcCount_end - usHcCount_start;
// if( (time_us + 1000) > temp_count_us )
// {
temp_count_us = temp_count_us - time_us;
time_us += temp_count_us;
// }
// else
// {
// time_us = temp_count_us;
// }
return time_us;
}
/******************************************************************
* 函 数 名 称:SR04_Read_Length
* 函 数 说 明:超声波测距
* 函 数 形 参:无
* 函 数 返 回:-RT_ERROR:错误 其他:成功
* 作 者:LC
* 备 注:
******************************************************************/
float SR04_Read_Length(void)
{
/*测5次数据计算一次平均值*/
float distances[5]; // 存储五次测量距离的数组
float length = 0;
float sum = 0;
int timeout_us = 1000000; // 超时时间
int i = 0;
while(i != 5)
{
rt_pin_write(SR04_TRIG, PIN_HIGH);//trig拉高信号,发出高电平
aicos_udelay(15);//持续时间超过10us
rt_pin_write(SR04_TRIG, PIN_LOW);//trig拉低信号,发出低电平
/*Echo发出信号 等待回响信号*/
/*
输入方波后,模块会自动发射8个40KHz的声波,与此同时回波引脚(echo)端的电平会由0变为1;
当超声波返回被模块接收到时,回波引 脚端的电平会由1变为0;
记下的这个时间即为超声波由发射到返回的总时长;
*/
while((Get_SR04_Echo() == 0) && timeout_us)//echo等待回响
{
aicos_udelay(1);
timeout_us--;
}
if(!timeout_us)
{
LOG_E("Time Out !");
return -RT_ERROR;
}
/* 定时器开启 */
OpenTimer();
/* 记录时间 */
usHcCount_start = aic_timer_get_us();
timeout_us = 1000000;
while((Get_SR04_Echo() == 1) && timeout_us)
{
aicos_udelay(1);
timeout_us--;
}
if(!timeout_us)
{
LOG_E("Time Out !");
return -RT_ERROR;
}
/* 关闭定时器 */
CloseTimer();
/* 记录时间 */
usHcCount_end = aic_timer_get_us();
aicos_mdelay(65); //根据说明书,每个周期至少需要等待60ms
/* 获取Echo高电平时间时间 */
uint32_t t = GetEchoTimer();
/*获取Echo高电平时间时间*/
length = (float)t / 58.0f; // 单位是cm
distances[i] = length; // 存储测量结果
sum += length;
// rt_kprintf("length = [%d.%d]\n",(int)length,(((uint32_t)(length*100))%100));
// rt_kprintf("sr04_us = [%d]\n",sr04_us);
i++; //每收到一次回响信号+1,收到5次就计算均值
}
// 对测量结果进行排序
for (int j = 0; j < 4; j++) // 冒泡排序,可根据需要选择更高效的排序算法
{
for (int k = 0; k < 4 - j; k++)
{
if (distances[k] > distances[k + 1])
{
// 交换两个元素
float temp = distances[k];
distances[k] = distances[k + 1];
distances[k + 1] = temp;
}
}
}
// 剔除最高和最低的值
sum -= distances[0]; // 剔除最低值
sum -= distances[4]; // 剔除最高值
// 计算三次平均值
return sum / 3.0f; // 返回三次平均值
}
/* 定时器超时回调函数 */
static rt_err_t hrtimer_handle(rt_device_t dev, rt_size_t size)
{
msHcCount++; // 1ms计数自增
return RT_EOK;
}
/******************************************************************
* 函 数 名 称:SR04_HW_TIMER_Init
* 函 数 说 明:硬件定时器初始化
* 函 数 形 参:无
* 函 数 返 回:-RT_ERROR:错误 RT_EOK:成功
* 作 者:LC
* 备 注:
******************************************************************/
int SR04_HW_TIMER_Init(void)
{
int ret = 0;
rt_hwtimer_mode_t mode = HWTIMER_MODE_PERIOD; // 定时器周期模式
rt_hwtimerval_t tm = {0}; // 定时器超时值
/* 寻找设备句柄 */
g_hrtimer_dev = rt_device_find(SR04_TIMER_NAME);
if(g_hrtimer_dev == RT_NULL)
{
LOG_E("Can't find %s device!", SR04_TIMER_NAME);
return -RT_ERROR;
}
/* 打开定时器设备 可读可写模式 */
ret = rt_device_open(g_hrtimer_dev, RT_DEVICE_OFLAG_RDWR);
if (ret != RT_EOK)
{
LOG_E("Failed to open %s device!", SR04_TIMER_NAME);
return -RT_ERROR;
}
/* 设置定时器超时回调函数 */
rt_device_set_rx_indicate(g_hrtimer_dev, hrtimer_handle);
/* 设置定时器模式 */
ret = rt_device_control(g_hrtimer_dev, HWTIMER_CTRL_MODE_SET, &mode);
if(ret != RT_EOK)
{
LOG_E("Failed to set mode! ret is %d", ret);
return -RT_ERROR;
}
/* 微秒 */
tm.usec = 1000; // 1ms超时时间
/* 设置定时器超时时间 */
if(!rt_device_write(g_hrtimer_dev, 0, &tm, sizeof(tm)))
{
LOG_E("set timeout value failed");
return -RT_ERROR;
}
/* 设置定时器停止计数*/
ret = rt_device_control(g_hrtimer_dev, HWTIMER_CTRL_STOP, NULL);
if(ret != RT_EOK)
{
LOG_E("Failed to STOP HW Timer");
return -RT_ERROR;
}
return RT_EOK;
}
/******************************************************************
* 函 数 名 称:SR04_Init
* 函 数 说 明:超声波初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:
******************************************************************/
void SR04_Init(void)
{
int ret = 0;
rt_pin_mode(SR04_TRIG, PIN_MODE_OUTPUT);
rt_pin_mode(SR04_ECHO, PIN_MODE_INPUT);
rt_pin_write(SR04_TRIG, PIN_HIGH);
/* 使用Hal库进行操作,避免额外的时间浪费造成误差! */
echo_pin = hal_gpio_name2pin(SR04_ECHO_PIN);
/* 硬件定时器初始化 */
ret = SR04_HW_TIMER_Init();
if(ret != RT_EOK)
{
LOG_E("failed to SR04_HW_TIMER_Init\n");
}
}
/******************************************************************
* 函 数 名 称:SR04_DeInit
* 函 数 说 明:超声波还原初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:
******************************************************************/
int SR04_DeInit(void)
{
int ret = 0;
/* 停止定时器计时 */
ret = CloseTimer();
if(ret != RT_EOK)
{
LOG_E("failed to STOP HW Timer !!");
return -RT_ERROR;
}
/* 关闭定时器设备 */
ret = rt_device_close(g_hrtimer_dev);
if(ret != RT_EOK)
{
LOG_E("failed to Close HW Timer !!");
return -RT_ERROR;
}
rt_kprintf("\n======SR04_DeInit successful=======\n");
return RT_EOK;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
bsp_sr04.h
#ifndef __BSP_SR04_H__
#define __BSP_SR04_H__
#include <stdio.h>
void SR04_Init(void); // SR04初始化
float SR04_Read_Length(void); // SR04测距
#endif
2
3
4
5
6
7
8
9
10
Kconfig
这个是一个menuconfig中的选项,如果在菜单中选中该选项,就会在rtconfig.h
中定义一个语句,用来if判断条件编译之类的。
config LCKFB_SR04_SENSOR
bool "Use SR04 ultrasonic ranging sensor"
select RT_USING_HWTIMER
select AIC_USING_HRTIMER1
default n
help
More information is available at: https://wiki.lckfb.com/
2
3
4
5
6
7
8
SConscript
自动化构建文件,如果定义了 LCKFB_SR04_SENSOR
和 USING_LCKFB_TRANSPLANT_CODE
就自动编译当前目录下的文件!!
Import('RTT_ROOT')
Import('rtconfig')
import rtconfig
from building import *
cwd = GetCurrentDir()
CPPPATH = [cwd]
src = []
if GetDepend('LCKFB_SR04_SENSOR') and GetDepend('USING_LCKFB_TRANSPLANT_CODE'):
src = Glob(os.path.join(cwd, '*.c'))
group = DefineGroup('lckfb-sr04-ultrasonic-ranging-sensor', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
test_sr04_ultrasonic_ranging_sensor.c
这个文件定义了一个用于处理HC-SR04超声波测距传感器的线程,初始化了传感器,并设置了线程的优先级、栈大小和时间片。
线程的主要任务是读取HC-SR04传感器的距离数据,并打印到控制台。该线程将连续读取指定次数的数据,默认为50次,并且在每次读取后会有短暂的延时。
线程入口函数逻辑
- 初始化时调用
SR04_Init
函数来初始化HC-SR04传感器。 - 定义一个循环计数器
while_count
,并初始化为1。 - 在一个无限循环中,首先尝试读取HC-SR04传感器的距离值。
- 如果读取成功,将读取到的距离值(单位为厘米)打印到控制台,距离值会被格式化为整数部分和小数部分。
- 如果读取失败,打印错误信息。
- 当循环次数达到100次时,提示用户可以通过输入命令
test_exit_sr04_sensor
来退出传感器读取循环。 - 在每次循环结束时,线程会挂起一段时间,这里是500毫秒。
此外,该文件还定义了两个命令,test_sr04
和test_exit_sr04_sensor
,它们分别用于启动和退出HC-SR04传感器线程。
test_sr04
命令创建并启动一个名为sr04_thread
的线程,该线程将执行sr04_thread_entry
函数。test_exit_sr04_sensor
命令用于删除sr04_thread
线程,从而退出传感器读取循环,并打印退出成功的消息。如果删除线程失败,将打印错误信息。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <sys/time.h>
#include <rtthread.h>
#include "rtdevice.h"
#include "aic_core.h"
#include "aic_hal_gpio.h"
#include "bsp_sr04.h"
#define THREAD_PRIORITY 25 // 线程优先级
#define THREAD_STACK_SIZE 4096 // 线程大小
#define THREAD_TIMESLICE 10 // 时间片
static rt_thread_t sr04_thread = RT_NULL; // 线程控制块
// SR04读取次数
// 默认读取50次
static int read_num = 50;
// 线程入口函数
static void sr04_thread_entry(void *param)
{
int while_count = 1;
SR04_Init();
while(while_count++)
{
float ret = SR04_Read_Length();
if(ret == -RT_ERROR)
{
LOG_E("failed to SR04_Read_Length()");
}
else
{
rt_kprintf("\nDistance: %d.%dcm\n", (int)ret,(((uint32_t)(ret*100))%100));
}
if(while_count >= 100)
{
while_count = 1;
rt_kprintf("\nType [test_exit_sr04_sensor] command to exit [SR04]\n");
rt_kprintf("Note: Pressing [TAB] as you type will autocomplete the command\n\n");
rt_thread_mdelay(2000);
}
rt_thread_mdelay(500);
}
}
static void test_sr04(int argc, char **argv)
{
/* 创建线程,名称是 sr04_thread,入口是 sr04_thread_entry */
sr04_thread = rt_thread_create("sr04_thread",
sr04_thread_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
/* 如果获得线程控制块,启动这个线程 */
if (sr04_thread != RT_NULL)
rt_thread_startup(sr04_thread);
}
// 导出函数为命令
MSH_CMD_EXPORT(test_sr04, Test SR04 ultrasonic ranging sensor);
/* ADS1115退出函数 */
static void test_exit_sr04_sensor(void)
{
int ret = rt_thread_delete(sr04_thread);
if(ret != RT_EOK)
{
LOG_E("failed to test_exit_sr04_sensor !!");
}
else
{
rt_kprintf("\n========SR04 exit successful !!========\n");
}
}
// 导出函数为命令
MSH_CMD_EXPORT(test_exit_sr04_sensor, quit SR04 sensor);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
移植验证
我们使用串口调试,将 USB转TTL模块 连接到衡山派开发板上面!!
具体的教程查看:串口调试(点击跳转🚀)
串口波特率默认为
115200
我们在输入下面的命令运行该模块的线程:
输入的时候按下
TAB键
会进行命令补全!!
test_sr04
模块上电效果: