一、整體功能概述
這套程序模擬了一個真實的無線傳感器網(wǎng)絡(luò)(如抄表系統(tǒng))的通信流程:
-
主機(Master / 采集端): 負責(zé)發(fā)起通信。它首先發(fā)送“暗號”(
kunkun)尋找從機,收到從機的確認(zhiyin)后,采集自身的 ADC 電壓數(shù)據(jù),將其轉(zhuǎn)換并打包發(fā)送給從機。最后,它會等待從機將收到的數(shù)據(jù)原樣發(fā)回,以驗證通信鏈路的絕對可靠性。 - 從機(Slave / 接收網(wǎng)關(guān)): 一直處于監(jiān)聽狀態(tài)。收到“暗號”后立刻回復(fù)確認。隨后接收主機發(fā)來的 ADC 數(shù)據(jù),并觸發(fā) LED 閃爍,最后將該數(shù)據(jù)原樣“回聲(Echo)”給主機。
int32_t main(void)
{
// 1. 硬件初始化
System_Init_Config();
// 2. 射頻初始化
if (rf_init() != OK)
{
while(1); // 失敗報警
}
rf_set_default_para();
// 3. 初始狀態(tài)設(shè)置 (編譯時決定)
#ifdef SLAVE_MODE
// [從機] 上電必須開啟接收,否則聽不到第一句。接收超時窗口(Timeout)
/*
情況 A(提前下班): 如果第 2 秒鐘,主機發(fā)來了 kunkun(快遞到了),
單片機會立刻抓起數(shù)據(jù)去處理,這個 15 秒的倒計時會瞬間作廢,根本不需要等滿 15 秒。
情況 B(超時放棄): 如果苦苦等了整整 15 秒,主機都沒發(fā)信號(可能主機沒開機),
從機就不會再傻等下去了。它會觸發(fā)一個“接收超時(RXTIMEOUT)”的標志位,然后重新安排下一次的監(jiān)聽。
*/
rf_enter_single_timeout_rx(15000);
/*
因為在無線通信中,從機是“被動方”。它剛上電的時候,
完全不知道主機什么時候會開口說話(主機可能 2 秒發(fā)一次,也可能 10 分鐘發(fā)一次)。
所以,從機一開機,必須立刻把“耳朵”張開,并且給一個足夠長的時間(15 秒),
確保在這個寬裕的時間段內(nèi),至少能“逮住”主機的一次呼叫。一旦逮住一次,雙方就建立起聯(lián)絡(luò)了。
*/
#endif
// [主機] 不需要預(yù)先接收,它會主動發(fā)送
while (1)
{
// === 1. 優(yōu)先處理中斷 (公共邏輯) ===
if (g_bIrqTriggered)
{
g_bIrqTriggered = 0;
rf_irq_process(); // SPI 讀取狀態(tài)
}
// === 2. 業(yè)務(wù)邏輯 (編譯時二選一) ===
#ifdef MASTER_MODE
OnMaster();
#endif
#ifdef SLAVE_MODE
OnSlave();
#endif
}
}
#ifndef __FUN_H
#define __FUN_H
// ==========================================
// 模式配置開關(guān)
// ==========================================
// 方案 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"
// 函數(shù)聲明
// 這樣寫的好處是:無論什么模式,main.c 都能看到這兩個函數(shù)聲明
// 避免編譯報錯,雖然我們在 main 里只會調(diào)用其中一個
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. 數(shù)據(jù)定義 (硬編碼 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. 公共函數(shù)
// ============================================
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)
// 發(fā)送 "kunkun" 確認從機在線
// ==========================================
// 1. 發(fā)送 "kunkun"
if (rf_single_tx_data(Kunkun, DATA_LEN, &tx_time) != OK)
{
return;
}
// 2. 等待發(fā)送完成 (帶防死鎖的中斷處理)
while (rf_get_transmit_flag() == RADIO_FLAG_IDLE)
{
if (g_bIrqTriggered)
{
g_bIrqTriggered = 0;
rf_irq_process();
}
}
rf_set_transmit_flag(RADIO_FLAG_IDLE); // 清除發(fā)送標志
// 3. 進入接收模式,等待從機回復(fù) "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();
}
}
// ==========================================
// 階段二:判斷握手結(jié)果 & 發(fā)送 ADC 數(shù)據(jù)
// ==========================================
// 檢查是否收到了數(shù)據(jù) (RXDONE)
if (rf_get_recv_flag() == RADIO_FLAG_RXDONE)
{
rf_set_recv_flag(RADIO_FLAG_IDLE); // 清除接收標志
// 5. 驗證內(nèi)容是否為 "zhiyin"
if (RxDoneParams.Size == DATA_LEN &&
RxDoneParams.Payload[0] == 'z' &&
RxDoneParams.Payload[1] == 'h')
{
// --- 握手成功!開始處理數(shù)據(jù)轉(zhuǎn)換 ---
// A. 讀取原始 ADC 碼值 (0~4095)
uint16_t adc_raw = Get_ADC_Average(8);
// B. 【核心修改】轉(zhuǎn)換為十進制電壓值 (mV)
// 公式:(adc_raw * 3300) / 4096
// 使用 (uint32_t) 強制轉(zhuǎn)換防止乘法溢出
uint16_t voltage_mv = (uint16_t)((uint32_t)adc_raw * 3300 / 4096);
// C. 打包數(shù)據(jù) (將16位電壓值拆分)
uint8_t tx_buffer[DATA_LEN];
tx_buffer[0] = (uint8_t)(voltage_mv >> 8); // 電壓高8位
tx_buffer[1] = (uint8_t)(voltage_mv); // 電壓低8位
// 填充剩余字節(jié)
for(int k=2; k zhiyin) ===
LedToggle(); // 提示收到握手
Delay_Ms(5); // 避讓延時,給主機切換接收留時間
g_bIrqTriggered = 0;
rf_single_tx_data(Zhiyin, DATA_LEN, &sl_tx_time);
}
else
{
// === 分支 B:數(shù)據(jù)回傳 (Echo) ===
// [調(diào)試看這里]:此時 slave_recv_val 存儲的是十進制電壓(mV)
// 你在調(diào)試窗口看這個變量,取消 Hex 顯示,就能看到 3200 左右的數(shù)字
slave_recv_val = ((uint16_t)pData[0] < 8) | pData[1];
// 1. 準備發(fā)送緩沖區(qū) (深拷貝)
for(int i = 0; i < DATA_LEN; i++)
{
slave_tx_buffer[i] = pData[i];
}
LedToggle(); // 提示收到數(shù)據(jù)
Delay_Ms(5); // 避讓延時
g_bIrqTriggered = 0;
// 原樣發(fā)回給主機進行校驗
rf_single_tx_data(slave_tx_buffer, DATA_LEN, &sl_tx_time);
}
// ==========================================
// 3. 修正后的發(fā)送等待邏輯
// ==========================================
uint32_t timeout_safety = 0;
// 只要還沒觸發(fā)中斷,就一直在這里等,同時處理可能的 SPI 任務(wù)
while (g_bIrqTriggered == 0 && timeout_safety < 1000000)
{
// 注意:PAN3031 的中斷處理通常在主循環(huán)或此處調(diào)用
// 如果 g_bIrqTriggered 在 EXTI 中斷里變1,循環(huán)會退出
timeout_safety++;
}
// 退出循環(huán)后,如果是正常中斷觸發(fā),處理它
if (g_bIrqTriggered)
{
g_bIrqTriggered = 0;
rf_irq_process(); // 更新狀態(tài)機,將 TX 狀態(tài)轉(zhuǎn)為 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);
}
}
// 占位函數(shù)
void OnMaster(void) {}
#endif // 結(jié)束 SLAVE_MODE
// 獲取一次 ADC 的原始值 (0~4095)
uint16_t Get_ADC_Value(void)
{
uint16_t value;
// 1. 啟動轉(zhuǎn)換
ADC_SoftwareStartConvCmd(ENABLE);
// 2. 等待轉(zhuǎn)換完成 (注意:這里要用死循環(huán)等待標志位變高)
// 原來的代碼 while(...) {...} 如果標志位沒變高會直接跳過,是錯的
while(ADC_GetITStatus(ADC_IT_EOC) == RESET);
// 3. 清除標志位
ADC_ClearITPendingBit(ADC_IT_EOC);
// 4. 讀取數(shù)據(jù)
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(); // 調(diào)用上面的基礎(chǔ)函數(shù)
// 稍微延時一點點,防止采樣過快讀到同樣的干擾
// 如果沒有 Delay 函數(shù),可以用空循環(huán)代替
}
return (uint16_t)(sum / times);
}
二、關(guān)鍵部分代碼解析與注釋
時間線:
- PAN3031 芯片收發(fā)完成,拉高物理引腳。
-
CW32 單片機被觸發(fā)物理中斷,進入
GPIOA_IRQHandler。 -
中斷函數(shù)火速把
g_bIrqTriggered和pan3031_irq_trigged_flag這兩個軟件變量變成 1。 -
主程序
while循環(huán)看到g_bIrqTriggered == 1,立刻跳出等待。 -
主程序調(diào)用
rf_irq_process(),它看到pan3031_irq_trigged_flag == true,準許執(zhí)行。 -
函數(shù)通過 SPI 查詢 PAN3031 具體原因,最終將結(jié)果翻譯成
RADIO_FLAG_RXDONE等業(yè)務(wù)標志位。
1.ADC 數(shù)據(jù)采集與處理(主機特有)
這是示例功能——獲取的物理數(shù)據(jù)。
// ==========================================
// 階段二:判斷握手結(jié)果 & 發(fā)送 ADC 數(shù)據(jù)
// ==========================================
// 【關(guān)鍵功能 1:數(shù)據(jù)采集與轉(zhuǎn)換】
// A. 讀取原始 ADC 碼值 (0~4095)
uint16_t adc_raw = Get_ADC_Average(8); // 多次采樣求平均,起到軟件濾波作用,抗干擾
// B. 【核心修改】轉(zhuǎn)換為十進制電壓值 (mV)
// 公式:(adc_raw * 3300) / 4096 (基于 3.3V 參考電壓)
// 使用 (uint32_t) 強制轉(zhuǎn)換防止 adc_raw * 3300 發(fā)生 16 位溢出
uint16_t voltage_mv = (uint16_t)((uint32_t)adc_raw * 3300 / 4096);
// C. 打包數(shù)據(jù) (將16位電壓值拆分成兩個 8 位字節(jié))
uint8_t tx_buffer[DATA_LEN];
tx_buffer[0] = (uint8_t)(voltage_mv >> 8); // 取電壓高 8 位放入數(shù)組第 0 個位置
tx_buffer[1] = (uint8_t)(voltage_mv); // 取電壓低 8 位放入數(shù)組第 1 個位置
2、回聲校驗機制 (Echo Check)
這是保證通信可靠性的重要手段。
// ==========================================
// 階段三:等待回聲校驗 (Echo Check) (主機端)
// ==========================================
// F. 進入接收,等待從機把剛才的電壓值原樣發(fā)回來
rf_enter_single_timeout_rx(200);
// ... (等待接收代碼略) ...
if (rf_get_recv_flag() == RADIO_FLAG_RXDONE)
{
rf_set_recv_flag(RADIO_FLAG_IDLE);
// 【關(guān)鍵功能 2:數(shù)據(jù)重組與校驗】
// 1. 將收到的兩個 8 位字節(jié)重新拼裝成 16 位的電壓數(shù)據(jù)
uint16_t echo_val = ((uint16_t)RxDoneParams.Payload[0] < 8) | RxDoneParams.Payload[1];
// 2. 驗證:發(fā)出的電壓值 == 收回的電壓值?
if (echo_val == voltage_mv)
{
// 通信鏈路完美閉環(huán)!說明數(shù)據(jù)在空中沒有發(fā)生誤碼
LedToggle();
}
}
3、從機的多路分支處理
從機需要根據(jù)收到的數(shù)據(jù)內(nèi)容,決定是進行“握手回應(yīng)”還是“數(shù)據(jù)回傳”。
// ============================================
// 4. 從機模式代碼 (SLAVE_MODE)
// ============================================
// 2. 邏輯分支判斷
// 【關(guān)鍵功能 3:協(xié)議解析】
// 檢查收到的數(shù)據(jù)長度是否為 6,且前三個字符是否為 'k' 'u' 'n'
if (len == DATA_LEN && pData[0] == 'k' && pData[1] == 'u' && pData[2] == 'n')
{
// === 分支 A:握手協(xié)議 ===
LedToggle();
Delay_Ms(5); // 必須加!給主機從“發(fā)送態(tài)”切換到“接收態(tài)”留出時間,否則主機聽不到回信
g_bIrqTriggered = 0;
rf_single_tx_data(Zhiyin, DATA_LEN, &sl_tx_time); // 回復(fù)確認暗號
}
else
{
// === 分支 B:數(shù)據(jù)回傳 (Echo) ===
// 解析主機發(fā)來的 16 位電壓數(shù)據(jù),主要用于在調(diào)試窗口查看
slave_recv_val = ((uint16_t)pData[0] < 8) | pData[1];
// 將收到的數(shù)據(jù)深拷貝到發(fā)送緩沖區(qū)
for(int i = 0; i < DATA_LEN; i++)
{
slave_tx_buffer[i] = pData[i];
}
LedToggle();
Delay_Ms(5);
g_bIrqTriggered = 0;
// 原樣發(fā)回給主機進行最終校驗
rf_single_tx_data(slave_tx_buffer, DATA_LEN, &sl_tx_time);
}
4、健壯的中斷等待機制
摒棄了官方 SDK 的“死等”,加入了基于全局變量 g_bIrqTriggered 的等待邏輯。
// 【關(guān)鍵功能 4:防死鎖等待機制】
uint32_t timeout_safety = 0;
// 只要沒觸發(fā)中斷,且沒有超時,就在這里等
while (g_bIrqTriggered == 0 && timeout_safety < 1000000)
{
timeout_safety++; // 軟件超時計數(shù)器,防止硬件死機導(dǎo)致程序永久卡死
}
// 退出循環(huán)后,如果是正常中斷觸發(fā),而不是超時退出的,則處理中斷
if (g_bIrqTriggered)
{
g_bIrqTriggered = 0;
rf_irq_process(); // 底層處理函數(shù),將狀態(tài)標志位更新為 TXDONE 或 RXDONE
}
/**
* @brief This funcation handles GPIOA
*/
extern volatile uint8_t g_bIrqTriggered;
void GPIOA_IRQHandler(void)
{
// 檢查是否是 PA1 引腳觸發(fā)
if (CW_GPIOA->ISR_f.PIN1)
{
// 1. 必須先清除中斷標志!
GPIOA_INTFLAG_CLR(bv1);
// 2. 【核心修改】通知主循環(huán)“有事發(fā)生了”?。。? // 只有加上這一句,OnMaster 里的 if (g_bIrqTriggered) 才會成立
g_bIrqTriggered = 1;
// 3. 調(diào)用原來的業(yè)務(wù)邏輯 (保留即可)
PAN3031_irq_handler();
}
}
審核編輯 黃宇
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。
舉報投訴
-
通信程序
+關(guān)注
關(guān)注
0文章
15瀏覽量
8691 -
CW32
+關(guān)注
關(guān)注
1文章
323瀏覽量
1935
發(fā)布評論請先 登錄
相關(guān)推薦
熱點推薦
CW32量產(chǎn)燒錄工具
線的程序燒錄。 燒錄器CW-Writer 一.燒錄器概況 圖1所示為燒錄器的實物展示圖。 ? 圖1 CW-Writer燒錄器 當(dāng)燒錄器通過USB口和PC機連接實現(xiàn)供電和通信功能,當(dāng)離線
【項目展示】基于CW32的遙控循跡小車
CW32循跡小車.zip_免費高速下載|百度網(wǎng)盤-分享無限制? 一、概述 CW32循跡、遙控小車具有循跡和遙控兩種功能,小車的硬件模塊由CW32F030C8T6小藍板、智能小車控制底板、BT04-E
【CW32無線抄表項目】W25Q+CW32程序示例
/Armink/SFUD 一、程序分析 硬件總線映射(引腳與時鐘的“避坑點”) ? #define FLASH_SPIx CW_SPI2// 注意:CW32 中 SPI1 在 APB2 總線,而 SPI2 通常
基于芯源CW32 MCU的LED閃爍示例及代碼分析
最近我在項目中使用了芯源的CW32 MCU,這是一款非常適合物聯(lián)網(wǎng)和低功耗應(yīng)用的微控制器。在初步學(xué)習(xí)和使用中,我做了一個簡單的LED閃爍實驗,通過這篇帖子給大家分享一下代碼及相關(guān)的配置步驟。
硬件
發(fā)表于 12-04 06:52
CW32系列MCU在Eclipse GCC + JLink下的使用示例分享
CW32系列MCU在Eclipse GCC + JLink下的使用示例:
1、下載安裝Eclipse IDE for Embedded C/C++ Developers。
2、下載安裝
發(fā)表于 02-02 06:57
【應(yīng)用筆記】CW32 自舉程序中使用的 ISP 協(xié)議
CW32 自舉程序中使用的 ISP 協(xié)議CW32 微控制器片上 FLASH 存儲器有一部分區(qū)域用于存儲 BootLoader 啟動程序,在芯片出廠時已編程,用戶可利用 BootLoad
發(fā)表于 06-06 13:26
cw32和stm32的區(qū)別
cw32和stm32的區(qū)別 CW32和STM32是兩種常見的單片機,被廣泛應(yīng)用于各種電子設(shè)備中。在本文中,我們將深入探討CW32和STM32之間的區(qū)別和優(yōu)劣勢。 1. 硬件性能 硬件性能是衡量單片機
應(yīng)用筆記-CW32 自舉程序中使用的 ISP 協(xié)議
CW32自舉程序中使用的ISP協(xié)議CW32微控制器片上FLASH存儲器有一部分區(qū)域用于存儲BootLoader啟動程序,在芯片出廠時已編程,用戶可利用BootLoader啟動
發(fā)表于 06-06 13:37
?7次下載
【CW32無線抄表項目】示例通信程序講解
評論