说明一下,下面的每一个十六进制数字,都是一个控制字,控制字的每一位都有自己的定义,需要读者自行记录!!(控制字的概念是比较合适的形容下面的宏定义)
首先上最常见到的宏定义(呕心沥血一个字一个字手打的):
//****************************************************
#define READ_REG 0x00
#define WRITE_REG 0x20
#define RD_RX_PLOAD 0x61 //0110 0001 读RX 有效数据:1 - 32,读操作从字节0开始
//当读RX有效数据完成后,FIFO寄存器有效数据被清除
//应用于接收模式
#define WR_TX_PLOAD 0xA0 // 1010 0000 写 TX有效数据:1 - 32字节,从字节0开始,
//应用于发射模式下
#define FLUSH_TX 0xE1 // 1110 0001 清除TX FIFO寄存器,应用于发射模式下
#define FLUSH_RX 0xE2 // 1110 0010 青春RX FIFO寄存器,应用于接收模式下
#define REUSE_TX_PL 0xE3 // 1110 0011 应用于发射器,重新使用上一包发射的数据,
// 当 CE = 1,数据被不断发射
#define NOP 0xFF //空操作,可用来读状态寄存器
//*************配置STATUS用的****************************************************************
#define RX_DR 0x40
#define TX_DS 0x20
#define MAX_RT 0x10
//************************************************************************************
#define CONFIG 0x00 //配置寄存器
//MASK_RX_DR 6 0 R/W 屏蔽终端 RX_DR
//1:IRQ不产生RX_RD中断
//0:RX_RD中断产生时IRQ为低电平
//MASK_TX_DS 5 0 R/W 屏蔽TX_DS
//1:IRQ不产生TX_DS中断
//0:TX_DS中断产生时IRQ为低电平
//MASK_MAX_RT 4 0 R/W 可屏蔽中断MAX_RT
//1:IRQ引脚不产生TX_DS中断
//:0:TX_DS中断IRQ引脚低电平
//EN_CRC 3 0 R/W CRC使能,如果EN_AA中任意一位为高,则EN_CRC强迫为高
//CRCO 2 0 R/W CRC模式
//0:8位CRC校验
//1:16位CRC校验
//PWR_UP 1 0 R/W
//1:上电
//0:掉电
//PRIM_RX 0 0 R/W
//1:接收模式
//0:发送模式
#define EN_AA 0x01 //使能自动应答
//[7:6]保留 00
//ENAA_P5 1 通道5自动应答允许
//ENAA_P4 1 通道4自动应答允许
//ENAA_P3 1 通道3自动应答允许
//ENAA_P2 1 通道2自动应答允许
//ENAA_P1 1 通道1自动应答允许
//ENAA_P0 1 通道0自动应答允许
#define EN_RXADDR 0x02 //接收地址允许
//7:6保留 00
//ERX_P5 5 0 R/W 接收通道5允许
//ERX_P5 4 0 R/W 接收通道4允许
//ERX_P5 3 0 R/W 接收通道3允许
//ERX_P5 2 0 R/W 接收通道2允许
//ERX_P5 1 0 R/W 接收通道1允许
//ERX_P5 0 0 R/W 接收通道0允许
#define SETUP_AW 0x03 //设置地址宽度
//[7:2]保留 00
//AW [1:0]
// 00:无效
// 01:3字节宽度
// 10: 4字节宽度
// 11: 5字节宽度
#define SETUP_RETR 0x04 //建立自动重发
//ARD [7:4]
//0000:等待250 + 86us
//0010: 等待500 + 86us
//0100:等待750 + 86us
//1000:等待1000 + 86us
//。。。
//1111: 等待4000 + 86us
//ARC [3:0]
//0000:禁止自动重发
//0010:自动重发2次
//1111:自动重发15次
#define RF_CH 0x05 //射频通道
//7 保留
//RC_CH [6:0] 0000010 设置工作通道频率
#define RF_SETUP 0x06 //射频寄存器
//[7:5] 保留
//PLL_LOCK 4 0 R/W 锁相允许,仅应用于测试模式
//RF_DR 3 1 R/W 数据传输
// 0 :1Mbps
// 1 : 2Mbps
//RF_PWR [2:1] 11 发射功率
//00:18dBm
//01:12dBm
//10:6dBm
//11:0dBm
//LNA_HCURR 0 1 低噪声放大器增益,默认是1
#define STATUS 0x07 //状态寄存器
//7 0 保留 R/W 默认是0
//RX_DR 6 0 R/W 接收数据中断。当收到有效数据包后置1,写1清除中断
//TX_DS 5 0 R/W 接收数据完成中断。
//数据发送完成后产生中断,如果工作在自动应答模式下,只有当接收
//应答信号后此位置1.写1清除中断。
//MAX_RT 4 0 R/W 重发次数溢出中断,写1清除中断。如果MAT_RT中断产生则
//必须清楚后系统才能进行通讯。
//RX_P_NO [3:1] 111 R 接收数据通道号:
//000 - 101:数据通道号
//110:未使用
//111:RX FIFO寄存器为空
//TX_FULL 0 0 R TX FIFO 寄存器满标志
//1:TX FIFO 寄存器满
//0:TX FIFO 寄存器未满,有可用空间
#define OBSERVE_TX 0x08 //发送检测寄存器
//PLOS_CNT [7:4] 0 R 数据包丢失计算器,当写RF_CH寄存器时此寄存器复位
//,当丢失15个数据包后此寄存器重启
//ARC_CNT [3:0] 0 R 重发计数器。发送新数据包时寄存器复位
#define CD 0x09 //---------------
//[7:1] 0000
//CD 0 0 R 载波检测
#define RX_ADDR_P0 0x0A //数据通道0接收地址,最大长度5个字节[39:0],先写低字节,
//所写字节由SETUP_AW设定
#define RX_ADDR_P1 0x0B //数据通道1接收地址,最大长度5个字节[39:0],先写低字节,
//所写字节由SETUP_AW设定
#define RX_ADDR_P2 0x0C //数据通道2接收地址,最低字节可设置 [7:0]
//高字节部分必须与RX_ADDR_PI[39:8]相等
#define RX_ADDR_P3 0x0D //数据通道3接收地址,最低字节可设置 [7:0]
//高字节必须与RX_ADDR_PI[39:8]相等
#define RX_ADDR_P4 0x0E //数据通道4接收地址,最低字节可设置 [7:0]
//高字节必须与RX_ADDR_PI[39:8]相等
#define RX_ADDR_P5 0x0F //数据通道3接收地址,最低字节可设置 [7:0]
//高字节必须与RX_ADDR_PI[39:8]相等
#define TX_ADDR 0x10 //[39:0] 发送地址 在增强型ShockBrust模式下,设置RX_ADDR_P0与此地址相等接收消息
#define RX_PW_P0 0x11 //--------------------------------
//RX_PW_P0 [5:0]接收数据通道0的有效数据宽度 值域:(0 -- 32)
//0:设置不合法
//1:1字节有效宽度
// 。。。
//32:32字节有效宽度
#define RX_PW_P1 0x12 //--------------------------------
//RX_PW_P1 [5:0]接收数据通道1的有效宽度 值域:(0 -- 32)
//0:设置不合法
//1:1字节有效宽度
// 。。。
//32:32字节有效宽度
#define RX_PW_P2 0x13 //----------------------------------------------
//RX_PW_P2 [5:0]值域:(0 -- 32)接收数据通道2的有效宽度
//0:设置不合法
//1:1字节有效宽度
// 。。。
//32:32字节有效宽度
#define RX_PW_P3 0x14 ////----------------------------------------------
//RX_PW_P3 [5:0]值域:(0 -- 32)接收数据通道2的有效宽度
//0:设置不合法
//1:1字节有效宽度
// 。。。
//32:32字节有效宽度
#define RX_PW_P4 0x15 ////----------------------------------------------
//RX_PW_P4 [5:0]值域:(0 -- 32)接收数据通道2的有效宽度
//0:设置不合法
//1:1字节有效宽度
// 。。。
//32:32字节有效宽度
#define RX_PW_P5 0x16 //----------------------------------------------
//RX_PW_P5[5:0]值域:(0 -- 32)接收数据通道2的有效宽度
//0:设置不合法
//1:1字节有效宽度
// 。。。
//32:32字节有效宽度
#define FIFO_STATUS 0x17 //FIFO状态寄存器
//7 保留
//6 R 若TX_REUSE = 1,当CE为高电平时候发送上一个数据包,
//TX_REUSE通过SPI指令REUSE_TX_PL设置,通过W_TX_PAYLOAD或者
//FLUSH_TX复位
//5 R TX FIFO寄存器满标志
//1: TX FIFO寄存器满标志
//0: TX FIFO寄存器未满,有可用空间
//4 R TX FIFO寄存器标志
//1:TX FIFO 寄存器空
//0:TX FIFO寄存器非空
//3:2 保留
//1 R RX FIFO寄存器标志
//1:RX FIFO 寄存器满
//0:RX FIFO 寄存器未满,有可用空间
//0 R RX FIFO寄存器空标志
//1:RX FIFO寄存器空标志
//0:RX FIFO寄存器非空
仔细看上面的内容,一个一个的看,然后用笔记录一下。之后开始讲解细节操作:
以上为对nRF24l01的操作位定义。那么对它进行操作的时候,应该就是通过SPI函数进行的,也就是说用SPI函数操作引脚然后达到控制nRF24l01的目的。
先来看nRF24l01的操作模式表格:
我们根据这个表来进行模式的切换。(本文介绍原理)
其中,PWR_UP PRIM_RX属于CONFIG控制字的内容。所以,我们要进行设置,如何设置呢?
我们利用SPI的函数:byte SPI_RW_Reg(unsigned char reg, unsigned char value) 操作寄存器。那么这个函数和操作寄存器有什么关系呢?我想这个问题很多人都没有想过。
首先看代码:
byte SPI_RW_Reg(unsigned char reg, unsigned char value)
{
byte status;
digitalWrite(CSN, 0);
status = SPI_RW(reg);
SPI_RW(value);
digitalWrite(CSN, 1);
return(status);
}
这是代码实现,其中
digitalWrite(CSN, 0);
很容易理解,就是写CSN引脚的电平,设置为0,然后
status = SPI_RW(reg);
SPI_RW 另外的函数,我们来看定义:
byte SPI_RW(unsigned char Byte)
{
byte i;
for(i=0;i<8;i++)
{
if(Byte&0x80) /*0x80的二进制为 1000 0000 ,一个控制字是8-bit,所以通过&运算可知道当前最高位的数字是多少,这个设计很巧妙,然后利用 Byte <<= 1;遍历控制字内的每一位,然后循环8次,就把控制字的8个bit全部遍历了一次!读者自行体会*/
{
digitalWrite(_MOSI, 1); //利用_MOSI(通信方向:主机出从机入)的电平变化输出数据!!!
}
else
{
digitalWrite(_MOSI, 0); }//利用_MOSI的电平变化输出数据!!!
//SPI时钟
digitalWrite(_SCK, 1);//SCK Singal of Clock(个人理解哈~)就是时钟信息,准备干活了,所以高电平开始工作
Byte <<= 1; //循环移位,遍历控制字的每一位!这与0x80有关系
if(digitalRead(_MISO) == 1)/*MISO(通信方向:从机出主机入),如果当前有信息通过MISO引脚进来了,那么 执行 Byte |= 1,这句话的作用是将“有信息输入”这个信息记录进Byte中,用于当操作者调用这个函数的时候获取寄存器状态时,可让操作者知道如下的事情发生:“在我进行发射信息操作时,有新的信息到来”,到来了几个信息,我查看一下Byte中有几个1,我就知道有几个信息*/
{
Byte |= 1;
}
digitalWrite(_SCK, 0); //干完活了,就让它眯着吧~~~
}
return(Byte); //返回执行的结果码,,(也就是新的控制字)
}
可见,我们的控制字就通过
digitalWrite(_MOSI, 0);
digitalWrite(_MOSI, 1);
这两条语句发出去了,因为MOSI连接的是NRF54l01的引脚,所以说就是把信息送到了nRF54l01中,所以就完成了寄存器的读写。
看到这里,最核心的东西就已经结束了,然后开始其他的操作:
如:
byte SPI_Write_Buf(unsigned char reg, unsigned char *pBuf, unsigned char bytes)
{
byte status,i;
digitalWrite(CSN, 0); //CSN时钟片选,低电平工作~也就是开始干活了
status = SPI_RW(reg); //写寄存器,按照reg的控制字要内容求进行读写寄存器
for(i=0;i<bytes; i++) //bytes是要写的字符宽度,也就是要写几个宽度的数据
{
SPI_RW(*pBuf++);//按照pBuf[]数组中的控制字进行相应的操作。
}
digitalWrite(CSN, 1); //干完活了,让它歇着~~一个劲干活,它会自杀(烧了)的
return(status);
}
然后开始下一个函数介绍:
byte SPI_RW_Reg(unsigned char reg, unsigned char value)
{
byte status;
digitalWrite(CSN, 0); //低电平开始干活~~
status = SPI_RW(reg); /*按照reg的十六进制数据要求读写寄存器(利用MOSI的变化输出信息到nRF54l01,具体见上文的 SPI_RW注释)*/
SPI_RW(value);/*不用怀疑,调用了同一个函数,当上面的命令执行完之后,寄存器到了一个新的状态,这个状态可以接收数据了~~因为nRF54l01*/
digitalWrite(CSN, 1);
return(status);
}
看名字就知道了,,,
Serial Peripheral Interface Read/Write Register
寄存器读写。这个是一个封装好的函数,封装的原理就是利用了
byte SPI_RW(unsigned char Byte)
这个函数的读写寄存器功能,进行的一次封装。找到相应的寄存器地址(reg),然后送入相应的值(value)。具体的如何寻址查其他资料~~因为我也没查到~~
然后下一个函数:
byte SPI_Read(unsigned char reg)
{
byte reg_val;
digitalWrite(CSN, 0);
SPI_RW(reg);
reg_val = SPI_RW(0);
digitalWrite(CSN, 1);
return(reg_val);
}
同样,这是一个读取寄存器状态的函数,利用控制字,根据相应的寄存器地址,然后执行控制字 0 ,利用SPI_RW的与操作就可以知道当前寄存器是什么状态了。非常巧妙的使用。
看到这里读者发现一个规律了吧?那就是:
变量声明----->digitaWrite(CSN,0)//(CPU开始工作)------>SPI_RW(reg)//读取控制寄存器的命令--------->其他操作------>digitalWrite(CSN,1);//休息一会,停止干活------->返回信息
然后开始下一个:
byte SPI_Read_Buf(unsigned char reg, unsigned char *pBuf, unsigned char bytes)
{
byte status,i;
digitalWrite(CSN, 0);
status = SPI_RW(reg);
for(i=0;i<bytes;i++)
{
pBuf[i] = SPI_RW(0);
}
digitalWrite(CSN, 1);
return(status);
}
这个函数的原理和
byte SPI_Write_Buf(unsigned char reg, unsigned char *pBuf, unsigned char bytes)
差不多的,只不过中间的循环不同而已,这个函数的for循环是读取数据。,另一个for循环是赋值操作~~
下面开始讲解为什么根据不同的控制字能实现不同的控制:
利用不同的控制字实现开断不同的电路,链接不同的设备,如执行了读操作,那么就会找到R_Register——00AA AAAA的位置。把别的断开,如果是执行了几个叠加的操作:WRITE_REG + RX_ADDR_P0 那么就会进行两步操作了~~找到001A AAAA 然后执行0000 1010 ~~
今天就这样,这是从昨天早上7点奋斗到晚上7点的学习成果~~