首先看两个版本关于NRF的宏定义:
开发板:
遥控器:
1,最基本的读写函数,函数的返回值就是读来的数据,形参就是写入的数据
stm32mini开发板例程中:
//SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI1_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
{
retry++;
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI1, TxData); //通过外设SPIx发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)//检查指定的SPI标志位设置与否:接受缓存非空标志位
{
retry++;
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mini遥控器中:
//发送数据,同时返回最近收到的数据
static u8 SPI_RWByte(SPI_TypeDef* SPIx , u8 TxData)
{
/* 通过外设SPIx发送一个数据 */
SPI_I2S_SendData(SPIx, TxData);
/* 检查指定的SPI标志位设置与否:发送缓存空标志位*/
while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET);//空标志为假,代表缓存不是空的,还没发送完,等待发完
/* 检查指定的SPI标志位设置与否:接受缓存非空标志位 */
while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET);//非空标志为假,代表接收缓存没有空,上次的还没移走,等空了可以接收数据
/* 返回通过SPIx最近接收的数据 */
return SPI_I2S_ReceiveData(SPIx);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
区别:开发板多了个超时退出,这个貌似作用不大
2,写寄存器函数
stm32mini开发板例程中:
//SPI写寄存器
//reg:指定寄存器地址
//value:写入的值
u8 NRF24L01_Write_Reg(u8 reg,u8 value)
{
u8 status;
NRF24L01_CSN=0; //使能SPI传输
status =SPI1_ReadWriteByte(reg);//发送寄存器号
SPI1_ReadWriteByte(value); //写入寄存器的值
NRF24L01_CSN=1; //禁止SPI传输
return(status); //返回状态值
}
1
2
3
4
5
6
7
8
9
10
11
12
mini遥控器中:
/* 写寄存器 */
static u8 writeReg(u8 reg,u8 value)
{
u8 status;
SPI2_CSN_L();
status=SPI_RWByte(NRF_SPI,reg|CMD_W_REG);
SPI_RWByte(NRF_SPI , value);
SPI2_CSN_H();
return status;
}
对比可知,形参一致,区别在开发板的status =SPI1_ReadWriteByte(reg);//发送寄存器号 和遥控器的status=SPI_RWByte(NRF_SPI,reg|CMD_W_REG);
其实最终都是一样的,只是在使用时,给的形参不同,
在开发板中,这样调用的
寄存器操作命令+寄存器地址
NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS,sta); //清除TX_DS或
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01); //使能通道0的接收地址
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40); //设置RF通信频率 MAX_RT中断标志
或者直接寄存器操作命令
NRF24L01_Write_Reg(FLUSH_RX,0xff);//清除RX FIFO寄存器
NRF24L01_Write_Reg(FLUSH_TX,0xff);//清除TX FIFO寄存器
在遥控器中,这样调用的
writeReg(REG_STATUS,0x70);/*清除标志*/
writeReg(REG_RF_CH, channel);
writeReg(REG_RF_SETUP,reg_rf);
writeReg(CMD_FLUSH_TX,0xff);
writeReg(CMD_FLUSH_RX,0xff);/* 冲洗RX_FIFO */
全都一个,不用管寄存器指令,直接传入地址,就因为区别在开发板的status =SPI1_ReadWriteByte(reg);//发送寄存器号 和遥控器的status=SPI_RWByte(NRF_SPI,reg|CMD_W_REG);,遥控器的函数是直接在函数内部将写写配置寄存器与寄存器地址融合,通过按位或,为什么可以这样呢?
因为寄存器操作命令中写配置寄存器为0x20,也就是0010 0000,低5位为寄存器地址,然后下方的寄存器地址刚好就是用了5位,000x xxxx,如NRF_FIFO_STATUS 0x17就是0001 0111,那么两者按位或的结果就是两者的和。
个人觉得遥控器的这个函数不错,可以推测其读寄存器函数也这样干的。
3,读寄存器
stm32mini开发板例程中:
sta=NRF24L01_Read_Reg(STATUS); //读取状态寄存器的值
搜索一下,发现没有使用 寄存器操作命令+寄存器地址的参数,只用到这一个,或许用不到这个寄存器操作命令,就是算用的,也是0x00,即0000 0000,零和任何数相加都等于那个数
mini遥控器中:
所以这里的这个CMD_R_REG 0x00 // 读寄存器指令和任何数或都等于那个数。
4,在指定位置写指定长度的数据
stm32mini开发板例程中:
//reg:寄存器(位置)
//*pBuf:数据指针
//len:数据长度
//返回值,此次读到的状态寄存器值
u8 NRF24L01_Write_Buf(u8 reg, u8 *pBuf, u8 len)
{
u8 status,u8_ctr;
NRF24L01_CSN = 0; //使能SPI传输
status = SPI1_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值
for(u8_ctr=0; u8_ctr
SPI1_ReadWriteByte(*pBuf++); //写入数据
NRF24L01_CSN = 1; //关闭SPI传输
return status; //返回读到的状态值
}
传给形参reg的是寄存器操作命令(读/写寄存器指令)+寄存器地址,和 寄存器操作命令(其他寄存器指令) 用来写待发送的连续数据(字节)到TX BUF 寄存器,和把地址写入到接收应答的通道0——RX_ADDR_P0,发送地址寄存器TX_ADDRESS,接收地址寄存器RX_ADDRESS,如:
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,buf,5);//写入5个字节的地址.
NRF24L01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);//写数据到TX BUF 32个字节
NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);//写RX节点地址
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,(u8*)TX_ADDRESS,TX_ADR_WIDTH);//写TX节点地址
mini遥控器中:
static u8 writeBuf(u8 cmd,u8 *pBuf,u8 len)
{
u8 status,i;
SPI2_CSN_L(); //拉低,使能器件,开启SPI通信
status=SPI_RWByte(NRF_SPI,cmd);//如发送一个写命令,返回一个许可状态
for(i=0;i
SPI_RWByte(NRF_SPI,*pBuf++);
SPI2_CSN_H(); //写完,拉高,失能器件,关闭SPI通信
return status;
}
和开发板一样的,但是他的形参是cmd命令的意思,寄存器操作命令(读/写寄存器指令)+寄存器地址就是个完整的命令,而其他寄存器指令不用加地址,就是一个完整的命令,注意到:writeBuf(u8 cmd,u8 *pBuf,u8 len)和writeReg(u8 reg,u8 value)函数的第一个形参不同,都是写操作,但writeBuf里面没有reg|CMD_W_REG,所以叫cmd,需把完整命令传给cmd,而writeBuf传被写的寄存器地址就行。
cmd完整命令:寄存器操作命令(读/写寄存器指令)+寄存器地址
reg 寄存器:寄存器地址
5,在指定位置读出指定长度的数据
stm32mini开发板例程中:
//reg:寄存器(位置)
//*pBuf:数据指针
//len:数据长度
//返回值,此次读到的状态寄存器值
u8 NRF24L01_Read_Buf(u8 reg,u8 *pBuf,u8 len)
{
u8 status,u8_ctr;
NRF24L01_CSN = 0; //使能SPI传输
status=SPI1_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值
for(u8_ctr=0;u8_ctr
pBuf[u8_ctr]=SPI1_ReadWriteByte(0XFF);//读出数据
NRF24L01_CSN=1; //关闭SPI传输
return status; //返回读到的状态值
}
传给形参reg的是寄存器操作命令(读/写寄存器指令)+寄存器地址,和**寄存器操作命令(其他寄存器指令)**用来读寄存器值里连续地址的数据
NRF24L01_Read_Buf(TX_ADDR,buf,5); //读出写入的地址
NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);//读取数据
1
2
mini遥控器中:
static u8 readBuf(u8 cmd,u8 *pBuf,u8 len)
{
u8 status,i;
SPI2_CSN_L();//拉低,使能器件
status=SPI_RWByte(NRF_SPI,cmd); //如发送一个读命令,返回一个许可状态
for(i=0;i
pBuf
=SPI_RWByte(NRF_SPI,0XFF);
SPI2_CSN_H();//读完后,拉高失能
return status;
}
形参是cmd命令的意思,寄存器操作命令(读/写寄存器指令)+寄存器地址就是个完整的命令,而其他寄存器指令不用加地址,就是一个完整的命令
6,发送数据包
stm32mini开发板例程中:
//启动NRF24L01发送一次数据
//txbuf:待发送数据首地址
//返回值:发送完成状况
u8 NRF24L01_TxPacket(u8 *txbuf)
{
u8 sta;
SPI1_SetSpeed(SPI_BaudRatePrescaler_8);//spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)
NRF24L01_CE=0;
NRF24L01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);//写数据到TX BUF 32个字节
NRF24L01_CE=1;//启动发送
while(NRF24L01_IRQ!=0);//等待发送完成
sta=NRF24L01_Read_Reg(STATUS); //读取状态寄存器的值
NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS,sta); //清除TX_DS或MAX_RT中断标志
if(sta&MAX_TX)//达到最大重发次数
{
NRF24L01_Write_Reg(FLUSH_TX,0xff);//清除TX FIFO寄存器
return MAX_TX;
}
if(sta&TX_OK)//发送完成
{
return TX_OK;
}
return 0xff;//其他原因发送失败
}
开发板的发送包函数,里面内容比较多,起发送作用的就一句:
NRF24L01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);//写数据到TX BUF 32个字节
1
剩下的代码就是做一些“善后工作”,即等待发送完成,查询状态寄存器,清除标志位,如MAX_RT标志置1后,无法再次发送,需要写1清除该标志位就能再次启动发送,工作在自动应答模式下,只有当接收到应答信号后TX_DS标志位置位1代表发送成功(对方成功接收),写1清0,所以用sta写sta,还清除TX FIFO寄存器 。
mini遥控器中:
void nrf_txPacket(u8 *tx_buf,u8 len)
{
NRF_CE_L(); //24L01 片选信号
writeBuf(CMD_W_TX_PAYLOAD,tx_buf,len);
NRF_CE_H();
}
遥控器里的没有做“善后工作”,可能得和其他函数搭配使用。
被下面函数调用
/* 发送数据包,并等待接收ACK(PTX模式) */
/* 返回值:1成功、0失败*/
u8 nrf_sendPacketWaitACK(u8 *sendBuf, u8 len, u8 *ackBuf, u8 *acklen)
{
if(len==0) return 0;
nrf_txPacket(sendBuf,len);//调用发送包函数发送长度为len个字节
while((readReg(REG_STATUS)&0x70) == 0);/* 等待事件 */
nrfEvent_e nrfEvent = nrf_checkEventandRxPacket(ackBuf, acklen);//查询是哪种事件发生了
if(nrfEvent == MAX_RT)//如果是重发失败
return 0;
return 1;
}
其中while((readReg(REG_STATUS)&0x70) == 0);里,0x07,写为二进制就是0111 0000,REG_STATUS和0x70位与就是得到状态寄存器的4,5,6位的情况,4-MAX-RT:达到最大重发次数,5—TX_DS发送完成,6-RX-DR有效数据来了,只要有一个事件发生,结果 (readReg(REG_STATUS)&0x70) !=0,退出while循环,一个都没发生者循环空语句,即等待。3个中断事件如下
还有调用查询事件并接收数据包函数:nrf_checkEventandRxPacket(ackBuf, acklen);原型如下:
nrfEvent_e nrf_checkEventandRxPacket(u8 *ackBuf, u8 *acklen)
{
nrfEvent_e nrfEvent = IDLE;
*acklen = 0;
u8 status = readReg(REG_STATUS);/*读事件标志寄存器*/
if(status&BIT_MAX_RT)/*重发失败*/
{
writeReg(CMD_FLUSH_TX,0xff);
nrfEvent = MAX_RT;
}
else if(status&BIT_RX_DR)/*接收数据到RX_FIFO*/
{
*acklen = nrf_rxPacket(ackBuf);//接收数据包,返回包长度len
nrfEvent = RX_DR;
}
else if(status&BIT_TX_DS)/*发送数据至TX_FIFO成功*/
{
nrfEvent = TX_DS;
}
writeReg(REG_STATUS,0x70);/*清除标志*/
u8 status1 = readReg(REG_STATUS);/*读事件标志寄存器*/
status1 = status1;
return nrfEvent;
}
看看事件状态(标志)寄存器的4,5,6位的情况是哪种情况发生了,并返回标志,设计了一个枚举,
当没有事件发生时是空闲:nrfEvent_e nrfEvent = IDLE;如果有哪个标志置1就返回该标志:
如果是最大次数标志置位,代表重发失败,得清空该标志,模块才能启动发送:writeReg(CMD_FLUSH_TX,0xff);返回的枚举变量nrfEvent = MAX_RT;
如果是数据到来了,那就调用上面的接收包函数:*acklen = nrf_rxPacket(ackBuf);,存储包,同时存储返回值——包长度,返回的枚举变量nrfEvent = RX_DR;
如果发生成功(即接收到了接收端的自动应答ACK),则返回的枚举变量nrfEvent = TX_DS;。
说明,此函数有三个功能,调用它,就可以知道是重发失败,还是发生成功,还是有数据来了,若是还自动接收数据,代码逻辑上,如果这三种情况都是,那一下就做了三件事,但接收和发生不能同时发生,所以返回值是其中的一种。最后writeReg(REG_STATUS,0x70);/清除标志/,最后两句代码不知道为什么,感觉有点多余
再回到nrf_sendPacketWaitACK函数,它里面就调用nrfEvent_e nrfEvent = nrf_checkEventandRxPacket(ackBuf, acklen); 结合上面的nrf_txPacket(sendBuf,len);和这个大函数的名字sendPacketWaitACK,可知是NFR在发送模式下掉用的,那只要两种情况,因为使能了重发,结果要么是重发失败,要么是发送成功即收到了应答信号。所以最后判断if(nrfEvent == MAX_RT),返回1就是后种情况。
对比一下开发板的u8 NRF24L01_TxPacket(u8 * txbuf)函数和nrf_sendPacketWaitACK(u8 * sendBuf, u8 len, u8 * ackBuf, u8 * acklen)函数,是不是很有感觉——过程相似? 但在遥控器代码里居然没被调用过,,,,
猜测接收包也有这种类似功能的函数,继续探索,,,,,
7,接收数据包,
stm32mini开发板例程中:
//启动NRF24L01发送一次数据
//txbuf:待发送数据首地址
//返回值:0,接收完成;其他,错误代码
u8 NRF24L01_RxPacket(u8 *rxbuf)
{
u8 sta;
SPI1_SetSpeed(SPI_BaudRatePrescaler_8); //spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)
sta=NRF24L01_Read_Reg(STATUS); //读取状态寄存器的值
NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS,sta); //清除TX_DS或MAX_RT中断标志
if(sta&RX_OK)//接收到数据
{
NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);//读取数据
NRF24L01_Write_Reg(FLUSH_RX,0xff);//清除RX FIFO寄存器
return 0;
}
return 1;//没收到任何数据
}
设置spi速度的那句可以放在spi初始化时调用,起发送包作用的也是一句:
NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);//读取数据
1
其他代码有 “善前工作:查询状态有没有接到数据收” 和“善后工作:清除RX FIFO寄存器 ”。还可以用利用中断来提示该接收数据了,如:while(NRF24L01_IRQ!=0);//等待数据到达,因为当数据到达该引脚也会被拉低,这和TX_DS标志位,MAX_RT标志位一样,中断来了就拉低IRQ。
mini遥控器中:
查询事件并接收数据包 函数,nrfEvent_e nrf_checkEventandRxPacket(u8 * ackBuf, u8 * acklen)类似开发板的u8 NRF24L01_RxPacket(u8 * rxbuf函数
nrfEvent_e nrf_checkEventandRxPacket(u8 *ackBuf, u8 *acklen)
{
nrfEvent_e nrfEvent = IDLE;
*acklen = 0;
u8 status = readReg(REG_STATUS);/*读事件标志寄存器*/
if(status&BIT_MAX_RT)/*重发失败*/
{
writeReg(CMD_FLUSH_TX,0xff);
nrfEvent = MAX_RT;
}
else if(status&BIT_RX_DR)/*接收数据到RX_FIFO*/
{
*acklen = nrf_rxPacket(ackBuf);//接收数据包,返回包长度len
nrfEvent = RX_DR;
}
else if(status&BIT_TX_DS)/*发送数据至TX_FIFO成功*/
{
nrfEvent = TX_DS;
}
writeReg(REG_STATUS,0x70);/*清除标志*/
u8 status1 = readReg(REG_STATUS);/*读事件标志寄存器*/
status1 = status1;
return nrfEvent;
}
里面调用
/* 接收数据包,返回包长度len */
u8 nrf_rxPacket(u8 *rx_buf)
{
u8 rx_len = readReg(CMD_RX_PL_WID);
if(rx_len>0 && rx_len<33)
{
NRF_CE_L();
readBuf(CMD_R_RX_PAYLOAD,rx_buf,rx_len);
NRF_CE_H();
}
else
rx_len = 0;
writeReg(CMD_FLUSH_RX,0xff);/* 冲洗RX_FIFO */
return rx_len;
}
也是通过
readBuf(CMD_R_RX_PAYLOAD,rx_buf,rx_len);
1
在调用之前,做了检查工作,查询数据长度是否合理,然后再接收,最后冲洗RX_FIFO,两函数返回值代表的含义不同,0表示失败,len表示成功。而开发板的返回值0代表成功,1代表失败。
8,检查NRF24L01是不是工作正常,ok不ok?
stm32mini开发板例程中:
//检测24L01是否存在
//返回值:0,成功;1,失败
u8 NRF24L01_Check(void)
{
u8 buf[5]={0XA5,0XA5,0XA5,0XA5,0XA5};
u8 i;
SPI1_SetSpeed(SPI_BaudRatePrescaler_8); //spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,buf,5);//写入5个字节的地址.
NRF24L01_Read_Buf(TX_ADDR,buf,5); //读出写入的地址
for(i=0;i<5;i++)if(buf!=0XA5)break;
if(i!=5)return 1;//检测24L01错误
return 0; //检测到24L01
}
NRF_WRITE_REG+TX_ADDR 寄存器写命令+寄存器地址,
mini遥控器中:
/* 检查MCU与24l01是否通讯正常 */
/* 方法:写入读出地址是否一致 */
ErrorStatus nrf_check(void)
{
uint64_t addr = 0;
NRF_Init();
writeBuf(CMD_W_REG |REG_TX_ADDR,(u8*)&nrfAddress,5);
readBuf(CMD_R_REG|REG_TX_ADDR,(u8*)&addr,5);
if(nrfAddress==addr)
return SUCCESS;
else
return ERROR;
}
CMD_W_REG |REG_TX_ADDR,就是前面讲述过的:寄存器写命令+寄存器地址,是先通过按位或的方式(结果相当于加法),变成一个完整的操作指令传给cmd。
都是通过写一个地址数据到寄存器,然后都出来,这个过程能实现,说明MCU与24l01是否通讯正常。
9,收发模式设置
stm32mini开发板例程中:
发送和接收模式分开
该函数初始化NRF24L01到RX模式
//设置RX地址,写RX数据宽度,选择RF频道,波特率和LNA HCURR
//当CE变高后,即进入RX模式,并可以接收数据了
void NRF24L01_RX_Mode(void)
{
NRF24L01_CE=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_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); //配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式
NRF24L01_CE = 1; //CE为高,进入接收模式
该函数初始化NRF24L01到TX模式
//设置TX地址,写TX数据宽度,设置RX自动应答的地址,填充TX发送数据,选择RF频道,波特率和LNA HCURR
//PWR_UP,CRC使能
//当CE变高后,即进入RX模式,并可以接收数据了
//CE为高大于10us,则启动发送.
void NRF24L01_TX_Mode(void)
{
NRF24L01_CE=0;
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,(u8*)TX_ADDRESS,TX_ADR_WIDTH);//写TX节点地址
NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH); //设置TX节点地址,主要为了使能ACK
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01); //使能通道0的自动应答
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01); //使能通道0的接收地址
NRF24L01_Write_Reg(NRF_WRITE_REG+SETUP_RETR,0x1a);//设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40); //设置RF通道为40
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f); //设置TX发射参数,0db增益,2Mbps,低噪声增益开启
NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG,0x0e); //配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式,开启所有中断
NRF24L01_CE=1;//CE为高,10us后启动发送
}
mini遥控器中:
一个函数,用来初始化设置模式
/* model: PTX_MODE、PRX_MODE */
void nrfInit(enum nrfMode model)
{
NRF_Init();
nrf_setAddress(nrfAddress);
nrf_setChannel(DEFAULT_CHANNEL);
nrf_setDataRate(DEFAULT_DATARATE);
nrf_setPower(DEFAULT_POWR);
nrf_setArd();
nrf_setArc(3);
if(model==PRX_MODE)
{
writeReg(REG_CONFIG, 0x0f); /* IRQ收发完成中断开启,16位CRC,PRX */
writeReg(REG_DYNPD,0x01); /* 使能RX_P0动态长度PLAYLOAD */
writeReg(REG_FEATURE,0x06); /* 使能动态长度PLAYLOAD、发送ACK PLAYLOAD */
writeReg(REG_EN_AA,0x01); /* 使能通道0的自动应答 */
writeReg(CMD_FLUSH_TX,0xff); /* 冲洗TX_FIFO */
writeReg(CMD_FLUSH_RX,0xff);
}
else
{
writeReg(REG_CONFIG, 0x0e); /* IRQ收发完成中断开启,16位CRC,PTX */
writeReg(REG_DYNPD,0x01); /* 使能RX_P0动态长度PLAYLOAD */
writeReg(REG_FEATURE,0x07); /* 使能动态长度、ACK PLAYLOAD发送、W_TX_PAYLOAD_NOACK */
writeReg(CMD_FLUSH_TX,0xff); /* 冲洗TX_FIFO */
writeReg(CMD_FLUSH_RX,0xff);
}
}
遥控器里的代码把设置NRF24L01的模块工作的参数用单独的函数封装起来了:
1)设置发射和接收应答通道0的地址
void nrf_setAddress(uint64_t address)
{
writeBuf(CMD_W_REG |REG_RX_ADDR_P0,(u8*)&address,5);//接收使用P0节点
writeBuf(CMD_W_REG |REG_TX_ADDR,(u8*)&address,5);
}
参考文档:
在增强型 ShockBurstTM 模式下 RX_ADDR_P0与TX_ADDR地址相等。
接收自动应答通道0的地址0x0A
发送通道地址0x10
通过writeBuf函数在指定位置写入连续字节,最多5个字节,40位。自动应答接收通道只能是接收通道0
2)/* 设置频率通道,channel:0~125 */
void nrf_setChannel(u8 channel){ if(channel<=125) writeReg(REG_RF_CH, channel);} 参考文档:
通过writeReg函数写入一个字节,设置工作通道频率,共7个可编程位,2的7次方=128,足够用来编0-125这126个通道
3)
void nrf_setChannel(u8 channel)
{
if(channel<=125)
writeReg(REG_RF_CH, channel);
}
void nrf_setDataRate(enum nrfRate dataRate)
{
u8 reg_rf = readReg(REG_RF_SETUP);
reg_rf &= ~((1<<5)|(1<<3));/* 清除原设速率 */
switch(dataRate)
{
case DATA_RATE_250K:
reg_rf |= 0x20;
break;
case DATA_RATE_1M:
reg_rf |= 0x00;
break;
case DATA_RATE_2M:
reg_rf |= 0x08;
break;
}
writeReg(REG_RF_SETUP,reg_rf);
}
参考文档:
中文版有些错误,对着英文版参考
看手册可知,速率由RF_SRTUP这个地址的第5位RF_DR_LOW和第3位RF_DR_HIGH 决定
[ 第5位 , 第3位 ]
[RF_DR_LOW, RF_DR_HIGH]:
‘00’ – 1Mbps
‘01’ – 2Mbps
‘10’ – 250kbps
‘11’ – Reserved
接下来就涉及到数字电子电路的知识了:
函数第一句就是读该寄存器(REG_RF_SETUP)的值了,也就是得到上面那8个位的情况,用reg_rf存着
第二句:reg_rf &= ~((1<<5)|(1<<3));从右往左看,就是先得到 第5位和第3位 的情况,取反,再和原理的自己位与,这样就只把 第5位和第3位清零了,其他位不变。结果还存在reg_rf。从右往左看,如下运算
reg_rf = (reg_rf )&(~((1<<5)|(1<<3)));
再详细分析一波:
1<<5就是0010 0000
1<<3就是0000 1000
(1<<5)|(1<<3)就是0010 1000
~(1<<5)|(1<<3)就是1101 0111
reg_rf=reg_rf&1101 0111,
0和(0,1)相位与都为0,1和(0,1)相位与都为(0,1),这样一操作结果变成第5位和第3位清零
4)/* 设置发射功率,power: 0->-18dB 1->-12dB 2->-6dB 3->0dB */
void nrf_setPower(enum nrfPower power)
{
u8 reg_rf = readReg(REG_RF_SETUP);
reg_rf &= 0xF8;/* 清除原设功率 */
switch(power)
{
case POWER_M18dBm:
reg_rf |= 0x00;
break;
case POWER_M12dBm:
reg_rf |= 0x02;
break;
case POWER_M6dBm:
reg_rf |= 0x04;
break;
case POWER_0dBm:
reg_rf |= 0x07;
break;
}
writeReg(REG_RF_SETUP,reg_rf);
}
操作的还是RF_SETUP,只不过是2:1两个位,配置如下
操作过程和上面一样,先把对应的位清零,再或一个要设置的值:
reg_rf &= 0xF8;/* 清除原设功率 */
0xF8就是1111 1000,最低位不用管,也就是2:1位起作用,要设置的也是这两位
dBm —— 2:1
18dBm——00——0000 0000——0x00
12dBm——01——0000 0010——0x02
6 dBm——10——0000 0100——0x04
0 dBm——11——0000 0110——0x06
所以,我怀疑它最后一句reg_rf |= 0x07;是错误的
5)/* 设置重发时间间隔,根据速率及收发字节大小设置 */
void nrf_setArd(void)
{
u8 reg_rf,reg_retr;
reg_rf=readReg(REG_RF_SETUP);
reg_retr=readReg(REG_SETUP_RETR);
if(!(reg_rf&0x20)) /* 速率不是250K(寄存器0x20) */
reg_retr|= 1<<4;/* (1+1)*250=500us,在接收32字节时 */
else
reg_retr|= 5<<4;/* (5+1)*250=1500us,在接收32字节时 */
writeReg(REG_SETUP_RETR,reg_retr);
}
如果是250K则间隔reg_retr设置为500us,如果1M或者2M则设置为1500us。在SETUP_RETR寄存器7:4位设置间隔,左移4位,就是对高四位进行操作。但是应该有个对高四位清零的操作reg_retr&=0x01吧,不清0 假如原来reg_retr的高四位ARD为0101,想设置为1001,使用reg_retr|= 5<<4,结果变成了1101.
6)/* 设置重发次数,arc:0~15 */
void nrf_setArc(u8 arc)
{
u8 reg_retr;
if(arc>15)//不能设置大于15的
return;
reg_retr=readReg(REG_SETUP_RETR);//读取SETUP_RETR,
reg_retr|= arc;//只修改低四位ARC,不需要移位
writeReg(REG_SETUP_RETR,reg_retr);
}
也是在SETUP_RETR寄存器3:0位设置
也需要对ARC这低四位先清0
10,
mini遥控器中其他几个相关函数:
/* 获取重发失败次数 */
u8 nrf_getTxRetry(void)
{
return readReg(REG_OBSERVE_TX)&0x0F;
}
1
2
3
4
/* 获取接收功率检测 */
u8 nrf_getRpd(void)
{
return readReg(REG_RPD);
}
1
2
3
4
/*设置nrf中断回调函数*/
void nrf_setIterruptCallback(void(*cb)(void))//参数为函数指针
{
interruptCb = cb;//把cb函数指针给interruptCb函数指针,两函数指针指向同一函数
//cb指针为形参,完事自动释放,而interruptCb为全局变量,多个函数共享
}
/*外部中断服务函数*/
void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line13)==SET)
{
if(interruptCb)//初始化的时候使用空指针常量0,保证指针不指向任何实际的函数或对象,当有指向函数时,为真。
{
interruptCb();//调用函数cb()
}
EXTI_ClearITPendingBit(EXTI_Line13);
}
}
最后回到
/* 初始化NRF24L01配置 */
/* model: PTX_MODE、PRX_MODE */
void nrfInit(enum nrfMode model)
{
NRF_Init();//初始化spi,设置空闲时钟为低电平,第一个边缘捕获,数据传输从MSB位等
nrf_setAddress(nrfAddress);//设置发送和接收通道0地址为0x123456789AULL
nrf_setChannel(DEFAULT_CHANNEL);//工作频率设置,默认频率通道为2
nrf_setDataRate(DEFAULT_DATARATE);//默认速度DATA_RATE_250K
nrf_setPower(DEFAULT_POWR);//默认功率POWER_0dBm
nrf_setArd();//重发间隔1500us
nrf_setArc(3);//重发3次
if(model==PRX_MODE)//接收模式
{
writeReg(REG_CONFIG, 0x0f); /* IRQ收发完成中断开启,16位CRC,PRX */
writeReg(REG_DYNPD,0x01); /* 使能RX_P0动态长度PLAYLOAD */
writeReg(REG_FEATURE,0x06); /* 使能动态长度PLAYLOAD、发送ACK PLAYLOAD */
writeReg(REG_EN_AA,0x01); /* 使能通道0的自动应答 */
writeReg(CMD_FLUSH_TX,0xff); /* 冲洗TX_FIFO */
writeReg(CMD_FLUSH_RX,0xff);
}
else//发送模式
{
writeReg(REG_CONFIG, 0x0e); /* IRQ收发完成中断开启,16位CRC,PTX */
writeReg(REG_DYNPD,0x01); /* 使能RX_P0动态长度PLAYLOAD */
writeReg(REG_FEATURE,0x07); /* 使能动态长度、ACK PLAYLOAD发送、W_TX_PAYLOAD_NOACK */
writeReg(CMD_FLUSH_TX,0xff); /* 冲洗TX_FIFO */
writeReg(CMD_FLUSH_RX,0xff);
}
}
首先看两个版本关于NRF的宏定义:
开发板:
遥控器:
1,最基本的读写函数,函数的返回值就是读来的数据,形参就是写入的数据
stm32mini开发板例程中:
//SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI1_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
{
retry++;
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI1, TxData); //通过外设SPIx发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)//检查指定的SPI标志位设置与否:接受缓存非空标志位
{
retry++;
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mini遥控器中:
//发送数据,同时返回最近收到的数据
static u8 SPI_RWByte(SPI_TypeDef* SPIx , u8 TxData)
{
/* 通过外设SPIx发送一个数据 */
SPI_I2S_SendData(SPIx, TxData);
/* 检查指定的SPI标志位设置与否:发送缓存空标志位*/
while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET);//空标志为假,代表缓存不是空的,还没发送完,等待发完
/* 检查指定的SPI标志位设置与否:接受缓存非空标志位 */
while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET);//非空标志为假,代表接收缓存没有空,上次的还没移走,等空了可以接收数据
/* 返回通过SPIx最近接收的数据 */
return SPI_I2S_ReceiveData(SPIx);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
区别:开发板多了个超时退出,这个貌似作用不大
2,写寄存器函数
stm32mini开发板例程中:
//SPI写寄存器
//reg:指定寄存器地址
//value:写入的值
u8 NRF24L01_Write_Reg(u8 reg,u8 value)
{
u8 status;
NRF24L01_CSN=0; //使能SPI传输
status =SPI1_ReadWriteByte(reg);//发送寄存器号
SPI1_ReadWriteByte(value); //写入寄存器的值
NRF24L01_CSN=1; //禁止SPI传输
return(status); //返回状态值
}
1
2
3
4
5
6
7
8
9
10
11
12
mini遥控器中:
/* 写寄存器 */
static u8 writeReg(u8 reg,u8 value)
{
u8 status;
SPI2_CSN_L();
status=SPI_RWByte(NRF_SPI,reg|CMD_W_REG);
SPI_RWByte(NRF_SPI , value);
SPI2_CSN_H();
return status;
}
对比可知,形参一致,区别在开发板的status =SPI1_ReadWriteByte(reg);//发送寄存器号 和遥控器的status=SPI_RWByte(NRF_SPI,reg|CMD_W_REG);
其实最终都是一样的,只是在使用时,给的形参不同,
在开发板中,这样调用的
寄存器操作命令+寄存器地址
NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS,sta); //清除TX_DS或
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01); //使能通道0的接收地址
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40); //设置RF通信频率 MAX_RT中断标志
或者直接寄存器操作命令
NRF24L01_Write_Reg(FLUSH_RX,0xff);//清除RX FIFO寄存器
NRF24L01_Write_Reg(FLUSH_TX,0xff);//清除TX FIFO寄存器
在遥控器中,这样调用的
writeReg(REG_STATUS,0x70);/*清除标志*/
writeReg(REG_RF_CH, channel);
writeReg(REG_RF_SETUP,reg_rf);
writeReg(CMD_FLUSH_TX,0xff);
writeReg(CMD_FLUSH_RX,0xff);/* 冲洗RX_FIFO */
全都一个,不用管寄存器指令,直接传入地址,就因为区别在开发板的status =SPI1_ReadWriteByte(reg);//发送寄存器号 和遥控器的status=SPI_RWByte(NRF_SPI,reg|CMD_W_REG);,遥控器的函数是直接在函数内部将写写配置寄存器与寄存器地址融合,通过按位或,为什么可以这样呢?
因为寄存器操作命令中写配置寄存器为0x20,也就是0010 0000,低5位为寄存器地址,然后下方的寄存器地址刚好就是用了5位,000x xxxx,如NRF_FIFO_STATUS 0x17就是0001 0111,那么两者按位或的结果就是两者的和。
个人觉得遥控器的这个函数不错,可以推测其读寄存器函数也这样干的。
3,读寄存器
stm32mini开发板例程中:
sta=NRF24L01_Read_Reg(STATUS); //读取状态寄存器的值
搜索一下,发现没有使用 寄存器操作命令+寄存器地址的参数,只用到这一个,或许用不到这个寄存器操作命令,就是算用的,也是0x00,即0000 0000,零和任何数相加都等于那个数
mini遥控器中:
所以这里的这个CMD_R_REG 0x00 // 读寄存器指令和任何数或都等于那个数。
4,在指定位置写指定长度的数据
stm32mini开发板例程中:
//reg:寄存器(位置)
//*pBuf:数据指针
//len:数据长度
//返回值,此次读到的状态寄存器值
u8 NRF24L01_Write_Buf(u8 reg, u8 *pBuf, u8 len)
{
u8 status,u8_ctr;
NRF24L01_CSN = 0; //使能SPI传输
status = SPI1_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值
for(u8_ctr=0; u8_ctr
SPI1_ReadWriteByte(*pBuf++); //写入数据
NRF24L01_CSN = 1; //关闭SPI传输
return status; //返回读到的状态值
}
传给形参reg的是寄存器操作命令(读/写寄存器指令)+寄存器地址,和 寄存器操作命令(其他寄存器指令) 用来写待发送的连续数据(字节)到TX BUF 寄存器,和把地址写入到接收应答的通道0——RX_ADDR_P0,发送地址寄存器TX_ADDRESS,接收地址寄存器RX_ADDRESS,如:
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,buf,5);//写入5个字节的地址.
NRF24L01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);//写数据到TX BUF 32个字节
NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);//写RX节点地址
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,(u8*)TX_ADDRESS,TX_ADR_WIDTH);//写TX节点地址
mini遥控器中:
static u8 writeBuf(u8 cmd,u8 *pBuf,u8 len)
{
u8 status,i;
SPI2_CSN_L(); //拉低,使能器件,开启SPI通信
status=SPI_RWByte(NRF_SPI,cmd);//如发送一个写命令,返回一个许可状态
for(i=0;i
SPI_RWByte(NRF_SPI,*pBuf++);
SPI2_CSN_H(); //写完,拉高,失能器件,关闭SPI通信
return status;
}
和开发板一样的,但是他的形参是cmd命令的意思,寄存器操作命令(读/写寄存器指令)+寄存器地址就是个完整的命令,而其他寄存器指令不用加地址,就是一个完整的命令,注意到:writeBuf(u8 cmd,u8 *pBuf,u8 len)和writeReg(u8 reg,u8 value)函数的第一个形参不同,都是写操作,但writeBuf里面没有reg|CMD_W_REG,所以叫cmd,需把完整命令传给cmd,而writeBuf传被写的寄存器地址就行。
cmd完整命令:寄存器操作命令(读/写寄存器指令)+寄存器地址
reg 寄存器:寄存器地址
5,在指定位置读出指定长度的数据
stm32mini开发板例程中:
//reg:寄存器(位置)
//*pBuf:数据指针
//len:数据长度
//返回值,此次读到的状态寄存器值
u8 NRF24L01_Read_Buf(u8 reg,u8 *pBuf,u8 len)
{
u8 status,u8_ctr;
NRF24L01_CSN = 0; //使能SPI传输
status=SPI1_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值
for(u8_ctr=0;u8_ctr
pBuf[u8_ctr]=SPI1_ReadWriteByte(0XFF);//读出数据
NRF24L01_CSN=1; //关闭SPI传输
return status; //返回读到的状态值
}
传给形参reg的是寄存器操作命令(读/写寄存器指令)+寄存器地址,和**寄存器操作命令(其他寄存器指令)**用来读寄存器值里连续地址的数据
NRF24L01_Read_Buf(TX_ADDR,buf,5); //读出写入的地址
NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);//读取数据
1
2
mini遥控器中:
static u8 readBuf(u8 cmd,u8 *pBuf,u8 len)
{
u8 status,i;
SPI2_CSN_L();//拉低,使能器件
status=SPI_RWByte(NRF_SPI,cmd); //如发送一个读命令,返回一个许可状态
for(i=0;i
pBuf
=SPI_RWByte(NRF_SPI,0XFF);
SPI2_CSN_H();//读完后,拉高失能
return status;
}
形参是cmd命令的意思,寄存器操作命令(读/写寄存器指令)+寄存器地址就是个完整的命令,而其他寄存器指令不用加地址,就是一个完整的命令
6,发送数据包
stm32mini开发板例程中:
//启动NRF24L01发送一次数据
//txbuf:待发送数据首地址
//返回值:发送完成状况
u8 NRF24L01_TxPacket(u8 *txbuf)
{
u8 sta;
SPI1_SetSpeed(SPI_BaudRatePrescaler_8);//spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)
NRF24L01_CE=0;
NRF24L01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);//写数据到TX BUF 32个字节
NRF24L01_CE=1;//启动发送
while(NRF24L01_IRQ!=0);//等待发送完成
sta=NRF24L01_Read_Reg(STATUS); //读取状态寄存器的值
NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS,sta); //清除TX_DS或MAX_RT中断标志
if(sta&MAX_TX)//达到最大重发次数
{
NRF24L01_Write_Reg(FLUSH_TX,0xff);//清除TX FIFO寄存器
return MAX_TX;
}
if(sta&TX_OK)//发送完成
{
return TX_OK;
}
return 0xff;//其他原因发送失败
}
开发板的发送包函数,里面内容比较多,起发送作用的就一句:
NRF24L01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);//写数据到TX BUF 32个字节
1
剩下的代码就是做一些“善后工作”,即等待发送完成,查询状态寄存器,清除标志位,如MAX_RT标志置1后,无法再次发送,需要写1清除该标志位就能再次启动发送,工作在自动应答模式下,只有当接收到应答信号后TX_DS标志位置位1代表发送成功(对方成功接收),写1清0,所以用sta写sta,还清除TX FIFO寄存器 。
mini遥控器中:
void nrf_txPacket(u8 *tx_buf,u8 len)
{
NRF_CE_L(); //24L01 片选信号
writeBuf(CMD_W_TX_PAYLOAD,tx_buf,len);
NRF_CE_H();
}
遥控器里的没有做“善后工作”,可能得和其他函数搭配使用。
被下面函数调用
/* 发送数据包,并等待接收ACK(PTX模式) */
/* 返回值:1成功、0失败*/
u8 nrf_sendPacketWaitACK(u8 *sendBuf, u8 len, u8 *ackBuf, u8 *acklen)
{
if(len==0) return 0;
nrf_txPacket(sendBuf,len);//调用发送包函数发送长度为len个字节
while((readReg(REG_STATUS)&0x70) == 0);/* 等待事件 */
nrfEvent_e nrfEvent = nrf_checkEventandRxPacket(ackBuf, acklen);//查询是哪种事件发生了
if(nrfEvent == MAX_RT)//如果是重发失败
return 0;
return 1;
}
其中while((readReg(REG_STATUS)&0x70) == 0);里,0x07,写为二进制就是0111 0000,REG_STATUS和0x70位与就是得到状态寄存器的4,5,6位的情况,4-MAX-RT:达到最大重发次数,5—TX_DS发送完成,6-RX-DR有效数据来了,只要有一个事件发生,结果 (readReg(REG_STATUS)&0x70) !=0,退出while循环,一个都没发生者循环空语句,即等待。3个中断事件如下
还有调用查询事件并接收数据包函数:nrf_checkEventandRxPacket(ackBuf, acklen);原型如下:
nrfEvent_e nrf_checkEventandRxPacket(u8 *ackBuf, u8 *acklen)
{
nrfEvent_e nrfEvent = IDLE;
*acklen = 0;
u8 status = readReg(REG_STATUS);/*读事件标志寄存器*/
if(status&BIT_MAX_RT)/*重发失败*/
{
writeReg(CMD_FLUSH_TX,0xff);
nrfEvent = MAX_RT;
}
else if(status&BIT_RX_DR)/*接收数据到RX_FIFO*/
{
*acklen = nrf_rxPacket(ackBuf);//接收数据包,返回包长度len
nrfEvent = RX_DR;
}
else if(status&BIT_TX_DS)/*发送数据至TX_FIFO成功*/
{
nrfEvent = TX_DS;
}
writeReg(REG_STATUS,0x70);/*清除标志*/
u8 status1 = readReg(REG_STATUS);/*读事件标志寄存器*/
status1 = status1;
return nrfEvent;
}
看看事件状态(标志)寄存器的4,5,6位的情况是哪种情况发生了,并返回标志,设计了一个枚举,
当没有事件发生时是空闲:nrfEvent_e nrfEvent = IDLE;如果有哪个标志置1就返回该标志:
如果是最大次数标志置位,代表重发失败,得清空该标志,模块才能启动发送:writeReg(CMD_FLUSH_TX,0xff);返回的枚举变量nrfEvent = MAX_RT;
如果是数据到来了,那就调用上面的接收包函数:*acklen = nrf_rxPacket(ackBuf);,存储包,同时存储返回值——包长度,返回的枚举变量nrfEvent = RX_DR;
如果发生成功(即接收到了接收端的自动应答ACK),则返回的枚举变量nrfEvent = TX_DS;。
说明,此函数有三个功能,调用它,就可以知道是重发失败,还是发生成功,还是有数据来了,若是还自动接收数据,代码逻辑上,如果这三种情况都是,那一下就做了三件事,但接收和发生不能同时发生,所以返回值是其中的一种。最后writeReg(REG_STATUS,0x70);/清除标志/,最后两句代码不知道为什么,感觉有点多余
再回到nrf_sendPacketWaitACK函数,它里面就调用nrfEvent_e nrfEvent = nrf_checkEventandRxPacket(ackBuf, acklen); 结合上面的nrf_txPacket(sendBuf,len);和这个大函数的名字sendPacketWaitACK,可知是NFR在发送模式下掉用的,那只要两种情况,因为使能了重发,结果要么是重发失败,要么是发送成功即收到了应答信号。所以最后判断if(nrfEvent == MAX_RT),返回1就是后种情况。
对比一下开发板的u8 NRF24L01_TxPacket(u8 * txbuf)函数和nrf_sendPacketWaitACK(u8 * sendBuf, u8 len, u8 * ackBuf, u8 * acklen)函数,是不是很有感觉——过程相似? 但在遥控器代码里居然没被调用过,,,,
猜测接收包也有这种类似功能的函数,继续探索,,,,,
7,接收数据包,
stm32mini开发板例程中:
//启动NRF24L01发送一次数据
//txbuf:待发送数据首地址
//返回值:0,接收完成;其他,错误代码
u8 NRF24L01_RxPacket(u8 *rxbuf)
{
u8 sta;
SPI1_SetSpeed(SPI_BaudRatePrescaler_8); //spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)
sta=NRF24L01_Read_Reg(STATUS); //读取状态寄存器的值
NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS,sta); //清除TX_DS或MAX_RT中断标志
if(sta&RX_OK)//接收到数据
{
NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);//读取数据
NRF24L01_Write_Reg(FLUSH_RX,0xff);//清除RX FIFO寄存器
return 0;
}
return 1;//没收到任何数据
}
设置spi速度的那句可以放在spi初始化时调用,起发送包作用的也是一句:
NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);//读取数据
1
其他代码有 “善前工作:查询状态有没有接到数据收” 和“善后工作:清除RX FIFO寄存器 ”。还可以用利用中断来提示该接收数据了,如:while(NRF24L01_IRQ!=0);//等待数据到达,因为当数据到达该引脚也会被拉低,这和TX_DS标志位,MAX_RT标志位一样,中断来了就拉低IRQ。
mini遥控器中:
查询事件并接收数据包 函数,nrfEvent_e nrf_checkEventandRxPacket(u8 * ackBuf, u8 * acklen)类似开发板的u8 NRF24L01_RxPacket(u8 * rxbuf函数
nrfEvent_e nrf_checkEventandRxPacket(u8 *ackBuf, u8 *acklen)
{
nrfEvent_e nrfEvent = IDLE;
*acklen = 0;
u8 status = readReg(REG_STATUS);/*读事件标志寄存器*/
if(status&BIT_MAX_RT)/*重发失败*/
{
writeReg(CMD_FLUSH_TX,0xff);
nrfEvent = MAX_RT;
}
else if(status&BIT_RX_DR)/*接收数据到RX_FIFO*/
{
*acklen = nrf_rxPacket(ackBuf);//接收数据包,返回包长度len
nrfEvent = RX_DR;
}
else if(status&BIT_TX_DS)/*发送数据至TX_FIFO成功*/
{
nrfEvent = TX_DS;
}
writeReg(REG_STATUS,0x70);/*清除标志*/
u8 status1 = readReg(REG_STATUS);/*读事件标志寄存器*/
status1 = status1;
return nrfEvent;
}
里面调用
/* 接收数据包,返回包长度len */
u8 nrf_rxPacket(u8 *rx_buf)
{
u8 rx_len = readReg(CMD_RX_PL_WID);
if(rx_len>0 && rx_len<33)
{
NRF_CE_L();
readBuf(CMD_R_RX_PAYLOAD,rx_buf,rx_len);
NRF_CE_H();
}
else
rx_len = 0;
writeReg(CMD_FLUSH_RX,0xff);/* 冲洗RX_FIFO */
return rx_len;
}
也是通过
readBuf(CMD_R_RX_PAYLOAD,rx_buf,rx_len);
1
在调用之前,做了检查工作,查询数据长度是否合理,然后再接收,最后冲洗RX_FIFO,两函数返回值代表的含义不同,0表示失败,len表示成功。而开发板的返回值0代表成功,1代表失败。
8,检查NRF24L01是不是工作正常,ok不ok?
stm32mini开发板例程中:
//检测24L01是否存在
//返回值:0,成功;1,失败
u8 NRF24L01_Check(void)
{
u8 buf[5]={0XA5,0XA5,0XA5,0XA5,0XA5};
u8 i;
SPI1_SetSpeed(SPI_BaudRatePrescaler_8); //spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,buf,5);//写入5个字节的地址.
NRF24L01_Read_Buf(TX_ADDR,buf,5); //读出写入的地址
for(i=0;i<5;i++)if(buf!=0XA5)break;
if(i!=5)return 1;//检测24L01错误
return 0; //检测到24L01
}
NRF_WRITE_REG+TX_ADDR 寄存器写命令+寄存器地址,
mini遥控器中:
/* 检查MCU与24l01是否通讯正常 */
/* 方法:写入读出地址是否一致 */
ErrorStatus nrf_check(void)
{
uint64_t addr = 0;
NRF_Init();
writeBuf(CMD_W_REG |REG_TX_ADDR,(u8*)&nrfAddress,5);
readBuf(CMD_R_REG|REG_TX_ADDR,(u8*)&addr,5);
if(nrfAddress==addr)
return SUCCESS;
else
return ERROR;
}
CMD_W_REG |REG_TX_ADDR,就是前面讲述过的:寄存器写命令+寄存器地址,是先通过按位或的方式(结果相当于加法),变成一个完整的操作指令传给cmd。
都是通过写一个地址数据到寄存器,然后都出来,这个过程能实现,说明MCU与24l01是否通讯正常。
9,收发模式设置
stm32mini开发板例程中:
发送和接收模式分开
该函数初始化NRF24L01到RX模式
//设置RX地址,写RX数据宽度,选择RF频道,波特率和LNA HCURR
//当CE变高后,即进入RX模式,并可以接收数据了
void NRF24L01_RX_Mode(void)
{
NRF24L01_CE=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_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); //配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式
NRF24L01_CE = 1; //CE为高,进入接收模式
该函数初始化NRF24L01到TX模式
//设置TX地址,写TX数据宽度,设置RX自动应答的地址,填充TX发送数据,选择RF频道,波特率和LNA HCURR
//PWR_UP,CRC使能
//当CE变高后,即进入RX模式,并可以接收数据了
//CE为高大于10us,则启动发送.
void NRF24L01_TX_Mode(void)
{
NRF24L01_CE=0;
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,(u8*)TX_ADDRESS,TX_ADR_WIDTH);//写TX节点地址
NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH); //设置TX节点地址,主要为了使能ACK
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01); //使能通道0的自动应答
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01); //使能通道0的接收地址
NRF24L01_Write_Reg(NRF_WRITE_REG+SETUP_RETR,0x1a);//设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40); //设置RF通道为40
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f); //设置TX发射参数,0db增益,2Mbps,低噪声增益开启
NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG,0x0e); //配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式,开启所有中断
NRF24L01_CE=1;//CE为高,10us后启动发送
}
mini遥控器中:
一个函数,用来初始化设置模式
/* model: PTX_MODE、PRX_MODE */
void nrfInit(enum nrfMode model)
{
NRF_Init();
nrf_setAddress(nrfAddress);
nrf_setChannel(DEFAULT_CHANNEL);
nrf_setDataRate(DEFAULT_DATARATE);
nrf_setPower(DEFAULT_POWR);
nrf_setArd();
nrf_setArc(3);
if(model==PRX_MODE)
{
writeReg(REG_CONFIG, 0x0f); /* IRQ收发完成中断开启,16位CRC,PRX */
writeReg(REG_DYNPD,0x01); /* 使能RX_P0动态长度PLAYLOAD */
writeReg(REG_FEATURE,0x06); /* 使能动态长度PLAYLOAD、发送ACK PLAYLOAD */
writeReg(REG_EN_AA,0x01); /* 使能通道0的自动应答 */
writeReg(CMD_FLUSH_TX,0xff); /* 冲洗TX_FIFO */
writeReg(CMD_FLUSH_RX,0xff);
}
else
{
writeReg(REG_CONFIG, 0x0e); /* IRQ收发完成中断开启,16位CRC,PTX */
writeReg(REG_DYNPD,0x01); /* 使能RX_P0动态长度PLAYLOAD */
writeReg(REG_FEATURE,0x07); /* 使能动态长度、ACK PLAYLOAD发送、W_TX_PAYLOAD_NOACK */
writeReg(CMD_FLUSH_TX,0xff); /* 冲洗TX_FIFO */
writeReg(CMD_FLUSH_RX,0xff);
}
}
遥控器里的代码把设置NRF24L01的模块工作的参数用单独的函数封装起来了:
1)设置发射和接收应答通道0的地址
void nrf_setAddress(uint64_t address)
{
writeBuf(CMD_W_REG |REG_RX_ADDR_P0,(u8*)&address,5);//接收使用P0节点
writeBuf(CMD_W_REG |REG_TX_ADDR,(u8*)&address,5);
}
参考文档:
在增强型 ShockBurstTM 模式下 RX_ADDR_P0与TX_ADDR地址相等。
接收自动应答通道0的地址0x0A
发送通道地址0x10
通过writeBuf函数在指定位置写入连续字节,最多5个字节,40位。自动应答接收通道只能是接收通道0
2)/* 设置频率通道,channel:0~125 */
void nrf_setChannel(u8 channel){ if(channel<=125) writeReg(REG_RF_CH, channel);} 参考文档:
通过writeReg函数写入一个字节,设置工作通道频率,共7个可编程位,2的7次方=128,足够用来编0-125这126个通道
3)
void nrf_setChannel(u8 channel)
{
if(channel<=125)
writeReg(REG_RF_CH, channel);
}
void nrf_setDataRate(enum nrfRate dataRate)
{
u8 reg_rf = readReg(REG_RF_SETUP);
reg_rf &= ~((1<<5)|(1<<3));/* 清除原设速率 */
switch(dataRate)
{
case DATA_RATE_250K:
reg_rf |= 0x20;
break;
case DATA_RATE_1M:
reg_rf |= 0x00;
break;
case DATA_RATE_2M:
reg_rf |= 0x08;
break;
}
writeReg(REG_RF_SETUP,reg_rf);
}
参考文档:
中文版有些错误,对着英文版参考
看手册可知,速率由RF_SRTUP这个地址的第5位RF_DR_LOW和第3位RF_DR_HIGH 决定
[ 第5位 , 第3位 ]
[RF_DR_LOW, RF_DR_HIGH]:
‘00’ – 1Mbps
‘01’ – 2Mbps
‘10’ – 250kbps
‘11’ – Reserved
接下来就涉及到数字电子电路的知识了:
函数第一句就是读该寄存器(REG_RF_SETUP)的值了,也就是得到上面那8个位的情况,用reg_rf存着
第二句:reg_rf &= ~((1<<5)|(1<<3));从右往左看,就是先得到 第5位和第3位 的情况,取反,再和原理的自己位与,这样就只把 第5位和第3位清零了,其他位不变。结果还存在reg_rf。从右往左看,如下运算
reg_rf = (reg_rf )&(~((1<<5)|(1<<3)));
再详细分析一波:
1<<5就是0010 0000
1<<3就是0000 1000
(1<<5)|(1<<3)就是0010 1000
~(1<<5)|(1<<3)就是1101 0111
reg_rf=reg_rf&1101 0111,
0和(0,1)相位与都为0,1和(0,1)相位与都为(0,1),这样一操作结果变成第5位和第3位清零
4)/* 设置发射功率,power: 0->-18dB 1->-12dB 2->-6dB 3->0dB */
void nrf_setPower(enum nrfPower power)
{
u8 reg_rf = readReg(REG_RF_SETUP);
reg_rf &= 0xF8;/* 清除原设功率 */
switch(power)
{
case POWER_M18dBm:
reg_rf |= 0x00;
break;
case POWER_M12dBm:
reg_rf |= 0x02;
break;
case POWER_M6dBm:
reg_rf |= 0x04;
break;
case POWER_0dBm:
reg_rf |= 0x07;
break;
}
writeReg(REG_RF_SETUP,reg_rf);
}
操作的还是RF_SETUP,只不过是2:1两个位,配置如下
操作过程和上面一样,先把对应的位清零,再或一个要设置的值:
reg_rf &= 0xF8;/* 清除原设功率 */
0xF8就是1111 1000,最低位不用管,也就是2:1位起作用,要设置的也是这两位
dBm —— 2:1
18dBm——00——0000 0000——0x00
12dBm——01——0000 0010——0x02
6 dBm——10——0000 0100——0x04
0 dBm——11——0000 0110——0x06
所以,我怀疑它最后一句reg_rf |= 0x07;是错误的
5)/* 设置重发时间间隔,根据速率及收发字节大小设置 */
void nrf_setArd(void)
{
u8 reg_rf,reg_retr;
reg_rf=readReg(REG_RF_SETUP);
reg_retr=readReg(REG_SETUP_RETR);
if(!(reg_rf&0x20)) /* 速率不是250K(寄存器0x20) */
reg_retr|= 1<<4;/* (1+1)*250=500us,在接收32字节时 */
else
reg_retr|= 5<<4;/* (5+1)*250=1500us,在接收32字节时 */
writeReg(REG_SETUP_RETR,reg_retr);
}
如果是250K则间隔reg_retr设置为500us,如果1M或者2M则设置为1500us。在SETUP_RETR寄存器7:4位设置间隔,左移4位,就是对高四位进行操作。但是应该有个对高四位清零的操作reg_retr&=0x01吧,不清0 假如原来reg_retr的高四位ARD为0101,想设置为1001,使用reg_retr|= 5<<4,结果变成了1101.
6)/* 设置重发次数,arc:0~15 */
void nrf_setArc(u8 arc)
{
u8 reg_retr;
if(arc>15)//不能设置大于15的
return;
reg_retr=readReg(REG_SETUP_RETR);//读取SETUP_RETR,
reg_retr|= arc;//只修改低四位ARC,不需要移位
writeReg(REG_SETUP_RETR,reg_retr);
}
也是在SETUP_RETR寄存器3:0位设置
也需要对ARC这低四位先清0
10,
mini遥控器中其他几个相关函数:
/* 获取重发失败次数 */
u8 nrf_getTxRetry(void)
{
return readReg(REG_OBSERVE_TX)&0x0F;
}
1
2
3
4
/* 获取接收功率检测 */
u8 nrf_getRpd(void)
{
return readReg(REG_RPD);
}
1
2
3
4
/*设置nrf中断回调函数*/
void nrf_setIterruptCallback(void(*cb)(void))//参数为函数指针
{
interruptCb = cb;//把cb函数指针给interruptCb函数指针,两函数指针指向同一函数
//cb指针为形参,完事自动释放,而interruptCb为全局变量,多个函数共享
}
/*外部中断服务函数*/
void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line13)==SET)
{
if(interruptCb)//初始化的时候使用空指针常量0,保证指针不指向任何实际的函数或对象,当有指向函数时,为真。
{
interruptCb();//调用函数cb()
}
EXTI_ClearITPendingBit(EXTI_Line13);
}
}
最后回到
/* 初始化NRF24L01配置 */
/* model: PTX_MODE、PRX_MODE */
void nrfInit(enum nrfMode model)
{
NRF_Init();//初始化spi,设置空闲时钟为低电平,第一个边缘捕获,数据传输从MSB位等
nrf_setAddress(nrfAddress);//设置发送和接收通道0地址为0x123456789AULL
nrf_setChannel(DEFAULT_CHANNEL);//工作频率设置,默认频率通道为2
nrf_setDataRate(DEFAULT_DATARATE);//默认速度DATA_RATE_250K
nrf_setPower(DEFAULT_POWR);//默认功率POWER_0dBm
nrf_setArd();//重发间隔1500us
nrf_setArc(3);//重发3次
if(model==PRX_MODE)//接收模式
{
writeReg(REG_CONFIG, 0x0f); /* IRQ收发完成中断开启,16位CRC,PRX */
writeReg(REG_DYNPD,0x01); /* 使能RX_P0动态长度PLAYLOAD */
writeReg(REG_FEATURE,0x06); /* 使能动态长度PLAYLOAD、发送ACK PLAYLOAD */
writeReg(REG_EN_AA,0x01); /* 使能通道0的自动应答 */
writeReg(CMD_FLUSH_TX,0xff); /* 冲洗TX_FIFO */
writeReg(CMD_FLUSH_RX,0xff);
}
else//发送模式
{
writeReg(REG_CONFIG, 0x0e); /* IRQ收发完成中断开启,16位CRC,PTX */
writeReg(REG_DYNPD,0x01); /* 使能RX_P0动态长度PLAYLOAD */
writeReg(REG_FEATURE,0x07); /* 使能动态长度、ACK PLAYLOAD发送、W_TX_PAYLOAD_NOACK */
writeReg(CMD_FLUSH_TX,0xff); /* 冲洗TX_FIFO */
writeReg(CMD_FLUSH_RX,0xff);
}
}
举报