nrf2401 - 最廉价的2.4G无线通信方案-程序员宅基地

技术标签: ViewUI  前端  嵌入式  

nRF2401

所有的使用Arduino 的朋友大多都会知道大名鼎鼎的XBee 这个土豪级的ZigBee 的通信模块。我们是做产品开发的,对于XBee这个产品可谓是又爱又恨,不得不承认他确实是一个好货,从做工到功能都无须质疑,让人最感到遗憾的是他并不太适合于做平民化的产品,¥150~¥300 多的集价只能让我们对它望而轻叹了,这货只能用来DIY玩一下,这样的售价在产品上应有将直接将产品的成本推到难以承受的地步,所以它必须被取代!

疯狂地google后终于也让我们找到了取代这个必备级土豪的最佳解决方案,开始还真是没低,但后来经多番在google上翻阅老外的博客后也确定了这一点,老外都觉得XBee贵!这个解决方案就是基于 nRF24L01 的 2.4G通信方案,它在淘宝上的售价极低,看了他的价格,如果在没有特殊情况下都没人再原意选择 ZigBee 和 蓝牙了吧,不相信?去淘一下就知道我是否言过其实。

好吧,废话不多说,先来看看 nRF23L01的介绍,(以下的文字来源于某厂商的文档资料,具体的文件请看附件)

NRF24L01是一款工作在2.4-2.5GHz世界通用ISM频段的单片收发芯片,无线收发器包括:频率发生器 增强型 SchockBurstTM 模式控制器 功率放大器 晶体放大器 调制器 解调器 输出功率频道选择和协议的设置可以通过SPI接口进行设置极低的电流消耗,当工作在发射模式下发射功率为6dBm时电流消耗为9.0mA 接受模式为12.3mA掉电模式和待机模式下电流消耗模式更低。

  1. 2.4Ghz 全球开放 ISM 频段免许可证使用
  2. 最高工作速率 2Mbps,高效 GFSK 调制,抗干扰能力强,特别 适合工业控制场合
  3. 126 频道,满足多点通信和跳频通信需要
  4. 内置硬件 CRC 检错和点对多点通信地址控制
  5. 低功耗 1.9 - 3.6V 工作,待机模式下状态为 22uA;掉电模 式下为 900nA
  6. 内置 2.4Ghz 天线,体积种类多样
  7. 模块可软件设地址,只有收到本机地址时才会输出数据(提 供中断指示),可直接接各种单片机使用,软件编程非常方便
  8. 内置专门稳压电路,使用各种电源包括 DC/DC 开关电源均有 很好的通信效果
  9. 2.54MM 间距接口,DIP 封装
  10. 工作于 Enhanced ShockBurst 具有 Automatic packet handling, Auto packet transaction handling,具有可选的内置包 应答机制,极大的降低丢包率。
  11. 与 51 系列单片机 P0 口连接时候,需要加 10K 的上 拉电阻,与其余口连接不需要。
  12. 其他系列的单片机,如果是 5V 的,请参考该系列单片机 IO 口输出电流大小,如果超过 10mA,需要串联
    电阻分压,否则容易烧毁模块! 如果是3.3V的,可以 直接和RF24l01模块的IO口线连接。 比如AVR系列单片机
    如果是5V 的,一般串接2K 的电阻

工作方式

NRF2401 有工作模式有五种:

  • 收发模式
  • 配置模式
  • 空闲模式
  • 关机模式

收发模式

收发模式有 Enhanced ShockBurstTM 收发模式、ShockBurstTM 收 发模式和直接收发模式三种,收发模式由器件配置字决定,具体配置 将在器件配置部分详细介绍。

Enhanced ShockBurstTM 收发模式

Enhanced ShockBurstTM 收发模式下,使用片内的先入先出堆栈区, 数据低速从微控制器送入,但高速(1Mbps)发射,这样可以尽量节能, 因此,使用低速的微控制器也能得到很高的射频数据发射速率。与射 频协议相关的所有高速信号处理都在片内进行,这种做法有三大好处: 尽量节能;低的系统费用(低速微处理器也能进行高速射频发射);数 据在空中停留时间短,抗干扰性高。Enhanced ShockBurstTM 技术同时也减小了整个系统的平均工作电流。

