nRF24L01 是一款工作在 2.4~2.5GHz 世界通用 ISM 频段的单片无线收发器芯片。无线收发器包括:频率发生器、增强型 SchockBurstTM 模式控制器、功率放大器、晶体振荡器、调制器、解调器。输出功率频道选择和协议的设置可以通过 SPI 接口进行设置。
方便的设置以及极低的功耗使得它被广泛地应用于无线鼠标、键盘;无线门禁、各种智能物联网设备通讯等方面。
参数
- 供电电压:1.9 V~3.6V;
- 最大发射功率:0 dBm;
- 最大数据传输率:2000 kbps;
- 发射模式下电流消耗(0dBm时):11.3 mA;
- 接收模式下电流消耗(2000kbps):12.3 mA;
- 接收模式数据传输率为 1000kbps 下的灵敏度:-85 dBm;
- 掉电模式下电流消耗:900 nA。
链接
这里我们借用论坛里的照片来说一下接线:
- VCC <-> 3.3V
- GND <-> GND
- CE <-> D9
- CSN <-> D10
- MOSI<-> D11
- MISO<-> D12
- SCK <-> D13
- IRQ <-> 不接
实验
由于 nRF24L01 是全双工的通信模块,所以每一个模块既可以做发送端也可以做接收端。这里我们先去下载 Mirf 库备用。
首先是初始化配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
String code; void setup() { Serial.begin(9600); code = "A message from another B!!!";//准备要发送的字符串 Mirf.cePin = 9; //设置CE引脚为D9 Mirf.csnPin = 10; //设置CE引脚为D10 Mirf.spi = &MirfHardwareSpi; Mirf.init(); //初始化nRF24L01 Mirf.setTADDR((byte *)"A");//发送地址 A Mirf.setRADDR((byte *)"B");//接收地址 B Mirf.payload = 32;//窗口大小,最大32. 也就是一次收发的数组长度,两端需要一致 //发送通道,可以填0~128,收发必须一致。 Mirf.channel = 0; Mirf.config(); Serial.println("I'm Sender B..."); } |
当然,这是发送端,这里接收端也是类似的,只不过地址要对应的改变:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void setup() { Serial.begin(9600); Mirf.cePin = 9; //设置CE引脚为D9 Mirf.csnPin = 10; //设置CE引脚为D10 Mirf.spi = &MirfHardwareSpi; Mirf.init(); //初始化nRF24L01 Mirf.setRADDR((byte *)"A");//接收地址 A Mirf.setTADDR((byte *)"B");//发送地址 B Mirf.payload = 32;//窗口大小,最大32. 也就是一次收发的数组长度,两端需要一致 //发送通道,可以填0~128,收发必须一致。 Mirf.channel = 0; Mirf.config(); Serial.println("I'm Reciver A..."); } |
Mirf.init()一旦被调用,前边的设置即不可更改,不过发送接收的名称、窗口大小,频道等都是可以实时改变的,这里我们就不做代码演示了,现在,我们来写一个简单的 echo ——即发送端 B 来发送我们预先写好的字符串,A 在接收到字符串之后立即把内容发回给 B。
这里我们先列举一下 Mirf 库常用到的方法:
- Mirf.isSending() 返回一个布尔量显示当前芯片是否正在发送信息;
- Mirf.send() 发送 byte 类型数组,即字符串必须经过转换才能发送;
- Mirf.getData() 从芯片里获取收到的信息,同样也是 byte 类型数组;
- Mirf.dataReady() 判断芯片是否有收到信息。
由于发送接收数据有些复杂要转换数据类型而不是直接发送字符串,这里我们封装两个函数来完成这个功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
String reciveDataString() {//从芯片里接收收到的信息 byte data[32];//缓存 String tmp; //字符串缓存 Mirf.getData(data); //接收数据到data数组 for (int i = 0; i < sizeof(data); ++i ) { //遍历data数组转换字符 tmp += char(data[i]); if(char(data[i]) == '\0')break;//拼接字符串直到完成 } return tmp; } void sendDataString(String str) {//发送数据,直接接受字符串形式参数 byte data[32]; //缓存 str.getBytes(data,32); //把字符串转换为byte数组,最大长度不超过32 //实际上这里应该手动实现超长字符串自动分片,但一般也用不到那么长吧…… Mirf.send(data);//调用类方法来发送数据 while(Mirf.isSending()) {}//判断是否发送完毕,如果还在发送就一直等待 } |
这样,这个收发函数就封装好了,现在我们来实现功能,首先是发送端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void loop() { sendDataString(code); String tmp; if(Mirf.dataReady()) //等待接收数据准备好 { tmp = reciveDataString(); Serial.println(tmp); } delay(1000); } |
然后是接收(echo)端:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void loop() { String tmp; if(Mirf.dataReady()) //等待接收数据准备好 { tmp = reciveDataString(); Serial.println(tmp); Serial.println("start sending!"); sendDataString(tmp); Serial.println("sent!"); } delay(200); } |
接下来则是把这两套不同的代码分别刷在两个不同的 Arduino 开发板上,同时通电,你就可以在串口终端看到结果了!
特性
这里有一点我们要提一下,这个 nRF24L01 无线模块是支持混合互联的,也就是说,它是属于那种“喊话”模式,即多个模块使用同一个接收标识也不会有问题,如果你这么做,它们就会各自收到一条一模一样的信息。只要窗口大小相同信道一致,那么对应地址的模块就会收到信息而不存在冲突。
它没有实现任何上层功能,除了最基本的比特发送和接收外,其他任何高级功能都需要你自行完成(比如分片、重传以及其他类似 TCP/IP 的功能)。
说起传输距离,使用模块自带小天线的话,确实不怎么远。
玄学
这位同学给出了一个解决办法,但我没有条件进行测试了,希望对大家有帮助。 :)
最后补充一句,收发模块的发送地址和接收地址的代码执行顺序不能相同,比如这样:
1 2 3 4 5 |
<del> Mirf.setRADDR((byte *)"A");//接收地址 A Mirf.setTADDR((byte *)"B");//发送地址 B ———————— Mirf.setRADDR((byte *)"B");//接收地址 B Mirf.setTADDR((byte *)"A");//发送地址 A</del> |
那么最终的结果就是两个模块无法互通——你必须把他们的顺序对换,像这样:
1 2 3 4 5 |
<del> Mirf.setRADDR((byte *)"A");//接收地址 A Mirf.setTADDR((byte *)"B");//发送地址 B ———————— Mirf.setTADDR((byte *)"A");//发送地址 A Mirf.setRADDR((byte *)"B");//接收地址 B</del> |
本文由 落格博客 原创撰写:落格博客 » nRF24L01 无线收发模块 Arduino
转载请保留出处和原文链接:https://www.logcg.com/archives/1823.html
博主你好,我试验过你的代码,关于玄学部分,其实把两个地址名称定义长一点问题就决解了,而且可以不必在初始化时定义接收端地址,只要在执行子函数前定义接收端地址,这样能做到一对多通讯
谢谢,不过我手头没设备了也没法测试,我加入到正文中吧:)
請問這式用什麼編譯程式寫的?
C++
您好,很高兴看到您的博客,我现在是浮点型的数据通过arduino和nrf24l01发送,请问浮点数据比如发送“21.23”,我是用这个浮点数据乘以100变成整形之后发送还是可以直接将21.23转换成字符串发送?
都可以,理论上浮点数也可以直接与string互换,你可以用C++试试看,如果可以互相转换,就不必转换整形。不过有一点,如果你确实需要固定格式的浮点数,那还是处理一下再转字符串比较好。