当前位置:C++技术网 > 资讯 > SPI控制nRF24l01操作释义

SPI控制nRF24l01操作释义

更新时间:2016-06-06 12:46:37浏览次数:1+次

说明一下,下面的每一个十六进制数字,都是一个控制,控制字的每一位都有自己的定义,需要读者自行记录!!(控制字的概念是比较合适的形容下面的宏定义)

首先上最常见到的宏定义(呕心沥血一个字一个字手打的):


//****************************************************
#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点的学习成果~~