希望通过两个stm32、两个nRF905无线通信模块、串口来实现两机通信。具体功能为:
板子A、B分别包含一个stm32单片机和一个nRF905无线模块,欲实现板子A、B之间的通信。
其中,PC端串口助手可向板子A的stm32发送字符‘A’控制板子B上的LED亮灯,发送字符‘B’控制板B的LED熄灭;
同样地,可通过按下板B的按键向板A发送一段字符。
但是网络上鲜有NRF905模块的深层解读,故引出此文,主要解读NRF905的官方英文文档,以及部分重要的库函数封装,不涉及功能逻辑,不上传整个工程。
以下为文档的Git地址:NRF905相关文档下载地址
文章目录
- 一、功能原理
- 二、一些函数的封装
一、功能原理
首先看引脚定义,作为一个集成化的模块,我们需要像关注黑盒模型那样关注它的输入、输出的格式即可。
(1) 需要重点关注的是图中红框的部分:
TRX_CE:芯片收/发的使能
CD: 载波检测
AM: 地址匹配
TX_EN: 发送/接收使能
其中,TRX_CE 和 TX_EN 都为输入,需要 MCU 对其写入高低电平来控制 NRF905 的发送、接收模式的启动与否。CD 和 AM 都为输出,用来告诉 MCU “我检测到了空中跟我有相同载波频率的发送器了,并且它发过来的数据包也是发给我的(通过地址判断)”,主要是在接收模式下使用。
关于它们四个的具体解释如下:
- NRF905 的工作模式
总的来说,在Power使能状态下,TRX_CE 和 TX_EN 共同控制 NRF905 的工作模式。
在模块发送之前,需要将数据包写入到模块中,这需要通过SPI编程,故此需要将 TRX_CE 拉低;
将 TRX_CE 和 TX_EN 都置1则设置为发送模式;
将 TX_EN 拉低,设置为接收模式。
另外,工作模式的转换还需要遵守规定。 从待机模式切换到接收或发送模式都需要延迟一个650us的时间,这样才能够保证数据包被成功接收/发送。
(2)需要关注绿框中的几个引脚,它们是模块与 MCU通信的桥梁。
模块通过
MOSI: 主机发送从机接收
MISO: 从机发送主机接收
SCK: SPI 通信的时钟线
SCN: SPI的使能位,置0为使能
与 MCU 进行通信。置于 SPI 的使用时序,数据手册里也有所提及,在Figure 6. 到 Figure 8,不再展示。
那么 MCU 具体是如何通过 SPI 总线与 NRF905 进行通信的呢? 发送的频率、功率、目标设备的地址、数据信息都是写入到 NRF905 的哪里呢?
模块包含有五个内部寄存器,每个寄存器有各自的功能。
五个寄存器具体的作用:
-
Status – Register :DR 标志位,当发送完一个完整的数据包(数据包的格式见下图)时或者接收到一个有效的数据包时,DR标志位置一;AM 标志位,接收模式下,当CD标志位置一后检查数据包中的地址是否和寄存器初始化时设置的本设备地址号一致,如果一致则AM标志位置一。(具体的描述见 数据手册-12 nRF905 features)
数据包中的 ADDR 和 PAYLOAD 是我们需要写入的,但是在真正发送之前,前导码和循环冗余校验会自动生成一并发送。 -
RF – Configuration Register :收发器的初始化配置,包括频率、输出功率、设备ID等。(具体的配置见下表)
配置时需按照下表来写 RF配置寄存器的这10个字节,至于每位代表的含义需要搭配表15使用。
先来大概看一下,我们需要结合Byte0和Byte1来设置发送频率、输出功率,需要Byte2来设置发送地址、接收地址的宽度,Byte5-8用来设置设备ID,最后的Byte9用来设置CRC模式等等。具体的配置待下文书写。 -
TX – Address:设置目标设备的的地址,也就是数据包中的ADDR。具体为多少个字节要在RF配置寄存器中设置。
-
TX – Payload:用来存放具体的发送信息了,具体为多少个字节要在RF配置寄存器中设置。
-
RX – Payload:用来存放具体的接收信息了,具体为多少个字节要在RF配置寄存器中设置。
现在我们知道了MCU通过SPI总线与模块的具体哪些寄存器进行读/写操作。在对具体的寄存器进行读/写操作之前,需要先发送SPI指令。
主要是用到三个指令:
- WC指令。写RF配置寄存器,所以我们需要通过SPI总线写入0x00(从0位置开始)
- WTA指令。向TX-ADDR寄存器写入目标设备的地址,需要SPI写入0x22.
- RRP指令。读取RX-payload寄存器,接收到的有效数据包就存在这里面。需要SPI写入0x24.
二、一些函数的封装
- SPI写一个字节
根据SPI时序来就好。
在SCK低电平时,将一位数据通过MOSI放入,在SCK高电平时读取MOSI上的一位数据。经过循环移位,可以读取到一个字节的数据。void NRF905_SPI_Write_Byte(uint8_t data) { for (uint8_t i=0; i<8; i++) { if (data & (0x80 << i)) { NRF905_MOSI_H; // 已宏定义,表示释放MOSI线。以下宏定义均同理 } else { NRF905_MOSI_L; // 拉低MOSI线 } NRF905_SCK_H; // 释放SCK线 NRF905_SCK_L; // 拉低SCK线 } }
- 写 RF – Configuration Register 配置寄存器
上一章节中提到,配置寄存器总共有10个字节,我们一个个来看。
每一个字节都需要搭配着查找Table 15. Configuration register description
来使用。
- 写Byte0
查找表15,可以看到九位CH_NO加上一个HFREQ_PLL位共同才能决定工作频率。具体的频率需要查找表24:
可以看到,欲使得工作频率为433MHz,需CH_NO[7:0]=[01001100]=0x4c,CH_NO[8]=0,HFREQ_PLL=0,而这后两位都属于Byte1.
总之,Byte0= 0x4c。 - Byte1
bit[7:6]=00,AUTO_RETRAN=0(不自动重发),RX_RED_PWR=0(普通模式),PA_PWR=11(+10dBm),HFREQ_PLL=0,CH_NO[8]=0。因此,Byte1=00001100=0x0c。
- Byte2
bit[7]=0,TX_AFW[2:0]=100,bit[3]=0,RX_AFW[2:0]=100。因此,Byte2=01000100=0x44.
-
Byte3
bit[7:6]=0, RX_PW[5:0]=100000(32个字节的接收Buffer)。因此,Byte3=00100000=0x20. -
Byte4
bit[7:6]=0, TX_PW[5:0]=100000(32字节的发送数据包)。因此,Byte4=00100000=0x20. -
Byte5~Byte8
4个字节从高到低来写设备ID。 -
Byte9
CRC_MODE=0(8位CRC校验位),CRC_EN=1(开启CRC校验), XOF[2:0]=011(外部晶振频率16MHz), UP_CLK_EN=0,UP_CLK_FREQ[1:0]=00(输出时频)。因此,Byte9=0101 1000=0x58./* 配置RF – Configuration Register */ void NRF905_Config(void) { NRF905_CSN_ENABLE; // 已宏定义,使能SPI NRF905_SPI_Write_Byte(WC); // SPI指令 /* * 将配置寄存器的10个字节依次写入,代码省略 */ NRF905_CSN_DISABLE; // 失能SPI }
- 写要发送的数据包
根据这个流程图来,首先将TX_EN置一、TRX_CE置零,然后通过SPI总线向TX – Address写入目标地址、向TX – Payload写入32个字节的数据,随后将TRX_CE置一,设置NRF905为发送模式。之后会根据配置寄存器的设置自动生成CRC和前导码,从而组成一个完成发送数据包。/* * NRF905在一般状态下长时间处于接收模式,当中断事件发生,方在回调函数中发送数据包。 */ void SendPacket(uint32_t TxBuffer) { NRF905_TX_EN_H; NRF905_TRX_CE_L; delay_us(650); // 从待机模式切换到发送模式,需要650us NRF905_CSN_ENABLE; NRF905_SPI_Write_Byte(WTP); // Write TX-payload for (uint8_t i = 0; i < 32; i++) { NRF905_SPI_Write_Byte(TxBuffer[i]); // 写入数据 } NRF905_SPI_Write_Byte(WTA); for (uint8_t i = 0; i < 4; i++) { NRF905_SPI_Write_Byte(RF_Config[i + 5]); // 写入目标地址 } NRF905_CSN_DISABLE; NRF905_TRX_CE_H; //开启发送模式 /* * 延时至少10us(根据Data rate,见Figure 9.TX timing) */ NRF905_TRX_CE_L; }
- 接收一个数据包
根据流程图,保证TX_EN为低,将TRX_CE置一,接收器开始检测目标频率,一旦检测到则CD标志位置一,随后再检查数据包中的ADDR与自身设备ID是否一致,一致的话AM标志位置一,对接收的数据包进行循环冗余校验,如果是有效信息则DR数据位置一。int main(void) { /* 许多初始化,省略。 */ NRF905_Config(); // 写RF配置寄存器,对NRF905进行初始化 NRF905_TX_EN_L; NRF905_TRX_CE_H; delay_us(650); while(1) { if(!binarySignal) // 一个二值信号量,常为0,当中断事件发生时变为1 { while(!(CD_ifH() & AM_ifH())) // CD标志位和AM标志位都置1 { delay_ms(100); NRF905_TX_EN_L; NRF905_TRX_CE_H; } ReceivePacket(RxBuffer); /* 处理逻辑 */ } } }
void ReceivePacket(uint32_t RxBuffer) { NRF905_CSN_ENABLE; NRF905_TRX_CE_L; NRF905_SPI_Write_Byte(RRP); // Read RX-payload for (uint8_t i = 0; i < 32; i++) { RxBuffer[i] = NRF905_SPI_Read_Byte(); } NRF905_CSN_DISABLE; NRF905_TRX_CE_L; }
本文的目的在于掌握阅读数据手册的能力,张贴的代码中包含许多伪代码,故而不能直接运行。此次阅读完英文数据手册,只觉层层递进、环环相扣、逻辑清晰。