一、思维导图
3种主要工作模式、8个命令字、每包32字节有效数据,等等这些,直接思维导图更有效,不用文字啰嗦。
对主要点进行了组织,包括了工作模式、命令字等图例,认真过一遍,心里马上就能有个框框。
点击一下图片能变成清晰的大图。当然,右击保存就更方便查看了。
二、 引脚连接,解释
明确一个,NRF24L01和SI24R1的引脚、程序是通用的,无需任何修改,市面上大部份的NRF24L01模块,用的是SI24R1的芯片。两者间通信也互通,已测试确认!
下面是一个通用模块的引脚,以安信可的SI24R1模块为例:
共8个引脚:
- VCC:3.3V。1.9~3.6V都成,不要接5V,马上烧!
- GND:接地
- CE: 模式控制。其高低电平,配合CONFIG寄存器PRIM_RX和PWR_UP两个位,可切换工作状态: 发送、接收 、待机 。
- IRQ: 中断信号引脚。发生以下三种中断时,引脚电平被NRF拉低:发送成功、接收到数据、已达最大重发次数。
- CSN: SPI的CS片选引脚
- SCK: SPI的时钟线引脚
- MOSI: SPI的主出从入数据引脚
- MISO: SPI的主入从出数据引脚
三、SPI初始化、函数封装
NRF的配置,就是把各种参数(数值),如频道,速率,目标地址等,用SPI方式写到指定地址(芯片的寄存器).
这个写入配置的动作,拆分开来看,理解为两部分,是在操作两种通信,别混乱。
首先主机按NRF datasheet的要求,设置和通过SPI通信,向NRF芯片特定地址(寄存器)写入参数值;
而这些写入的数值,就是用于控制NRF与别一个NRF的通信参数。
这两个通信,理解一下~
1:主机和NRF间的通信:
- 使用SPI,主机完成对NRF操控,所有操控其实就是4个操作:写参数、读参数、写要发送的数据,读出收到的数据;
- 上面的操控,分拆到SPI的操作上,就是常用的6个函数:SPI初始化、字节收发、写1字节,读1字节,写N字节,读N字节;
- 写入参数:是NRF与NRF间通信的参数值, 如频道 ,速率,CRC校检,自动回答,自动重发,目标地址....
- 读取参数,主要是STATUS状态寄存器的值,用于判断中断源;
- 写入发送数据,把待发送的数据写到TX_FIFO. NRF按包发送数据,包中有效数据最大32字节;要手动做分包处理;
- 读出收到数据,检测到RX_DR中断发生后(IRQ引脚被拉低),用SPI把RX_FIFO缓冲区的值,读取存放到指自已的缓冲区;
- NRF的SPI通信速度可达10MHz,但为保证数据传输的完整不掉包,尽量不要超过8MHz;
- NRF要求SPI通信时,在上升沿采样数据,要注意时序。
2:NRF和NRF间的收发通信:
- NRF按主机刚才写到芯片的参数值开始工作(手动),通过电波传输到另一个芯片中或接收别一芯片的数据(自动)。
- 这部份通信我们要做的,仅是控制CE引脚的高低电平,配合CONFIG寄存器,使NRF在接收、发送、待机三种状态切换。
- NRF间的无线通信,基本没我们什么事。
编程顺序: GPIO配置 > SPI配置 > SPI(5个小函数) > 写NRF配置 > NRF中断处理(3种情况) > NRF收、发(2个主函数)
GPIO初始化代码:
/*** SPI通信引脚, CS, SCK, MOSI, MISO ***/
GPIOSet (NRF24L01_SPI_CSN_GPIO, NRF24L01_SPI_CSN_PIN, G_MODE_OUT , G_OTYPE_PP, G_OSPEED_25M, G_PUPD_UP , 0); // cs
GPIOSet (NRF24L01_SPI_CLK_GPIO , NRF24L01_SPI_CLK_PIN , G_MODE_AF , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN , G_AF_SPI2 ); // sck
GPIOSet (NRF24L01_SPI_MOSI_GPIO ,NRF24L01_SPI_MOSI_PIN , G_MODE_AF , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN , G_AF_SPI2 ); // mosi
GPIOSet (NRF24L01_SPI_MISO_GPIO ,NRF24L01_SPI_MISO_PIN , G_MODE_AF , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN , G_AF_SPI2 ); // miso
/*** NRF控制引脚, CE, IRQ ***/
GPIOSet (NRF24L01_CE_GPIO , NRF24L01_CE_PIN , G_MODE_OUT , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN , 0); // CE
GPIOSet (NRF24L01_IRQ_GPIO , NRF24L01_IRQ_PIN , G_MODE_IN , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_UP , 0); // IRQ NRF24L01芯片的IRQ引脚下拉能力很弱,注意外部上拉电阻大小,及MCU的上下拉
GPIOSet是个自己封装的初始化函数,不用管,重点看其中的参数就好,注意各上下拉。
SPI 初始化代码:
/*** SPI通信部分 ***/
CSN_HIGH; //失能NRF
NRF24L01_SPI_EN_CLOCK ; // 时钟
NRF24L01_SPIX->CR1 = 0; // 清0
NRF24L01_SPIX->CR1 |= 0<<0; // 采样沿数, NRF要求上升沿采样 CPHA:时钟相位,0x1=在第2个时钟边沿进行数据采样,
NRF24L01_SPIX->CR1 |= 0<<1; // 时钟线闲时极性, CPOL:时钟极性,0x1=空闲状态时,SCK保持高电平
NRF24L01_SPIX->CR1 |= 1<<2; // 主从模式, 0=从,1=主
NRF24L01_SPIX->CR1 |= 2<<3; // 波特率控制[5:3], 0=fPCLK/2, 1=/4倍 2=/8 3/16
NRF24L01_SPIX->CR1 |= 0<<7; // LSB先行, 0=MSB, 1=LSB
NRF24L01_SPIX->CR1 |= 1<<8; // 内部从器件选择,根据9位设置(失能内部NSS)
NRF24L01_SPIX->CR1 |= 1<<9; // 软件从器件管理 : 0=禁止软件管理从设备, 1=使能软件从器件管理(软件NSS)
NRF24L01_SPIX->CR1 |= 0<<11; // 数据帧格式, 0=8位, 1=16位
NRF24L01_SPIX->CR1 |= 1<<6; // 使能
SPI 初始化重点:
- NRF要求在上升沿时采集数值。这就要闲时电平和采样沿要配合好。
- NRF的SPI可达10MHz, 但实际使用时,不要超过8MHz。这个在SPI的波特率中可控制,因为SPI通信速率受限低速一方。
- 重要:如果多个设备共用同一SPI,把各自的SPI配置封装成函数,每次使用SPI通信前各自调用一次,以使用自己的配置。
插个话题,为什么使用寄存器操作编程?因为:使用寄存器=简单+清晰,你看,不是吗?
SPI收发函数
/*****************************************************************************
*函 数: u8 SPI_RW(u8 Data)
*功 能: SPI写入一个字节,并返回一个字节
*参 数: 要写入的一字节
*返回值: 返回一字节数据
*****************************************************************************/
u8 SPI_SendByte(u8 Data)
{
u8 retry =0;
while((NRF24L01_SPIX ->SR & 2) == 0){ // 理解方式,应该把前式的结果理解为一个寄存器位值,如果这个位值是等号后面的值,就等待
retry++;
if(retry>200) return 0;
}
NRF24L01_SPIX ->DR = Data;
retry=0;
while((NRF24L01_SPIX->SR & 1) == 0 ){
retry++;
if(retry>200) return 0;
}
return NRF24L01_SPIX->DR ;
}
/*****************************************************************************
*函 数:u8 Nrf24l01_WriteReg(u8 reg,u8 value)
*功 能:向指定寄存器地址,写一个字节数据
*参 数:reg: 寄存器地址
* val: 要写入的值
*返回值:status
*****************************************************************************/
u8 Nrf24l01_WriteReg(u8 reg,u8 value)
{
u8 status;
CSN_LOW;
status = SPI_SendByte(reg) ;
SPI_SendByte(value);
CSN_HIGH;
return status;
}
/*****************************************************************************
*函 数:u8 NRF24l01_read_reg(u8 reg)
*功 能:向指定寄存器地址,读出一字节数据
*参 数:reg: 寄存器地址
*返回值:reg_val(第二个读取到的字节)
*****************************************************************************/
u8 Nrf24l01_ReadReg(u8 reg)
{
u8 reg_val;
CSN_LOW;
SPI_SendByte(reg);
reg_val = SPI_SendByte(0xFF);
CSN_HIGH;
return reg_val;
}
/*****************************************************************************
*函 数:u8 Nrf24l01_WriteBuf(u8 reg, u8 *pBuf, u8 len)
*功 能:写一组数据到寄存器
*参 数:reg: 寄存器地址
* pBuf: 要写入数据的地址
* len: 要写入的数据长度
*返回值:status
*备 注:NRF2401代码移植只需把SPI驱动修改成自己的即可
*****************************************************************************/
u8 Nrf24l01_WriteBuf(u8 reg, u8 *pBuf, u8 len)
{
u8 status;
CSN_LOW;
status = SPI_SendByte(reg);
for(u8 i=0; i
SPI_SendByte(pBuf
);
}
CSN_HIGH;
return status;
}
/*****************************************************************************
*函 数: u8 vNrf24l01_ReadBuf(u8 reg, u8 *pBuf, u8 len)
*功 能: 向指定寄存器地址,读出指定长度的数据
*参 数: reg : 寄存器地址
* pBuf : 数据存放缓冲区
* len : 读取的字节数量
*返回值: status : 设备状态字
*****************************************************************************/
u8 Nrf24l01_ReadBuf(u8 reg, u8 *pBuf, u8 len)
{
u8 status;
CSN_LOW;
status = SPI_SendByte(reg);
for(u8 i = 0; i
pBuf = SPI_SendByte(0xFF);
}
CSN_HIGH;
return status;
}
注意:在进入下一步配置NRF24L01前,必须先测试一下SPI通信是否成功 。一般是往NRF的发送地址寄存器写入五个字节,再读出来,把读出的数据和原数据对比, 就能知道SPI是否配置正确、 NRF24L01是否连接成功。
代码程序中,有个连接测试函数,可以作模块的连接,没有贴上来,留邮箱吧。
四、NRF24L01参数写入
使用上面初始化的SPI,和刚封装好的几个函数,就可以把需要的参数,写到NRF特定的地址(寄存器), 完成对其配置。
NRF24L01 参数配置代码:
/*** NRF24L01通信配置 30,2M,***/
CE_LOW; // 热待机模式, 只有在ce置低时,才能配置寄存器
//delayUs(2000); // PowerDown 切换为 PowerUp需要1.5ms
Nrf24l01_WriteReg(W_REGISTER + RF_CH, 30); // 射频通道,即频率(0-125)
Nrf24l01_WriteReg(W_REGISTER + RF_SETUP, 0x0F); // 设置TX发射参数,0db增益,2Mbps,低噪声增益关闭 (注意:低噪声增益关闭/开启直接影响通信,要开启都开启,要关闭都关闭0x0f)0x07
Nrf24l01_WriteReg(W_REGISTER + SETUP_AW, 0x03); // 地址长度,默认值时0x03,即5字节
Nrf24l01_WriteBuf(W_REGISTER + TX_ADDR, (u8*)TX_ADDRESS, 5); // 写TX节点地址, 地址宽度:5字节,40位
Nrf24l01_WriteBuf(W_REGISTER + RX_ADDR_P0, (u8*)TX_ADDRESS, 5); // 设置TX节点地址,主要为了使能ACK,, 地址宽度:5字节,40位
Nrf24l01_WriteReg(W_REGISTER + SETUP_RETR, 0x0A); // 设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次 0x1A
Nrf24l01_WriteReg(W_REGISTER + EN_RXADDR, 0x01); // 使能通道0的接收地址
Nrf24l01_WriteReg(W_REGISTER + EN_AA, 0x01); // 使能通道0自动应答
Nrf24l01_WriteReg(W_REGISTER + RX_PW_P0, 32); // 选择通道0的有效数据宽度
Nrf24l01_WriteReg(FLUSH_TX, 0xff); // 清除TX_FIFO
Nrf24l01_WriteReg(W_REGISTER+STATUS, 0X7E); // 清除所有中断,防止一进去接收模式就触发中断
Nrf24l01_WriteReg(W_REGISTER+CONFIG, 0x0F); // 配置为接收模式
CE_HIGH; // CE置高,进入状态
重点:
- CE引脚置低,方可配置NRF寄存器! 各教程都这教的,但测试过不置低也可正常配置。
- RF_CH,:频道,2.4G为基址,1M为间隔值,如上面的30,代表使用2.430G的频率进行芯片间通信。可设值0~125。
- RF_SETUP: 发射功率可配置,说白了就是的耗电程度。接收状态的功率是不能设置的,所以接收才是耗电的大头。数据传输率,常说的空中速率,一般设置2M
- 地址长度,5字节,这个很独特
- TX_ADDR:数据要发送到的目标地址
- RX_ADDR_P0: 接收通道0的接收地址,接收这个地址设备发来的数据,这里设为和TX_ADDR一致,是为了自动应答。
- NRF共有6个接收通道,p0~p5, 可同时监听6个不同地址的信号,p0通道也用于作自动应答作用。
- TX_FIFO, 发送数据缓冲区,96字节,32字节为一组,共3组,可理解为缓冲区可存放3组发送数据。
- RX_FIFO, 同上,两个FIFO相互独立。 虽然是FIFO,虽然有3组,但最好还是一包一包收发,状态切换时间难把握。
- 倒数第二行,配置为接收模式,注意,这个时候还没开始工作的,还处于PownDown状态,状态和模式是两回事。
- 最后一行,CE置高10us后,才进入工作状态,因之前配置的是接收模式,所以将进入接收状态
五、中断处理函数
为什么要先说中断?感觉先了解了中断,那么发送、接收就更好理解。
其实不应该叫中断的,但这样好理解,还是遵从约定俗成吧。
中断时, IRQ电平被拉低,是由NRF控制产生的,三种情况可触发:发送成功、达到重发最大次数、接收到数据。
发送成功:
- 1:PTX发送数据后,马上开始计时,130us后切换到接收模式,此时计时还在继续
- 2:PRX在收到数据后,经CRC校检,数据完整后发回ACK信号
- 3:PTX在规定时间内收到ACK信号,则置位TX_DS标志,IRQ引脚被拉低
达到重发最大次数:
- 1:PTX发送数据后,马上开始计时,130us后切换到接收模式,此时计时还在继续
- 2:PTX在规定时间内,没收到ACK信号,原因很多:如PRX没收到数据,CRC校验错误....
- 3:PTX再次发送一次数据,重复步骤1
- 4:SETUP_RETR寄存器可设置重发的次数,达到最大次数后,MAX_RT位被置位,IRQ引脚被拉低。
接收到数据:
- PRX收到数据,经CRC校检,数据完整,有效数据存放到RX_FIFO,RX_DR位置高,IRQ引脚被拉低。
说说清理中断,要清理的中断有两处:
- NRF的中断位,上面的三种标志中断位,都在寄存器STATUS中,各位置1可清0,IRQ引脚即回到高电平。
- 单片机mcu的中断位,清理外部中断线标志位,否则会在中断函数里死循环。
中断处理函数代码:
void NRF24L01_IRQ_IRQHANDLER(void)
{
u8 status=0 ;
CE_LOW; // 拉低CE,以便读取NRF中STATUS中的数据
status = Nrf24l01_ReadReg(R_REGISTER + STATUS); // 读取STATUS中的数据,以便判断是由什么中断源触发的IRQ中断
/*** 发送完成中断 ***/
if(status & STATUS_TX){
Nrf24l01_WriteReg(W_REGISTER+STATUS, STATUS_TX); // 清NRF中断:发送完成
Nrf24l01_WriteReg(FLUSH_TX, 0xff); // 清发送缓冲区:TX_FIFO
printf("rn发送成功!!!!rn");
vNrf24l01_RxMode (); // 切换为接收状态
}
/*** 接收完成中断 ***/
if(status & STATUS_RX){
memset (NRF_RX_DATA , 0, 32);
Nrf24l01_ReadBuf(R_RX_PAYLOAD, NRF_RX_DATA , RX_PAYLO_WIDTH); // 读数据
Nrf24l01_WriteReg(W_REGISTER+STATUS, STATUS_RX); // 清NRF中断:收到数据
Nrf24l01_WriteReg(FLUSH_RX,0xff); // 清除RX FIFO(注意:这句话很必要)
printf("rn接收到数据: %srn", NRF_RX_DATA);
vNrf24l01_RxMode (); // 切换为接收状态
}
/*** 最大重发次数中断 ***/
if(status & STATUS_MAX){
Nrf24l01_WriteReg(W_REGISTER+STATUS, 0x70); // 清NRF中断:三个
Nrf24l01_WriteReg(FLUSH_TX, 0xff); // 清除TX_FIFO
printf("rn发送失败,达到最大重发次数!!!rn");
vNrf24l01_RxMode(); // 切换为接收状态
}
EXTI->PR |= NRF24L01_IRQ_PIN ; // 清理外部中断线标志位
}
一、思维导图
3种主要工作模式、8个命令字、每包32字节有效数据,等等这些,直接思维导图更有效,不用文字啰嗦。
对主要点进行了组织,包括了工作模式、命令字等图例,认真过一遍,心里马上就能有个框框。
点击一下图片能变成清晰的大图。当然,右击保存就更方便查看了。
二、 引脚连接,解释
明确一个,NRF24L01和SI24R1的引脚、程序是通用的,无需任何修改,市面上大部份的NRF24L01模块,用的是SI24R1的芯片。两者间通信也互通,已测试确认!
下面是一个通用模块的引脚,以安信可的SI24R1模块为例:
共8个引脚:
- VCC:3.3V。1.9~3.6V都成,不要接5V,马上烧!
- GND:接地
- CE: 模式控制。其高低电平,配合CONFIG寄存器PRIM_RX和PWR_UP两个位,可切换工作状态: 发送、接收 、待机 。
- IRQ: 中断信号引脚。发生以下三种中断时,引脚电平被NRF拉低:发送成功、接收到数据、已达最大重发次数。
- CSN: SPI的CS片选引脚
- SCK: SPI的时钟线引脚
- MOSI: SPI的主出从入数据引脚
- MISO: SPI的主入从出数据引脚
三、SPI初始化、函数封装
NRF的配置,就是把各种参数(数值),如频道,速率,目标地址等,用SPI方式写到指定地址(芯片的寄存器).
这个写入配置的动作,拆分开来看,理解为两部分,是在操作两种通信,别混乱。
首先主机按NRF datasheet的要求,设置和通过SPI通信,向NRF芯片特定地址(寄存器)写入参数值;
而这些写入的数值,就是用于控制NRF与别一个NRF的通信参数。
这两个通信,理解一下~
1:主机和NRF间的通信:
- 使用SPI,主机完成对NRF操控,所有操控其实就是4个操作:写参数、读参数、写要发送的数据,读出收到的数据;
- 上面的操控,分拆到SPI的操作上,就是常用的6个函数:SPI初始化、字节收发、写1字节,读1字节,写N字节,读N字节;
- 写入参数:是NRF与NRF间通信的参数值, 如频道 ,速率,CRC校检,自动回答,自动重发,目标地址....
- 读取参数,主要是STATUS状态寄存器的值,用于判断中断源;
- 写入发送数据,把待发送的数据写到TX_FIFO. NRF按包发送数据,包中有效数据最大32字节;要手动做分包处理;
- 读出收到数据,检测到RX_DR中断发生后(IRQ引脚被拉低),用SPI把RX_FIFO缓冲区的值,读取存放到指自已的缓冲区;
- NRF的SPI通信速度可达10MHz,但为保证数据传输的完整不掉包,尽量不要超过8MHz;
- NRF要求SPI通信时,在上升沿采样数据,要注意时序。
2:NRF和NRF间的收发通信:
- NRF按主机刚才写到芯片的参数值开始工作(手动),通过电波传输到另一个芯片中或接收别一芯片的数据(自动)。
- 这部份通信我们要做的,仅是控制CE引脚的高低电平,配合CONFIG寄存器,使NRF在接收、发送、待机三种状态切换。
- NRF间的无线通信,基本没我们什么事。
编程顺序: GPIO配置 > SPI配置 > SPI(5个小函数) > 写NRF配置 > NRF中断处理(3种情况) > NRF收、发(2个主函数)
GPIO初始化代码:
/*** SPI通信引脚, CS, SCK, MOSI, MISO ***/
GPIOSet (NRF24L01_SPI_CSN_GPIO, NRF24L01_SPI_CSN_PIN, G_MODE_OUT , G_OTYPE_PP, G_OSPEED_25M, G_PUPD_UP , 0); // cs
GPIOSet (NRF24L01_SPI_CLK_GPIO , NRF24L01_SPI_CLK_PIN , G_MODE_AF , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN , G_AF_SPI2 ); // sck
GPIOSet (NRF24L01_SPI_MOSI_GPIO ,NRF24L01_SPI_MOSI_PIN , G_MODE_AF , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN , G_AF_SPI2 ); // mosi
GPIOSet (NRF24L01_SPI_MISO_GPIO ,NRF24L01_SPI_MISO_PIN , G_MODE_AF , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN , G_AF_SPI2 ); // miso
/*** NRF控制引脚, CE, IRQ ***/
GPIOSet (NRF24L01_CE_GPIO , NRF24L01_CE_PIN , G_MODE_OUT , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN , 0); // CE
GPIOSet (NRF24L01_IRQ_GPIO , NRF24L01_IRQ_PIN , G_MODE_IN , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_UP , 0); // IRQ NRF24L01芯片的IRQ引脚下拉能力很弱,注意外部上拉电阻大小,及MCU的上下拉
GPIOSet是个自己封装的初始化函数,不用管,重点看其中的参数就好,注意各上下拉。
SPI 初始化代码:
/*** SPI通信部分 ***/
CSN_HIGH; //失能NRF
NRF24L01_SPI_EN_CLOCK ; // 时钟
NRF24L01_SPIX->CR1 = 0; // 清0
NRF24L01_SPIX->CR1 |= 0<<0; // 采样沿数, NRF要求上升沿采样 CPHA:时钟相位,0x1=在第2个时钟边沿进行数据采样,
NRF24L01_SPIX->CR1 |= 0<<1; // 时钟线闲时极性, CPOL:时钟极性,0x1=空闲状态时,SCK保持高电平
NRF24L01_SPIX->CR1 |= 1<<2; // 主从模式, 0=从,1=主
NRF24L01_SPIX->CR1 |= 2<<3; // 波特率控制[5:3], 0=fPCLK/2, 1=/4倍 2=/8 3/16
NRF24L01_SPIX->CR1 |= 0<<7; // LSB先行, 0=MSB, 1=LSB
NRF24L01_SPIX->CR1 |= 1<<8; // 内部从器件选择,根据9位设置(失能内部NSS)
NRF24L01_SPIX->CR1 |= 1<<9; // 软件从器件管理 : 0=禁止软件管理从设备, 1=使能软件从器件管理(软件NSS)
NRF24L01_SPIX->CR1 |= 0<<11; // 数据帧格式, 0=8位, 1=16位
NRF24L01_SPIX->CR1 |= 1<<6; // 使能
SPI 初始化重点:
- NRF要求在上升沿时采集数值。这就要闲时电平和采样沿要配合好。
- NRF的SPI可达10MHz, 但实际使用时,不要超过8MHz。这个在SPI的波特率中可控制,因为SPI通信速率受限低速一方。
- 重要:如果多个设备共用同一SPI,把各自的SPI配置封装成函数,每次使用SPI通信前各自调用一次,以使用自己的配置。
插个话题,为什么使用寄存器操作编程?因为:使用寄存器=简单+清晰,你看,不是吗?
SPI收发函数
/*****************************************************************************
*函 数: u8 SPI_RW(u8 Data)
*功 能: SPI写入一个字节,并返回一个字节
*参 数: 要写入的一字节
*返回值: 返回一字节数据
*****************************************************************************/
u8 SPI_SendByte(u8 Data)
{
u8 retry =0;
while((NRF24L01_SPIX ->SR & 2) == 0){ // 理解方式,应该把前式的结果理解为一个寄存器位值,如果这个位值是等号后面的值,就等待
retry++;
if(retry>200) return 0;
}
NRF24L01_SPIX ->DR = Data;
retry=0;
while((NRF24L01_SPIX->SR & 1) == 0 ){
retry++;
if(retry>200) return 0;
}
return NRF24L01_SPIX->DR ;
}
/*****************************************************************************
*函 数:u8 Nrf24l01_WriteReg(u8 reg,u8 value)
*功 能:向指定寄存器地址,写一个字节数据
*参 数:reg: 寄存器地址
* val: 要写入的值
*返回值:status
*****************************************************************************/
u8 Nrf24l01_WriteReg(u8 reg,u8 value)
{
u8 status;
CSN_LOW;
status = SPI_SendByte(reg) ;
SPI_SendByte(value);
CSN_HIGH;
return status;
}
/*****************************************************************************
*函 数:u8 NRF24l01_read_reg(u8 reg)
*功 能:向指定寄存器地址,读出一字节数据
*参 数:reg: 寄存器地址
*返回值:reg_val(第二个读取到的字节)
*****************************************************************************/
u8 Nrf24l01_ReadReg(u8 reg)
{
u8 reg_val;
CSN_LOW;
SPI_SendByte(reg);
reg_val = SPI_SendByte(0xFF);
CSN_HIGH;
return reg_val;
}
/*****************************************************************************
*函 数:u8 Nrf24l01_WriteBuf(u8 reg, u8 *pBuf, u8 len)
*功 能:写一组数据到寄存器
*参 数:reg: 寄存器地址
* pBuf: 要写入数据的地址
* len: 要写入的数据长度
*返回值:status
*备 注:NRF2401代码移植只需把SPI驱动修改成自己的即可
*****************************************************************************/
u8 Nrf24l01_WriteBuf(u8 reg, u8 *pBuf, u8 len)
{
u8 status;
CSN_LOW;
status = SPI_SendByte(reg);
for(u8 i=0; i
SPI_SendByte(pBuf
);
}
CSN_HIGH;
return status;
}
/*****************************************************************************
*函 数: u8 vNrf24l01_ReadBuf(u8 reg, u8 *pBuf, u8 len)
*功 能: 向指定寄存器地址,读出指定长度的数据
*参 数: reg : 寄存器地址
* pBuf : 数据存放缓冲区
* len : 读取的字节数量
*返回值: status : 设备状态字
*****************************************************************************/
u8 Nrf24l01_ReadBuf(u8 reg, u8 *pBuf, u8 len)
{
u8 status;
CSN_LOW;
status = SPI_SendByte(reg);
for(u8 i = 0; i
pBuf = SPI_SendByte(0xFF);
}
CSN_HIGH;
return status;
}
注意:在进入下一步配置NRF24L01前,必须先测试一下SPI通信是否成功 。一般是往NRF的发送地址寄存器写入五个字节,再读出来,把读出的数据和原数据对比, 就能知道SPI是否配置正确、 NRF24L01是否连接成功。
代码程序中,有个连接测试函数,可以作模块的连接,没有贴上来,留邮箱吧。
四、NRF24L01参数写入
使用上面初始化的SPI,和刚封装好的几个函数,就可以把需要的参数,写到NRF特定的地址(寄存器), 完成对其配置。
NRF24L01 参数配置代码:
/*** NRF24L01通信配置 30,2M,***/
CE_LOW; // 热待机模式, 只有在ce置低时,才能配置寄存器
//delayUs(2000); // PowerDown 切换为 PowerUp需要1.5ms
Nrf24l01_WriteReg(W_REGISTER + RF_CH, 30); // 射频通道,即频率(0-125)
Nrf24l01_WriteReg(W_REGISTER + RF_SETUP, 0x0F); // 设置TX发射参数,0db增益,2Mbps,低噪声增益关闭 (注意:低噪声增益关闭/开启直接影响通信,要开启都开启,要关闭都关闭0x0f)0x07
Nrf24l01_WriteReg(W_REGISTER + SETUP_AW, 0x03); // 地址长度,默认值时0x03,即5字节
Nrf24l01_WriteBuf(W_REGISTER + TX_ADDR, (u8*)TX_ADDRESS, 5); // 写TX节点地址, 地址宽度:5字节,40位
Nrf24l01_WriteBuf(W_REGISTER + RX_ADDR_P0, (u8*)TX_ADDRESS, 5); // 设置TX节点地址,主要为了使能ACK,, 地址宽度:5字节,40位
Nrf24l01_WriteReg(W_REGISTER + SETUP_RETR, 0x0A); // 设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次 0x1A
Nrf24l01_WriteReg(W_REGISTER + EN_RXADDR, 0x01); // 使能通道0的接收地址
Nrf24l01_WriteReg(W_REGISTER + EN_AA, 0x01); // 使能通道0自动应答
Nrf24l01_WriteReg(W_REGISTER + RX_PW_P0, 32); // 选择通道0的有效数据宽度
Nrf24l01_WriteReg(FLUSH_TX, 0xff); // 清除TX_FIFO
Nrf24l01_WriteReg(W_REGISTER+STATUS, 0X7E); // 清除所有中断,防止一进去接收模式就触发中断
Nrf24l01_WriteReg(W_REGISTER+CONFIG, 0x0F); // 配置为接收模式
CE_HIGH; // CE置高,进入状态
重点:
- CE引脚置低,方可配置NRF寄存器! 各教程都这教的,但测试过不置低也可正常配置。
- RF_CH,:频道,2.4G为基址,1M为间隔值,如上面的30,代表使用2.430G的频率进行芯片间通信。可设值0~125。
- RF_SETUP: 发射功率可配置,说白了就是的耗电程度。接收状态的功率是不能设置的,所以接收才是耗电的大头。数据传输率,常说的空中速率,一般设置2M
- 地址长度,5字节,这个很独特
- TX_ADDR:数据要发送到的目标地址
- RX_ADDR_P0: 接收通道0的接收地址,接收这个地址设备发来的数据,这里设为和TX_ADDR一致,是为了自动应答。
- NRF共有6个接收通道,p0~p5, 可同时监听6个不同地址的信号,p0通道也用于作自动应答作用。
- TX_FIFO, 发送数据缓冲区,96字节,32字节为一组,共3组,可理解为缓冲区可存放3组发送数据。
- RX_FIFO, 同上,两个FIFO相互独立。 虽然是FIFO,虽然有3组,但最好还是一包一包收发,状态切换时间难把握。
- 倒数第二行,配置为接收模式,注意,这个时候还没开始工作的,还处于PownDown状态,状态和模式是两回事。
- 最后一行,CE置高10us后,才进入工作状态,因之前配置的是接收模式,所以将进入接收状态
五、中断处理函数
为什么要先说中断?感觉先了解了中断,那么发送、接收就更好理解。
其实不应该叫中断的,但这样好理解,还是遵从约定俗成吧。
中断时, IRQ电平被拉低,是由NRF控制产生的,三种情况可触发:发送成功、达到重发最大次数、接收到数据。
发送成功:
- 1:PTX发送数据后,马上开始计时,130us后切换到接收模式,此时计时还在继续
- 2:PRX在收到数据后,经CRC校检,数据完整后发回ACK信号
- 3:PTX在规定时间内收到ACK信号,则置位TX_DS标志,IRQ引脚被拉低
达到重发最大次数:
- 1:PTX发送数据后,马上开始计时,130us后切换到接收模式,此时计时还在继续
- 2:PTX在规定时间内,没收到ACK信号,原因很多:如PRX没收到数据,CRC校验错误....
- 3:PTX再次发送一次数据,重复步骤1
- 4:SETUP_RETR寄存器可设置重发的次数,达到最大次数后,MAX_RT位被置位,IRQ引脚被拉低。
接收到数据:
- PRX收到数据,经CRC校检,数据完整,有效数据存放到RX_FIFO,RX_DR位置高,IRQ引脚被拉低。
说说清理中断,要清理的中断有两处:
- NRF的中断位,上面的三种标志中断位,都在寄存器STATUS中,各位置1可清0,IRQ引脚即回到高电平。
- 单片机mcu的中断位,清理外部中断线标志位,否则会在中断函数里死循环。
中断处理函数代码:
void NRF24L01_IRQ_IRQHANDLER(void)
{
u8 status=0 ;
CE_LOW; // 拉低CE,以便读取NRF中STATUS中的数据
status = Nrf24l01_ReadReg(R_REGISTER + STATUS); // 读取STATUS中的数据,以便判断是由什么中断源触发的IRQ中断
/*** 发送完成中断 ***/
if(status & STATUS_TX){
Nrf24l01_WriteReg(W_REGISTER+STATUS, STATUS_TX); // 清NRF中断:发送完成
Nrf24l01_WriteReg(FLUSH_TX, 0xff); // 清发送缓冲区:TX_FIFO
printf("rn发送成功!!!!rn");
vNrf24l01_RxMode (); // 切换为接收状态
}
/*** 接收完成中断 ***/
if(status & STATUS_RX){
memset (NRF_RX_DATA , 0, 32);
Nrf24l01_ReadBuf(R_RX_PAYLOAD, NRF_RX_DATA , RX_PAYLO_WIDTH); // 读数据
Nrf24l01_WriteReg(W_REGISTER+STATUS, STATUS_RX); // 清NRF中断:收到数据
Nrf24l01_WriteReg(FLUSH_RX,0xff); // 清除RX FIFO(注意:这句话很必要)
printf("rn接收到数据: %srn", NRF_RX_DATA);
vNrf24l01_RxMode (); // 切换为接收状态
}
/*** 最大重发次数中断 ***/
if(status & STATUS_MAX){
Nrf24l01_WriteReg(W_REGISTER+STATUS, 0x70); // 清NRF中断:三个
Nrf24l01_WriteReg(FLUSH_TX, 0xff); // 清除TX_FIFO
printf("rn发送失败,达到最大重发次数!!!rn");
vNrf24l01_RxMode(); // 切换为接收状态
}
EXTI->PR |= NRF24L01_IRQ_PIN ; // 清理外部中断线标志位
}
举报