一、SPI通信基础知识
SPI(Serial Peripheral Interface)是一种同步串行通信协议,常用于 MCU 与外设之间的高速通信。
SPI 通信通常包含以下信号线:
在本方案中:
CW32 作为 SPI 主机
PAN3031 作为 SPI 从机
SPI 通信由 CW32 主动发起
SCK(Serial Clock):由主机产生的时钟信号
MOSI(Master Out Slave In):主机发送数据给从机
MISO(Master In Slave Out):从机发送数据给主机
CS/SS(Chip Select):片选信号,用于选择通信的从机
二、IQR外部中断知识
IRQ(Interrupt Request)是外设向 MCU 发出的中断请求信号。
PAN3031 通过 IRQ 引脚向 CW32 通知以下事件(示例):
有数据可读取
状态发生变化
需要 MCU 处理的事件发生
MCU 在检测到 IRQ 触发后,会进入中断服务函数(ISR),通常只做:
事件标志位设置
简单状态记录
不建议在中断中直接进行 SPI 通信,而应在主循环或任务中处理。
三、CW32与PAN3031的通信整体流程
CW32 与 PAN3031 的通信方式为:
以两块 CW32 搭配两块 PAN3031 为例:
主机端(Master):
A1: 第一块 CW32(大脑/指挥官)
A2: 第一块 PAN3031(嘴巴和耳朵/无线网卡)
从机端(Slave):
B1: 第二块 CW32(大脑/指挥官)
B2: 第二块 PAN3031(嘴巴和耳朵/无线网卡)
通信全过程分为两部分:
第一段路:大脑指挥网卡 (板级有线通信 - SPI)
A1 与 A2 之间(以及 B1 与 B2 之间)是通过 SPI 接口 连接的。 这就像是老板(CW32)通过内线电话指挥秘书(PAN3031)。
物理连接:它们之间用杜邦线线连接(CSN、SCK、MOSI、MISO)。
通信方式:
A1 (CW32) 发送指令:通过 MOSI 线告诉 A2 “把数据发出去”或者“把配置改一下”。
A2 (PAN3031) 反馈数据:通过 MISO 线把收到的无线数据传回给 A1。
通知机制 (IRQ):当 A2 收到无线数据或者发完数据时,它会拉动 IRQ 引脚(按门铃),告诉 A1 “有情况,快来处理”。
第二段路:网卡隔空对话 (板间无线通信 - 2.4G RF)
A2 与 B2 之间是通过 2.4GHz 无线射频信号 连接的。 这就像是两个拿着对讲机的人在隔空喊话。
物理连接:没有导线,靠天线发射电磁波。
通信方式:
调制 (Tx):A2 把 A1 给它的数字信号(0101...)转换成高频无线电波发射出去。
解调 (Rx):B2 的天线捕捉到这些电波,把它还原成数字信号(0101...)。
特点:这是半双工的,也就是说 A2 在说的时候,B2 必须在听;不能两个人同时说话,否则信号会打架(同频干扰)。
完整的数据传输接力跑 (以主机发送 "kunkun" 为例)
整个通信过程就像一次接力赛,数据是从 A1 的内存 跑到 B1 的内存:
打包 (A1):主机 CW32 (A1) 把字符串 "kunkun" 准备好。
下达指令 (A1 -> A2):A1 通过 SPI 接口,把数据写入 A2 的发送缓冲区,并命令 A2:“发射!”
发射 (A2 -> 空中):A2 启动射频电路,把数据变成无线电波发向空中。
接收 (空中 -> B2):从机 PAN3031 (B2) 的天线捕捉到信号,解析出 "kunkun",存入自己的接收缓冲区。
按门铃 (B2 -> B1):B2 拉高 IRQ 引脚,触发 B1 的外部中断。
读取 (B1 -> B2):从机 CW32 (B1) 收到中断后,通过 SPI 接口 读取 B2 缓冲区里的数据。
处理 (B1):B1 拿到了 "kunkun"。
四、SPI硬件连接说明
SPI 硬件连接如下(示例):
| CW32 引脚 | PAN3031 引脚 | 说明 |
| SCK | SCK | SPI 时钟 |
| MOSI | MOSI | 主发从收 |
| MISO | MISO | 从发主收 |
| GPIOx | CS | SPI 片选 |
| GPIOy | IRQ | 外部中断 |
CS 信号通常为 低电平有效,需由软件控制。
五、SPI工作模式说明
SPI 通信就像两个人跳绳,必须在同一个节奏点上起跳才不会绊倒。为了让 CW32(主机)能正确读写 PAN3031(从机),双方的配置必须完全一致。
PAN3031 使用的是标准的 SPI Mode 0,CW32 初始化 SPI 时必须严格按照以下参数配置:
SPI 模式:Mode 0
时钟极性 (CPOL) = 0:
表示 SCLK 时钟线在空闲状态(没有传输数据时)保持 低电平 (Low Level)。
时钟相位 (CPHA) = 0:
表示在时钟的 第一个边沿(对于 Mode 0 来说是 上升沿)进行数据采样(读取数据)。
而在第二个边沿(下降沿)进行数据切换(发送下一位数据)。
数据位顺序 (Bit Order):
MSB First(高位先发)。
六、IRQ触发方式说明
PAN3031 的 IRQ 信号为 上升沿 触发。
CW32 需将对应 GPIO 配置为外部中断输入,并设置正确的触发方式。
七、PAN3031模块使用说明(详细版)
PAN3031 SDK使用流程
初始化流程

