【CW32无线抄表项目】CW32搭配PAN3031通信教程

描述

一、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 状态图

 

智能水表

智能水表

智能水表

智能水表

智能水表

智能水表

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分