一、整体功能概述这套程序模拟了一个真实的无线传感器网络如抄表系统的通信流程主机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_MODE OnMaster(); #endif #ifdef SLAVE_MODE OnSlave(); #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_MODE static 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 k2; kDATA_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_MODE extern 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); }二、关键部分代码解析与注释时间线PAN3031 芯片收发完成拉高物理引脚。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 个位置2、回声校验机制 (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(); } }3、从机的多路分支处理从机需要根据收到的数据内容决定是进行“握手回应”还是“数据回传”。// // 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); }4、健壮的中断等待机制摒弃了官方 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(); } }