在 Enhanced ShockBurstTM 收发模式下, NRF24L01 自动处理字头
和 CRC 校验码。在接收数据时,自动把字头和 CRC 校验码移去。在送 数据时,自动加上字头和 CRC 校验码,在发送模式下,置 CE 为高,至 少 10us,将时发送过程完成后。

Enhanced ShockBurstTM 发射流程
  1. 把接收机的地址和要发送的数据按时序送入 NRF24L01;
  2. 配置 CONFIG 寄存器,使之进入发送模式。C. 微控制器把 CE 置高 (至少 10us),激 发 NRF24L01 进行 Enhanced ShockBurstTM 发射; D. N24L01 的 Enhanced ShockBurstTM 发射(1) 给射频前端供电; (2) 射频数据打包(加字头、CRC 校验码); (3) 高速发射数据包; (4) 发射完成,NRF24L01 进入空闲状态。4.1.1.2 Enhanced ShockBurstTM 接收流程 A. 配置本机地址和要接收的数据包大小;B. 配置 CONFIG 寄存器,使之进入接收模式,把 CE 置高。
  3. 130us 后,NRF24L01 进入监视状态,等待数据包的到来; D.收到 正确的数据包(正确的地址和 CRC 校验码),NRF2401 自动把字头、地址
    和 CRC 校验位移去;
  4. NRF24L01 通过把 STATUS 寄存器的 RX_DR 置位(STATUS 一般引起微
    控制器中断)通知微控制器;F. 微控制器把数据从 NewMsg_RF2401 读 出;
  5. 所有数据读取完毕后,可以清除 STATUS 寄存器。NRF2401 可以进入 [ShockBurstTM 收发模式]

ShockBurstTM 收发模式可以与 Nrf2401a,02,E1 及 E2 兼容

空闲模式

NRF24L01 的空闲模式是为了减小平均工作电流而设计,其最大的 优点是,实现节能的同时,缩短芯片的起动时间。在空闲模式下,部分片内晶振仍在工作,此时的工作电流跟外部晶振的频率有关。

关机模式

在关机模式下,为了得到最小的工作电流,一般此时的工作电流为 900nA 左右。关机模式下,配置字的内容也会被保持在 NRF2401 片内, 这是该模式与断电状态最大的区别。

配置 NRF24L01 模块

NRF2401 的所有配置工作都是通过 SPI 完成,共有 30 字节的配置字。
官方推荐 NRF24L01 工作于 Enhanced ShockBurstTM 收发模式,这 种工作模式下,系统的程序编制会更加简单,并且稳定性也会更高, 因此,下文着重介绍把 NRF24L01 配置为 Enhanced ShockBurstTM 收发 模式的器件配置方法。

ShockBurstTM 的配置字使 NRF24L01 能够处理射频协议,在配置完 成后,在 NRF24L01 工作的过程中,只需改变其最低一个字节中的内容, 以实现接收模式和发送模式之间切换。

ShockBurstTM 的配置字可以分为以下四个部分:

  • 数据宽度:声明射频数据包中数据占用的位数。这使得 NRF24L01 能够区分接收数据包中的数据和 CRC 校验码;
  • 地址宽度:声明射频数据包中地址占用的位数。这使得 NRF24L01 能够区分地址和数据;
  • 地址:接收数据的地址,有通道 0 到通道 5 的地址;
  • CRC:使 NRF24L01 能够生成 CRC 校验码和解码。

当使用 NRF24L01 片内的 CRC 技术时,要确保在配置字(CONFIG 的 EN_CRC)
中 CRC 校验被使能,并且发送和接收使用相同的协议。 NRF24L01 配置字的 CONFIG 寄存器的位描述如下表所示。

工作模式由 PWR_UP register 、PRIM_RX register 和 CE 决定,下表

Arduino 上的实验

