详情请看以下博客,受益匪浅
nRF24L01发送接收调试应用笔记
开贴详细谈NRF24L01
由于网上教程很多很详细,我这里就简单记录一下基本内容及重要的部分
1、先来看下写寄存器操作
//SPI写寄存器
//reg:指定寄存器地址
//value:写入的值
u8 NRF24L01_Write_Reg(u8 reg,u8 value)
{
u8 status;
NRF24L01_CSN=0; //使能SPI传输
status =SPI2_ReadWriteByte(reg);//发送寄存器号
SPI2_ReadWriteByte(value); //写入寄存器的值
NRF24L01_CSN=1; //禁止SPI传输
return(status); //返回状态值
}
//SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
//由于SPI的传输特性,读取和写入必须是同时进行的,所以这个函数为ReadWriteByte,即读和写共用同一个函数,读数据的时候,一般发送一个0XFF,OXFF代表是空操作
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最近接收的数据
}
上面两个函数相信大家找的代码中肯定都是已经写好的,一般不需要我们自己写底层驱动,所以重点是要理解怎么调用它们,即
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01);
上面这个就是调用写寄存器函数,第一个参数就是我们要写入的寄存器的地址,需要注意的是这个写寄存器指令是有一定的格式的:如下图
即这个写寄存器指令的格式为:
即这个指令的8位数据的高三位是指定的001,而低五位才是我们实际要写入的寄存器地址,所以我们一般的做法就是定义一个变量NRF_WRITE_REG = 0x20
,0x20也就是0010 0000
,即先保证高三位是固定的001
,然后我们再使用这个NRF_WRITE_REG
去加上我们实际要操作的寄存器地址,比如现在要操作寄存器EN_RXADDR
,(相信大家找到的程序里面肯定都有各个寄存器的地址,都是已经宏定义好的),那么我们的做法就是NRF_WRITE_REG+EN_RXADDR
,这样就完成了写寄存器指令的构成,然后就可以查手册写入具体参数了,接着往下看即可。
第二个参数就是要写入寄存器中的具体数据了,这个具体数据一般大家找到的程序也都是已经写好的,如果需要自己改动的话,就需要去看看NRF24L01的数据手册查看寄存器的每一位都代表啥意思了,NRF24L01中文参考手册请点击
比如上面写的EN_RXADDR
在手册中如下
写入0x01就代表使能了接收数据通道0,具体数据可以根据自己需要进行更改。
比如常见的接收模式的配置如下
void NRF24L01_RX_Mode(void)
{
NRF24L01_CE=0;
//共有6个通道,每一个通道都可以设置为不同的地址(上电之后有默认的地址),RX_ADDR_P0代表通道0,这里重新设置地址,RX_ADDRESS代表为通道0设置地址(即不使用上电默认的地址)
NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);//写RX节点地址
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01); //使能通道0的自动应答,NRF24L01共有6个通道
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01);//使能通道0的接收地址
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40); //设置RF通信频率,当有多个模块在同一个场合同时工作时,通信频率尽量选择的间隔远一些
NRF24L01_Write_Reg(NRF_WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);//选择通道0的有效数据宽度(单位为字节)
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f);//设置TX发射参数,0db增益,2Mbps,低噪声增益开启
NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG, 0x0f);//配置基本工作模式的参数;高四位为0,代表开启所有中断。低四位:PWR_UP,EN_CRC,16BIT_CRC,接收模式 ,最低位控制发送模式或者接收模式,1为发送模式,0为接收模式
NRF24L01_CE = 1; //CE为高,进入接收模式
}
其中第一行的NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);
的作用是NRF24L01_Write_Reg
函数的升级版,即对指定寄存器连续写入多个字节的数据,具体程序如下:
u8 NRF24L01_Write_Buf(u8 reg, u8 *pBuf, u8 len)
{
u8 status,u8_ctr;
NRF24L01_CSN = 0; //使能SPI传输
status = SPI2_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值
for(u8_ctr=0; u8_ctr<len; u8_ctr++)SPI2_ReadWriteByte(*pBuf++); //写入数据
NRF24L01_CSN = 1; //关闭SPI传输
return status; //返回读到的状态值
}
NRF24L01中有些寄存器是只能够写入1个字节的数据,比如EN_RXADDR(配置接受地址是否被允许)
寄存器,就可以使用NRF24L01_Write_Reg
函数,而有些寄存器可以写入多个字节的数据,比如RX_ADDR_P0(数据通道0接收地址,最大长度5个字节,低字节在前)
寄存器就可以写入多个字节,这是就可以使用NRF24L01_Write_Buf
函数了
2、再来看下读寄存器操作
//读取SPI寄存器值
//reg:要读的寄存器
u8 NRF24L01_Read_Reg(u8 reg)
{
u8 reg_val;
NRF24L01_CSN = 0; //使能SPI传输
SPI2_ReadWriteByte(reg); //发送寄存器号
reg_val=SPI2_ReadWriteByte(0XFF);//读取寄存器内容
NRF24L01_CSN = 1; //禁止SPI传输
return(reg_val); //返回状态值
}
sta=NRF24L01_Read_Reg(STATUS); //读取状态寄存器的值
NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);//读取数据
读寄存器和写寄存器基本一样,先看下读寄存器的指令格式
即这个指令的8位数据的高3位固定为000,低5位位要读取的寄存器地址,即我们模仿写寄存器的操作,也可以定义一个变量NRF_WRITE_REG=0x00
,0x00也就是0000 0000
,即高3位保证为000,然后再加上寄存器地址,比如NRF_WRITE_REG+STATUS
的作用就是去读取STATUS寄存器中的内容。当然了,由于NRF_WRITE_REG=0x00
,所以说我们也可以不加上NRF_WRITE_REG
,而是直接写入寄存器地址即可,(任何数加上0还是它本身),也就是sta=NRF24L01_Read_Reg(STATUS);
,读取到的内容就会返回到sta中。
注意:在读写寄存器的程序中我们都使用到了同一个函数SPI2_ReadWriteByte
//SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
//由于SPI的传输特性,读取和写入必须是同时进行的,所以这个函数为ReadWriteByte,即读和写共用同一个函数,读数据的时候,一般发送一个0XFF,OXFF代表是空操作
u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
{
//当发送缓冲区有数据时,会一直进入此循环,TXE表示发送空,RESET表示否,即发送非空,也就是有数据正在等待发送
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标志位设置与否:接受缓存非空标志位
{
//当接收缓存空(没有接收到数据)时,会一直进入此循环,RXNE表示非空,RESET表示否,即接收空,也就是没有接收到数据
retry++;
if(retry>200)return 0;
}
//当接收缓冲区非空时,跳出上述循环,然后返回刚刚接收到的数据
return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据
}
该函数的具体工作过程请看里面的注释,应该都能够理解啥意思。
因为SPI协议的特殊性,发送和接收是同时进行的,8个时钟过去之后,发送端把数据发给接收端的同时,接收端也会给发送端发送一次数据,即8个时钟双方就完成了一次数据的交互。所以我们才把写和读的函数使用一个函数SPI2_ReadWriteByte
来完成。
3、SPI初始化注意事项
void SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE );//PORTB时钟使能
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2,ENABLE );//SPI2时钟使能
SPI_Cmd(SPI2, DISABLE); 必须先禁用,才能改变MODE
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);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
//注意:以下两个步骤十分重要,千万不要写错
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行同步时钟的空闲状态为低电平 注意这里是空闲状态是低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //串行同步时钟的第1个跳变沿数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
//下面为配置波特率,NRF24L01采用2FSK进行通信,所以要设定合适的波特率,可设置为1M、2M,有的还可以设置250kbps
//SPI2在APB1时钟线上,APB1上的SPI2时钟频率为36MHz
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; //定义波特率预分频的值:波特率预分频值为256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(SPI2, ENABLE); //使能SPI外设
SPI2_ReadWriteByte(0xff);//启动传输
}
(1)两个NRF24L01进行通信,两个模式可以都设置成主机模式,这里的主机和从机配置模式应该不是我们所理解的两个NRF24L01进行通信--------必须一个配置成主机模式,另一个配置成从机模式;而是两个NRF24L01都可以配置成主机模式,然后再把两个NRF24L01一个设置成发送模式,而另一个设置成接收模式,应该是发送方习惯称之为主机,接收方习惯称之为从机,目前我的理解是这样的,不知道有没有什么问题。
(2)当配置成主机模式后,下面这两个配置也很重要,
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行同步时钟的空闲状态为低电平 注意这里是空闲状态是低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //串行同步时钟的第1个跳变沿数据被采样
反正我在调试的时候,这两项必须这样配置,一旦更改到其他的,NRF24L01就会一直自检失败,我也不知道为啥。
4、两个NRF24L01通信调试注意事项
记住,一定不要两个同时进行调试,因为这样你根本不知道问题是出在发送方还是接收方,我们一定要一个个的调试,一般先调试发送方(禁止自动应答,禁止通道x的接收地址,禁止重复发送,也就是只开启发送方,不开启和接收相关的任何东西),再调试接收方(调试完发送方之后,让发送方重复进行发送,然后接收方禁止自动应答,即接收方只开启接收通道即可);发送方和接收方调试成功与否一般都是通过查看STATUS寄存器或者NRF_FIFO_STATUS寄存器来判断的,具体步骤请看
5、目前我存在的问题
发送方经过查看STATUS寄存器和NRF_FIFO_STATUS寄存器都表示发送已经成功了,就说明发送端已经没问题了,但是接收方却一直收不到数据(接收方的STATUS寄存器一直为0x0e,也就是接收缓冲为空,也就是没有收到数据),目前还不知道是因为什么,等待后续继续调试中。
另一篇NRF24L01