参数配置流程


1. PAN3031_set_freq (设置中心频率)
干什么用的: 设置模块工作在哪个频段(比如 2.4GHz、470MHz 等)。
小白比喻:调对讲机频道。如果主机在 1 频道喊话,从机在 2 频道听,两人永远对不上话。
2. PAN3031_set_code_rate (设置编码纠错率)
干什么用的: 决定在发送真实数据时,夹带多少“冗余纠错码”。
小白比喻:给快递包防震膜。空气中干扰很多,容易丢包。纠错率设得越高(膜包得越厚),就算数据在空中损坏了一点,接收端也能自己推算修复回来;但代价是有效数据的传输效率变低了。
3. PAN3031_set_bw (设置带宽 Bandwidth)
干什么用的: 决定无线信号占据的频率宽度。
小白比喻:修多宽的马路。带宽越大,数据传得越快;但马路越宽,越容易受到旁边车道(其他无线电信号)的干扰,接收灵敏度会下降。
4. PAN3031_set_sf (设置扩频因子 Spreading Factor)
干什么用的: 这是扩频通信(如 LoRa 调制)里非常关键的参数,决定每个数据符号的持续时间。
小白比喻:讲话的语速。SF 值越大,相当于你讲话越慢、发音越长,哪怕距离很远、环境很吵对方也能听清(穿墙能力大增);但缺点是一句话要说很久,模块工作时间变长,非常费电。
5. PAN3031_set_tx_power (设置发射功率)
干什么用的: 设置芯片射频引脚输出的能量大小(比如 10dBm、22dBm)。
小白比喻:嗓门有多大。功率调得越高,信号覆盖范围越广,但也意味着你的电池会被抽干得越快。
6. PAN3031_set_crc (设置 CRC 循环冗余校验)
干什么用的: 开启后,硬件会自动在数据包尾部追加一段校验码。
小白比喻:贴封条防伪。接收端拿到包裹后,会检查封条是否完好。如果发现对不上,说明数据在空中被干扰串味了,直接丢弃,防止单片机读到错误的 ADC 抄表数据。

发送流程


接收流程



PAN3031 部分SDK接口函数