从开始到现在可能你一直纳闷为什么一直没有看到哪里说这NRF24L01可以与Arduino吧,是的 NRF24L01是在STC和51单片机上的应用很多,开始时我也一直很担心他能否与Arduino兼容,不过从他的通信原来看他本来就是使用SPI接口来通信,而只要支持SPI的板子,只要原理相通那就应该可以与Arduino共同工作。在Github上狂扫一次,证明了我的想法是对的。这里要感谢那些为NRF24L01编写使用库的贡献者们,没有他们那就无法将这么好的板子用在Arduino上了,在Github上只要搜一下NRF24L01会找到很多的repository, 要NRF24L01只需要在程序中引入一个为NRF24L01写的库就行了。 我是使用这个库的:https://github.com/aaronds/arduino-nrf24l01

下载库后将其解压至arduino 的libraries目录下即可使用。

以下一项使用两块Arduino 模块 + nRF24L01 实现2.4G的双向通信实验,旨在实现普通的2.4G 遥控发射器与遥控接收器。

注意事项

此实验中必须注意以下事项,否则可能出现烧坏模块的风险或无法正确完成实验。

基准电源

基准电压采用3.3v,可从Arduino 的3.3v输出获取。此处需要说明的是如果使用 Arduino pro min 5v 16MHZ 的板子进行实验可连接FT232RL的USB转TTL的板子,由于 Arduino pro min 上只有一个电压输出,因此3.3v电压要从USB转TTL板上获取。

IMG_nRF24L01_jumper

如图所示,只有让Arduino和nRF24L01 同源同压降可以避免另外加装电平转换板匹配Arduino的输出电平电压与nRF24L01的输入电平电压的麻烦。

另外,如采用3.7v电池供电时需要将nRF24L01的,电源输入VCC端接一阻值为20K的电阻,将nRF2401的工作电压稳定在 2.4v 避免nRF24L01(1.8v~3.6v)超压工作损坏板子。

Arduino 的时钟频率

当将Arduino pro mini转用3.3v供电时可以从Arduino IDE中选择 Arduino pro min 3.3v 8MHZ 的驱动写板,这是一次失误中获取的经验,如果采用了这个选项向板子写入程序不会报错,且会显示成功。但是,写入的程序将会以 8Mhz 的时钟频率运行,这一点甚为之令人不解,因此即使使用3.3v供电也应该采用Arduino pro min 5v 16MHZ 的驱动写板。作此注解的原因是要明确指出,发射板与接收板的工作时钟频率一定要保持一至,否则可以通信成功但数据将会出现乱码。

意义

此实验有两个重大的意义:

  1. 提供基于2.4G廉价通信方案的
  2. 找到基于下位机的数据传输格式
  3. 为我们以后的无线产品增加传统遥控器的操控方案

实验过程

需要连接两件Arduino 板子,一为发射机(Teransmiter),一为接收机 (Receiver),Arduino 与 nRF24L01 的接线是固定的,在程序中无需要重新对 Arduino 的针脚进行定义,因为引入的Mrif库会提供默认定义,Arduino 的默认定义如下,(此为固定的定义不可修改):

  • MISO -> 12
  • MOSI -> 11
  • SCK -> 13
  • CE -> 8
  • CSN -> 7

以下是Arduino 与 NRF24L01连接的针口对照表,下文的代码会按此针脚连接编写:

功能口 NRF2401 Arduino
MISO 7 12
MOSI 6 11
SCK 5 13
CE 3 8
CSN 4 7
VCC 2 3V3
GND 1 GND
IRQ* 8 0

nRF24L01引线

将对应的针脚连接到nRF24L01中,完成接线。

发射机

发射机还需要使用JoyStick模块,此模块就是两个10K可调电位器与一个微触开关构成,既有模拟数据输入也有数字输入。针脚与Arduino 的连接如下:

  • GND -> 连地线
  • +5V -> 主供电
  • VRX -> X轴输出,连接Arduino的 A0
  • VRY -> Y轴输出,连接Arduino的 A1
  • SW -> Swither 微动按键,接数字输入 6

以下的示例只是从客户端向指定的服务端发送当前的时间,然后服务端收到数据后作出响应,将原数据包发回给客户端。这个示例是很常用的,因为两个设备在工作前通常都需要进行匹配,用通信的术语来说就是“握手”,以下是 客户端的代码:

发射机代码:

/**
 * Transmitter 发射机 (遥控器)
 *
 * Pins:
 * Hardware SPI:    
 * MISO -> 12
 * MOSI -> 11
 * SCK -> 13
 *
 * Configurable:
 * CE -> 8
 * CSN -> 7
 *
 */

