Arduino很棒的原因有很多,其中之一是它们能够连接到几乎任何东西。但有时您想在不使用任何电线的情况下连接到您的 Arduino,有多种方法可供选择。在本文中,我将研究一种 Arduino 无线通信方法,即 nRF24L01 模块。这是一种使用 2.4 GHz 频段提供 2 路通信的廉价模块。
本文翻译自DronebotWorkshop.com
译者:DIY百事
目录
1 简介
2 无线通讯
3 2.4 GHz 频段
4 nRF24L01
5 nRF24L01 连接
5.1 SPI 总线
5.2模块连接
5.3电源注意事项
5.4 nRF24L01 适配器模块
6 nRF24L01 和 Arduino
6.1 Arduino 库和连接
6.2 RadioHead 库
6.3连接 Arduinos
6.4 RadioHead 示例代码 – 客户端和服务器
6.5 RadioHead 示例代码——可靠的数据报
7操纵杆演示
8机器人汽车遥控器
8.1使用模拟引脚作为数字引脚
8.2操纵杆发射器示意图
8.3操纵杆接收器示意图
9结论
介绍图1
nRF24L01 模块。这是一种使用 2.4 GHz 频段提供 2 路通信的廉价模块。该频段无需许可即可免费用于低功率设备,并且在某些情况下有效距离长达一公里。
nRF24L01 有多种不同的配置可供选择,在本文中,我将介绍几个比较流行的配置。我们会将它们连接起来并使用一个非常广泛的库来促进它们之间的通信。
图2
在我们让它们工作后,我们将使用它们来构建一些有趣的东西——我们之前开发的机器人汽车底座的无线操纵杆控制。
在我们开始之前,让我们检查一下 nRF24L01 以及您在设计无线设备时需要考虑的一些因素。
无线通讯自 1880 年代后期以来,我们一直在以无线方式发送信息,托马斯爱迪生使用电磁感应系统将电报信号从移动的火车发送到轨道旁边的一组电线。无线设备是我们生活的一部分,大多数工作使用以下方法之一:
- 信号通过红外线光束发送
- 使用无线电波发送信号
与红外线光束相比,无线电波具有许多优势,最明显的是无线电波可以(在一定程度上)穿过墙壁和大多数其他障碍物。
无线电波绝不是一种完美的通信方式,它们会受到许多来源的干扰,并且可能会被金属或厚墙阻挡。但它们确实在许多低速数据应用程序中发挥作用,因此非常适合想要构建远程控制设备或需要无线发送数据的 Arduino 和 Raspberry Pi 实验者。
无线电信号基于改变或“调制”无线传输到接收器的“载波”的概念。在接收端,载波被分离,信号被“解调”以从中提取原始信息。
由于这些载波会相互干扰,因此它们的分配受到严格控制,每个国家都有一个政府部门负责管理它们。非法使用无线电载波频率会导致非常严厉的罚款,所以不要这样做。
像我们这样的实验者可以使用很多频率,其中最受欢迎的频率之一是 2.4 GHz 频段。
2.4 GHz 频段2.4 GHz 工业、科学和医疗 (ISM) 频段已保留用于未经许可的低功耗设备,这使其非常适合构建远程控制的 Arduino 设备。当然,它还非常适合您家中的许多其他设备,如无线路由器、无绳电话、蓝牙小玩意和其他无线设备。
讨论的频段从 2.400.0 GHz 到 2.483.5 GHz,为了让多个设备共存成为可能,它被分解为多个通道。尽管有 14 个频道可用,但并非每个频道在每个地区都是合法的。频道 1 到 11 在世界的大多数地方都是合法的,因此建议您将实验限制在这些频道中。
由于在 2.4 GHz 频段上进行通信是一项非常常见的功能,因此有许多模块专门用于此目的。我们今天要玩的可能是最受欢迎的,它可以在 网上以几元或更少的价格购买。
nRF24L01nRF24L01 是用于构建 2.4 GHz 发射器和接收器或“收发器”的通用芯片的部件号。该芯片已被用于创建一些简单且廉价的模块,这些模块可用于使用 2.4 GHz 频段传输和接收数据。
有多种基于 nRF24L01 的模块可用,我将在本文中使用两个非常常见的模块。如果您有不同的模块,它应该可以正常工作,请务必注意接线,尤其是电源要求。
我使用的两个模块非常相似并且可以互换,它们之间的区别在于其中一个具有内置低噪声放大器 (LNA) 和外部天线连接。我更喜欢这个,即使它有点贵,因为它可以用于在相当长的距离内进行可靠的数据通信。除非你住在城堡里,否则它可能足以覆盖你的整个房子。
所有实验,包括无线操纵杆,都可以使用任一模块构建(它们共享相同的引脚),但使用带有外部天线的模型可以获得更好的范围。
nRF24L01 连接nRF24L01 有一个 8 针连接器,可与外界连接。此连接器在两种类型的 nRF24L01 模块之间通用。尽管 nRF24L01 由 1.9 至 3.9 伏电源供电,但逻辑引脚可承受 5 伏电压,因此它们可以直接与 Arduino 或其他 5 伏逻辑微控制器一起使用。
SPI总线nRF24L01 使用串行外设接口或 SPI 总线进行通信。这是许多微控制器和微型计算机(包括 Arduino 和 Raspberry Pi)使用的标准总线。
SPI 总线使用主从的概念,在大多数常见应用中,微控制器或微机是主,nRF24L01 是从。与 I2C 总线不同,SPI 总线上的从机数量是有限的,在 Arduino Uno 上,您最多可以使用两个 SPI 从机。
SPI 总线是双向总线,这意味着主机和从机可以同时发送和接收,但是我们将与 nRF24L01 一起使用的库不会这样做。
每个从设备都需要由主设备选择才能进行通信。在任何给定时间,只有一个从站可以通信。nRF24L01 和其他从设备有一个中断引脚,可以在需要通信时提醒主设备,但我们今天将使用的库忽略了这一点,因此在我们的应用程序中,我们不会将中断引脚连接到 Arduino。
模块连接与 nRF24L01 模块的连接如下:
图3
- GND地。这是接地引脚。它通常通过将引脚包裹在一个正方形中来标记,因此它可以用作识别其他引脚的参考。
- VCC逻辑电平。正电压。这可以是 1.9 到 3.9 伏之间的任何电压。它不耐受 5 伏电压!
- CE片选。Chip Enable,高电平有效引脚。选择后,nRF24L01 将发送或接收,具体取决于它当前处于哪种模式。
- CSN。片选否。这是一个低电平有效的引脚,它是 SPI 总线用来选择 nRF24L01 从机的引脚。
- SCK 。时钟引脚,SPI 总线 Master 提供的外部时钟源。
- MOSI。主出从入。nRF24L01 的输入。
- MISO。主入从出。nRF24L01 的输出。
- IRQ中断请求。中断输出引脚。
使用外部天线的 nRF24L01 样式也有用于连接天线的 SMA 连接器。
电源注意事项由于 nRF24L01 的电源范围为 1.9 – 3.9 伏,因此它可以由电池供电。使用 3.3 伏电源为模块供电也很常见。
选择电源时,应注意 nRF24L01 在以最高功率传输时会消耗相当多的电流。您的电源应该能够提供至少 300 mA 的电流。
电源上的噪声也会导致 nRF24L01 出现问题。建议在电源线上放置一个滤波电容器(100 微法是理想的),尽可能靠近 nRF24L01 模块,以消除电源噪声。
解决电源问题的另一种方法,也是我建议您采用的方法,是为您的 nRF24L01 使用适配器模块。
nRF24L01 适配器模块nRF24L01 适配器模块是一种非常便宜的原型开发板,可简化 nRF24L01 的工作。我建议你使用一个,我在本文中包含的所有原理图中都展示了它。
适配器模块有一个 8 针母连接器,允许您插入 nRF24L01,它可以容纳带有集成或外部天线的模块。它还具有用于 SPI 和中断连接的 6 针公连接器和用于电源输入的 2 针连接器。
适配器模块有自己的 3.3 伏稳压器和一组滤波电容器,因此您可以使用 5 伏电源为其供电。假设您的电源具有所需的电流能力,适配器模块将解决上述所有电源问题。
由于这些模块的售价便宜,因此推荐使用它们。它们可以方便您的 nRF24L01 设计。
nRF24L01 和 Arduino在我们的实验中,我们将使用两个 nRF24L01 模块和几个 Arduino Uno。您当然可以使用其他型号的 Arduino,但如果您这样做,您可能需要更改引脚排列,因为不同的 Arduino 型号对 SPI 总线使用不同的引脚。
我将在这里描述 Arduino Uno 和 Arduino Mega 2560 的引脚排列。后面(在原理图中)我将只使用 Arduino Uno,因此如果您使用的是 Mega 2560,则需要相应地替换引脚编号。我们的 Robot Car 项目基于 Arduino Uno,我们将修改该项目以使用无线操纵杆。
Arduino 库和连接与其他无线电模块一样,nRf24L01 有许多可用的库。使用库将真正简化使用这些模块创建项目的过程。
以下库均适用于 nRF24L01 模块:
- TMRh20 – 这个库已经存在好几年了。它非常适合创建安全的无线通信设备。您可以在TMRh20 项目博客上阅读更多内容,并在Arduino 设备的TMRh20 GitHub 存储库分支上获取最新版本。
- RF24 – 这是一个旧标准,已在许多 nRF24L01 项目中使用。它已被 RadioHead 和 TMRh20 库取代。
- Mirf 库——基于Tinkerer库。这是一个更古老的库,因此您不会再找到太多基于它的项目。
- RadioHead - 这是一个具有许多高级功能的现代库,能够支持许多 RF 模块。
在我们将要执行的实验中,对于我们的无线游戏杆项目,我们将使用 RadioHead 库。
请注意,并非上面列出的所有库都是Arduino 自带的。
RadioHead 库RadioHead 是 Mike McCauley 为 Airspayce 公司编写的库。我在之前的一篇文章中使用过它,将廉价的 433MHz 发送和接收模块与 Arduino 结合使用。
这是一个高级库,它允许在诸如 nRF24L01 之类的 RF 模块之间进行多种分组无线电通信方法。它包含用于不同射频模块的许多不同驱动程序,nRF24L01 的驱动程序是RH_NRF24 驱动程序。
您可以在RadioHead 网站上了解有关 RadioHead 库的更多信息并下载您需要在 Arduino IDE 中安装的 ZIP 文件。在页面描述顶部附近查找指向 ZIP 文件的链接。
下载 ZIP 文件后,您需要将其安装在 Arduino IDE 中。这是一个非常简单的过程:
- 打开 Arduino IDE。
- 从顶部菜单栏中选择Sketch,
- 从代码菜单下拉菜单中选择包括库。
- 从包含库子菜单中选择添加 .ZIP 库。
- 使用对话框选择您下载的 ZIP 文件。
- 将安装 RadioHead 库。
在您的 Arduino IDE 中安装 RadioHead 库后,您就可以开始使用 nRF24L01 进行实验了。
连接 ArduinoRadioHead 库附带了许多示例代码,用于说明其用法。我们将从这些代码中的一些代码开始我们的实验,然后我们将为我们的操纵杆项目修改其中的一些代码。
以下是您需要为 Arduino Uno 进行的连接:
图4
请注意,您需要制作其中两个电路!我们将其中一个电路称为服务器,另一个称为客户端。两者的接线是相同的。
另请注意,我使用 nRF24L01 适配器模块来说明原理图,该模块具有自己的稳压器,可为 nRF24L01 提供 3.3 伏电压。如果您愿意,您可以直接连接到 nRF24L01 本身,但您需要使用 Arduino 的 3.3 伏输出,而不是 5 伏输出(这可能会破坏您的 nRF24L01 模块)。
请注意,许多 Arduino 仿制板在 nRF24L01 的 3.3 伏输出上没有足够的电流。即使你的有,建议在电源线上使用一个滤波电容器。但是,您最好的选择还是简单地使用 nRF24L01 适配器模块,这样您就可以避免很多问题!
如果您使用的是 Arduino Mega 2560,那么 RadioHead 库的引脚分配会有所不同:
- GND。当然,这仍然是接地,因此它连接到 Mega 2560 接地引脚之一。
- VCC。同样,这仍然是电源连接。请参阅上面有关使用哪种电压的说明。
- CE。这与 Arduino Uno 相同,它连接到 Mega 2560 上的引脚 8。
- CSN。将此连接到 Mega 2560 上的输出引脚 53。
- SCK。将此连接到 Mega 2560 上的输出引脚 52。
- MOSI。这转到 Mega 2560 上的引脚 51。
- MISO。最后,这是 Mega 2560 上的引脚 50。
请注意,nRF24L01 上的 IRQ 引脚未与任一 Arduino 板一起使用,因为它被 RadioHead 库忽略。
一切就绪后,您就可以开始运行第一个代码了。
RadioHead 示例代码 – 客户端和服务器我们将运行的第一个代码是 RadioHead 库中包含的基本客户端和服务器示例。您可以按如下方式找到并加载它们:
- 打开 Arduino IDE(您可能已经这样做了)。
- 从顶部菜单栏中打开文件菜单。
- 选择示例。将显示一个子菜单。
- 向下滚动示例子菜单到底部标题为来自自定义库的示例的部分。
- 从菜单中选择RadioHead。此菜单旁边将出现另一个子菜单。
- 从 RadioHead 子菜单中选择nrf24。
- 将显示 RadioHead RH_NRF24 驱动程序的示例代码列表。
我们需要加载两个代码,每个 Arduino 上一个。如果您没有拥有两台计算机的奢侈,那么您可以使用一台计算机单独完成这些操作。
我们需要的代码如下:
- 在服务器 Arduino 上加载nrf24_server代码。
- 在客户端计算机上加载nrf24_client代码。
代码得到了很好的评论,因此我将仅介绍此处的一些基本要素。
每个代码都包括 RadioHead RH_NRF24 库以及 Arduino SPI 库。然后他们都声明了一个无线电驱动程序的实例。如果您想更改接线或使用不同类型的 Arduino,您可以在声明驱动程序时添加可选参数。
之后,它们都进入设置例程,从设置串口监视器和初始化驱动程序开始。然后调用setChannel方法将无线电信道从默认信道(即信道 2)更改为信道 1。如果您发现任何 2.4 GHz 设备(即无线鼠标)干扰实验,您可以尝试不同的信道。
您还可以添加参数来更改模块的数据速率和发射功率。较慢的数据速率将导致更长的操作范围。只要确保在服务器和客户端之间保持相同的数据速率和通道。
然后我们继续循环。
在服务器端,循环从寻找来自客户端的消息开始。如果收到,则将其放入缓冲区,然后输出到串口监视器。之后,回复“And hello back to you”被放入一个数组并发送给客户端。
在客户端,循环以相反的方式开始。一条消息“Hello World”被放入一个数组并发送到服务器。然后我们等待接收来自客户端的消息(“And hello back to you”消息)。如果/当我们收到消息时,它会被输出到串口监视器上。
如果您有幸拥有两台计算机,则可以同时打开串口监视器并观察服务器和客户端之间的交互。如果您只有一台计算机,那么我建议您使用电池或 USB 电源为一台 Arduino 供电,同时使用计算机为另一台计算机供电和监控。
虽然这是一个非常基本的代码,但它确实说明了 RadioHead 库如何使 nRF24L01 的工作变得容易。它还有一个实际用途——您可以使用它(用一个 Arduino 电池供电)来确定使用两个模块可以达到的范围。如果您同时拥有带集成天线的模块和带外置天线的模块,您很快就会看到外置天线模块的真正优势。
RadioHead 示例代码 – 可靠的数据传送以前的代码效果很好,对于许多应用程序,它们都是您所需要的。但是,如果您遇到的情况是您传输的数据必须正确无误地接收,那么您将需要查看另一种方法。
当您将一个大文件分成几个小部分时,这可能是一个要求。发送端需要确保接收端完整地接收到每一位。如果不是,则需要重新发送数据。
Internet 上的数据传输使用此原理。
交换数据的 RadioHead 可靠数据传送方法也使用这种验证数据完整性的方法。它不需要任何特殊编码,因为 Reliable Datagram 库在后台为您完成所有工作。我们现在来看看。
对于此实验,您无需进行任何接线更改,因为连接与之前的实验相同。
返回到Radiohead的库的例子代码nrf24并选择以下两个代码:
- 在服务器 Arduino 上加载nrf24_reliable_datagram_server代码。
- 在客户端 Arduino 上加载nrf24_reliable_datagram_client代码。
同样,这些代码得到了很好的评论,它们与我们刚刚看到的代码也有很多相似之处,所以我将在这里主要讨论不同之处。
除了在前面的代码中加载的两个库之外,这些代码还加载了 RadioHead RHReliableDatagram库。
然后定义了两个常量,一个 CLIENT_ADDRESS 和 SERVER_ADDRESS。这些地址不是无线电信道,而是在服务器和客户端之间交换的数据报包中使用的地址。
创建无线电驱动程序的实例后,每个代码都使用上面定义的地址之一设置数据报管理器(这是服务器和客户端的不同之处)。
在设置例程中,设置串口监视器并初始化数据报管理器。
在设置例程之外和循环之前,使用要发送的数据定义一个数组并定义一个缓冲区。
在循环中,操作与之前的代码非常相似。服务器等待从客户端接收到消息,将其输出到串口监视器,然后发送自己的消息。客户端反过来做同样的事情。
您会在串口监视器中注意到的一件事是双方都打印出接收到的数据包中包含的地址。
试用演示并查看结果。在远距离,您偶尔会注意到接收数据时会出现轻微延迟,这会在数据包丢失并需要重新发送时发生。
与之前的代码一样,这个效果很好。我们现在将修改此代码以发送一些操纵杆数据,然后使用它来构建我们的无线操纵杆。
操纵杆演示前两组代码说明了如何交换文本数据,如“Hello World”和“And hello back to you”,这本身就很有用。但在许多情况下,您会希望在两个 Arduino 之间无线交换数字数据。这方面的一个例子是从远程传感器向基站发送数据。
在我们的下一个实验中,我们将从操纵杆发送数据到远程接收器。操纵杆的每个轴都将作为单个字节与“虚拟字节”一起发送,我将在适当的时候解释其用途。根据操纵杆的位置,轴数据的值范围为 0 到 255。
您当然可以使用此代码发送其他传感器数据,它不需要来自操纵杆。你的想象力是这里唯一的限制。
每个代码都是我们之前看到的可靠数据报代码的修改版本,您会识别出很多代码。重新发明轮子毫无意义!
在我们开始之前,您需要拿一个 Arduinos 并为其添加一个操纵杆。连线如下图:
图5
如您所见,操纵杆连接非常简单。如果您没有操纵杆,只需使用两个电位计,因为这就是模拟操纵杆的全部功能——一个用于 x 轴的电位器,另一个用于 y 轴的电位器。每个控件都连接到 Arduino 的模拟输入之一。
在另一端,将 Arduino 和 nRF24L01 保持原样连接,您唯一需要更改的就是代码。
这是操纵杆方面的代码。请注意,它基于 RadioHead Reliable Datagram Client 代码,这只是我的任意选择,因为我可以轻松地使用服务器代码。
/*
nRF24L01 Joystick Transmitter
nrf24l01-joy-xmit-demo.ino
nRF24L01 Transmitter with Joystick
Use with Joystick Receiver Demo
DroneBot Workshop 2018
https://dronebotworkshop.com
*/
// Include RadioHead ReliableDatagram & NRF24 Libraries
#include <RHReliableDatagram.h>
#include <RH_NRF24.h>
// Include dependant SPI Library
#include <SPI.h>
// Define Joystick Connections
#define JoyStick_X_PIN A0
#define JoyStick_Y_PIN A1
// Define addresses for radio channels
#define CLIENT_ADDRESS 1
#define SERVER_ADDRESS 2
// Create an instance of the radio driver
RH_NRF24 RadioDriver;
// Sets the radio driver to NRF24 and the client address to 1
RHReliableDatagram RadioManager(RadioDriver, CLIENT_ADDRESS);
// Declare unsigned 8-bit joystick array
uint8_t joystick[3];
// Define the Message Buffer
uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN];
void setup()
{
// Setup Serial Monitor
Serial.begin(9600);
// Initialize RadioManager with defaults - 2.402 GHz (channel 2), 2Mbps, 0dBm
if (!RadioManager.init())
Serial.println("init failed");
}
void loop()
{
// Print to Serial Monitor
Serial.println("Reading joystick values ");
// Read Joystick values and map to values of 0 - 255
joystick[0] = map(analogRead(JoyStick_X_PIN), 0, 1023, 0, 255);
joystick[1] = map(analogRead(JoyStick_Y_PIN), 0, 1023, 0, 255);
joystick[2] = 100;
//Display the joystick values in the serial monitor.
Serial.println("-----------");
Serial.print("x:");
Serial.println(joystick[0]);
Serial.print("y:");
Serial.println(joystick[1]);
Serial.println("Sending Joystick data to nrf24_reliable_datagram_server");
//Send a message containing Joystick data to manager_server
if (RadioManager.sendtoWait(joystick, sizeof(joystick), SERVER_ADDRESS))
{
// Now wait for a reply from the server
uint8_t len = sizeof(buf);
uint8_t from;
if (RadioManager.recvfromAckTimeout(buf, &len, 2000, &from))
{
Serial.print("got reply from : 0x");
Serial.print(from, HEX);
Serial.print(": ");
Serial.println((char*)buf);
}
else
{
Serial.println("No reply, is nrf24_reliable_datagram_server running?");
}
}
else
Serial.println("sendtoWait failed");
delay(100);// Wait a bit before next transmission
}
代码以与 RadioHead Reliable Datagram 代码相同的方式开始,它加载所需的库。我们还定义了操纵杆使用的输入,以及可靠数据报包的客户端和服务器地址。
然后,我们定义了一个名为“joystick”的 8 位无符号整数数组,其中包含三个元素:
- 操纵杆 [0]是 x 轴值。
- 操纵杆 [1]是 y 轴值。
- 操纵杆 [2]是“虚拟”值。
发送“虚拟”值的唯一原因是要有第三个字节,因为我们最终的无线操纵杆代码将使用这个字节来指示电机方向。通过在这个演示代码中定义它,我们可以在必要时使用我们在这里构建的接收器对最终产品进行故障排除。
我为虚拟值指定了 100 的值,您可以很好地指定 0 到 255 之间的任何值。它现在只是为了测试数据完整性。
设置例程与 RadioHead Reliable Datagram 示例相同。
在循环中,我们启动串口监视器,因为我们将使用它来监视操纵杆值。然后,我们继续在每个操纵杆模拟输入上使用 Arduino模拟读取函数获取这些操纵杆值。
由于 Arduino 的模数转换器是 10 位转换器,我们将从每个操纵杆返回 0 到 1023 的值。我们使用 Arduino map命令将其转换为 0 到 255 的范围。每个 hvalues 被分配给操纵杆数组中的相应元素。
代码的其余部分与可靠数据报代码非常相似。数组被发送到服务器端,我们等待看看是否得到回复。然后我们再做一遍。
另一端(即“操纵杆接收器”)甚至更简单。这是代码:
/*
nRF24L01 Joystick Receiver Demo
nrf24l01-joy-rcv-demo.ino
nRF24L01 Receiver with Joystick Decode
Use with Joystick Transmitter Demo
DroneBot Workshop 2018
https://dronebotworkshop.com
*/
// Include RadioHead ReliableDatagram & NRF24 Libraries
#include <RHReliableDatagram.h>
#include <RH_NRF24.h>
// Include dependant SPI Library
#include <SPI.h>
// Define addresses for radio channels
#define CLIENT_ADDRESS 1
#define SERVER_ADDRESS 2
// Create an instance of the radio driver
RH_NRF24 RadioDriver;
// Sets the radio driver to NRF24 and the server address to 2
RHReliableDatagram RadioManager(RadioDriver, SERVER_ADDRESS);
// Define a message to return if values received
uint8_t ReturnMessage[] = "JoyStick Data Received";
// Define the Message Buffer
uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN];
void setup()
{
// Setup Serial Monitor
Serial.begin(9600);
// Initialize RadioManager with defaults - 2.402 GHz (channel 2), 2Mbps, 0dBm
if (!RadioManager.init())
Serial.println("init failed");
}
void loop()
{
if (RadioManager.available())
{
// Wait for a message addressed to us from the client
uint8_t len = sizeof(buf);
uint8_t from;
if (RadioManager.recvfromAck(buf, &len, &from))
//Serial Print the values of joystick
{
Serial.print("got request from : 0x");
Serial.print(from, HEX);
Serial.print(": X = ");
Serial.print(buf[0]);
Serial.print(" Y = ");
Serial.print(buf[1]);
Serial.print(" Z = ");
Serial.println(buf[2]);
// Send a reply back to the originator client, check for error
if (!RadioManager.sendtoWait(ReturnMessage, sizeof(ReturnMessage), from))
Serial.println("sendtoWait failed");
}
}
}
此代码与 RadioHead Reliable Datagram 服务器代码之间的唯一区别是我们在接收到的数据数组中显示数值而不是文本。我们将它们标记为 X、Y 和 Z:
- X 是 x 轴读数。
- Y 是 y 轴读数。
- Z 是“虚拟值”。
加载 bnoth 代码后运行它们,至少将接收器连接到计算机,以便您可以观察串口监视器。当您移动操纵杆时,您应该观察值的变化。
机器人汽车遥控器所以现在我们可以看到我们如何发送操纵杆值。是时候把它们放在一起创建我们的机器人汽车遥控操纵杆了。
在操纵杆一侧,您已经完成接线,您需要更改的只是代码。在另一端,虽然你需要一辆机器人汽车!
如果您按照上一篇文章“使用速度传感器构建机器人汽车”中的说明进行操作,那么您已经拥有一辆机器人汽车。如果没有,那么去看看那篇文章,了解如何使用便宜的套件组装机器人汽车。
由于该项目不使用速度传感器,因此您可以忽略该部分。如果您已经建造了机器人汽车,那么就让它保持原样,您不需要移除速度传感器。
如果您确实制造了原车,则需要进行一些更改,特别是 L298N H 桥电机控制器的连接方式。原始设计使用了 nRF24L01 所需的一些引脚,因此需要它。
这是新的机器人汽车示意图:
图6
与 nRF24L01 模块的连接与我们其他实验中的连接完全相同,这并不奇怪。您可以使用任何类型的 nRF24L01 模块,但我强烈建议使用带有外部天线的模型以提高性能,至少在汽车方面是这样。
使用模拟引脚作为数字引脚在电机方面,您会注意到与原始设计相比,电机 A 的引脚已移动。其中之一可能会让您感到惊讶——L289N H 桥IN1引脚连接到 Arduino 模拟引脚 A0。为什么是模拟引脚?
如果您查看作为 Arduino Uno 核心的 ATMega328 的规格,您就会明白我为什么使用 A0。事实证明,Arduino 上的“模拟”引脚也可以很好地用作数字 I/O 引脚。
我的 L298N 电机控制器需要一个额外的引脚,我有几个选择:
- 我不想使用引脚 2 或 3,因为机器人汽车将这些用于速度传感器。即使传感器不是这个设计的一部分,我也想让它们自由。
- 引脚 1 和 2 可能看起来很有前途,但它们也是特殊引脚——它们用作串行接口的 RX 和 TX 线。我想让它们免费,也是由于内部原因,它们无论如何都不会在这个应用程序中很好地工作。
- 我选择使用引脚 A0。它和模拟引脚,但它也是 Arduino Uno 上的数字 I/O 引脚 #14。
只要引脚在代码中定义为输出,它就可以正常工作。
连接好汽车后,剩下的就是加载代码。我们将首先查看操纵杆代码。
操纵杆发射器示意图如您所料,远程操纵杆代码与我们之前看到的操纵杆演示代码非常相似。她在它的所有荣耀中:
/*
nRF24L01 Joystick Transmitter
nrf24l01-joy-xmit-car.ino
nRF24L01 Transmitter with Joystick for Robot Car
Use with Joystick Receiver for Robot Car
DroneBot Workshop 2018
https://dronebotworkshop.com
*/
// Include RadioHead ReliableDatagram & NRF24 Libraries
#include <RHReliableDatagram.h>
#include <RH_NRF24.h>
// Include dependant SPI Library
#include <SPI.h>
// Define Joystick Connections
#define joyVertA0
#define joyHorzA1
// Define Joystick Values - Start at 512 (middle position)
int joyposVert = 512;
int joyposHorz = 512;
// Define addresses for radio channels
#define CLIENT_ADDRESS 1
#define SERVER_ADDRESS 2
// Create an instance of the radio driver
RH_NRF24 RadioDriver;
// Sets the radio driver to NRF24 and the client address to 1
RHReliableDatagram RadioManager(RadioDriver, CLIENT_ADDRESS);
// Declare unsigned 8-bit motorcontrol array
// 2 Bytes for motor speeds plus 1 byte for direction control
uint8_t motorcontrol[3];
// Define the Message Buffer
uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN];
void setup()
{
// Setup Serial Monitor
Serial.begin(9600);
// Initialize RadioManager with defaults - 2.402 GHz (channel 2), 2Mbps, 0dBm
if (!RadioManager.init())
Serial.println("init failed");
// Set initial motor direction as forward
motorcontrol[2] = 0;
}
void loop()
{
// Print to Serial Monitor
Serial.println("Reading motorcontrol values ");
// Read the Joystick X and Y positions
joyposVert = analogRead(joyVert);
joyposHorz = analogRead(joyHorz);
// Determine if this is a forward or backward motion
// Do this by reading the Verticle Value
// Apply results to MotorSpeed and to Direction
if (joyposVert < 460)
{
// This is Backward
// Set Motors backward
motorcontrol[2] = 1;
//Determine Motor Speeds
// As we are going backwards we need to reverse readings
motorcontrol[0] = map(joyposVert, 460, 0, 0, 255);
motorcontrol[1] = map(joyposVert, 460, 0, 0, 255);
}
else if (joyposVert > 564)
{
// This is Forward
// Set Motors forward
motorcontrol[2] = 0;
//Determine Motor Speeds
motorcontrol[0] = map(joyposVert, 564, 1023, 0, 255);
motorcontrol[1] = map(joyposVert, 564, 1023, 0, 255);
}
else
{
// This is Stopped
motorcontrol[0] = 0;
motorcontrol[1] = 0;
motorcontrol[2] = 0;
}
// Now do the steering
// The Horizontal position will "weigh" the motor speed
// Values for each motor
if (joyposHorz < 460)
{
// Move Left
// As we are going left we need to reverse readings
// Map the number to a value of 255 maximum
joyposHorz = map(joyposHorz, 460, 0, 0, 255);
motorcontrol[0] = motorcontrol[0] - joyposHorz;
motorcontrol[1] = motorcontrol[1] joyposHorz;
// Don't exceed range of 0-255 for motor speeds
if (motorcontrol[0] < 0)motorcontrol[0] = 0;
if (motorcontrol[1] > 255)motorcontrol[1] = 255;
}
else if (joyposHorz > 564)
{
// Move Right
// Map the number to a value of 255 maximum
joyposHorz = map(joyposHorz, 564, 1023, 0, 255);
motorcontrol[0] = motorcontrol[0] joyposHorz;
motorcontrol[1] = motorcontrol[1] - joyposHorz;
// Don't exceed range of 0-255 for motor speeds
if (motorcontrol[0] > 255)motorcontrol[0] = 255;
if (motorcontrol[1] < 0)motorcontrol[1] = 0;
}
// Adjust to prevent "buzzing" at very low speed
if (motorcontrol[0] < 8)motorcontrol[0] = 0;
if (motorcontrol[1] < 8)motorcontrol[1] = 0;
//Display the Motor Control values in the serial monitor.
Serial.print("Motor A: ");
Serial.print(motorcontrol[0]);
Serial.print(" - Motor B: ");
Serial.print(motorcontrol[1]);
Serial.print(" - Direction: ");
Serial.println(motorcontrol[2]);
//Send a message containing Motor Control data to manager_server
if (RadioManager.sendtoWait(motorcontrol, sizeof(motorcontrol), SERVER_ADDRESS))
{
// Now wait for a reply from the server
uint8_t len = sizeof(buf);
uint8_t from;
if (RadioManager.recvfromAckTimeout(buf, &len, 2000, &from))
{
Serial.print("got reply from : 0x");
Serial.print(from, HEX);
Serial.print(": ");
Serial.println((char*)buf);
}
else
{
Serial.println("No reply, is nrf24_reliable_datagram_server running?");
}
}
else
Serial.println("sendtoWait failed");
delay(100);// Wait a bit before next transmission
}
那些阅读过“使用 L298N 双 H 桥和 Arduino 控制直流电机”文章的人可能会认出这里的一些代码,因为它取自我用来演示带有机器人汽车的操纵杆的代码(那个使用一条电线)。
我们首先像以前一样定义我们的库。然后我们定义用于操纵杆输入的模拟引脚以及几个保存这些输入值的变量。
这个代码的真正区别在于我们如何处理这些值。我们希望操纵杆按如下方式操作:
- 如果我们向前推动操纵杆,汽车应该向前行驶。我们推得越远,它应该走得越快。
- 如果我们将操纵杆向后拉向我们,汽车应该倒车。我们拉得越远,它应该走得越快。
- 如果我们将其向左移动,汽车应该向左转向。
- 如果我们将它向右移动,汽车应该向右转向。
- 如果我们将操纵杆留在中间位置,那么汽车根本不会移动。
我建立了操纵杆水平和垂直行程的“中间值”,使其值在 460 和 564 之间。确切的中间值当然是 511.5,但我们使用的是整数,我们还必须考虑到两美元操纵杆不是大多数精密仪器可用。
如果您按照代码进行操作,您应该会看到其中的逻辑。我们确定垂直操纵杆是否在 564 以上,如果是,我们将向前行驶。因此,我们将motorcontrol[2]变量设置为 0 值,在此代码中表示“前进”。
如果我们的垂直操纵杆低于 460,那么我们将motorcontrol[2]设置为 1 以表示我们想要倒退。
然后,我们使用 Arduino 映射函数将操纵杆值映射到 0 到 255 范围内的电机速度值,这些值分配给motorcontrol[0]和motorcontrol[1]变量。
水平控制以相同的方式起作用,不同之处在于它对速度值应用偏移量以使一个电机比另一个电机旋转得更快。
然后我们将数组中的三个变量发送到发射器,就像我们在操纵杆演示代码中所做的那样。
操纵杆接收器代码接收器代码,即在汽车本身上运行的代码,实际上非常简单。这里是:
/*
nRF24L01 Joystick Receiver for Robot Car
nrf24l01-joy-rcv-car.ino
nRF24L01 Receiver and L298N driver for Robot Car
Use with Joystick Transmitter for Robot Car
DroneBot Workshop 2018
https://dronebotworkshop.com
*/
// Include RadioHead ReliableDatagram & NRF24 Libraries
#include <RHReliableDatagram.h>
#include <RH_NRF24.h>
// Include dependant SPI Library
#include <SPI.h>
// Define addresses for radio channels
#define CLIENT_ADDRESS 1
#define SERVER_ADDRESS 2
// Motor A Connections
int enA = 9;
int in1 = 14;
int in2 = 4;
// Motor B Connections
int enB = 5;
int in3 = 7;
int in4 = 6;
// Create an instance of the radio driver
RH_NRF24 RadioDriver;
// Sets the radio driver to NRF24 and the server address to 2
RHReliableDatagram RadioManager(RadioDriver, SERVER_ADDRESS);
// Define a message to return if values received
uint8_t ReturnMessage[] = "JoyStick Data Received";
// Define the Message Buffer
uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN];
void setup()
{
// Setup Serial Monitor
Serial.begin(9600);
// Set all the motor control pins to outputs
pinMode(enA, OUTPUT);
pinMode(enB, OUTPUT);
pinMode(in1, OUTPUT);
pinMode(in2, OUTPUT);
pinMode(in3, OUTPUT);
pinMode(in4, OUTPUT);
// Initialize RadioManager with defaults - 2.402 GHz (channel 2), 2Mbps, 0dBm
if (!RadioManager.init())
Serial.println("init failed");
}
void loop()
{
if (RadioManager.available())
{
// Wait for a message addressed to us from the client
uint8_t len = sizeof(buf);
uint8_t from;
if (RadioManager.recvfromAck(buf, &len, &from))
{
//Serial Print the values of joystick
//Serial.print("got request from : 0x");
//Serial.print(from, HEX);
//Serial.print(": MotorA = ");
//Serial.print(buf[0]);
//Serial.print(" MotorB = ");
//Serial.print(buf[1]);
//Serial.print(" Dir = ");
//Serial.println(buf[2]);
// Set Motor Direction
if (buf[2] == 1)
{
// Motors are backwards
digitalWrite(in1, LOW);
digitalWrite(in2, HIGH);
digitalWrite(in3, LOW);
digitalWrite(in4, HIGH);
}else{
// Motors are forwards
digitalWrite(in1, HIGH);
digitalWrite(in2, LOW);
digitalWrite(in3, HIGH);
digitalWrite(in4, LOW);
}
// Drive Motors
analogWrite(enA, buf[1]);
analogWrite(enB, buf[0]);
// Send a reply back to the originator client, check for error
if (!RadioManager.sendtoWait(ReturnMessage, sizeof(ReturnMessage), from))
Serial.println("sendtoWait failed");
}
}
}
本质上,这与我们在上一个演示中使用的接收器代码相同,但添加了用于定义电机连接的变量。请注意,我们使用 Arduino 引脚“14”作为变量in1,这当然是模拟 A0 引脚。如果你愿意,你实际上可以在这里替换一个“A0”,它会工作得很好。
在设置中,我们将电机控制器引脚定义为输出。请注意,用于两条 L298N 使能线的引脚需要能够进行 PWM,因为这是调节电机速度的方式。
您可能会注意到我已经注释掉了所有的串口监视器语句。它们并不是真正必要的,只是在汽车运行时挡路,但如果您需要对汽车进行故障排除,则可以“不加注意”。
缓冲器值对应于我们在发射器中使用的电机控制值。所以buf[2]控制电机方向,buf[0]和buf[1]处理电机速度。
电机使用 Arduino 模拟写入功能通过 PWM 进行控制。
加载代码并启动一切。你现在应该有一辆遥控机器人车了!
结论如您所见,由于 RadioHead 库,nRF24L01 可用于创建一些非常有用的无线项目,只需很少的代码。如果您不喜欢建造机器人汽车,您仍然可以找到这种强大组合的很多用途