agc(自动增益控制)
功能: 全称 Automatic Gain Control。它会根据收到的无线信号强弱,自动调节内部放大器的放大倍数。
小白比喻:“自动音量调节”。如果发射端离得很近,信号太强,它就调低增益防止“震耳朵”;如果离得远信号弱,它就自动调高增益,确保无论远近,芯片都能清晰地“听见”数据。
AGC 的核心原理是一个 “检测 -> 反馈 -> 调整” 的闭环系统。
技术实现流程:
信号进入: 天线接收到的原始无线信号进入芯片。
强度检测: 芯片内部有一个“侦察兵”(信号强度检测器),实时测量这个信号的功率(也就是我们常说的 RSSI)。
比较判断: 芯片内部设定了一个“理想音量”范围。
如果信号太强: 会导致后面的电路“破音”(饱和失真),数据就全乱了。
如果信号太弱: 背景噪音就会盖过数据。
反馈调整: 侦察兵发现信号太强,就立刻下令让 LNA(低噪声放大器) 降低放大倍数(减小增益)。
发现信号太弱,就下令让 LNA 全力放大(增大增益)。
antenna_init(天线初始化)
功能: 配置射频端口和天线开关。
小白比喻:“切换车道”。无线芯片通常有一个天线,但有“发”和“收”两条路。这个步骤是初始化天线开关的控制逻辑,确保你想发的时候数据能传给天线,想收的时候天线信号能传给芯片。
Antenna_init 的原理本质上是 “单刀双掷开关(SPDT)” 的逻辑配置。
技术实现流程:
链路分离: 无线芯片内部其实有两套完全独立的系统:发射机(TX)产生强大的信号,接收机(RX)负责捕捉微弱的信号。
物理矛盾: 但是,为了节省成本和空间,整个设备通常只有一根天线。
开关切换: 在天线和芯片之间,有一个射频开关(RF Switch)。
发送时: 开关必须拨向“发射链路”,把功率放大器(PA)产生的信号推向天线。
接收时: 开关必须拨向“接收链路”,把天线捕捉到的微弱波浪送给接收器。
初始化保护:antenna_init 就是在初始化阶段,配置好控制这个“开关”的 GPIO 引脚逻辑。

节能模式:芯片的“休息时间”
PAN3031_MODE_DEEP_SLEEP(深度睡眠)
状态: 芯片几乎完全关闭,功耗降到最低(微安级)。
小白比喻:彻底关机。这是抄表系统最常用的状态。电表每天 99% 的时间都应该处于这个模式来省电。
PAN3031_MODE_SLEEP(睡眠)
状态: 功耗略高于深度睡眠,但保留了寄存器的配置。
小白比喻:电脑休眠。唤醒速度比深度睡眠快一点,不需要重新加载所有配置。
准备模式:芯片的“热身阶段”
这三个 STB (Standby) 模式是处于休眠和工作之间的中间地带,它们决定了芯片内部哪些组件(如晶振、频率合成器)是开着的。
PAN3031_MODE_STB1 / STB2
状态: 基础待机。内部晶振开始起振。
小白比喻:坐在板凳上热身。虽然没下场比赛,但已经穿好运动鞋了。
STB1:深度省电的“浅睡”模式
在 STB1 模式下,芯片关闭了外部晶振,只靠内部一个很弱的 RC 电路维持基本逻辑。
优点: 非常省电,比 STB2 能多省下不少微安级的电流。
缺点: 当你需要发数据时,从 STB1 切换到 TX(发射)需要一段“暖机时间”,因为外部晶振从静止到稳定震荡需要几百微秒甚至毫秒级的时间。
STB2:随时待命的“备战”模式
在 STB2 模式下,芯片已经把昂贵且精确的外部晶振给跑起来了。
优点: 响应极其灵敏。如果你在做一个需要频繁快速回应(比如 10ms 内必须回信)的协议,STB2 是唯一的选择。
缺点: 功耗相对较高。如果一直停在 STB2,你的电表电池可能撑不了几年。
PAN3031_MODE_STB3
状态: 高级待机。频率合成器(决定你发什么频率)已经锁定。
小白比喻:在起跑线上蹲好了。这是进入“发射”或“接收”之前的最后一站。在你之前的代码里,进入 rf_single_tx_data 之前都会先切换到这个模式。
工作模式:芯片的“上场比赛”
PAN3031_MODE_TX(发射模式)
状态: 功率放大器开启,全力向天线推送电磁波。
小白比喻:放声大喊。这是最费电的时刻,所以发送完数据一定要赶紧让它睡觉。
PAN3031_MODE_RX(接收模式)
状态: 接收链路全开,实时捕捉空中的微弱信号。
小白比喻:竖起耳朵细听。在抄表项目中,电表端通常只在发送完数据后的那几百毫秒开启这个模式来等确认。