#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>

const int VRX=A0;
const int VRY=A1;
const int SW = 5;
const int X0=525;
const int Y0=496;
byte joyStick[2];

void setup(){
  Serial.begin(9600);
  
  pinMode(SW, INPUT);
  pinMode(VRX, INPUT);
  pinMode(VRY, INPUT);
  digitalWrite(SW, LOW);

  Mirf.spi = &MirfHardwareSpi;
  Mirf.init();
  Mirf.setRADDR((byte *)"clie1");
  Mirf.payload = sizeof(joyStick);
  Mirf.config();  

  Serial.println("Transmitter ready");
}

void loop() {

  int val_x =map(analogRead(VRX),0,1024,0,255);
  int val_y=map(analogRead(VRY),0,1024,0,255);

  //技巧:有多少个数就用多少个数组单元去储存
  
  joyStick[0] = val_x;
  joyStick[1] = val_y; 

  //这个时间值用于判断是否连接超时
  //unsigned long time = millis();
  //long time_long=long(time);
  
  Serial.print("X:");
  Serial.print(val_x);
  Serial.print(" Y:");
  Serial.println(val_y);

  //设置当前发射器的地址 
  Mirf.setTADDR((byte *)"serv1");

  //将读入的值写到SPI中
  Mirf.send((byte *) &joyStick);

  while(Mirf.isSending()) {
      // 如果正在发送时什么都不做,这里只是一个等待,等isSending完成后才能退出循环
  }
}

这个发射机代码极为之简单,以下对其进行逐一分解诠释:

setup()

Mirfsetup()函数中有两个地方需要注意:

  1. setRADDR() 函数需要设定当前发射机的地址 clie1 这个地址是随机定的,但也是唯一用于此机上的地址,要与此机通信则需要这个地址。
  2. 从软件概念上 发射机 Transmiter 则是 通信 客户端这点需要紧记。
  3. 当采用数据包通信而不是指令通信时需要先定义数据包的大小,即 Mirf.payload=sizeof(joyStick) 而非 Mirf.payload=sizeof(unsigned long)
loop()

这里采用 map() 函数,将 0~1024 之间的值映射成 0~255的值域,因为接收机的模拟输出范围也只有255,另外包中的每一位,即joyStick的每个元素的值域是 0~255 的8位整数,所以必须先进行此转换。

接下来是采用 setTADDR() 指定目标收受器的地址,调用 send方法送数据。

另外 此处的 while 循环是一个死循环,如果发送未能完成(Mirf.isSending()==false)将会一直在此周期内不退出,当然死循环是不会出现的,因为硬件上会自动置位isSending()函数也会返会flase

接收机 Receiver

所有代码如下:

/**
 * 接收机
 *
 * Pins:
 * Hardware SPI:
 * MISO -> 12
 * MOSI -> 11
 * SCK -> 13
 *
 * Configurable:
 * CE -> 8
 * CSN -> 7
 *
 * Note: To see best case latency comment out all Serial.println
 * statements not displaying the result and load 
 * 'ping_server_interupt' on the server.
 */

#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>

byte joyStick[2];

void setup() {
  
  Serial.begin(9600);
  Mirf.spi = &MirfHardwareSpi;
  Mirf.init();     
  Mirf.setRADDR((byte *)"serv1");   
  Mirf.payload = sizeof(joyStick);
  
  /*
   * Write channel and payload config then power up reciver.
   */
   
  /*
   * To change channel:
   * 
   * Mirf.channel = 10;
   *
   * NB: Make sure channel is legal in your area.
   */
   
  Mirf.config();

  Serial.println("Receiver ready");
  
}

void loop() {
  
  while(!Mirf.dataReady()){
  }

  Mirf.getData((byte *) &joyStick);
  
  int vx=joyStick[0];
  int vy=joyStick[1];

  if (vx < 130) {
    
  }   // step forward
  
  if (vx > 130) {
    
  }  // step backward
 
  if (vy < 123) {  

  } // step left
  
  if (vy > 123) {  
  } // step right
  
  Serial.print("X:");
  Serial.print(vx);
  Serial.print(" Y:");
  Serial.println(vy);  

} 

