概述

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

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

硬件

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

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

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

准备知识

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

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

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

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

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

上图讲述了芯片的状态机,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 txP12 = 0;  //CE for tx P10 = 0;  //SPCLKP00 = 0;  //MOSIclr_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 Pipe0nrf_reg_write(SETUP_RETR, 0x10);            // 500us + 86us, 0 retrans...      nrf_reg_write(RF_CH, chn);                  // 接收频率 2400 + chn MHznrf_reg_write(RX_PW_P0, TX_PLOAD_WIDTH);    // Select same RX payload width as TX Payload widthnrf_reg_write(RF_SETUP, 0x07);              // 0x07 TX_PWR:0dBm, Datarate:1Mbps, LNA:HCURRnrf_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 ,TXnrf_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 驱动源码及详解相关推荐

  1. Android 源码编译详解【合集篇】

    Android 源码编译详解[一]:服务器硬件配置及机型推荐 做 Android系统开发多年,开发环境都是入职就搭建好了,入职时拿个账号密码就直接开始搞开发了,年初换了新公司,所有的项目都是刚起步,一 ...

  2. Android四大组件之bindService源码实现详解

        Android四大组件之bindService源码实现详解 Android四大组件源码实现详解系列博客目录: Android应用进程创建流程大揭秘 Android四大组件之bindServic ...

  3. jsp漂亮的登录界面源码_【案例+源码】详解MVC框架模式及其应用

    案例+源码]详解MVC框架模式及其应用 写在开头: 首先我们需要知道,框架模式.模式.开发模式是三种不同的概念,但他们的目的都一样:解耦! 1.关于MVC框架模型 MVC是三个单词的缩写: M,Mod ...

  4. 使用Gin框架集成JWT,源码、详解、面试问题

    使用Gin框架集成JWT,源码.详解.面试问题 一.什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519). ...

  5. [RK3399][Android7.1] 4通道ADC芯片ES7210驱动源码

    Platform: RK3399 OS: Android 7.1 Kernel: v4.4.83 说明: 内核里并没有关于ADC芯片ES7210的源码,驱动基于ES7243,分享给大家. /** AL ...

  6. 源码|详解分布式事务之 Seata-Client 原理及流程

    前言 在分布式系统中,分布式事务是一个必须要解决的问题,目前使用较多的是最终一致性方案.自年初阿里开源了Fescar(四月初更名为Seata)后,该项目受到了极大的关注,目前已接近 8000 Star ...

  7. Android源码编译详解【四】:Android 6.0_源码的下载与编译

     1.AOSP源码下载  AOSP:即为"Android Open-Source Project"的缩写,中文意为 :"安卓开放源代码项目". Google官方 ...

  8. Ubuntu下android-4.0.3_r1源码下载,阅读工具安装配置,源码编译详解

    备注: android源码动辄6.7G,新版本的则10G左右,所以要有足够大的硬盘空间. android应用开发环境搭建:http://www.cnblogs.com/pharen/archive/2 ...

  9. 源码泄露详解 一文了解常见源码泄露

    前言 这几天回顾做过的ctf题 看到好多题有源码泄露 本篇做个归纳总结 1..git源码泄露 在运行git init初始化代码库的时候,会在当前目录下面产生一个.git的隐藏文件夹 这个文件夹包含所有 ...

最新文章

  1. 软件测试面试问答大全(2)
  2. 计算机风险评估管理程序,第5章 信息安全风险评估实施流程
  3. 关于“程序员996”,互联网大神发话了,能顶用吗?
  4. es7 创建模板时,报错 Validation Failed: 1: index patterns are missing
  5. 是如何通过阿里面试的?
  6. c++实现文件传输之三:断点续传与多线程传输转
  7. git上传代码的时候出现:Please tell me who you are.解决方法
  8. 股市跷跷板—债券基金
  9. 对话昔日“斜杠青年”西蒙斯:传奇数学家的别样人生
  10. 2^n+1的因数分解问题
  11. 迅龙万兆网络 驱动 linux,锐龙双万兆“小妖板” :ASRock 华擎 发布 X570D4I-2T ITX主板...
  12. 一年的总结(09-10)
  13. 2019CSP广州两日游
  14. 如何零基础入门仿真 - version 2
  15. Hadoop各种版本的百度网盘下载
  16. 计算机网络04之流量控制
  17. RGD-全氟化碳纳米乳MRI显影剂/标记乳糖基白蛋白的超顺磁性氧化铁粒子(LAC-HSA-SPIO)
  18. FM17520的调试
  19. GP-NFC-Management of Multiple Secure Elements
  20. 易语言隔行扫描算法 支持库

热门文章

  1. cl.fe3.xyz index.php,2_FE_Diabetes.ipynb
  2. 计算机没考好的检讨书300百以上,考试反思检讨书300字范文7篇
  3. vnpy 查询持仓量_vn.py 数据入库
  4. PIC16F648A-E/SS PIC16 8位 微控制器,7KB(4Kx14)
  5. 会声会影2022旗舰终极版详细功能介绍
  6. React,手写简易redux(二)- By Viga
  7. 微星主板黑苹果_技嘉X79-UD7主板设置一键启动u盘方法【详解】
  8. android网络搜索不到wifi,Android智能手机搜索不到路由器无线信号怎么办?
  9. 人眼感知到的颜色与真实物理世界的颜色有什么区别?
  10. rman备份指定备份集对应文件