RF_PARA_TYPE_FREQ(中心频率)
技术内幕: 指无线电波能量最集中的那个点(例如 470MHz 或 433MHz)。
实战要点:
必须匹配:主机和从机的频率误差不能超过一定范围(通常是晶振精度的几倍),否则由于“偏频”会导致信号极差甚至搜不到包。
避开干扰:如果小区里有大量同频段的无线设备,可以微调频率(跳频)来寻找一个“安静”的频道。
RF_PARA_TYPE_CR(编码纠错率)
技术内幕: 全称 Code Rate。它在原始数据中加入冗余的校验位(例如 4/5 代表 4 位数据加 1 位冗余)。
实战要点:
抗干扰性:设置越高(如 4/8),抗突发干扰能力越强,就算空中丢了几位数据,芯片也能靠算法补回来。
副作用:纠错位越多,整个数据包就越长,空中飞行时间(ToA) 增加,从而增加功耗。
RF_PARA_TYPE_BW(带宽)
技术内幕: 指信号占据的频率宽度(如 125kHz, 250kHz, 500kHz)。
实战要点:
速率 vs. 灵敏度:马路越宽(BW 大),车速越快(数据率高),但路上的噪音也多(底噪高,灵敏度差)。
抄表选择:通常选择较小的带宽(如 125kHz)来换取更高的接收灵敏度,确保能穿透更厚的墙。
RF_PARA_TYPE_SF(扩频因子)
技术内幕:Spreading Factor,是 LoRa/扩频通信的灵魂。它决定了一个数据符号被拉得有多长。
实战要点:
穿墙神器:SF 越大(如 SF12),信号在噪声中被识别的能力越强,距离翻倍。
功耗陷阱:SF 每增加一级,数据在空中停留的时间几乎翻倍。这会导致 PAN3031 的发射电流持续时间变长,严重缩短电表寿命。
RF_PARA_TYPE_TXPOWER(发射功率)
技术内幕: 芯片输出信号的强度,单位通常为 dBm。
实战要点:
按需调整:如果电表就在网关旁边,没必要开 20dBm(最大功率),调低功率能有效节省电量。
法规限制:不同国家和地区对不同频段的最大功率有法律限制,开发时需查阅当地标准。
RF_PARA_TYPE_CRC(循环冗余校验)
技术内幕: 在数据包末尾添加计算好的校验码。
实战要点:
数据保真:无线环境非常脏,数据包很容易被干扰成乱码。开启 CRC 后,如果收到的包计算结果不符,硬件会自动丢弃,防止你的单片机读到错误的“水费”或“电费”数据。
核心变量:内部状态记录员
程序定义了两个 static(静态)变量,它们就像是挂在实验室门口的记事板,用来记录无线模块当前的工作状态。
packet_received:专门记录接收的状态。是收到了?还是超时了?还是出错了?
packet_transmit:专门记录发送的状态。是正在发?还是发完了?
RxDoneParams:这是一个结构体,它像是一个快递暂存柜。当收到新包裹(数据)时,包裹的内容、重量(长度)、信号强度等信息都会暂时存放在这里。

RADIO_FLAG_IDLE0 空闲。此时模块没活干,可以安排新任务
RADIO_FLAG_TXDONE1发送成功。信件已成功打向空中
RADIO_FLAG_RXDONE2接收成功。抓到了一个完整的有效数据包
RADIO_FLAG_RXTIMEOUT3接收超时。等了半天没人理我,放弃等待
RADIO_FLAG_RXERR4接收错误。抓到了包,但数据内容坏了(校验失败)
RADIO_FLAG_PLHDRXDONE5报头接收完成。 —— 报头接收完成


(获取函数):这是询问窗口。比如 rf_get_recv_flag() 就是在问:“现在接收到哪一步了?”
(设置函数):这是更新窗口。比如 rf_set_recv_flag(status) 就是在通知系统:“我已经处理完这个包了,请把状态重置为空闲。”




