2.4G无线芯片NRF24L01 驱动源码及详解

 

概述

    NRF24L01/NRF24L01+ 是挪威NordicVLSI公司出品的2.4G射频收发器件,主打高速率,低功耗,应用简单功能。其性能卓越(我说的原厂真货),深受国内开发者欢迎。原厂芯片价格一直比较贵,加上国内不往优质走就往便宜靠这德性,市场上有各种假冒货。也有专业模仿的替换芯片,价格亲民同时品质较好。

    说简单易用是相对其它射频芯片,射频芯片本身有一定的门槛,小白阶段调试时遇到收、发不通问题时,缺乏了调试手段,大多都傻眼了。本文尽管往细里讲,希望别人看后少进坑。

 

硬件

    射频芯片对硬件要求比较高,而本人是软件开发,只能简单讲讲。

第一个事情是天线要严格按照参考电路来,除非你是大牛,否则别乱改。我硬件同事用频谱分析仪分析过不同的板子,得到的结论就是人家的参考电路还是比较优质的。

第二个事情就是我们用国产的芯片时,射频连接不稳定,有丢包问题。经过定位是VCC没有经过电容滤波。

 

准备知识

    我的项目是两个NRF24L01芯片分别作为主端和从端,双向通讯。而芯片同一时间只能收,或者只能发,也是就半双工。要实现全双工只能通过软件分时切换。我没有使用芯片的自动应答,自动重发功能,都是由上层软件保障的。更没有用到Enhanced ShockBurst功能。

开工第一步就是看芯片资料,要了解这个芯频率范围,最好设置时能绕过wifi。还有射频参数及对应寄存器的值。发送及接收缓存,通道个数,中断接收等等都要了解清楚,否则容易出问题。

开工前要了解SPI接口,如下图(红框里面的几个管脚):

2.4G无线芯片NRF24L01 驱动源码及详解_第1张图片

如果是使用MCU的SPI接口,就要先初始化。如果像我这样直接用GPIO模拟就要初始化管脚的GPIO属性。都要满足以下时序图。

2.4G无线芯片NRF24L01 驱动源码及详解_第2张图片

后面的代码会注释如何实现这个时序,这里先跳过。像我这样要使这个芯片TX、RX切换,就一定要了解以下这个图:

2.4G无线芯片NRF24L01 驱动源码及详解_第3张图片

上图讲述了芯片的状态机,RX、TX状态切换是需要一点时间的,要遵守。否则会出现问题,我当时软件也有些bug,两个问题加在一起出现各种没收到,发多了塞在rx管道里面,flush还flush不掉之类的诡异现象,说多了都是泪。

下面直接贴代码,注释丰富,仅供参考。

/**********************************************************************************
文件描述 : NRF 24L01芯片驱动
作者     : gavinpeng
时间     : 2017.6.8
***********************************************************************************/

// 头文件,略。。。

#define NRF_ADR_WIDTH    5   // 5  bytes TX(RX) address width
#define TX_PLOAD_WIDTH  32   // 32 bytes TX payload
#define RX_PLOAD_WIDTH  32   // 32 bytes RX payload

#define NRF_RX_DR       (1 << 6) // Data Ready Rx FIFO interrupt
#define NRF_TX_DS       (1 << 5) // Data Send Tx FIFO 

#define NRFW_REG        0x20  // Define write command to register
#define RD_RX_PLOAD_W   0x60
#define RD_RX_PLOAD     0x61  // Define RX payload register address
#define WR_TX_PLOAD     0xA0  // Define TX payload register address
#define FLUSH_TX        0xE1  // Define flush TX register command
#define FLUSH_RX        0xE2  // Define flush RX register command
#define REUSE_TX_PL     0xE3  // Define reuse TX payload register command
#define NOP             0xFF  // Define No Operation, might be used to read status register

