仅作为个人学习笔记
芯片简介
nRF24L01是由NORDIC生产的工作在2.4GHz~2.5GHz的ISM 频段的单片无线收发器芯片。无线收发器包括:频率发生器、增强型“SchockBurst”模式控制器、功率放大器、晶体振荡器、调制器和解调器。 应用领域 ● 无线鼠标 键盘 游戏机操纵杆 ● 无线门禁 ● 无线数据通讯 ● 安防系统 ● 遥控装置 ● 遥感勘测 ● 智能运动设备 ● 工业传感器 ● 玩具
——百度百科
引脚及功能
该接口仅供参考,以自己的模块手册上的引脚说明为准。
图片来源:NRF24l01模块说明书
下图是nRF24L01的引脚功能(IO方向是相对模块而言的)
图片来源:nRF24L01中文说明书
上面这些引脚中,除CE 和IRQ 外就是标准的SPI信号引脚了(假设大家对SPI已经很熟悉了,这里不作介绍)
- CE:Chip Enable,芯片使能,在发送和接收过程中都要将这个引脚拉高。
- IRQ: 低电平触发,当状态寄存器中 TX_DS、RX_DR 或 MAX_RT 为高时触发中断,当 MCU 给中断源写 1 时,中断引脚被禁止。默认状态下所有的中断源是被禁止的。
工作模式
工作模式由 PWR_UP 寄存器、PRIM_RX 寄存器 和 CE 决定,详见下表:
图片来源:nRF24L01中文说明书
下面内容均来自《nRF24L01中文说明书》,这是一个加密的PDF文档,复制都要密码,所以下面的内容都是手打😨,就当是练习打字吧。【简单看看就好,我第一次看也很晕😵(如果只是想把模块用起来,可以直接跳过这些介绍)】
待机模式 待机模式 I 在保证快速启动的同时减少系统平均消耗电流。在待机模式 I 下,晶振正常工作。在待机模式 II 下部分时钟缓冲器处在工作模式。当发送端 TX FIFO 寄存器为空并且 CE 为高电平时进入待机模式 II。在待机模式期间,寄存器配置字内容保持不变。
掉电模式 在掉电模式下,nRF20L01 各功能关闭,保持电流消耗最小。进入掉电模式后,nRF24L01 停止工作,但寄存器内容保持不变。掉电模式由寄存器 PWR_UP 位来控制。
数据包处理方式 nRF24L01 有如下几种数据包处理方式:
- ShockBurstTM(与 nRF2401,nRF24E1,nRF2402,nRF24E2 数据传输率为 1Mbps 时相同)
- 增强型 ShockBurstTM 模式
ShockBurstTM 模式:ShockBurst 模式下 nRF24L01 可以与成本较低的低速 MCU 相连。高速信号处理是芯片内部的射频协议处理的,nRF24L01 提供的 SPI 接口,数据率取决于单片机本身的接口速度。ShockBurst 模式通过允许与单片机低速通信而无线部分高速通信,减小了通信的平均消耗电流。 在 ShockBurst 接收模式下,nRF24L01 自动生成前导码及 CRC校验。数据发送完毕后 IRQ 通知 MCU.减少了 MCU 的查询时间,也就意味着减少了 MCU 的工作量同时减少了软件的开发时间。nRF24L01 内部有三个不同的 RX FIFO 寄存器(6个通道共享此寄存器)和三个不同的 TX FIFO 寄存器。在掉电模式下、待机模式下和数据传输的过程中 MCU 可以随时访问 FIFO 寄存器。这就允许 SPI 接口可以以低速进行数据传送,并且可以应用于 MCU 硬件上没有 SPI 接口的情况下。 增强型ShockBurstTM 模式:增强型 ShockBurst 模式可以使得双向链表协议执行起来更为容易、有效。典型的双向链表为:发送方要求终端设备在接收到数据后有应答信号,以便发送方检测有无数据丢失。一旦数据丢失,则通过重新发送功能将丢失的数据恢复。增强型 ShockBurst 模式可以同时控制应答及重发功能而无需增加 MCU 工作量。
nRF24L01 在接收模式下可以接收6路不同通道的数据,见上图。每个数据通道使用不同的地址,但是共用相同的频道。也就是说6个不同的 nRF24L01 设置为发送模式后可以与用一个设置为接收模式的 nRF24L01 进行通讯,而设置为接收模式的 nRF24L01 可以对这个6个发送端进行识别。数据通道0是唯一的一个可以配置为 40 位自身地址的数据通道。1~5数据通道都为8位自身地址和32位公用地址。所有的数据通道都可以设置为增强型 ShockBurst 模式。 nRF24L01 在确认收到数据后记录地址,并以此地址为目标地址发送应答信号。在发送端,数据通道0被用作接收应答信号,因此,数据通道0的接收地址要与发送端地址相等以确保接收到正确的应到信号。见下图选地址举例。
nRF24L01 配置为增强型的 ShockBurst 发送模式时,只要 MCU 有数据要发送,nRF24L01 就会启动 ShockBurst 模式来发送数据。在发送完数据后 nRF24L01 转到接收模式并等待终端的应答信号。如果没有接收到应答信号, nRF24L01 将重发相同的数据包,直到收到应答信号或重发次数超过 SETUP_RETR_ARC 寄存器中设置的值为止,如果重发次数超过了设定值则产生 MAX_RT 中断。 只要收到确认信号,nRF24L01 就认为最后一包数据已经发送成功(接收方已经收到数据),把 TX_FIFO 中的数据清除并产生 TX_DS 中断(IRQ 引脚置高)。
增强型 ShockBurstTM发送模式
- 配置寄存器位 PRIM_RX 为低
- 当 MCU 有数据要发送时,接收节点地址(TX_ADDR)和有效数据(TX_PLD)通过 SPI 接口写入 nRF24L01。发送数据的长度以字节计数,从 MCU 写入 TX FIFO。当 CSN 为低时数据被不断的写入。发送端发送完数据后,将通道0设置为接收模式来接收应答信号,其接收地址(RX_ADDR_P0)与接收端地址(TX_ADDR)相同。例:在上面那张选地址举例图中,数据通道5的发送端(TX5)及接收端(RX)地址设置如下:
TX5: TX_ADDR=0xB3B4B5B605 TX5: RX_ADDR_P5=0xB3B4B5B605 RX: RX_ADDR_P5=0xB3B4B5B605 - 设置 CE 为高,启动发射。CE 高电平持续时间最小为10us。
- nRF24L01 ShockBurstTM模式:
<>无系统上电 <>启动内部 16MHz 时钟 <>无线发送数据包 <>高速发送数据(由 MCU 设定为 1Mbps 或 2Mbps) - 如果启动了自动应答模式(自动重发计数器不等于0,ENAA_P0=1),无线芯片立即进入接收模式。如果在有效应答时间范围内接收到应答信号,则认为数据成功发送到了数据端,此时状态寄存器的TX_DS 位置高并把数据从 TX FIFO 中清除掉。如果在设定时间范围内没有接收到应答信号,则重新发送数据。如果自动重发计数器(ARC_CNT)溢出(超过了编程设定的值),则状态寄存器的 MAX_RT 位置高。不清除 TX FIFO 中的数据。当 MAX_RT 或 TX_DS 为高电平时 IRQ 引脚产生中断。IRQ 中断通过写状态寄存器来复位。如果重发次数在到达设定的最大重发次数时还没有收到应答信号的话,在 MAX_RX 中断清除之前不会重发数据包。数据包丢失计数器(POLS_CNT)在每次产生 MAX_RT 中断后加1。也就是说:重发计数器 ARC_CNT 计算重发数据包次数,PLOS_CNT 计算在到达最大允许重发次数时仍没有发送成功的数据包个数。
如果 CE 置低,则系统进入待机模式 I。如果不设置 CE 为低,则系统会发送 TX FIFO 寄存器中下一包数据。如果 TX FIFO 寄存器为空并且 CE 为高则系统进入待机模式 II。
增强型 ShockBurstTM接收模式
- ShockBurstTM接收模式是通过设置寄存器中 PRIM_RX 位为高来选择的。准备接收数据的通道必须被使能(EN_RXADDR 寄存器),所有工作在增强型 ShockBurstTM 模式下数据通道的自动应答功能是由 EN_AA 寄存器来使能的,有效数据宽度是由 RX_PW_Px 寄存器来设置的。
- 接收模式由设置 CE 为高来启动。
- 130us 后 nRF24L01 开始检测空中信息。
- 接收到有效的数据包后(地址匹配、CRC 校验正确),数据存储在 RX_FIFO 中,同时 RX_DR 位置高,并产生中断。状态寄存器中 RX_P_NO 位显示数据是由哪个通道接收到的。
- 如果使能自动确认信号,则发送确认信号。
- MCU 设置 CE 脚为低,进入待机模式 I(低功耗模式)。
- MCU 将数据以合适的速率通过 SPI 口将数据读出。
- 芯片准备好进入发送模式、接收模式或掉电模式。
数据通道
nRF24L01 设置为接收模式时可以接受6路不同地址相同频率的数据。每个数据通道拥有自己的地址并且可以通过寄存器来进行配置。 数据通道是通过寄存器 EN_RXADDR 来设置的,默认状态下只有数据通道0和数据通道1是开启状态。 每一个数据通道的地址是通过寄存器 RX_ADDR_Px 来配置的。通常情况下允许不同的地址通道设置完全相同的地址。 数据通道0有40位可配置地址。数据通道1~5的地址为:32位共用地址+各自的地址(最低字节)。 下图是数据通道1~5的地址设置方法举例。所有数据通道可以设置为多达40位,但是1~5数据通道的最低位必须不同。 当从一个数据通道中接收到数据,并且此数据通道设置为应答方式,则 nRF24L01 在收到数据后产生应答信号,此应答信号的目标地址为接收通道地址。
SPI 指令
CSN 为低后 SPI 接口等待执行指令。每一条指令的执行都必须通过一次 CSN 由高到低的变化。
SPI 指令表
指令名称 | 指令格式 | 操作 |
---|
R_REGISTER | 000A AAAA | 读配置寄存器。AAAAA 指出读操作的寄存器地址 | W_REGISTER | 001A AAAA | 写配置寄存器。AAAAA 指出写操作的寄存器地址。只有在掉电模式和待机模式下可操作 | R_RX_PAYLOAD | 0110 0001 | 读 RX 有效数据:1-32 字节。读操作全部从字节0开始。当读 RX 有效数据完成后,FIFO 寄存器中有效数据被清除。应用于接收模式下。 | W_RX_PAYLOAD | 1010 0000 | 写 TX 有效数据:1-32字节。写操作从字节0开始。应用于发射模式下 | FLUSH_TX | 1110 0001 | 清除TX FIFO 寄存器,应用于发生模式下 | FLUSH_RX | 1110 0010 | 清除RX FIFO 寄存器,应用于接收模式下。在传输应答信号过程中不应执行此指令。也就是说,若传输应答信号过程中执行此指令的话将使得应答信号不能被完整的传输。 | REUSE_TX_PL | 1110 0011 | 应用于发射端。重新使用上一包发射的有效数据。当 CE=1 时,数据被不断重新发射。在发射数据包过程中必须禁止数据包重利用功能。 | NOP | 1111 1111 | 空操作。可用来读状态寄存器。 |
寄存器地址
#define NRF_READ_REG 0x00
#define NRF_WRITE_REG 0x20
#define RD_RX_PLOAD 0x61
#define WR_TX_PLOAD 0xA0
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
#define REUSE_TX_PL 0xE3
#define NOP 0xFF
#define CONFIG 0x00
#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 STATUS 0x07
#define MAX_TX 0x10
#define TX_OK 0x20
#define RX_OK 0x40
#define OBSERVE_TX 0x08
#define CD 0x09
#define RX_ADDR_P0 0x0A
#define RX_ADDR_P1 0x0B
#define RX_ADDR_P2 0x0C
#define RX_ADDR_P3 0x0D
#define RX_ADDR_P4 0x0E
#define RX_ADDR_P5 0x0F
#define TX_ADDR 0x10
#define RX_PW_P0 0x11
#define RX_PW_P1 0x12
#define RX_PW_P2 0x13
#define RX_PW_P3 0x14
#define RX_PW_P4 0x15
#define RX_PW_P5 0x16
#define NRF_FIFO_STATUS 0x17
NRF24L01模块驱动(STM32)
上面这些理论知识过于枯燥,还是代码来得直接,代码来自正点原子例程。【目前只操作通道0,即只考虑1对1通信】
源码下载地址(来自正点原子官方论坛,需要登录后下载) http://www.openedv.com/forum.php?mod=viewthread&tid=294345&highlight=NRF24L01 如果不想登录,直接点这个下载链接也可(同一个资源)
NRF24L01检测函数
u8 NRF24L01_Check(void)
{
u8 buf[5]={0XA5,0XA5,0XA5,0XA5,0XA5};
u8 i;
SPI2_SetSpeed(SPI_BaudRatePrescaler_4);
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,buf,5);
NRF24L01_Read_Buf(TX_ADDR,buf,5);
for(i=0;i<5;i++)if(buf[i]!=0XA5)break;
if(i!=5)return 1;
return 0;
}
NRF24L01写寄存器
u8 NRF24L01_Write_Reg(u8 reg,u8 value)
{
u8 status;
NRF24L01_CSN=0;
status =SPI2_ReadWriteByte(reg);
SPI2_ReadWriteByte(value);
NRF24L01_CSN=1;
return(status);
}
NRF24L01读寄存器
u8 NRF24L01_Read_Reg(u8 reg)
{
u8 reg_val;
NRF24L01_CSN = 0;
SPI2_ReadWriteByte(reg);
reg_val=SPI2_ReadWriteByte(0XFF);
NRF24L01_CSN = 1;
return(reg_val);
}
NRF24L01读数据
u8 NRF24L01_Read_Buf(u8 reg,u8 *pBuf,u8 len)
{
u8 status,u8_ctr;
NRF24L01_CSN = 0;
status=SPI2_ReadWriteByte(reg);
for(u8_ctr=0;u8_ctr<len;u8_ctr++)pBuf[u8_ctr]=SPI2_ReadWriteByte(0XFF);
NRF24L01_CSN=1;
return status;
}
NRF24L01写数据
u8 NRF24L01_Write_Buf(u8 reg, u8 *pBuf, u8 len)
{
u8 status,u8_ctr;
NRF24L01_CSN = 0;
status = SPI2_ReadWriteByte(reg);
for(u8_ctr=0; u8_ctr<len; u8_ctr++)SPI2_ReadWriteByte(*pBuf++);
NRF24L01_CSN = 1;
return status;
}
NRF24L01发送数据
u8 NRF24L01_TxPacket(u8 *txbuf)
{
u8 sta;
SPI2_SetSpeed(SPI_BaudRatePrescaler_8);
NRF24L01_CE=0;
NRF24L01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);
NRF24L01_CE=1;
while(NRF24L01_IRQ!=0);
sta=NRF24L01_Read_Reg(STATUS);
NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS,sta);
if(sta&MAX_TX)
{
NRF24L01_Write_Reg(FLUSH_TX,0xff);
return MAX_TX;
}
if(sta&TX_OK)
{
return TX_OK;
}
return 0xff;
}
NRF24L01接收数据
u8 NRF24L01_RxPacket(u8 *rxbuf)
{
u8 sta;
SPI2_SetSpeed(SPI_BaudRatePrescaler_8);
sta=NRF24L01_Read_Reg(STATUS);
NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS,sta);
if(sta&RX_OK)
{
NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);
NRF24L01_Write_Reg(FLUSH_RX,0xff);
return 0;
}
return 1;
}
NRF24L01设置RX模式
void NRF24L01_RX_Mode(void)
{
NRF24L01_CE=0;
NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01);
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01);
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40);
NRF24L01_Write_Reg(NRF_WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f);
NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG, 0x0f);
NRF24L01_CE = 1;
}
NRF24L01设置TX模式
void NRF24L01_TX_Mode(void)
{
NRF24L01_CE=0;
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,(u8*)TX_ADDRESS,TX_ADR_WIDTH);
NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01);
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01);
NRF24L01_Write_Reg(NRF_WRITE_REG+SETUP_RETR,0x1a);
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40);
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f);
NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG,0x0e);
NRF24L01_CE=1;
}
【注】两个NRF24L01 通信的核心是知道对方的地址(自定义),两个模块的地址可以相同,但通常情况下允许不同的地址通道设置完全相同的地址。
const u8 TX_ADDRESS[TX_ADR_WIDTH]={0x4,0x3,0x2,0x1,0x0};
const u8 RX_ADDRESS[RX_ADR_WIDTH]={0x4,0x4,0x4,0x4,0x4};
简单的通讯代码
依然是正点原子的例程,删掉了LCD和温湿度相关代码。
代码主要流程:
- 使用 NRF24L01_Init() 函数对模块进行初始化,初始化相关GPIO,SPI 接口等。
- 循环运行 NRF24L01_Check(),进行硬件检查,如果正常退出循环,否则一直循环检查。检测原理:向TX_ADDR 寄存器写数据,再读出数据,判断读出的数据是否与写入的相同(函数代码见上文)。
- 通过按键设置 NRF24L01 的模式,按 KEY0 进入接收模式,按 WK_UP 进入发送模式。
int main(void)
{
u8 key,mode,key_val;
u16 t=0;
u8 tmp_buf[33];
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200);
LED_Init();
KEY_Init();
NRF24L01_Init();
while(NRF24L01_Check())
{
printf("NRF24L01 Error\r\n");
delay_ms(500);
}
printf("NRF24L01 OK\r\n");
while(1)
{
key=KEY_Scan(0);
if(key==KEY0_PRES)
{
mode=0;
break;
}else if(key==WKUP_PRES)
{
mode=1;
break;
}
t++;
if(t==100)
printf("KEY0:RX_Mode WK_UP:TX_Mode\r\n");
if(t==200)
{
t=0;
}
delay_ms(10);
}
if(mode==0)
{
printf("NRF24L01 RX_Mode\r\n");
NRF24L01_RX_Mode();
while(1)
{
if(NRF24L01_RxPacket(tmp_buf)==0)
{
printf("接受到数据:%s\r\n", tmp_buf);
}
};
}
else
{
printf("NRF24L01 TX_Mode\r\n");
NRF24L01_TX_Mode();
while(1)
{
key=KEY_Scan(0);
if(key==KEY0_PRES)
{
sprintf(tmp_buf, "%s", "I'm STM32F103ZET6\r\n");
NRF24L01_TxPacket(tmp_buf);
}
else if(key==WKUP_PRES)
{
sprintf(tmp_buf, "%s", "NRF24L01 Test\r\n");
NRF24L01_TxPacket(tmp_buf);
}
}
}
}
上面只实现了1对1的单向通信(两块单片机对发),没涉及到自动应答、自动重发和中断等其他高级功能,且只用到了它们的0号数据通道。
第一次使用这个模块,先掌握这么多吧,这个模块的测试实验会在另一篇笔记中展示。
|