将在zephyr下进行开发,zephyr中对nxp主流开发板均有适配,如下
(.venv) l@l:~/zephyrproject$ west boards | grep "frdm" frdm_mcxa153 frdm_ke17z frdm_kl25z frdm_mcxa266 frdm_mcxa346 frdm_mcxa366 frdm_mcxw72 frdm_mcxw23 frdm_mcxw70 frdm_rw612 frdm_imx93 frdm_k22f frdm_imxrt1186 frdm_mcxw71 frdm_mcxl255 frdm_mcxa577 frdm_ke16z frdm_mcxn947 frdm_mcxe247 frdm_mcxc242 frdm_mcxc444 frdm_kw41z frdm_mcxa344 frdm_mcxe31b frdm_k32l2b3 frdm_imx91 frdm_ke17z512 frdm_mcxa156 frdm_k82f frdm_k64f frdm_mcxn236 frdm_ke15z
其中frdm_macx153就是本次使用的开发板,所以不需要再进行board适配了
本次使用的模块有,US-100超声波模块和c4001毫米波传感器
但是这两种模块zephyr里都没有驱动,所以需要自己写驱动
首先介绍一下US-100

US-100 是一款超声波测距传感器,外形和常见的 HC-SR04 几乎一样,但多了几个实用特性。
核心特点:
宽电压供电:支持 2.4V~5.5V,3V 和 5V 系统都能直接用,不需要电平转换。
双工作模式,靠背面跳线切换:
拔掉跳线 → HC-SR04 兼容模式,用 Trigger/Echo 引脚,通过回波脉冲宽度算距离。
装上跳线 → 串口 UART 模式,9600 波特率通信。发 0x55 读回两字节(16 位)得到毫米距离;发 0x50 读回温度(摄氏度)。UART 模式适合接电脑 USB 转串口,或者那些做不了 HC-SR04 精确时序的设备。
主要参数:
工作电流:2mA
测距范围:2cm~450cm(实际 10cm~250cm 效果最好)
精度:0.3cm + 1%
测量角度:小于 15°
工作温度:-20~+70℃
尺寸:45 × 20mm(不含排针),重 9g
UART 模式还能读温度,可以用来做声速温度补偿,让测距更准——这是相比 HC-SR04 的一个明显优势。
因为常用的HC-SR04用的是Trigger/Echo 引脚,通过回波脉冲宽度算距离,所以驱动也选择这个模式
驱动如下
/*
* HC-SR04 ultrasonic distance sensor.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT vnd_hcsr04
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/atomic.h>
#include <my_module/hcsr04.h>
LOG_MODULE_REGISTER(hcsr04, CONFIG_HCSR04_LOG_LEVEL);
/* Datasheet asks for a trigger pulse of at least 10us. */
#define HCSR04_TRIG_PULSE_US 10U
/*
* Speed of sound in dry air at 20 degC is 343 m/s, i.e. 0.343 mm/us. The pulse
* covers the distance twice, so mm = us * 343 / 2000. Worst case us is bounded
* by HCSR04_ECHO_MAX_US below, so the multiplication stays in 32 bits.
*/
#define HCSR04_SOUND_MM_PER_MS 343U
/*
* Nothing in range gives a ~38ms pulse. Anything at or above that is a
* "no object" report rather than a distance, and 4m (the rated maximum) is
* only 23ms, so treat the rest as out of range.
*/
#define HCSR04_ECHO_MAX_US 36000U
struct hcsr04_config {
struct gpio_dt_spec trig;
struct gpio_dt_spec echo;
uint16_t timeout_ms;
uint16_t interval_ms;
};
struct hcsr04_data {
const struct device *dev;
struct gpio_callback echo_cb;
/* Serializes fetches, and guards distance_mm against a concurrent get. */
struct k_mutex lock;
/* Given by the echo ISR once a full pulse has been captured. */
struct k_sem done;
/* ISR state: timestamp of the rising edge, and whether we saw one. */
uint32_t rise_cycles;
bool rise_seen;
atomic_t pulse_cycles;
int64_t next_trigger_uptime;
uint32_t distance_mm;
};
static void hcsr04_echo_handler(const struct device *port, struct gpio_callback *cb, uint32_t pins)
{
struct hcsr04_data *data = CONTAINER_OF(cb, struct hcsr04_data, echo_cb);
const struct hcsr04_config *config = data->dev->config;
uint32_t now = k_cycle_get_32();
int level;
ARG_UNUSED(port);
ARG_UNUSED(pins);
level = gpio_pin_get_dt(&config->echo);
if (level < 0) {
return;
}
if (level == 1) {
data->rise_cycles = now;
data->rise_seen = true;
return;
}
/* A falling edge without a rising one is noise from a previous run. */
if (!data->rise_seen) {
return;
}
data->rise_seen = false;
atomic_set(&data->pulse_cycles, now - data->rise_cycles);
(void)gpio_pin_interrupt_configure_dt(&config->echo, GPIO_INT_DISABLE);
k_sem_give(&data->done);
}
/* Emits the trigger pulse and waits for the echo. Returns the pulse width. */
static int hcsr04_measure(const struct device *dev, uint32_t *echo_us)
{
const struct hcsr04_config *config = dev->config;
struct hcsr04_data *data = dev->data;
int64_t wait_ms;
int ret;
/* The sensor needs a quiet period between bursts, see measure-interval-ms. */
wait_ms = data->next_trigger_uptime - k_uptime_get();
if (wait_ms > 0) {
k_msleep((int32_t)wait_ms);
}
data->rise_seen = false;
k_sem_reset(&data->done);
ret = gpio_pin_interrupt_configure_dt(&config->echo, GPIO_INT_EDGE_BOTH);
if (ret < 0) {
LOG_ERR("failed to enable echo interrupt: %d", ret);
return ret;
}
ret = gpio_pin_set_dt(&config->trig, 1);
if (ret < 0) {
goto disable;
}
k_busy_wait(HCSR04_TRIG_PULSE_US);
ret = gpio_pin_set_dt(&config->trig, 0);
if (ret < 0) {
goto disable;
}
data->next_trigger_uptime = k_uptime_get() + config->interval_ms;
if (k_sem_take(&data->done, K_MSEC(config->timeout_ms)) != 0) {
LOG_WRN("no echo within %u ms", config->timeout_ms);
ret = -EIO;
goto disable;
}
*echo_us = k_cyc_to_us_floor32((uint32_t)atomic_get(&data->pulse_cycles));
return 0;
disable:
(void)gpio_pin_interrupt_configure_dt(&config->echo, GPIO_INT_DISABLE);
return ret;
}
static int hcsr04_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
struct hcsr04_data *data = dev->data;
uint32_t echo_us;
int ret;
if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_DISTANCE) {
return -ENOTSUP;
}
k_mutex_lock(&data->lock, K_FOREVER);
ret = hcsr04_measure(dev, &echo_us);
if (ret == 0) {
if (echo_us >= HCSR04_ECHO_MAX_US) {
LOG_DBG("echo %u us: nothing in range", echo_us);
ret = -ERANGE;
} else {
data->distance_mm = echo_us * HCSR04_SOUND_MM_PER_MS / 2000U;
}
}
k_mutex_unlock(&data->lock);
return ret;
}
static int hcsr04_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
struct hcsr04_data *data = dev->data;
if (chan != SENSOR_CHAN_DISTANCE) {
return -ENOTSUP;
}
k_mutex_lock(&data->lock, K_FOREVER);
/* SENSOR_CHAN_DISTANCE is in metres, the measurement is in millimetres. */
sensor_value_from_milli(val, (int64_t)data->distance_mm);
k_mutex_unlock(&data->lock);
return 0;
}
static int hcsr04_init(const struct device *dev)
{
const struct hcsr04_config *config = dev->config;
struct hcsr04_data *data = dev->data;
int ret;
if (!gpio_is_ready_dt(&config->trig)) {
LOG_ERR("trig port %s not ready", config->trig.port->name);
return -ENODEV;
}
if (!gpio_is_ready_dt(&config->echo)) {
LOG_ERR("echo port %s not ready", config->echo.port->name);
return -ENODEV;
}
ret = gpio_pin_configure_dt(&config->trig, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("failed to configure trig pin: %d", ret);
return ret;
}
ret = gpio_pin_configure_dt(&config->echo, GPIO_INPUT);
if (ret < 0) {
LOG_ERR("failed to configure echo pin: %d", ret);
return ret;
}
/* Keep it off until a fetch asks for it, so stray edges are ignored. */
ret = gpio_pin_interrupt_configure_dt(&config->echo, GPIO_INT_DISABLE);
if (ret < 0) {
LOG_ERR("echo pin does not support interrupts: %d", ret);
return ret;
}
gpio_init_callback(&data->echo_cb, hcsr04_echo_handler, BIT(config->echo.pin));
ret = gpio_add_callback_dt(&config->echo, &data->echo_cb);
if (ret < 0) {
LOG_ERR("failed to add echo callback: %d", ret);
return ret;
}
k_mutex_init(&data->lock);
k_sem_init(&data->done, 0, 1);
return 0;
}
static DEVICE_API(sensor, hcsr04_driver_api) = {
.sample_fetch = hcsr04_sample_fetch,
.channel_get = hcsr04_channel_get,
};
#define HCSR04_DEFINE(i)
static struct hcsr04_data hcsr04_data_##i = {
.dev = DEVICE_DT_INST_GET(i),
};
static const struct hcsr04_config hcsr04_config_##i = {
.trig = GPIO_DT_SPEC_INST_GET(i, trig_gpios),
.echo = GPIO_DT_SPEC_INST_GET(i, echo_gpios),
.timeout_ms = DT_INST_PROP(i, timeout_ms),
.interval_ms = DT_INST_PROP(i, measure_interval_ms),
};
SENSOR_DEVICE_DT_INST_DEFINE(i, hcsr04_init, NULL, &hcsr04_data_##i, &hcsr04_config_##i,
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY,
&hcsr04_driver_api);
DT_INST_FOREACH_STATUS_OKAY(HCSR04_DEFINE)整体结构:
配置与数据分离,这是 Zephyr 驱动的惯例。hcsr04_config 存放编译期不变的东西(trig/echo 引脚、超时、测量间隔),从设备树读取;hcsr04_data 存放运行期状态(测量结果、同步原语、ISR 中间状态)。
工作原理:
测距靠 hcsr04_measure。流程是:先保证距上次触发有足够静默期,然后使能 echo 引脚的双边沿中断,在 trig 引脚上打一个至少 10μs 的脉冲,再等信号量。
回波捕获在中断里做,hcsr04_echo_handler:
上升沿 → 记录时间戳 rise_cycles
下降沿 → 计算脉冲宽度存入原子变量 pulse_cycles,关中断,给信号量放行
距离换算用声速。20℃ 干燥空气声速 343 m/s,声波往返两次
即代码里的 echo_us * 343 / 2000。、
测试一下

