一整体功能概述
这套程序模拟了一个真实的无线传感器网络(如抄表系统)的通信流程:
主机(Master / 采集端): 负责发起通信。它首先发送“暗号”(kunkun)寻找从机,收到从机的确认(zhiyin)后,采集自身的 ADC 电压数据,将其转换并打包发送给从机。最后,它会等待从机将收到的数据原样发回,以验证通信链路的绝对可靠性。
从机(Slave / 接收网关): 一直处于监听状态。收到“暗号”后立刻回复确认。随后接收主机发来的 ADC 数据,并触发 LED 闪烁,最后将该数据原样“回声(Echo)”给主机。
int32_t main(void){// 1. 硬件初始化System_Init_Config();// 2. 射频初始化if (rf_init() != OK){while(1); // 失败报警}rf_set_default_para();// 3. 初始状态设置 (编译时决定)#ifdef SLAVE_MODE// [从机] 上电必须开启接收,否则听不到第一句。接收超时窗口(Timeout)/*情况 A(提前下班): 如果第 2 秒钟,主机发来了 kunkun(快递到了),单片机会立刻抓起数据去处理,这个 15 秒的倒计时会瞬间作废,根本不需要等满 15 秒。情况 B(超时放弃): 如果苦苦等了整整 15 秒,主机都没发信号(可能主机没开机),从机就不会再傻等下去了。它会触发一个“接收超时(RXTIMEOUT)”的标志位,然后重新安排下一次的监听。*/rf_enter_single_timeout_rx(15000);/*因为在无线通信中,从机是“被动方”。它刚上电的时候,完全不知道主机什么时候会开口说话(主机可能 2 秒发一次,也可能 10 分钟发一次)。所以,从机一开机,必须立刻把“耳朵”张开,并且给一个足够长的时间(15 秒),确保在这个宽裕的时间段内,至少能“逮住”主机的一次呼叫。一旦逮住一次,双方就建立起联络了。*/#endif// [主机] 不需要预先接收,它会主动发送while (1){// === 1. 优先处理中断 (公共逻辑) ===if (g_bIrqTriggered){g_bIrqTriggered = 0;rf_irq_process(); // SPI 读取状态}// === 2. 业务逻辑 (编译时二选一) ===#ifdef MASTER_MODEOnMaster();#endif#ifdef SLAVE_MODEOnSlave();#endif}}
#ifndef __FUN_H#define __FUN_H// ==========================================// 模式配置开关// ==========================================// 方案 A:如果是主机,保留这行,注释掉 SLAVE_MODE#define MASTER_MODE// 方案 B:如果是从机,保留这行,注释掉 MASTER_MODE//#define SLAVE_MODE// --- 安全检查 (防止你忘了选,或者两个都选了) ---#if defined(MASTER_MODE) && defined(SLAVE_MODE)#error "错误:不能同时定义 MASTER_MODE 和 SLAVE_MODE!请注释掉一个。"#elif !defined(MASTER_MODE) && !defined(SLAVE_MODE)#error "错误:你没有定义任何模式!请在 fun.h 中选择 MASTER_MODE 或 SLAVE_MODE。"#endif#include "main.h"// 函数声明// 这样写的好处是:无论什么模式,main.c 都能看到这两个函数声明// 避免编译报错,虽然我们在 main 里只会调用其中一个void OnSlave(void);void LedToggle(void);void OnMaster(void);void Pulse_PA8(void);uint16_t Get_ADC_Value(void);uint16_t Get_ADC_Average(uint8_t times);#endif
#include "fun.h"#include "init.h"#include "delay.h"#include "radio.h"#include "buffer.h"// ============================================// 1. 数据定义 (硬编码 kunkun 和 zhiyin)// ============================================// 为了防止 buffer.c/h 未定义,我们在局部定义一份static uint8_t Kunkun[] = {'k', 'u', 'n', 'k', 'u', 'n'};static uint8_t Zhiyin[] = {'z', 'h', 'i', 'y', 'i', 'n'};#define DATA_LEN 6// --- 补回丢失的全局变量 ---double Rssi_dBm; // 用于存储最近一次接收信号的强度 (单位: dBm)double Snr_value; // 用于存储最近一次接收信号的信噪比 (单位: dB)// 外部变量声明extern struct RxDoneMsg RxDoneParams;extern uint8_t rx_test_buf[];// ============================================// 2. 公共函数// ============================================void LedToggle(void){GPIO_WritePin(LED_PORT, LED_PIN, GPIO_Pin_RESET); // 亮// Delay_Ms(200);GPIO_WritePin(LED_PORT, LED_PIN, GPIO_Pin_SET); // 灭// Delay_Ms(200);}// ============================================// 3. 主机模式代码 (MASTER_MODE)// ============================================#ifdef MASTER_MODEstatic uint32_t tx_time = 0;extern volatile uint8_t g_bIrqTriggered; // 引用 main.c 定义的变量void OnMaster(void){// ==========================================// 阶段一:握手 (Handshake)// 发送 "kunkun" 确认从机在线// ==========================================// 1. 发送 "kunkun"if (rf_single_tx_data(Kunkun, DATA_LEN, &tx_time) != OK){return;}// 2. 等待发送完成 (带防死锁的中断处理)while (rf_get_transmit_flag() == RADIO_FLAG_IDLE){if (g_bIrqTriggered){g_bIrqTriggered = 0;rf_irq_process();}}rf_set_transmit_flag(RADIO_FLAG_IDLE); // 清除发送标志// 3. 进入接收模式,等待从机回复 "zhiyin"rf_enter_single_timeout_rx(200);// 4. 等待接收完成 (带防死锁)while (rf_get_recv_flag() == RADIO_FLAG_IDLE){if (g_bIrqTriggered){g_bIrqTriggered = 0;rf_irq_process();}}// ==========================================// 阶段二:判断握手结果 & 发送 ADC 数据// ==========================================// 检查是否收到了数据 (RXDONE)if (rf_get_recv_flag() == RADIO_FLAG_RXDONE){rf_set_recv_flag(RADIO_FLAG_IDLE); // 清除接收标志// 5. 验证内容是否为 "zhiyin"if (RxDoneParams.Size == DATA_LEN &&RxDoneParams.Payload[0] == 'z' &&RxDoneParams.Payload[1] == 'h'){// --- 握手成功!开始处理数据转换 ---// A. 读取原始 ADC 码值 (0~4095)uint16_t adc_raw = Get_ADC_Average(8);// B. 【核心修改】转换为十进制电压值 (mV)// 公式:(adc_raw * 3300) / 4096// 使用 (uint32_t) 强制转换防止乘法溢出uint16_t voltage_mv = (uint16_t)((uint32_t)adc_raw * 3300 / 4096);// C. 打包数据 (将16位电压值拆分)uint8_t tx_buffer[DATA_LEN];tx_buffer[0] = (uint8_t)(voltage_mv >> 8); // 电压高8位tx_buffer[1] = (uint8_t)(voltage_mv); // 电压低8位// 填充剩余字节for(int k=2; k<DATA_LEN; k++){tx_buffer[k] = 0;}// D. 发送转换后的电压数据给从机Delay_Ms(5);g_bIrqTriggered = 0;// 启动发送rf_single_tx_data(tx_buffer, DATA_LEN, &tx_time);// E. 等待发送完成uint32_t timeout_safety = 0;while (rf_get_transmit_flag() == RADIO_FLAG_IDLE && timeout_safety < 10000000){if (g_bIrqTriggered){g_bIrqTriggered = 0;rf_irq_process();}timeout_safety++;}rf_set_transmit_flag(RADIO_FLAG_IDLE);// ==========================================// 阶段三:等待回声校验 (Echo Check)// ==========================================// F. 进入接收,等待从机把刚才的电压值发回来rf_enter_single_timeout_rx(200);// 等待接收完成while (rf_get_recv_flag() == RADIO_FLAG_IDLE){if (g_bIrqTriggered){g_bIrqTriggered = 0;rf_irq_process();}}// 检查是否收到回传数据if (rf_get_recv_flag() == RADIO_FLAG_RXDONE){rf_set_recv_flag(RADIO_FLAG_IDLE);// 1. 还原接收到的电压数据 (高8位<<8 | 低8位)uint16_t echo_val = ((uint16_t)RxDoneParams.Payload[0] << 8) | RxDoneParams.Payload[1];// 2. 验证:发出的电压值 == 收回的电压值?// 注意:这里必须对比转换后的 voltage_mv,逻辑才闭环if (echo_val == voltage_mv){// 通信链路完美闭环!LedToggle();}}}}else{// 握手失败 (接收超时或 CRC 错误)rf_set_recv_flag(RADIO_FLAG_IDLE);}// ==========================================// 阶段四:周期延时// ==========================================Delay_Ms(2000); // 2秒发送一次}// 占位函数void OnSlave(void) {}#endif // 结束 MASTER_MODE// ============================================// 4. 从机模式代码 (SLAVE_MODE)// ============================================#ifdef SLAVE_MODEextern volatile uint8_t g_bIrqTriggered;extern uint8_t *pData;extern uint16_t slave_recv_val;uint8_t slave_tx_buffer[DATA_LEN];static uint32_t sl_tx_time = 0;void OnSlave(void){// --- 情况1:收到数据 (RXDONE) ---if (rf_get_recv_flag() == RADIO_FLAG_RXDONE){// 1. 读取参数 & 清除标志Rssi_dBm = RxDoneParams.Rssi;Snr_value = RxDoneParams.Snr;rf_set_recv_flag(RADIO_FLAG_IDLE);pData = RxDoneParams.Payload;uint8_t len = RxDoneParams.Size;// 2. 逻辑分支判断if (len == DATA_LEN && pData[0] == 'k' && pData[1] == 'u' && pData[2] == 'n'){// === 分支 A:握手 (kunkun -> zhiyin) ===LedToggle(); // 提示收到握手Delay_Ms(5); // 避让延时,给主机切换接收留时间g_bIrqTriggered = 0;rf_single_tx_data(Zhiyin, DATA_LEN, &sl_tx_time);}else{// === 分支 B:数据回传 (Echo) ===// [调试看这里]:此时 slave_recv_val 存储的是十进制电压(mV)// 你在调试窗口看这个变量,取消 Hex 显示,就能看到 3200 左右的数字slave_recv_val = ((uint16_t)pData[0] << 8) | pData[1];// 1. 准备发送缓冲区 (深拷贝)for(int i = 0; i < DATA_LEN; i++){slave_tx_buffer[i] = pData[i];}LedToggle(); // 提示收到数据Delay_Ms(5); // 避让延时g_bIrqTriggered = 0;// 原样发回给主机进行校验rf_single_tx_data(slave_tx_buffer, DATA_LEN, &sl_tx_time);}// ==========================================// 3. 修正后的发送等待逻辑// ==========================================uint32_t timeout_safety = 0;// 只要还没触发中断,就一直在这里等,同时处理可能的 SPI 任务while (g_bIrqTriggered == 0 && timeout_safety < 1000000){// 注意:PAN3031 的中断处理通常在主循环或此处调用// 如果 g_bIrqTriggered 在 EXTI 中断里变1,循环会退出timeout_safety++;}// 退出循环后,如果是正常中断触发,处理它if (g_bIrqTriggered){g_bIrqTriggered = 0;rf_irq_process(); // 更新状态机,将 TX 状态转为 IDLE}rf_set_transmit_flag(RADIO_FLAG_IDLE);// 4. 重新进入接收模式// 建议保持 15 秒或更长,确保从机始终“醒着”等主机点名rf_enter_single_timeout_rx(15000);}// --- 情况2:接收超时或错误 ---if ((rf_get_recv_flag() == RADIO_FLAG_RXTIMEOUT) || (rf_get_recv_flag() == RADIO_FLAG_RXERR)){rf_set_recv_flag(RADIO_FLAG_IDLE);rf_enter_single_timeout_rx(15000);}}// 占位函数void OnMaster(void) {}#endif // 结束 SLAVE_MODE// 获取一次 ADC 的原始值 (0~4095)uint16_t Get_ADC_Value(void){uint16_t value;// 1. 启动转换ADC_SoftwareStartConvCmd(ENABLE);// 2. 等待转换完成 (注意:这里要用死循环等待标志位变高)// 原来的代码 while(...) {...} 如果标志位没变高会直接跳过,是错的while(ADC_GetITStatus(ADC_IT_EOC) == RESET);// 3. 清除标志位ADC_ClearITPendingBit(ADC_IT_EOC);// 4. 读取数据value = ADC_GetConversionValue();return value;}// 获取滤波后的 ADC 值 (平均值滤波)uint16_t Get_ADC_Average(uint8_t times){uint32_t sum = 0;uint8_t i;for(i = 0; i < times; i++){sum += Get_ADC_Value(); // 调用上面的基础函数// 稍微延时一点点,防止采样过快读到同样的干扰// 如果没有 Delay 函数,可以用空循环代替}return (uint16_t)(sum / times);}
二关键部分代码解析与注释
时间线:
CW32 单片机被触发物理中断,进入 GPIOA_IRQHandler。
中断函数火速把 g_bIrqTriggered 和 pan3031_irq_trigged_flag 这两个软件变量变成 1。
主程序 while 循环看到 g_bIrqTriggered == 1,立刻跳出等待。
主程序调用 rf_irq_process(),它看到 pan3031_irq_trigged_flag == true,准许执行。
函数通过 SPI 查询 PAN3031 具体原因,最终将结果翻译成 RADIO_FLAG_RXDONE 等业务标志位。
1.ADC 数据采集与处理(主机特有)
这是示例功能——获取的物理数据。
// ==========================================// 阶段二:判断握手结果 & 发送 ADC 数据// ==========================================// 【关键功能 1:数据采集与转换】// A. 读取原始 ADC 码值 (0~4095)uint16_t adc_raw = Get_ADC_Average(8); // 多次采样求平均,起到软件滤波作用,抗干扰// B. 【核心修改】转换为十进制电压值 (mV)// 公式:(adc_raw * 3300) / 4096 (基于 3.3V 参考电压)// 使用 (uint32_t) 强制转换防止 adc_raw * 3300 发生 16 位溢出uint16_t voltage_mv = (uint16_t)((uint32_t)adc_raw * 3300 / 4096);// C. 打包数据 (将16位电压值拆分成两个 8 位字节)uint8_t tx_buffer[DATA_LEN];tx_buffer[0] = (uint8_t)(voltage_mv >> 8); // 取电压高 8 位放入数组第 0 个位置tx_buffer[1] = (uint8_t)(voltage_mv); // 取电压低 8 位放入数组第 1 个位置
回声校验机制 (Echo Check)
这是保证通信可靠性的重要手段。
// ==========================================// 阶段三:等待回声校验 (Echo Check) (主机端)// ==========================================// F. 进入接收,等待从机把刚才的电压值原样发回来rf_enter_single_timeout_rx(200);// ... (等待接收代码略) ...if (rf_get_recv_flag() == RADIO_FLAG_RXDONE){rf_set_recv_flag(RADIO_FLAG_IDLE);// 【关键功能 2:数据重组与校验】// 1. 将收到的两个 8 位字节重新拼装成 16 位的电压数据uint16_t echo_val = ((uint16_t)RxDoneParams.Payload[0] << 8) | RxDoneParams.Payload[1];// 2. 验证:发出的电压值 == 收回的电压值?if (echo_val == voltage_mv){// 通信链路完美闭环!说明数据在空中没有发生误码LedToggle();}}
从机的多路分支处理
从机需要根据收到的数据内容,决定是进行“握手回应”还是“数据回传”。
// ============================================// 4. 从机模式代码 (SLAVE_MODE)// ============================================// 2. 逻辑分支判断// 【关键功能 3:协议解析】// 检查收到的数据长度是否为 6,且前三个字符是否为 'k' 'u' 'n'if (len == DATA_LEN && pData[0] == 'k' && pData[1] == 'u' && pData[2] == 'n'){// === 分支 A:握手协议 ===LedToggle();Delay_Ms(5); // 必须加!给主机从“发送态”切换到“接收态”留出时间,否则主机听不到回信g_bIrqTriggered = 0;rf_single_tx_data(Zhiyin, DATA_LEN, &sl_tx_time); // 回复确认暗号}else{// === 分支 B:数据回传 (Echo) ===// 解析主机发来的 16 位电压数据,主要用于在调试窗口查看slave_recv_val = ((uint16_t)pData[0] << 8) | pData[1];// 将收到的数据深拷贝到发送缓冲区for(int i = 0; i < DATA_LEN; i++){slave_tx_buffer[i] = pData[i];}LedToggle();Delay_Ms(5);g_bIrqTriggered = 0;// 原样发回给主机进行最终校验rf_single_tx_data(slave_tx_buffer, DATA_LEN, &sl_tx_time);}
健壮的中断等待机制
摒弃了官方 SDK 的“死等”,加入了基于全局变量 g_bIrqTriggered 的等待逻辑。
// 【关键功能 4:防死锁等待机制】uint32_t timeout_safety = 0;// 只要没触发中断,且没有超时,就在这里等while (g_bIrqTriggered == 0 && timeout_safety < 1000000){timeout_safety++; // 软件超时计数器,防止硬件死机导致程序永久卡死}// 退出循环后,如果是正常中断触发,而不是超时退出的,则处理中断if (g_bIrqTriggered){g_bIrqTriggered = 0;rf_irq_process(); // 底层处理函数,将状态标志位更新为 TXDONE 或 RXDONE}
/*** @brief This funcation handles GPIOA*/extern volatile uint8_t g_bIrqTriggered;void GPIOA_IRQHandler(void){// 检查是否是 PA1 引脚触发if (CW_GPIOA->ISR_f.PIN1){// 1. 必须先清除中断标志!GPIOA_INTFLAG_CLR(bv1);// 2. 【核心修改】通知主循环“有事发生了”!!!// 只有加上这一句,OnMaster 里的 if (g_bIrqTriggered) 才会成立g_bIrqTriggered = 1;// 3. 调用原来的业务逻辑 (保留即可)PAN3031_irq_handler();}}
扫码加入QQ群3群| 610403240
165