此处代码就不作过多解释,在了解完发射机原理这里的原理是一至的,只是过程相反,用while循环来等待数据接收完成,并从具有相同位数joyStick 数据缓冲中取出数组。

值得注意的是这里有两个常量:130123,其实这两个值是对控制杆上取得的中位0值,理论值应该是255的一半也就是128,但板子没有这么精确,这里只是取出这个两相对的中位0值。

这里有4个if语句,作用是将两路的VRX和VRY信号分拆为VRF(前),VRB(后),VRL(左),VRR(右),代码中还有一个逻辑尚未写入,就是需要将VRF,VRB,VRL,VRR的位移量计算出来。公式如下:

  • VRF = XZero - (XZero - VRX)
  • VRB = VRX - XZero
  • VRL = YZero - (YZero -VRY)
  • VRR = VRY - YZero

说明:XZero=130 , YZero=123

结论:

如果需要向接收机发送更为精确的控制值,可在发射机读入 VRX和VRY后就开始分解并计算出VRF,VRB,VRL,VRR然后进行8位数映射再传送至接收机,,这样可以更为精确地计算出位移量,而不至于在转换时丢失。

参考阅读

转载于:https://www.cnblogs.com/Ray-liang/p/9786132.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_33924220/article/details/94195995

智能推荐

python opencv resize函数_python opencv 等比例调整(缩放)图片分辨率大小代码 cv2.resize()...-程序员宅基地

文章浏览阅读1.3k次。# -*- coding: utf-8 -*-"""@File : 200113_等比例调整图像分辨率大小.py@Time : 2020/1/13 13:38@Author : Dontla@Email : [email protected]@Software: PyCharm"""import cv2def img_resize(image):height, width = image...._opencv小图等比例缩放

【OFDM、OOK、PPM、QAM的BER仿真】绘制不同调制方案的误码率曲线研究(Matlab代码实现)-程序员宅基地

文章浏览阅读42次。对于这些调制技术的误码率(BER)研究是非常重要的,因为它们可以帮助我们了解在不同信道条件下系统的性能表现。通过以上步骤,您可以进行OFDM、OOK、PPM和QAM的误码率仿真研究,并绘制它们的误码率曲线,以便更好地了解它们在不同信道条件下的性能特点。针对这些调制技术的BER研究是非常重要的,可以帮助我们更好地了解这些技术在不同信道条件下的性能表现,从而指导系统设计和优化。6. 分析结果:根据误码率曲线的比较,分析每种调制方案在不同信噪比条件下的性能,包括其容忍的信道条件和适用的应用场景。_ber仿真

【已解决】Vue的Element框架,日期组件(el-date-picker)的@change事件,不会触发。_el-date-picker @change不触发-程序员宅基地

文章浏览阅读2.5w次,点赞3次,收藏3次。1、场景照抄官方的实例,绑定了 myData.Age 这个值。实际选择某个日期后,从 vuetool(开发工具)看,值已经更新了,但视图未更新。2、尝试绑定另一个值: myData,可以正常的触发 @change 方法。可能是:值绑定到子对象时,组件没有侦测到。3、解决使用 @blur 代替 @change 方法。再判断下 “值有没有更新” 即可。如有更好的方法,欢迎评论!..._el-date-picker @change不触发

PCL学习:滤波—Projectlnliers投影滤波_projectinliers-程序员宅基地