//***************************************************//
// SPI(nRF24L01) registers(addresses)
#define CONFIG          0x00  // 'Config' register address
#define EN_AA           0x01  // 'Enable Auto Acknowledgment' register address
#define EN_RXADDR       0x02  // 'Enabled RX addresses' register address
#define SETUP_AW        0x03  // 'Setup address width' register address
#define SETUP_RETR      0x04  // 'Setup Auto. Retrans' register address
#define RF_CH           0x05  // 'RF channel' register address
#define RF_SETUP        0x06  // 'RF setup' register address
#define STATUS          0x07  // 'Status' register address
#define OBSERVE_TX      0x08  // 'Observe TX' register address
#define CD              0x09  // 'Carrier Detect' register address
#define RX_ADDR_P0      0x0A  // 'RX address pipe0' register address
#define RX_ADDR_P1      0x0B  // 'RX address pipe1' register address
#define RX_ADDR_P2      0x0C  // 'RX address pipe2' register address
#define RX_ADDR_P3      0x0D  // 'RX address pipe3' register address
#define RX_ADDR_P4      0x0E  // 'RX address pipe4' register address
#define RX_ADDR_P5      0x0F  // 'RX address pipe5' register address
#define TX_ADDR         0x10  // 'TX address' register address
#define RX_PW_P0        0x11  // 'RX payload width, pipe0' register address
#define RX_PW_P1        0x12  // 'RX payload width, pipe1' register address
#define RX_PW_P2        0x13  // 'RX payload width, pipe2' register address
#define RX_PW_P3        0x14  // 'RX payload width, pipe3' register address
#define RX_PW_P4        0x15  // 'RX payload width, pipe4' register address
#define RX_PW_P5        0x16  // 'RX payload width, pipe5' register address
#define FIFO_STATUS     0x17  // 'FIFO Status Register' register address
#define DNYPD           0x1C  //  动态包长使能,对应于各个通道
#define NRF_FEATURE     0x1D  //  特性

#define NRF_RX_EMPTY    0x0E  // rXFIFO为空
#define STA_MARK_RX     0X40
#define STA_MARK_TX     0X20
#define STA_MARK_MX     0X10	

// Define SPI pins  
sbit CE   = P1^2;   // Chip Enable pin signal (output)  
sbit CSN  = P1^1;   // Slave Select pin, (output to CSN, nRF24L01)  
sbit SCK  = P1^0;   // Serial Clock pin, (output)  

/* 片选函数 */
#define nrf_cs_enable()  (CSN = 0)
#define nrf_cs_disable() (CSN = 1)

/* CE(去)使能函数 */
#define nrf_ce_enable()  (CE = 1)
#define nrf_ce_disable() (CE = 0)

extern void delay_ms(UINT16 n);
extern void delay_us(UINT16 n);

/**********************************************************
函数描述 : spi接口 gpio 管脚初始化
输入参数 : 
输出参数 : 
返回值   : 
作者/时间: gavinpeng / 2017.6.8
************************************************************/
void nrf_spi_io_init(void)
{   
    P11_PushPull_Mode; // P11 (CS for tx) 推挽输出    
    P12_PushPull_Mode; // P12 (CE for tx) 推挽输出    
    P10_PushPull_Mode; // P10 (SPCLK) 推挽输出    
    P00_PushPull_Mode; // P00 (MOSI)  推挽输出    
    P01_Input_Mode;    // P01 (MISO)  输入    

    P11 = 1;  //CS for tx
    P12 = 0;  //CE for tx 
    P10 = 0;  //SPCLK
    P00 = 0;  //MOSI

    clr_SPIEN; // 禁止SPI
}  

/**********************************************************
函数描述 : spi接口读写一个字节
输入参数 : val -- 要写出spi 数据寄存器的值
输出参数 : 
返回值   : spi 数据寄存器读出数据
作者/时间: gavinpeng / 2017.6.8
************************************************************/
unsigned char nrf_spi_rw(unsigned char val)
{
    unsigned char i;  

    for ( i = 0; i < 8; i++ )   // 循环8次  
    {  
        MOSI = (val & 0x80);    // byte最高位输出到MOSI  
        val <<= 1;              // 低一位移位到最高位  
        SCK = 1;                // 拉高SCK,nRF24L01从MOSI读入1位数据,同时从MISO输出1位数据  
        val |= MISO;            // 读MISO到byte最低位  
        SCK = 0;                // SCK置低  
    }

    return(val);                // 返回读出的一字节  
}

/**********************************************************
函数描述 : 写一段寄存器
输入参数 : reg -- 寄存器地址
           buf -- 数据首指针
           num -- 要写出的数据的字节数
输出参数 : 
返回值   : 状态值
作者/时间: gavinpeng / 2017.6.8
************************************************************/
void nrf_buf_write(unsigned char reg, unsigned char *buf, unsigned char num)
{
    unsigned char i;

    nrf_cs_enable();

    /* 写命令要求最高3个bit的值是001 */
    (void)nrf_spi_rw(((reg & 0x1F) | NRFW_REG));

    for ( i = 0; i < num; i++ )
        (void)nrf_spi_rw(buf[i]);

    nrf_cs_disable();

    return;
}

/**********************************************************
函数描述 : 读芯片 状态 寄存器的值
输入参数 : 
输出参数 : 
返回值   : 寄存器值
作者/时间: gavinpeng / 2017.6.8
************************************************************/
unsigned char nrf_reg_read_status(void)
{
    unsigned char val;

    nrf_cs_enable();

    /* 读状态寄存器,省略了" & 0x1F" */
    (void)nrf_spi_rw(STATUS);

    val = nrf_spi_rw(NOP);

    nrf_cs_disable();

    return val;
}

