本文章记录两个NRF24L01无线模块实现双向传输的软件设计~
为什么可以双向传输呢?这要归功于它具有Enhanced ShockBurst,可以工作在主接收和主发送模式,在主接收方可以将我们自己的数据附在ACK Packet实现双向传输,所以此次双向传输会将两个NRF分别设置成主接收和主发送。
· 实物(模块非单独芯片):
网上最便宜的就这种不带运放的模块,大家如果想用NRF24L01的话,建议大家使用加了运放的模块 ,传输距离会比较远一点,没运放的话距离10米就开始出现丢包现象了。另外,进口的和国产的用起来距离一样,所以优先选择国产,因为进口的还贵点。
·芯片引脚定义说明(芯片):
·**数据包格式 **:
这里没必要介绍NRF24L01了,相信要用这芯片或者模块的朋友应该提前了解过了。
· 寄存器表
该芯片有命令寄存器和功能寄存器。在使用每一款芯片之前,要养成查看数据手册的习惯。这是我自己边看边翻译的,可能有翻译地不对的,见谅哈。
命令寄存器表:
命令名 | 命令字 | #数据字节 | 操作 |
---|---|---|---|
R_REGISTER | 000A AAAA | 1到5,低位在前 | 读命令/状态寄存器 |
W_REGISTER | 001A AAAA | 1到5,低位在前 | 写命令/状态寄存器 |
R_RX_PAYLOAD | 0110 0001 | 1到32,低位在前 | 读RX_Payload(1到32个字节) |
W_TX_PAYLOAD | 1010 0000 | 1到32,低位在前 | 写TX_Payload(1到32个字节) |
FLUSH_TX | 1110 0001 | 0 | Flush TX FIFO,发送模式使用 |
FLUSH_RX | 1110 0010 | 0 | Flush RX FIFO,接收模式使用 |
REUSE_TX_PL | 1110 0011 | 0 | 重用上次发送的载荷(数据包) |
R_RX_PL_WID | 0110 0000 | 1 | 读取RX payload宽度 |
W_ACK_PAYLOAD | 1010 1PPP | 1到32,低位在前 | 写载荷(这些载荷数据同应答包一起从PPP通道发出) |
W_TX_PAYLOAD_NO ACK | 1011 0000 | 1到32,低位在前 | 失能自动应答,用于发送模式 |
NOP | 1111 1111 | 0 | 无操作,可用于读取状态寄存器 |
上面的AAAAA就是后面即将看到的功能寄存器的映射地址。
功能寄存器地址映射表:
地址 | 助记符 | 位 | 复位值 | 类型 | 描述 |
---|---|---|---|---|---|
00 | CONFIG | 配置寄存器 | |||
Reserved | 7 | 0 | R/w | 只能为0 | |
MASK_RX_DR | 6 | 0 | R/w | 接收中断标志。0:使能 | |
MASK_TX_D | 5 | 0 | R/w | 发送中断标志。0:使能 | |
MASK_MAX_RT | 4 | 0 | R/w | 最大重发次数中断。0:使能 | |
EN_CRC | 3 | 1 | R/w | 使能CRC | |
CRCO | 2 | 0 | R/w | CRC编码方案选择,1~2bytes | |
PWR_UP | 1 | 0 | R/w | 1:上电,0:下电 | |
PRIM_RX | 0 | 0 | R/w | 收发控制。1:PRX;0:PTX | |
01 | EN_AA | 使能自动应答 | |||
Reserved | 7:6 | 00 | R/w | 只能为0 | |
ENAA_P5 | 5 | 1 | R/w | 使能通道5自动应答机制 | |
ENAA_P4 | 4 | 1 | R/w | 使能通道4自动应答机制 | |
ENAA_P3 | 3 | 1 | R/w | 使能通道3自动应答机制 | |
ENAA_P2 | 2 | 1 | R/w | 使能通道2自动应答机制 | |
ENAA_P1 | 1 | 1 | R/w | 使能通道1自动应答机制 | |
ENAA_P0 | 0 | 1 | R/w | 使能通道0自动应答机制 | |
02 | EN_RXADDR | 使能 RX 地址 | |||
Reserved | 7:6 | 00 | R/w | 只能为0 | |
ERX_P5 | 5 | 0 | R/w | 使能通道5 | |
ERX_P4 | 4 | 0 | R/w | 使能通道4 | |
ERX_P3 | 3 | 0 | R/w | 使能通道3 | |
ERX_P2 | 2 | 0 | R/w | 使能通道2 | |
ERX_P1 | 1 | 0 | R/w | 使能通道1 | |
ERX_P0 | 0 | 0 | R/w | 使能通道0 | |
03 | SETUP_AW | 设置地址宽度(所有通道的地址) | |||
Reserved | 7:2 | 000000 | R/w | 只能为0 | |
AW | 1:0 | 11 | R/w | 收发地址宽度,’11’-5bytes | |
04 | SETUP_RETR | 设置自动重发射机制 | |||
ARD | 7:4 | 0000 | R/w | 自动重发延时’0001’-等待500us | |
ARC | 3:0 | 0011 | R/w | 自动重发计数 | |
05 | RF_CH | 设置RF频宽 | |||
Reserved | 7 | 0 | R/w | 只能为0 | |
RF_CH | 6:0 | 0000010 | R/w | 设置RF的工作频宽 | |
06 | RF_SETUP | RF设置寄存器 | |||
CONT_WAVE | 7 | 0 | R/w | 为1使能连续传送载波 | |
Reserved | 6 | 0 | R/w | 只能为0 | |
RF_DR_LOW | 5 | 0 | R/w | 设置RF数据250kbps | |
PLL_LOCK | 4 | 0 | R/w | 仅使用于测试? | |
RF_DR_HIGH | 3 | 1 | R/w | ‘00’-1M,’01’-2M,’10’-250kbps | |
RF_PWR | 2:1 | 11 | R/w | 设置RF输出增益 ’00’-18dbm,’11’-0dbm | |
Obsolete | 0 | 无意义 | |||
07 | STATUS | 状态寄存器 | |||
Reserved | 7 | 0 | R/w | 只能为0 | |
RX_DR | 6 | 0 | R/w | 数据就绪RXFIFO中断,写1清除 | |
TX_DS | 5 | 0 | R/w | 数据发送RXFIFO中断,写1清除 | |
MAX_RT | 4 | 0 | R/w | 最大字节重发中断,写1清除 | |
RX_P_NO | 3:1 | 111 | R | 数据通道号 | |
TX_FULL | 0 | 0 | R | TX FIFO满标志(满为1) | |
08 | OBSERVE_TX | 发射监测寄存器 | |||
PLOS_CNT | 7:4 | 0 | R | 丢包计数 | |
ARC_CNT | 3:0 | 0 | R | 重发射数据包次数 | |
09 | RPD | 接收电源检测 | |||
Reserved | 7:1 | 000000 | R | 只能为0 | |
RPD | 0 | 0 | R | 接收电源检测 | |
0A | RX_ADDR_P0 | 39:0 | E7E7E7E7E7 | R/W | 通道0接收地址(5个字节) |
0B | RX_ADDR_P1 | 39:0 | C2C2C2C2C2 | R/W | 通道1接收地址(5个字节) |
0C | RX_ADDR_P2 | 7:0 | C3 | R/W | 通道2接收地址(1个字节(低)) |
0D | RX_ADDR_P3 | 7:0 | C4 | R/W | 通道3接收地址(1个字节(低)) |
0E | RX_ADDR_P4 | 7:0 | C5 | R/W | 通道4接收地址(1个字节(低)) |
0F | RX_ADDR_P5 | 7:0 | C6 | R/W | 通道5接收地址(1个字节(低)) |
10 | TX_ADDR | 39:0 | E7E7E7E7E7 | R/W | 发射地址,仅适用于PTX |
11 | RX_PW_P0 | 在通道0中RX_Payload的字节个数 | |||
Reserved | 7:6 | 00 | R/W | 只能为0 | |
RX_PW_P0 | 5:0 | 0 | R/W | 接收数据通道0字节数 | |
12 | RX_PW_P1 | … | … | … | 在通道1中RX_Payload的字节个数 |
13 | RX_PW_P2 | … | … | … | 在通道2中RX_Payload的字节个数 |
14 | RX_PW_P3 | … | … | … | 在通道3中RX_Payload的字节个数 |
15 | RX_PW_P4 | … | … | … | 在通道4中RX_Payload的字节个数 |
16 | RX_PW_P5 | … | … | … | 在通道5中RX_Payload的字节个数 |
17 | FIFO_STATUS | FIFIO状态寄存器 | |||
Reserved | 7 | 0 | R/W | 只能为0 | |
TX_REUSE | 6 | 0 | R | 重用TX Payload | |
TX_FULL | 5 | 0 | R | TX FIFO满标志 | |
TX_EMPTY | 4 | 1 | R | TX FIFO空标志 | |
Reserved | 3:2 | 00 | R/W | 只能为0 | |
RX_FULL | 1 | 0 | R | RX FIFO满标志 | |
RX_EMPTY | 0 | 1 | R | RX FIFO空标志 | |
1C | DYNPD | 使能动态数据包长度 | |||
Reserved | 7:6 | 0 | R/W | 只能为0 | |
DPL_P5 | 5 | 0 | R/W | 使能pipe 5动态数据包长度 | |
DPL_P4 | 4 | 0 | R/W | 使能pipe 4动态数据包长度 | |
DPL_P3 | 3 | 0 | R/W | 使能pipe 3动态数据包长度 | |
DPL_P2 | 2 | 0 | R/W | 使能pipe 2动态数据包长度 | |
DPL_P1 | 1 | 0 | R/W | 使能pipe 1动态数据包长度 | |
DPL_P0 | 0 | 0 | R/W | 使能pipe 0动态数据包长度 | |
1D | FEATURE | R/W | 特征寄存器 | ||
Reserved | 7:3 | 0 | R/W | 只能为0 | |
EN_DPL | 2 | 0 | R/W | 使能动态数据包长度 | |
EN_ACK_PAY | 1 | 0 | R/W | 使能数据包应答 | |
EN_DYN_ACK | 0 | 0 | R/W | 使能写发送数据包非应答命令 |
如果NRF24L01用作具有Enhanced ShockedBurst 的PTX设备,那么将TX_ADDR寄存器里边的数值(地址)设置成通道0接收地址,使两个相同。
接下来看看该芯片的时序图:
(1) 写时序
(2) 读时序
注:C7-C0 表示命令,S7~S0表示状态,Dx为数据位。
从时序上可以知道,读写时序是:先拉低片选CSN,在SCK的第一个上升沿开始传输数据;SCK在无效的状态下为低电平。这就给我们提示:在配置硬件SPI的时候要配置成SPI模式0(时钟极性0,时钟相位0)。
那么现在根据当前了解到的知识,配置STM32的SPI,这里使用标准库函数:
//初始化24L01的IO口
//这个程序是在开发板正点原子精英板F103上面调试的,SPI2的总线上挂载两个设备,将另一个设备(W25Q)的片选拉高,不选中该设备,防止它的影响
#define W25Q_CSN_PIN GPIO_Pin_12
#define NRF_CSN_PIN GPIO_Pin_7
//初始化相关的IO口(片选以及SPI)
void MySPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOG, ENABLE); //使能PB,G端口时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//SPI2时钟使能
GPIO_InitStructure.GPIO_Pin = W25Q_CSN_PIN; //PB12上拉 防止W25X的干扰
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化指定IO
GPIO_SetBits(GPIOB,W25Q_CSN_PIN);//上拉
GPIO_InitStructure.GPIO_Pin = NRF_CSN_PIN|NRF_CE_PIN; //PG8 7 推挽
GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化指定IO
GPIO_InitStructure.GPIO_Pin = NRF_IRQ_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PG6 输入
GPIO_Init(GPIOG, &GPIO_InitStructure);
GPIO_ResetBits(GPIOG,NRF_IRQ_PIN|NRF_CSN_PIN|NRF_CE_PIN);//PG6,7,8上拉
/****************************SPI2_Init*******************************/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15上拉
SPI_Cmd(SPI2, DISABLE); // SPI外设不使能
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //SPI主机
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //时钟悬空低
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //数据捕获于第1个时钟沿
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由软件控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; //定义波特率预分频的值:波特率预分频值为16
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI2, ENABLE); //使能SPI外设
/********************************************************************/
}
//SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
{
retry++;
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET)//检查指定的SPI标志位设置与否:接受缓存非空标志位
{
retry++;
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据
}
至此,SPI的初始化配置和通行API函数已经写好,接下来define下片选以及CE使能的IO口:
#define NRF24L01_CE PGout(8) //24L01片选信号
#define NRF24L01_CSN PGout(7) //SPI片选信号
/******下面的中断引脚程序上不用,调试的时候用过而已******/
#define NRF24L01_IRQ PGin(6) //IRQ主机数据输入
接下来就进一步根据时序图来编写读写函数了:
/***************************************************************
* 写寄存器
****************************************************************/
uint8_t Write_Reg(uint8_t reg, uint8_t value)
{
uint8_t status;
NRF24L01_CSN = 0; /* 选通器件 */
status = SPI2_ReadWriteByte(reg); /* 写寄存器地址 */
SPI2_ReadWriteByte(value); /* 写数据 */
NRF24L01_CSN = 1; /* 禁止该器件 */
return status;
}
/***************************************************************
* 读寄存器
****************************************************************/
uint8_t Read_Reg(uint8_t reg)
{
uint8_t reg_val;
NRF24L01_CSN = 0; /* 选通器件 */
SPI2_ReadWriteByte(reg); /* 写寄存器地址 */
reg_val = SPI2_ReadWriteByte(0); /* 读取该寄存器返回数据 */
NRF24L01_CSN = 1; /* 禁止该器件 */
return reg_val;
}
扩展至连续读连续写,那么还有下面函数:
/****************************************************************
* 写缓冲区----------------------------------
*****************************************************************/
uint8_t Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t uchars)
{
uint8_t i;
uint8_t status;
NRF24L01_CSN = 0; /* 选通器件 */
status = SPI2_ReadWriteByte(reg); /* 写寄存器地址 */
for(i=0; i<uchars; i++)
{
SPI2_ReadWriteByte(pBuf[i]); /* 写数据 */
}
NRF24L01_CSN = 1; /* 禁止该器件 */
return status;
}
/****************************************************************
* 读缓冲区-------------------------------
****************************************************************/
uint8_t Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t uchars)
{
uint8_t i;
uint8_t status;
NRF24L01_CSN = 0; /* 选通器件 */
status = SPI2_ReadWriteByte(reg); /* 写寄存器地址 */
for(i=0; i<uchars; i++)
{
pBuf[i] = SPI2_ReadWriteByte(0); /* 读取返回数据 */
}
NRF24L01_CSN = 1; /* 禁止该器件 */
return status;
}
至此,单片机与NRF芯片的通信手段已经搭好了,可以开始配置NRF24L01(+)了。
首先声明寄存器
#define TX_ADR_WIDTH 5
#define RX_ADR_WIDTH 5
#define RX_PLOAD_WIDTH 50 //最大64
#define TX_PLOAD_WIDTH 50 //最大64
//***************************************NRF24L01寄存器指令*******************************************************
#define NRF_READ_REG 0x00 // 读寄存器指令
#define NRF_WRITE_REG 0x20 // 写寄存器指令
#define R_RX_PL_WID 0x60 // 读取RX payload宽度指令
#define RD_RX_PLOAD 0x61 // 读取接收数据指令
#define WR_TX_PLOAD 0xA0 // 写待发数据指令
#define FLUSH_TX 0xE1 // 冲洗发送 FIFO指令
#define FLUSH_RX 0xE2 // 冲洗接收 FIFO指令
#define REUSE_TX_PL 0xE3 // 定义重复装载数据指令
#define NOP 0xFF // 保留
//*************************************(nRF24L01)寄存器映射地址****************************************************
#define CONFIG 0x00 // 配置收发状态,CRC校验模式以及收发状态响应方式
#define EN_AA 0x01 // 自动应答功能设置
#define EN_RXADDR 0x02 // 可用信道设置
#define SETUP_AW 0x03 // 收发地址宽度设置
#define SETUP_RETR 0x04 // 自动重发功能设置
#define RF_CH 0x05 // 工作频率设置
#define RF_SETUP 0x06 // 发射速率、功耗功能设置
#define NRFRegSTATUS 0x07 // 状态寄存器
#define OBSERVE_TX 0x08 // 发送监测功能
#define CD 0x09 // 地址检测
#define RX_ADDR_P0 0x0A // 频道0接收数据地址
#define RX_ADDR_P1 0x0B // 频道1接收数据地址
#define RX_ADDR_P2 0x0C // 频道2接收数据地址
#define RX_ADDR_P3 0x0D // 频道3接收数据地址
#define RX_ADDR_P4 0x0E // 频道4接收数据地址
#define RX_ADDR_P5 0x0F // 频道5接收数据地址
#define TX_ADDR 0x10 // 发送地址寄存器
#define RX_PW_P0 0x11 // 接收频道0接收数据长度
#define RX_PW_P1 0x12 // 接收频道1接收数据长度
#define RX_PW_P2 0x13 // 接收频道2接收数据长度
#define RX_PW_P3 0x14 // 接收频道3接收数据长度
#define RX_PW_P4 0x15 // 接收频道4接收数据长度
#define RX_PW_P5 0x16 // 接收频道5接收数据长度
#define FIFO_STATUS 0x17 // FIFO栈入栈出状态寄存器设置
#define DYNPD 0x1C // 使能动态数据包长度
#define FEATURE 0x1D // 主要使能DYNPD
下面就开始写NRF的初始化函数:
void NRF_Init(u8 mode, u8 ch)//初始化,mode=PTX/PRX,ch频宽
{
MySPI2_Init();
NRF24L01_CE = 0;
Write_Reg(NRF_WRITE_REG + SETUP_AW, 0x03);
Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(uint8_t *)RX_ADDRESS,5);
Write_Buf(NRF_WRITE_REG+TX_ADDR,(uint8_t *)TX_ADDRESS,5);
Write_Reg(NRF_WRITE_REG+EN_AA,0x01); //使能通道0的自动应答
Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01); //使能通道0的接收地址
Write_Reg(NRF_WRITE_REG+SETUP_RETR,0x1a); //设置自动重发间隔时间:500us;最大自动重发次数:10次 2M波特率下
Write_Reg(NRF_WRITE_REG+RF_CH,ch); //设置RF通道为CHANAL
Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f); //设置TX发射参数,0db增益,2Mbps,低噪声增益开启
if(mode==1) //PRX
{
Write_Reg(NRF_WRITE_REG + CONFIG, 0x0f); // IRQ收发完成中断开启,16位CRC,主接收
Write_Reg(FLUSH_TX,0xff);
Write_Reg(FLUSH_RX,0xff);
Write_Reg(NRF_WRITE_REG+0x1c,0x01);
Write_Reg(NRF_WRITE_REG+0x1d,0x06);
}
else if(mode==2) //TX2
{
Write_Reg(NRF_WRITE_REG + CONFIG, 0x0e); // IRQ收发完成中断开启,16位CRC,主发送
Write_Reg(FLUSH_TX,0xff);
Write_Reg(FLUSH_RX,0xff);
Write_Reg(NRF_WRITE_REG+0x1c,0x01);
Write_Reg(NRF_WRITE_REG+0x1d,0x06);
}
NRF24L01_CE = 1;
}
数据传输涉及到发射数据包和接收数据包,另外,有必要检测NRF是否初始化正常,还需要检测环节。
//就是试着通过SPI写一串数据,然后再读出来,对比下,没问题返回1
u8 NRF_Check(void)//检查NRF模块是否正常工作
{
u8 buf1[5];
u8 i;
/*写入5个字节的地址. */
Write_Buf(NRF_WRITE_REG+TX_ADDR,(uint8_t *)TX_ADDRESS,5);
/*读出写入的地址 */
Read_Buf(TX_ADDR,buf1,5);
/*比较*/
for(i=0;i<5;i++)
{
if(buf1[i]!=TX_ADDRESS[i])
break;
}
if(i==5)
return 1; //MCU与NRF成功连接
else
return 0; //MCU与NRF不正常连接
}
/****************************************************************
*打包发送
*****************************************************************/
void TxPacket(uint8_t * tx_buf, uint8_t len)
{
NRF24L01_CE = 0;
//可向别的模块(地址)发射数据
Write_Buf(NRF_WRITE_REG + RX_ADDR_P0, TX_ADDRESS, 5); // 装载接收端地址
Write_Buf(WR_TX_PLOAD, tx_buf, len); // 装载数据
NRF24L01_CE = 1; //置高CE,激发数据发送
}
void NRF_Send_Data(u8 *data , u8 length)
{
TxPacket(data,length);
//while(NRF24L01_IRQ!=0);
}
void Receive_Data(void)//检查是否有通信事件
{
u8 sta = Read_Reg(NRF_READ_REG + NRFRegSTATUS);
//while(NRF24L01_IRQ!=0);//等待发送完成
//接收到数据包后,再对数据进行辨别
if(sta & (1<<RX_DR))//接收中断
{
u8 rx_len = Read_Reg(R_RX_PL_WID);
Read_Buf(RD_RX_PLOAD,NRF24L01RXDATA,rx_len);
//Data_Receive_PROGRAM
printf("...");
Data_Receive_PRO();
}
if(sta & (1<<MAX_RT))
{
if(sta & 0x01) //TX FIFO FULL
{
Write_Reg(FLUSH_TX,0xff);
}
}
//很多单片机或者是其他芯片,清除状态为都是往相应位写1
Write_Reg(NRF_WRITE_REG + NRFRegSTATUS, sta);//写1清除状态寄存器
sta = Read_Reg(NRF_READ_REG + NRFRegSTATUS);
}
void Data_Receive_PRO(void)
{
if((NRF24L01RXDATA[0] == 0x01) && (NRF24L01RXDATA[1]==0x02))//帧头
{
Rec_ADC_Raw_Val = ((uint16_t)NRF24L01RXDATA[2]<<8) + NRF24L01RXDATA[3];
}
else
return;
}
至此,NRF24L01的驱动程序已经准备好了,接下来主程序调用:
(1)主发送主程序demo:
#include "led.h"
#include "sys.h"
#include "usart.h"
#include "24l01.h"
uint16_t ms_2=0,ms_10=0,ms_500=0;
uint8_t Txdata_buffer1[64]={0x01,0x02,0x12,0x34,0x56,0x78,0x9A,0xBC,0xDE};
uint16_t Rec_ADC_Raw_Val;
static u8 blink_flag=0;
int main(void)
{
u8 blink_flag2=0;
//简单点,省去配置通用定时器的麻烦,直接用滴答定时器
//注意这里开启滴答定时器中断就不要用正点原子的delay.c了
//一个完美的程序尽量不去用延时,占用CPU资源
//不精确延时可以自己写
SysTick_Config(SystemCoreClock / 1000);//开启滴答定时器中断(72000000/1000)/72MHz = 1ms,即定时1ms中断一次
uart_init(115200); //串口初始化为115200
LED_Init(); //初始化与LED连接的硬件接口
//下面的函数第一个入口参数为PTX,表示设置为主发送
//其实,作为两个nRF实现双向通讯,另一方只需要设置为PRX即可。
NRF_Init(PTX,80); //初始化NRF24L01
while(NRF_Check()==0)
printf("NRF24L01 disconnected!/n");
while(1)
{
if(ms_10>100)//每100ms发射一次数据包,闪一次灯,表明正常执行
{
ms_10 = 0;
/*调试用****/
if(blink_flag2 == 0)
{
PEout(5) = 0;
blink_flag2 = 1;
}
else if(blink_flag2 == 1)
{
PEout(5) = 1;
blink_flag2 = 0;
}
/*****只自加这个元素*******/
Txdata_buffer1[2]++;
NRF_Send_Data(Txdata_buffer1,7);
}
if(ms_2>2)//每2ms检查NRF是否有通信事件
{
Receive_Data();
ms_2 =0;
}
if(ms_500>500)//每0.5秒led灯闪一次,串口打印接收到的ADC原始值。
{
if(blink_flag == 0){
PBout(5) = 0;
blink_flag = 1;
}
else{
PBout(5) = 1;
blink_flag = 0;
}
printf("the Raw ADC val is %d.\n",Rec_ADC_Raw_Val);
ms_500 = 0;
}
}
}
上面的ms_2,ms_10,ms_500在文件stm32f10x_it.c中的滴答定时器中断更新。
void SysTick_Handler(void)
{
ms_10++;
ms_500++;
ms_2++;
}
(2)主接收主程序demo:
//平台用的是潘多拉开发板stm32L475
int main(void)
{
HAL_Init();
SystemClock_Config(); //初始化系统时钟为80M
delay_init(80); //初始化延时函数 80M系统时钟
Usart1_Init(115200);
ADC1IN3_Init();
LED_Init(); //初始化LED
NRF_Init(PRX,80);
while(NRF_Check()==0);
LCD_Init();//IIC接口屏
LCD_Clear(WHITE);
TIM2_Init(100 - 1, 8000 - 1);//10ms
//Iwdg_Init();
while(1)
{
LED_Function();//每0.5秒LED闪一次
Schedule_100ms();//每100ms在显示屏上面,这里只显示了在主发送方那边自加的Txdata_buffer1[2]
Schedule_Rec_data();//每10ms检查有没有接收到数据
Schedule_Send_data();//每50ms发送数据包
}
}
void Schedule_Send_data(void)
{
uint8_t temp[10];
if(count_Send_data>5)
{
temp[0] = 0x01;
temp[1] = 0x02;
temp[2] = adc_test/256;
temp[3] = adc_test%256;
NRF_Send_Data(temp, 4);
count_Send_data = 0;
}
}
void Schedule_Rec_data(void)
{
if(count_Rec_data>1)
{
Receive_Data();
count_Rec_data = 0;
}
}
void Schedule_100ms(void)
{
if(counter_100ms > 10)
{
Get_ADC();
Get_AHT10_Data();
LCD_ShowNum(100, 200, Rec_Test_Buf[0] , 5, 16);
counter_100ms = 0;
}
}