文章浏览阅读1.5k次,点赞2次,收藏8次。Projectlnliersclass pcl: : Projectlnliers< PointT >类 Projectlnliers 使用一个模型和一组的内点的索引,将内点投影到模型形成新的一个独立点云。关键成员函数 void setModelType(int model) 通过用户给定的参数设置使用的模型类型 ,参数 Model 为模型类型(见 mo..._projectinliers

未处理System.BadImageFormatException”类型的未经处理的异常在 xxxxxxx.exe 中发生_“system.badimageformatexception”类型的未经处理的异常在 未知模块。 -程序员宅基地

文章浏览阅读2.4k次。“System.BadImageFormatException”类型的未经处理的异常在 xxxx.exe 中发生其他信息: 未能加载文件或程序集“xxxxxxx, Version=xxxxxx,xxxxxxx”或它的某一个依赖项。试图加载格式不正确的程序。此原因是由于 ” 目标程序的目标平台与 依赖项的目标编译平台不一致导致,把所有的项目都修改到同一目标平台下(X86、X64或AnyCPU)进行编译,一般即可解决问题“。若果以上方式不能解决,可采用如下方式:右键选择配置管理器,在这里修改平台。_“system.badimageformatexception”类型的未经处理的异常在 未知模块。 中发生

PC移植安卓---2018/04/26_电脑软件移植安卓-程序员宅基地

文章浏览阅读2.4k次。记录一下碰到的问题:1.Assetbundle加载问题: 原PC打包后的AssetBundle导入安卓工程后,加载会出问题。同时工程打包APK时,StreamingAssets中不能有中文。解决方案: (1).加入PinYinConvert类,用于将中文转换为拼音(多音字可能会出错,例如空调转换为KongDiao||阿拉伯数字不支持,如Ⅰ、Ⅱ、Ⅲ、Ⅳ(IIII)、Ⅴ、Ⅵ、Ⅶ、Ⅷ、Ⅸ、Ⅹ..._电脑软件移植安卓

随便推点

聊聊线程之run方法_start 是同步还是异步-程序员宅基地

文章浏览阅读2.4k次。话不多说参考书籍 汪文君补充知识:start是异步,run是同步,start的执行会经过JNI方法然后被任务执行调度器告知给系统内核分配时间片进行创建线程并执行,而直接调用run不经过本地方法就是普通对象执行实例方法。什么是线程?1.现在几乎百分之百的操作系统都支持多任务的执行,对计算机来说每一个人物就是一个进程(Process),在每一个进程内部至少要有一个线程实在运行中,有时线..._start 是同步还是异步

制作非缘勿扰页面特效----JQuery_单击标题“非缘勿扰”,<dd>元素中有id属性的<span>的文本(主演、导演、标签、剧情-程序员宅基地

文章浏览阅读5.3k次,点赞9次,收藏34次。我主要用了层次选择器和属性选择器可以随意选择,方便简单为主大体CSS格式 大家自行构造网页主体<body> <div class='main' > <div class='left'> <img src="images/pic.gif" /> <br/><br/> <img src="images/col.gif" alt="收藏本片"/&_单击标题“非缘勿扰”,元素中有id属性的的文本(主演、导演、标签、剧情

有了这6款浏览器插件,浏览器居然“活了”?!媳妇儿直呼“大开眼界”_浏览器插件助手-程序员宅基地

文章浏览阅读901次,点赞20次,收藏23次。浏览器是每台电脑的必装软件,去浏览器搜索资源和信息已经成为我们的日常,我媳妇儿原本也以为浏览器就是上网冲浪而已,哪有那么强大,但经过我的演示之后她惊呆了,直接给我竖起大拇指道:“原来浏览器还能这么用?大开眼界!今天来给大家介绍几款实用的浏览器插件,学会之后让你的浏览器“活过来”!_浏览器插件助手

NumPy科学数学库_数学中常用的环境有numpy-程序员宅基地

文章浏览阅读101次。NumPy是Python中最常用的科学数学计算库之一,它提供了高效的多维数组对象以及对这些数组进行操作的函数NumPy的核心是ndarray(N-dimensional array)对象,它是一个用于存储同类型数据的多维数组Numpy通常与SciPy(Scientific Python)和 Matplotlib(绘图库)一起使用,用于替代MatLabSciPy是一个开源的Python算法库和数学工具包;Matplotlib是Python语言及其Numpy的可视化操作界面'''_数学中常用的环境有numpy

dind(docker in docker)学习-程序员宅基地

文章浏览阅读1.1w次。docker in docker说白了,就是在docker容器内启动一个docker daemon,对外提供服务。优点在于:镜像和容器都在一个隔离的环境,保持操作者的干净环境。想到了再补充 :)一:低版本启动及访问启动1.12.6-dinddocker run --privileged -d --name mydocker docker:1.12.6-dind在其他容器访问d..._dind

推荐文章

热门文章

相关标签