/**********************************************************
函数描述 : 读芯片寄存器的值
输入参数 : reg -- 寄存器地址
输出参数 : 
返回值   : 寄存器值
作者/时间: gavinpeng / 2017.6.8
************************************************************/
unsigned char nrf_reg_read(unsigned char reg)
{
    nrf_cs_enable();

    /* 读命令要求最高3个bit都是0 */
    (void)nrf_spi_rw(reg & 0x1F);
    reg = nrf_spi_rw(NOP);

    nrf_cs_disable();

    return reg;
}

/**********************************************************
函数描述 : 写芯片寄存器的值
输入参数 : reg -- 寄存器地址, 带有读写标志
           value -- 要写入的值
输出参数 : 
返回值   : 寄存器值
作者/时间: gavinpeng / 2017.6.8
************************************************************/
void nrf_reg_write(unsigned char reg, unsigned char value)
{
    nrf_cs_enable();

    /* 写命令要求最高3个bit的值是001 */
    (void)nrf_spi_rw(((reg & 0x1F) | NRFW_REG));
    (void)nrf_spi_rw(value);

    nrf_cs_disable();

    return;
}

/**********************************************************
函数描述 : 读接收到的数据
输入参数 : 
输出参数 : buf -- 缓存
返回值   : 高8位是通道号,低8位是字节数
作者/时间: gavinpeng / 2017.6.8
************************************************************/
unsigned char nrf_payload_read(unsigned char *buf)
{
    unsigned char i;
    unsigned char len;

    /* 1. 读数据长度 */

    nrf_cs_enable();

    (void)nrf_spi_rw(RD_RX_PLOAD_W);
    len = nrf_spi_rw(NOP);

    nrf_cs_disable();

    if ( len == 0 )
        return 0;

    /* 2. 读数据 */

    nrf_cs_enable();

    (void)nrf_spi_rw(RD_RX_PLOAD); /* 读payload命令 */

    for ( i = 0; i < len; i++ )
        buf[i] = nrf_spi_rw(NOP);

    nrf_cs_disable();

    return len;
}

/**********************************************************
函数描述 : 发送数据出去
输入参数 : buf -- 缓存
           num -- 要写出内容字节数
输出参数 : 
返回值   : 
作者/时间: gavinpeng / 2017.6.8
************************************************************/
void nrf_payload_write(unsigned char *buf, unsigned char num)
{
    unsigned char i;

    nrf_cs_enable();  /* 使能 + 延时 */

    /* 写命令 */
    (void)nrf_spi_rw(WR_TX_PLOAD);

    /* 写数据 */
    for ( i = 0; i < num; i++ )
        (void)nrf_spi_rw(buf[i]);

    nrf_cs_disable(); 
}

/**********************************************************
函数描述 : flush rx fifo
输入参数 : 
输出参数 : 
返回值   : 
作者/时间: gavinpeng / 2017.6.8
************************************************************/
void nrf_flush_buf_rx(void)
{
    nrf_cs_enable();
    (void)nrf_spi_rw(FLUSH_RX);
    nrf_cs_disable();
}

/**********************************************************
函数描述 : flush tx fifo
输入参数 : 
输出参数 : 
返回值   : 
作者/时间: gavinpeng / 2017.6.8
************************************************************/
void nrf_flush_buf_tx(void)
{
    nrf_cs_enable();
    (void)nrf_spi_rw(FLUSH_TX);
    nrf_cs_disable();
}

/**********************************************************
函数描述 : 将模块初始化为接收模式
输入参数 : 
输出参数 : 
返回值   : 
作者/时间: gavinpeng / 2017.6.8
************************************************************/
void nrf_init_mode_rx(void)
{
    unsigned char nrf_buf[RX_PLOAD_WIDTH];
    unsigned char nrf_buf_len = 0;
    unsigned char status = 0;

    nrf_ce_disable();
  	nrf_reg_write(CONFIG, 0x7F);// power up ,RX模式
    nrf_ce_enable();

    /* 没有接收标志但是有通道号可能是fifo error了, 要flush */
    status = nrf_reg_read_status();
    if ( ((status & 0x0E) == 0) && ((status & 0x40) == 0) ) 
    {
        nrf_buf_len = nrf_payload_read(nrf_buf);
        nrf_flush_buf_rx();
    }
}