图形基本没问题,精度也我预想的要高一些,基本能够完成近距离场景下的测距
C4001

C4001 24GHz 毫米波人体存在检测传感器,最大检测范围 25 米、100°*40° 波束角和 24GHz 工作频率。该传感器可在 -40℃ 至 85℃ 的温度范围内工作,波特率为 9600,尺寸紧凑,仅为 26mm*30mm。该传感器采用 FMCW 调制,专为高级人体存在检测应用而设计,可在各种环境条件下提供可靠的性能。
板子上提供了 VIN、GND、RX、TX 和 OUT 引脚,官方只提供了arduino库,所以zephyr驱动也需要自己写。
该模块同样支持两种模式,常规的uart模式和iic模式
UART 模式下,传感器和主控是一问一答加主动上报的关系:主控通过 TX/RX 两根线跟模块通信,配置好检测模式后,模块会按固定波特率(9600)持续往外吐数据帧。写 Zephyr 驱动时,底层要绑定一个 UART 控制器,通过 uart_poll_in(轮询)或中断回调把字节收进缓冲区,再按 C4001 的帧格式解析出"有人/没人""距离""速度"这些信息。因为是标准串口,几乎不挑板子,任何带 UART 的 MCU 都能跑,是最通用、最适合上手的方式。缺点是波特率固定、收发要自己管缓冲和帧同步,解析逻辑得照着厂商协议一点点对。
I2C 模式下,传感器作为从机挂在 I2C 总线上,默认地址 0x2A(可选 0x2B),主控通过读写寄存器的方式跟它交互——想拿存在状态、距离、速度,就去读对应的寄存器地址。写 Zephyr 驱动时,底层绑定一个 I2C 控制器,用 i2c_reg_read/i2c_write_read 这类标准 API 按寄存器地址取值即可,不用像 UART 那样自己做帧同步和缓冲管理,解析负担轻很多。而且 I2C 是总线结构,同一条线上可以再挂别的器件,省引脚。代价是需要额外的 SCL/SDA 上拉电阻,对布线和总线时序更敏感,长距离或干扰环境下不如 UART 。
官方主推的是uart模式,从板子上的丝印也能看出来。所以驱动也按uart来写
C4001 的串口协议是纯 ASCII 字符串,像个小型命令行终端,例如:
配置命令:ASCII 字符串,以回车换行(rn)结尾,参数用空格分隔。例如 setRange 0.6 6rn
读取命令的响应:以 Response 开头,rn 结尾
命令执行结果:成功回 Done,失败回 Error
模块甚至会打印一个命令提示符 DFRobot:/>
这对写解析器来说其实更简单——不用算校验和,按行读、按字符串匹配就行。
驱动代码太长,就不再贴了,看一下效果

这颗毫米波雷达的实测精度达不到我最初设想的桌面级水平。从波形上看,蓝色曲线(距离)和紫色曲线(速度)在匀速往复一个周期的测试中形态基本正常,数据是可用的。
但它的定位决定了能力边界:这是一颗面向室内存在性检测的模块,距离量程为1.2m-35m,近距离精度和分辨率都不足以支撑精细的桌面级测量。
因此需要对项目重新规划,采用双模块分工的方案:毫米波模块负责远距离测距与唤醒,超声波模块负责近距离测距,两者互补覆盖完整量程。
我要赚赏金
