项目介绍和创意介绍
2025年贸泽电子M-Design创意设计竞赛,我选择的方向是安全监测。
此项目主控是 Teensy4.1,采用雷达模块进行人体存在检测,根据检测到的距离分等级预警,然后通过语音合成模块播报预警信息。这个创意一个适用的场景是工厂叉车运输大件物品,某些时候驾驶员粗心驾驶出现不合规的情况,例如载物过高、过宽遮挡驾驶员视线,驾驶员倒车行驶等情况,防止车辆在行进过程中撞到人出现事故。
硬件介绍
本项目使用的硬件为 Teensy4.1 + 某款60G雷达模块 + DFRobot 语音合成模块。
主控 Teensy4.1
Teensy4.1 是 PJRC 公司开发的一款高性能、多功能的微控制开发板,它是 Teensy 系列中的旗舰产品。基于 NXP i.MX RT1062 Cortex-M7 处理器,它提供了极高的处理速度和丰富的外设接口,适用于从简单的电子项目到复杂的数据处理和实时控制系统的广泛应用场景。
- 处理器性能:采用主频高达600MHz的32位 ARM Cortex-M7 ,并带有 FPU,支持双精度浮点操作;
- 内存与存储:拥有接近8MB Flash 和 1MB RAM,此外还支持外部 SD 卡易扩展存储空间;
- USB功能:支持 USB 主机和设备模式,可以模拟多种 USB 设备类型,如键盘、鼠标、MIDI等;
- 网络通信:除了内置的 Ethernet 接口外,还可以通过专用接口添加 WiFi 和 Bluethooth 模块;
- GPIO 及外设:提供大量可编程GPIO管脚,支持 I2C/SPI/UART 等常见串行通信协议,还有CAN总线、SDIO、Camera等高级接口;
雷达模块
此款雷达模块是 60G 毫米波雷达 SoC,单一芯片上完整集成了雷达收发机、数字基带、PMIC以及MCU等关键单元,传感器采用先进的 FCCSP 封装、1T2R AIP 天线,片上集成了硬件加速器、外围电路极其简单。具有高级程度、超小尺寸、超低功耗、性能优异等特点。
- 工作频率:59-64GHz
- 搭载MCU: ARM Cortex-M0+ 160MHz, 192KBSRAM + 512KB Flash
- ADC: 10Msps 16bit 实时采样
- HWA: 1024点 FFT , 2x1D CA-CFAR
- 供电电压:3.0V~4.2V
- 通讯接口:UART/SPI/I2C
DFRobot语音合成模块
DFR0760 是由 DFRobot 生产的一种语音合成模块,它能够让开发者轻松地将文字转换为语音,从而在各种项目中添加语音反馈功能。这款模块基于先进的语音合成技术,旨在为用户提供自然流程的语音输出体验。
主要特点:
- 易于集成:DFR0760 模块涉及简介,便于集成到各类电子项目中,支持多种通信接口如UART、I2C等
- 高质量语音:采用高效的语音合成算法,能能够提供清晰自然的人声输出,使得语音提示活信息传达更加准确有效
- 多语言支持:可以支持包括但不限于中文、英文等多种语言的文本转语音服务,满足不同地区的用户群体的需求
- 自定义配置:允许用户通过发送特定的指令来调整语速、音调、音量等参数,已达到最佳的听觉效果
- 小尺寸设计:紧凑的设计让其非常适合空间有限的应用场景,比如嵌入式系统或者手持设备。
方案框图和项目设计思路
方案框图
- 主控和雷达模块通过UART通信,主机发送命令获取雷达检测结果;
- 主控和语音合成模块通过I2C通信,主机发送控制调整语速、语调等参数,并发送文本播放语音;
设计思路
- 主控先和雷达模块通信,调通UART通信,发送命令获取雷达检测结果;
- 校验雷达返回的数据,从中抽取有用的信息并保存到全局变量;
- 主控和语音合成模块通过I2C接口通信,调通I2C通信,配置语调、语速参数,成功播放语音;
- 结合雷达检测数据,把检测距离分等级,根据距离等级播报对应的警告信息;
原理图和PCB介绍
原理图
- 主控和雷达是UART通信,使用 Teensy4.1 Serial7,即 UART7_RX -- IO28, UART7_TX -- IO29 分别对应雷达模块的 TX/RX
- 主控和语音合成模块是 I2C 通信,使用 Teensy4.1 I2C0,即 SCL -- IO19, SDA -- IO18,分别对应语音合成模块的 SCL/SDA。需要注意语音合成模块的档位拨到 I2C 档位。
实物连接图
软件流程图和关键代码介绍
软件流程图
- 硬件初始化:包括串口、LED,然后初始化雷达(即串口初始化),然后初始化语音模块(即I2C初始化,并下发参数配置语音合成模块的音调、语速等参数);
- 雷达检测:串口通信,主控下发命令,获取雷达检测结果。由于通信使用字节流,需要校验字节流是否完整,然后从中抽取检测信息,包括距离、置信度等;
- 根据雷达检测结果,把距离分成4个等级,不同的等级播报不同的语音。如检测到人在4米以外,播报语音;4米到3米,播报“注意有人”;当距离在2米以内,播报“危险停车”;
关键代码
主流程
void setup()
{
bsp_serial_setup(TEENSY_SERIAL_LOG_SPEED);
bsp_led_init();
radar_init();
speech_setup();
}
雷达初始化
/**
* @brief Teensy4.1 的雷达模块串口
* IO28, RX7 -- Radar TX
* IO29, TX7 -- Radar RX
*/
#define gRadarSerial Serial7
/**
* @brief Teensy4.1 的雷达模块串口波特率
*/
#define TEENSY_MOD_RADAR_SPEED 921600
void radar_init(void)
{
gRadarSerial.begin(TEENSY_MOD_RADAR_SPEED);
gRadarSerial.setTimeout(50); // 设置读取超时时间为 50ms
}
雷达获取检测结果
通过UART发送命令(这里是字节流 58, 30, 00, 88, 00)然后等待雷达模块发送检测结果,也是字节流。读取之后需要进行校验 ,然后从中提取雷达检测结果。
/**
* @brief 获取雷达扫描结果,一次需要读取 25 个字节,并且需要校验
*
* @param
* @return
*/
void radar_scan(void)
{
byte temp[] = { 0x58, 0x30, 0x00, 0x88, 0x00 };
byte readBuffer[25] = { 0 };
// 打印雷达中断管脚状态
// Serial.println("> radar_scan_it: " + String(digitalRead(TEENSY_RADAR_INT_PIN)));
gRadarSerial.write(temp, sizeof(temp));
gRadarSerial.flush(); // 等待所有数据发送完成
if (gRadarSerial.available()) {
gRadarSerial.readBytes((char*)readBuffer, sizeof(readBuffer));
if (!radar_resp_frame_verify((char*)readBuffer, sizeof(readBuffer))) {
return false;
}
if (!radar_parse_scan_result((char*)readBuffer, sizeof(readBuffer), &gRadarDetInfo)) {
return false;
}
Serial.println("> range_val: " + String(gRadarDetInfo.range_val));
// Serial.println("--------------------------");
// Serial.println("radar_scan_result");
// Serial.println("is_detected: " + String(gRadarDetInfo.is_deteced));
// Serial.println("det_result: " + String(gRadarDetInfo.det_result));
// Serial.println("angle_val: " + String(gRadarDetInfo.angle_val));
// Serial.println("velo_val: " + String(gRadarDetInfo.velo_val));
// Serial.println("rb_conf: " + String(gRadarDetInfo.rb_conf));
// Serial.println("angle_conf: " + String(gRadarDetInfo.angle_conf));
// Serial.println("frame_idx: " + String(gRadarDetInfo.frame_idx));
// Serial.println("--------------------------");
return true;
}
return false;
}
校验雷达检测结果字节流
函数 radar_resp_frame_verify() 把读取的字节流进行校验,函数实现如下:
/**
* @brief 回复帧校验,比较帧头和计算校验和
*
* @param buf
* @param bufLen
* @return
*/
static bool radar_resp_frame_verify(char* buf, int bufLen)
{
uint16_t checkSum = 0, checkSumCalc = 0;
if ((!buf) || (bufLen <= 0)) {
return false;
}
// Dump frame first
// buffer_dump(buf, bufLen);
// [1] Check frame head
if (buf[0] != RADAR_RESP_FRAME_HEADER) {
Serial.println("Frame header error!");
return false;
}
// [2] Check frame
checkSum = (uint16_t)((buf[bufLen - 1] << 8) | buf[bufLen - 2]);
// 第一个字节到倒数第三个字节的累加和
for (int i = 0; i < bufLen - 2; ++i) {
checkSumCalc += buf[i];
}
if (checkSum != checkSumCalc) {
Serial.println("CheckSum Error: " + String(checkSumCalc) + " != " + String(checkSum));
return false;
}
// Serial.println("CheckSum OK: " + String(checkSum));
return true;
}
抽取雷达检测结果
函数 radar_parse_scan_result() 把字节流转换为结构体,从中抽取雷达检测结果,包含检测结果、距离、角度、置信度等信息。
static bool radar_parse_scan_result(char* buf, int bufLen, radar_det_info_t* detInfo)
{
if (!buf || bufLen < RADAR_RESP_FRAME_DET_INFO_SIZE) {
return false;
}
if (!detInfo) {
return false;
}
// 手动从 buf 字节转换到 detInfo 结构体,注意字节序,小端模式
detInfo->is_deteced = buf[3];
detInfo->det_result = buf[4];
detInfo->range_val = (uint16_t)((buf[6] << 8) | buf[5]);
detInfo->angle_val = (int16_t)((buf[8] << 8) | buf[7]);
detInfo->velo_val = (int16_t)((buf[10] << 8) | buf[9]);
detInfo->rb_conf = buf[17];
detInfo->angle_conf = buf[18];
detInfo->frame_idx = (uint32_t)((buf[22] << 24) | (buf[21] << 16) | (buf[20] << 8) | buf[19]);
return true;
}
距离分等级播报语音
在 loop() 函数中获取雷达检测结果,然后根据距离等级分别播报不同的语音。
void loop()
{
// bsp_led_toggle();
radar_scan();
// 播放语音结果
curTimeMs = millis();
if (curTimeMs - oldTimeMs > 10) {
oldTimeMs = curTimeMs;
if (gRadarDetInfo.is_deteced && (gRadarDetInfo.range_val > 0)) {
// 根据距离划分等级
uint16_t rangeCM = gRadarDetInfo.range_val / 10;
if (rangeCM > DIST_LVL_INFO) {
bsp_led_off();
return;
}
if ((rangeCM <= DIST_LVL_INFO) && (rangeCM > DIST_LVL_VERBOSE)) {
bsp_led_blink();
ss.speak(String(DIST_MSG_INFO));
return;
}
if ((rangeCM <= DIST_LVL_VERBOSE) && (rangeCM > DIST_LVL_CRITIAL)) {
bsp_led_blink();
ss.speak(String(DIST_MSG_WARN));
return;
}
if (rangeCM <= DIST_LVL_CRITIAL) {
bsp_led_blink();
ss.speak(String(DIST_MSG_CRITIAL));
return;
}
}
}
}
距离等级和播报的语音见下面的宏定义:
#define DIST_LVL_INFO 400 // 400cm,注意行人
#define DIST_LVL_VERBOSE 300 // 300cm, 有人靠近
#define DIST_LVL_CRITIAL 200 // 200cm,危险停车
#define DIST_MSG_INFO "注意有人"
#define DIST_MSG_WARN "有人靠近"
#define DIST_MSG_CRITIAL "危险停车"
功能展示图及说明
主控放在面包板上,雷达模块和语音合成模块通过杜邦线连接到主控上。
下图是雷达检测人体距离的折线图,距离为0值是人走出了雷达检测人体范围(角度、雷达探测距离限制),人体距离在 2000 毫米以内语音模块播报“危险停车”;人体距离大于2000毫米且小于 3000毫米,语音模块播报"有人靠近";人体距离大于3000毫米且小于4000毫米,语音模块播报 "注意有人";人体距离大于 4000 毫米或者没有检测到人体,没有播报信息。
语音合成模块 DFR0760,有多种参数可配,如音量、语速、音色(男声、女声、唐老鸭等)、语调等。尝试调节音量1~9个档位,都效果明显。但是音色,分别尝试几个类型,都没有明显的区别,只有一个女声类型,没有成功播放唐老鸭的声音,比较遗憾。当前女声,音调自然,拟人声效果不错。
设计中遇到的难题和解决方法
第一个问题:第一次使用语音合成模块,从网站下载了推荐的代码,https://gitee.com/DFRobot/DFRobot_SpeechSynthesis 但是在初始化过程失败,追查代码、增加打印信息,都没发现问题,后来多方查找,在官方的 gitee 仓库发现了一个新的库文件(官网没有列出来),使用这个版本 https://gitee.com/dfrobot/DFRobot_SpeechSynthesis_V2 的代码就可以成功通过I2C与语音模块通信。
第二个问题:语音合成模块供电电压范围是 3.3V~5V。我先接在主控的 3.3V 上,语音模块没有播放语音。先是怀疑是 I2C 管脚没有连接正确,几经尝试,分别试了 I2C 和 UART 两种通信方式,都没能成功播放语音。甚至在库中加调试语句都没有解决。后来把接到主控的5V管脚上就好了,就能正常播放语音。
对本次竞赛的心得体会
感谢贸泽电子和电子森林联合举办的 M-Design 活动,让我学习了更多传感器和模块的用法,有的时候不是软件的问题,需要从硬件上找原因。