1. rf_enter_single_rx (进入单次接收模式)
简单理解: 这个函数相当于让无线模块“张开耳朵听一次”。
它的作用: 将模块从空闲状态切换到接收状态。
它的执行逻辑:
准备: 同样先进入待机状态并切换到发送模式。
点火: 调整振荡器匹配发送频率。
投递: 调用 PAN3031_send_packet 正式把数据包打向空中。
记录: 记录下这次发送耗费的时间,方便后续计算功耗或排查延迟。
buf:要发送的“信件内容”(数据缓冲区指针)。
size:这封信有多长(数据长度)。
tx_time:发送这封信花了多少时间(由函数自动计算并返回)。
热身: 先进入 STB3 (待机) 模式。
换向: 将射频口切换到接收方向。
调频: 调整内部振荡器 (VCO) 匹配接收频率。
锁定: 设置为 SINGLE (单次) 接收模式,意味着收到一个完整的数据包后,模块会自动停下来,不会一直傻听。

2. rf_single_tx_data (单次数据发送)
简单理解: 这个函数相当于“把写好的信投递出去”。
它的参数:
它的执行逻辑:


PAN3031 SDK示例加移植


/* --- PAN3031 Tx-rx 模式示例程序 --- */
// 1. 初始化阶段
ret = rf_init(); // 执行射频芯片初始化
if (ret != OK) {
DDL_Printf(" RF Init Fail"); // 如果初始化失败,打印错误信息
while (1); // 程序在此死循环,不再继续运行
}
rf_set_default_para(); // 配置射频芯片的默认通信参数(频率、功率等)
// 2. 初始数据发送
if (rf_single_tx_data(tx_test_buf, TX_LEN, &tx_time) != OK) {
DDL_Printf("tx fail rn"); // 尝试发送第一包数据,若失败则报错
}
else {
txcnt++; // 发送计数加 1
DDL_Printf("Tx cnt %drn", txcnt); // 打印当前发送次数
while (RADIO_FLAG_IDLE == rf_get_transmit_flag()); // 等待硬件发送标志位改变,确保发送动作完成
rf_set_transmit_flag(RADIO_FLAG_IDLE); // 手动将发送标志位清零,准备下次操作
rf_sleep(); // 令射频模块进入休眠状态以省电
rf_sleep_wakeup(); // 唤醒模块,准备切换到接收状态
rf_enter_single_timeout_rx(15000); // 开启单次超时接收,设置超时时间为 15000 毫秒(15秒)
}
// 3. 主循环处理阶段
while (1) {
SysTick_Delay(5); // 短暂延时,降低系统负担
// --- 情况 A:接收成功 ---
if (rf_get_recv_flag() == RADIO_FLAG_RXDONE) { // 判断硬件标志位是否为“接收完成”
BSP_LED_Toggle(); // 翻转 LED 灯状态,直观显示接收成功
rf_set_recv_flag(RADIO_FLAG_IDLE); // 清除接收标志位
// 打印信号质量信息:信噪比 (SNR) 和 信号强度 (RSSI)
DDL_Printf("Rx : SNR: %f ,RSSI: %f rn", RxDoneParams.Snr, RxDoneParams.Rssi);
// 循环打印收到的原始十六进制数据内容
for (i = 0; i < RxDoneParams.Size; i++) {
DDL_Printf("0x%02x ", RxDoneParams.Payload[i]);
}
DDL_Printf("rn");
rxcnt++; // 接收成功计数加 1
DDL_Printf("###Rx cnt %d##rn", rxcnt);
rf_sleep(); // 接收任务完成,先进入休眠
rf_sleep_wakeup(); // 唤醒,准备下一次循环发送
SysTick_Delay(3000); // 延时 3 秒后再进行下一次动作
// 成功接收后,再次发起数据发送(形成收发循环)
if (rf_single_tx_data(tx_test_buf, TX_LEN, &tx_time) != OK) {
DDL_Printf("tx fail rn");
}
else {
txcnt++;
DDL_Printf("Tx cnt %drn", txcnt);
while (RADIO_FLAG_IDLE == rf_get_transmit_flag()); // 等待发送结束
rf_set_transmit_flag(RADIO_FLAG_IDLE);
rf_sleep();
rf_sleep_wakeup();
rf_enter_single_timeout_rx(15000); // 再次进入接收等待状态
}
}
// --- 情况 B:接收超时或出错 ---
if ((rf_get_recv_flag() == RADIO_FLAG_RXTIMEOUT) || (rf_get_recv_flag() == RADIO_FLAG_RXERR)) {
rf_set_recv_flag(RADIO_FLAG_IDLE); // 清除异常标志位
DDL_Printf("Rxerrrn"); // 打印接收错误或超时提示
rf_sleep();
rf_sleep_wakeup();
HAL_Delay(10000); // 出错后延时 10 秒(避开干扰或重试间隔)
// 即使失败,也尝试再次发送数据
if (rf_single_tx_data(tx_test_buf, TX_LEN, &tx_time) != OK) {
DDL_Printf("tx fail rn");
}
else {
txcnt++;
DDL_Printf("Tx cnt %drn", txcnt);
while (RADIO_FLAG_IDLE == rf_get_transmit_flag());
rf_set_transmit_flag(RADIO_FLAG_IDLE);
rf_sleep();
rf_sleep_wakeup();
rf_enter_single_timeout_rx(15000); // 重新进入接收等待
}
}
}
值得参考点
业务闭环完整: 涵盖了“发送 -> 等待接收 -> 成功处理 -> 失败/超时兜底”的完整通信全生命周期。
状态切换规范: 在每次收发状态切换前,都老老实实地调用了 rf_sleep() 和 rf_sleep_wakeup()。这种“先归零再启动”的做法能有效防止射频芯片内部状态机卡死。
易读性高: 纯顺序执行逻辑,没有复杂的结构体指针嵌套,适合初学者顺着流程往下读。
缺点
1.代码严重冗余:
rf_single_tx_data 及其后续的 while 等待、rf_sleep 等逻辑,在代码中出现了三次:
初始化后第一次发送(第 14-26 行)。
接收成功后再次发送(第 52-64 行)。
接收失败后再次发送(第 81-93 行)。
在工程中,如果一段逻辑在两处以上被用到,必须封装成函数。这不仅是为了美观,更是为了方便维护。如果你想修改发送后的超时时间,在冗余代码里你要改三次,漏改一个就是 Bug;在函数里你只需要改一次。
修改方式:
// 封装成一个通用的“发送并处理”函数
void App_RF_Transmit_Flow(void) {
if (rf_single_tx_data(tx_test_buf, TX_LEN, &tx_time) == OK) {
txcnt++;
// 等待发送完成(建议加入下文提到的超时机制)
while (RADIO_FLAG_IDLE == rf_get_transmit_flag());
rf_set_transmit_flag(RADIO_FLAG_IDLE);
rf_sleep();
rf_sleep_wakeup();
rf_enter_single_timeout_rx(15000); // 开启下一次接收窗口
}
}
2. “死等”式阻塞:
程序中多次使用 while (RADIO_FLAG_IDLE == rf_get_transmit_flag());。 这段代码的意思是:只要射频芯片不回传“我空闲了”的信号,单片机就永远停在这里。
实际应用中的做法:绝对不允许出现无条件的死循环。 如果 SPI 线松了、无线模块受静电干扰挂了,或者由于强电磁干扰导致标志位没跳变,你的单片机就会直接“变砖”,不再响应任何按键或采集任务。工业级代码必须有超时退出或中断驱动机制。
修改方式(增加安全计数器):
uint32_t timeout_cnt = 0x100000; // 设置一个足够长的安全计数器
while (RADIO_FLAG_IDLE == rf_get_transmit_flag()) {
if (--timeout_cnt == 0) {
DDL_Printf("Hard Err: RF Hardware No Response!rn");
// 这里可以执行硬件复位或跳出循环
break;
}
}
PAN3031 状态图






-
SPI
+关注
关注
17文章
1897浏览量
102081 -
无线射频
+关注
关注
4文章
220浏览量
28033 -
智能水表
+关注
关注
4文章
218浏览量
24392 -
CW32
+关注
关注
1文章
323浏览量
1953
发布评论请先 登录
【项目展示】基于CW32的遥控循迹小车
【CW32无线抄表项目】W25Q+CW32程序示例
【CW32无线抄表项目】CW32搭配PAN3031通信教程
评论