若该文为原创文章,转载请注明原文出处。
1. 引言
R60AFD1 是一款60GHz毫米波雷达传感器模块,常用于人体存在检测、跌倒报警及运动状态识别等场景。该模块采用UART串口通信,支持两种工作模式:主动上报模式 与查询-应答模式。本文针对查询-应答模式,提供一套完整的STM32F103C8T6驱动代码,使用标准外设库开发,通过USART2(PA2 TX,PA3 RX)实现与模块的交互,可获取人体存在、活跃度及跌倒状态。
2. 硬件连接
| STM32F103C8T6 | R60AFD1模块 |
|---|---|
| 3.3V / 5V | VCC |
| GND | GND |
| PA2 (USART2_TX) | RX |
| PA3 (USART2_RX) | TX |
注意事项:
-
模块典型供电电压为3.3V或5V,请以手册为准。
-
确保共地,且电源能提供足够电流(峰值约93mA)。
-
模块建议顶装(天花板),安装高度2.2~3m,探测半径约2m(跌倒有效范围)。
3. 通信协议(查询-应答)
本驱动使用的命令帧格式固定为10字节,结构如下:
| Byte0 | Byte1 | Byte2 | Byte3 | Byte4 | Byte5 | Byte6 | Byte7 | Byte8 | Byte9 |
|---|---|---|---|---|---|---|---|---|---|
| 0x53 | 0x59 | CMD1 | CMD2 | 0x00 | 0x01 | 0x0F | SUM | 0x54 | 0x43 |
-
帧头 :
53 59 -
命令字 :
CMD1、CMD2决定查询类型 -
固定字段 :
00 01 0F -
校验和:前7个字节(Byte0~Byte6)的累加和
-
帧尾 :
54 43
3.1 查询命令定义
| 功能 | CMD1 | CMD2 | 命令帧(十六进制) |
|---|---|---|---|
| 人体存在查询 | 0x80 | 0x81 | 53 59 80 81 00 01 0F BD 54 43 |
| 人体活跃查询 | 0x80 | 0x82 | 53 59 80 82 00 01 0F BE 54 43 |
| 跌倒状态查询 | 0x83 | 0x81 | 53 59 83 81 00 01 0F C0 54 43 |
3.2 应答帧格式
模块收到查询命令后返回相同结构的数据帧,其中 Byte6 为有效数据:
| 查询类型 | 应答数据 Byte6 | 含义 |
|---|---|---|
| 人体存在(0x80,0x81) | 0x00 | 无人 |
| 0x01 | 有人 | |
| 人体活跃(0x80,0x82) | 0x01 | 静止 |
| 0x02 | 活跃 | |
| 跌倒状态(0x83,0x81) | 0x00 | 未跌倒 |
| 0x01 | 跌倒 |
应答示例:
53 59 80 81 00 01 01 AF 54 43 表示检测到有人。
4. 驱动代码实现
4.1 文件结构
-
R60AFD1.h:函数声明与状态宏定义 -
R60AFD1.c:串口初始化、命令发送、接收解析与状态管理 -
中断服务函数:添加到
stm32f10x_it.c
4.2 完整代码
R60AFD1.h
#ifndef __R60AFD1_H
#define __R60AFD1_H
#include "stm32f10x.h"
#include <stdbool.h>
// 状态位掩码
#define R60_HUM_EXT 0x01 // 人体存在
#define R60_HUM_ACT 0x02 // 人体活跃
#define R60_HUM_FAL 0x04 // 跌倒状态
void R60AFD1_Init(uint32_t baudrate); // 初始化串口及模块配置
void R60AFD1_QueryHumanExist(void); // 查询人体存在
void R60AFD1_QueryHumanActive(void); // 查询活跃度
void R60AFD1_QueryFall(void); // 查询跌倒状态
uint8_t R60AFD1_GetState(void); // 获取状态掩码
bool R60AFD1_IsPersonExist(void); // 是否有人
bool R60AFD1_IsActive(void); // 是否活跃(true:活跃 false:静止)
bool R60AFD1_IsFallDetected(void); // 是否跌倒
void R60AFD1_ClearFallFlag(void); // 清除跌倒标志
void R60AFD1_Process(void); // 后台处理(超时复位)
void R60AFD1_IRQHandler(void); // 串口中断处理函数
#endif
R60AFD1.c
#include "R60AFD1.h"
// 串口接收缓冲区(底层驱动)
#define RX_BUF_SIZE 32
uint8_t rxBuffer[RX_BUF_SIZE];
uint8_t rxIndex = 0;
volatile uint8_t rxComplete = 0; // 1: 收到完整一帧
volatile uint8_t rxTimeout = 0; // 接收超时标志
// 雷达状态
uint8_t radarState = 0; // 使用 R60_HUM_EXT/ACT/FAL 位
uint8_t lastFallState = 0;
// 查询命令帧模板
const uint8_t CMD_FRAME_TEMPLATE[10] = {0x53,0x59,0xFF,0xFF,0x00,0x01,0x0F,0xFF,0x54,0x43};
// 计算校验和(前7个字节累加)
static uint8_t calcChecksum(const uint8_t *data) {
uint8_t sum = 0;
for (uint8_t i = 0; i < 7; i++) {
sum += data[i];
}
return sum;
}
// 发送10字节命令帧
static void sendCommand(uint8_t cmd1, uint8_t cmd2) {
uint8_t frame[10];
memcpy(frame, CMD_FRAME_TEMPLATE, 10);
frame[2] = cmd1;
frame[3] = cmd2;
frame[7] = calcChecksum(frame);
// 通过USART2发送
for (uint8_t i = 0; i < 10; i++) {
while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
USART_SendData(USART2, frame[i]);
}
}
// 查询API
void R60AFD1_QueryHumanExist(void) {
sendCommand(0x80, 0x81);
}
void R60AFD1_QueryHumanActive(void) {
sendCommand(0x80, 0x82);
}
void R60AFD1_QueryFall(void) {
sendCommand(0x83, 0x81);
}
// 解析接收到的帧(在串口中断中调用)
static void parseFrame(void) {
if (rxComplete == 0) return;
rxComplete = 0;
// 长度、帧头、帧尾检查
if (rxIndex != 10) goto clear;
if (rxBuffer[0] != 0x53 || rxBuffer[1] != 0x59 ||
rxBuffer[8] != 0x54 || rxBuffer[9] != 0x43) goto clear;
// 校验和验证
uint8_t calcSum = 0;
for (uint8_t i = 0; i < 7; i++) calcSum += rxBuffer[i];
if (calcSum != rxBuffer[7]) goto clear;
// 根据命令字解析数据
uint8_t cmd1 = rxBuffer[2];
uint8_t cmd2 = rxBuffer[3];
uint8_t data = rxBuffer[6];
if (cmd1 == 0x83 && cmd2 == 0x81) { // 跌倒状态应答
if (data == 0x01) {
radarState |= R60_HUM_FAL;
} else {
radarState &= ~R60_HUM_FAL;
}
} else if (cmd1 == 0x80 && cmd2 == 0x81) { // 人体存在应答
if (data == 0x01) {
radarState |= R60_HUM_EXT;
} else {
radarState &= ~R60_HUM_EXT;
}
} else if (cmd1 == 0x80 && cmd2 == 0x82) { // 人体活跃应答
if (data == 0x02) {
radarState |= R60_HUM_ACT;
} else {
radarState &= ~R60_HUM_ACT;
}
}
clear:
// 清空缓冲区,准备接收下一帧
rxIndex = 0;
rxTimeout = 0;
}
// 串口中断中接收单字节(在 stm32f10x_it.c 中调用)
void R60AFD1_IRQHandler(void) {
if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) {
uint8_t data = USART_ReceiveData(USART2);
if (rxIndex < RX_BUF_SIZE) {
rxBuffer[rxIndex++] = data;
}
// 简单超时机制:每次收到数据重置定时器,在Process中处理超时
rxTimeout = 10; // 约10ms超时(需配合主循环周期)
// 如果收到10个字节,认为一帧完成
if (rxIndex >= 10) {
rxComplete = 1;
parseFrame(); // 立即解析
}
}
}
// 后台处理(超时复位)
void R60AFD1_Process(void) {
if (rxTimeout > 0) {
rxTimeout--;
if (rxTimeout == 0 && rxIndex > 0) {
// 接收超时,清空缓冲区
rxIndex = 0;
}
}
}
// 获取状态
uint8_t R60AFD1_GetState(void) {
return radarState;
}
bool R60AFD1_IsPersonExist(void) {
return (radarState & R60_HUM_EXT) != 0;
}
bool R60AFD1_IsActive(void) {
return (radarState & R60_HUM_ACT) != 0;
}
bool R60AFD1_IsFallDetected(void) {
return (radarState & R60_HUM_FAL) != 0;
}
void R60AFD1_ClearFallFlag(void) {
radarState &= ~R60_HUM_FAL;
}
// 初始化USART2 (PA2 TX, PA3 RX)
void R60AFD1_Init(uint32_t baudrate) {
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef usartInitStruct;
NVIC_InitTypeDef nvicInitStruct;
/*配置USART2和GPIO时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
/*GPIO配置*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
usartInitStruct.USART_BaudRate = 115200;
usartInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件控流
usartInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //接收和发送
usartInitStruct.USART_Parity = USART_Parity_No; //无校验
usartInitStruct.USART_StopBits = USART_StopBits_1; //1位停止位
usartInitStruct.USART_WordLength = USART_WordLength_8b; //8位数据位
USART_Init(USART2,&usartInitStruct);
USART_Cmd(USART2,ENABLE); //使能串口
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); //使能接收中断
nvicInitStruct.NVIC_IRQChannel = USART2_IRQn;
nvicInitStruct.NVIC_IRQChannelCmd = ENABLE;
nvicInitStruct.NVIC_IRQChannelPreemptionPriority = 0;
nvicInitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&nvicInitStruct);
// 清空状态
radarState = 0;
rxIndex = 0;
rxComplete = 0;
rxTimeout = 0;
// 可选:发送初始化配置命令(根据原代码中的 sti_R60aTxInit)
// 发送配置帧:53 59 80 00 00 01 00 2D 54 43
uint8_t initFrame[10] = {0x53,0x59,0x80,0x00,0x00,0x01,0x00,0x2D,0x54,0x43};
for (uint8_t i = 0; i < 10; i++) {
while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
USART_SendData(USART2, initFrame[i]);
}
}
void USART2_IRQHandler(void) {
R60AFD1_IRQHandler();
}
5. 使用方法与示例
5.1 主函数示例
#include "delay.h"
#include "sys.h"
#include "OLED.h"
#include "usart.h"
#include "ESP8266.h"
#include "Timer.h"
#include "led.h"
//网络协议层
#include "onenet.h"
//C库
#include <string.h>
#include "stdio.h"
#include "math.h"
#include "R60AFD1.h"
char Timer_IT=0;
u8 LIGHT = 3;
u16 Lsens_val = 0;
u8 Pir_Status = 0;
u16 Pir_Time_Cnt = 0;
// 定时查询(使用SysTick或简单延时)
uint32_t lastQuery = 0;
uint8_t queryStep = 0;
int main(void)
{
unsigned char *dataPtr = NULL;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断控制器分组设置
delay_init();
Usart1_Init(115200);
// Usart3_Init(115200);
R60AFD1_Init(115200); // 根据雷达模块实际波特率设置(常见115200)
OLED_Init();
OLED_Clear();
// ESP8266_Init();
Timer_Init();
Timer_IT = 5;
while(1)
{
// if(Timer_IT >= 5)//每隔3秒定时上传数据
// {
// Timer_IT=0;
// OneNet_SendData(); //发送数据到云平台
// ESP8266_Clear(); //情况串口接收区缓存
// }
//
// dataPtr = ESP8266_GetIPD(0);//获取云平台下发的数据
// if(dataPtr != NULL)//如果不为空,解析
// {
// OneNet_RevPro(dataPtr); //平台返回数据检测
// }
// 每100ms轮询一次后台处理
R60AFD1_Process();
// 每500ms依次查询三种状态
switch (queryStep) {
case 0: R60AFD1_QueryHumanExist(); break;
case 1: R60AFD1_QueryHumanActive(); break;
case 2: R60AFD1_QueryFall(); break;
}
queryStep = (queryStep + 1) % 3;
// 应用逻辑
if (R60AFD1_IsPersonExist()) {
// 有人
if (R60AFD1_IsActive()) {
// 活跃
} else {
// 静止
}
if (R60AFD1_IsFallDetected()) {
// 触发跌倒报警
// ... 执行动作
R60AFD1_ClearFallFlag(); // 清除标志,避免重复报警
}
} else {
// 无人
}
delay_ms(5000);
}
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Timer_IT++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
5.2 关键点说明
-
查询周期:建议间隔≥200ms,避免串口拥塞。示例中使用500ms轮询一次,三种状态依次查询。
-
跌倒标志 :跌倒状态是边沿触发,查询到后需调用
R60AFD1_ClearFallFlag()清除,否则下次查询仍会认为跌倒。 -
超时处理 :
R60AFD1_Process()必须在主循环中定期调用,用于清空接收不完整的数据帧。
6. 常见问题与调试
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 串口无数据 | 波特率不匹配 / 硬件接线错误 | 核对模块手册波特率(常见115200),检查共地 |
| 接收帧校验失败 | 协议中校验和算法错误 / 命令帧格式错误 | 确认前7字节累加和是否正确,对比手册范例 |
| 跌倒状态无法清除 | 未调用 ClearFallFlag |
在报警处理代码中调用清除函数 |
| 人体存在状态更新延迟 | 查询频率过低 / 模块响应时间较长 | 适当提高查询频率,但注意模块处理能力 |
| 模块无应答 | 未发送配置帧或模块处于主动上报模式 | 查阅手册,确保模块工作在查询模式 |
7. 总结
本文基于STM32F103C8T6标准外设库,实现了R60AFD1毫米波雷达模块的查询-应答驱动。通过USART2发送固定格式的命令帧,并解析返回数据,可稳定获取人体存在、活跃度及跌倒状态。代码结构清晰,便于移植到其他STM32系列或不同串口。实际应用时请务必参考模块最新数据手册,核对协议细节(如校验和、帧长度等)。若模块支持主动上报模式,也可按类似思路调整解析逻辑。
参考资料
如有侵权,或需要完整代码,请及时联系博主。