/**********************************************************
函数描述 : 初始化nrf
输入参数 : 
输出参数 : 
返回值   : 
作者/时间: gavinpeng / 2017.6.8
************************************************************/
int nrf_init(void)
{
    unsigned char chn = 0;
    unsigned char addr_rx[5] = {0xA5, 0x5A, 0xA5, 0x01, 0x01};  // 重点:对端的rx,tx地址跟本端交换
    unsigned char addr_tx[5] = {0xA5, 0x5A, 0xA5, 0x02, 0x02};

	chn = nrf_chn_get(); // 通道号从别的地方获取
	
    nrf_ce_disable();

    nrf_buf_write(TX_ADDR, addr_tx, NRF_ADR_WIDTH);     // 要发送的目的地址
    nrf_buf_write(RX_ADDR_P0, addr_rx, NRF_ADR_WIDTH);  // 自身(接收)地址

	// 射频参数要求收、发双方要一致
    nrf_reg_write(EN_AA, 0x0);                  // 去使能通道0的自动应答
    nrf_reg_write(EN_RXADDR, 0x01);             // Enable Pipe0
    nrf_reg_write(SETUP_RETR, 0x10);            // 500us + 86us, 0 retrans...  	
    nrf_reg_write(RF_CH, chn);                  // 接收频率 2400 + chn MHz
    nrf_reg_write(RX_PW_P0, TX_PLOAD_WIDTH);    // Select same RX payload width as TX Payload width
    nrf_reg_write(RF_SETUP, 0x07);              // 0x07 TX_PWR:0dBm, Datarate:1Mbps, LNA:HCURR
    nrf_reg_write(DNYPD,0x01);                  // 0通道使能动态长度
    nrf_reg_write(NRF_FEATURE, 0x04);           // 特性:  使能动态长度

    nrf_ce_enable();

    return 0;
}

/**********************************************************
函数描述 : 接收从端数据,这个函数会被频繁调用
输入参数 : 
输出参数 : 
返回值   : 
作者/时间: gavinpeng / 2017.6.8
************************************************************/
void nrf_recv_data(void)
{
    unsigned char nrf_buf[RX_PLOAD_WIDTH];
    unsigned char status;
    unsigned char nrf_buf_len;

	// 读状态寄存器,如果有数据就接收处理
    status = nrf_reg_read_status();
    if ( status & 0x40 )
    {
        nrf_buf_len = nrf_payload_read(nrf_buf);
        nrf_reg_write(STATUS, status);

		// 长度检查。要注意,因为本项目没有小于2字节的数据帧,所以认为是异常。
        if ( (nrf_buf_len < 2) || (nrf_buf_len > RX_PLOAD_WIDTH) )
        {
            nrf_flush_buf_rx();
            return;
        }
        else
        {
		    // 这里是处理接收到的数据
            chan_nrf_recv(nrf_buf, nrf_buf_len);
        }
    }
}

/**********************************************************
函数描述 : 向对端发送数据
输入参数 : buf -- 缓存
           len -- 要写入内容的字节数
输出参数 : 
返回值   : 成功返回0,失败返回<0
作者/时间: gavinpeng / 2017.6.8
************************************************************/
int nrf_send_data(unsigned char *buf, unsigned char len)
{
    char ret;
    unsigned char status = 0;
    unsigned short count = 0;

    nrf_ce_disable();

    /* 数据内容 */
    nrf_payload_write(buf, len);
    nrf_reg_write(CONFIG, 0x7E); // power up ,TX

    nrf_ce_enable();
    delay_us(10);
    nrf_ce_disable();

    // 等发送完成
    count = 0;
	ret = -1;
    while ( ++count < 30000 )
    {
        status = nrf_reg_read_status();
        if ( status & STA_MARK_TX )
        {
            nrf_reg_write(STATUS, status);
			ret = 0;
            break;
        }
        else if ( status & STA_MARK_MX )
        {
            nrf_reg_write(STATUS, status);
            nrf_flush_buf_tx();
            break;
        }
    }

    return ret;
}

上面的代码有要注意的地方。

    一是发送数据后,通过读状态寄存器知道发送完成,然后返回。实测是对端在更早一点的时候就已经读到数据,大概是这个样子:"一一_____||___一一",红色部分代表对端已收到,本端延时了一点才有寄存器状态变更(我没有使用中断的方式)。

    二是跟我没有使用中断的方式有关,我用的轮询方式不断去查是否有收到数据,所以会很频繁调用nrf_recv_data函数。

打完收工。

 

自己用SI24R1(国产NRF24L01)芯片做的基于2.4G无线串口USB转TTL工具:

2.4G无线芯片NRF24L01 驱动源码及详解_第4张图片

https://item.taobao.com/item.htm?spm=a1z38n.10677092.0.0.558c1debH1EHHx&id=579411892273

你可能感兴趣的:(单片机)