分享使用Arduino IDE的ESP32 ESP-NOW入门教程

这篇教程介绍了如何使用 ESP32 ESP-NOW 协议在 ESP32 设备间进行无线通信,包括单向和双向通信。ESP-NOW 提供低功耗、安全的点对点通信,无需 WiFi 或路由器。文章详细阐述了协议特点、配置步骤、代码示例以及如何保护通信安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ESP32 ESP-NOW 是专门为其 ESP8266 和 ESP32 微控制器开发的无线通信协议。它为 ESP32 设备提供低功耗、安全、低成本、直接的无线通信解决方案。借助 ESP-NOW,多个 ESP32 设备可以相互通信,无需 WiFi 或路由器,从而创建设备的网状网络。

在本教程中,我们将学习使用 ESP32 ESP-NOW 在 ESP32 设备之间进行无线通信。无论您想要发送传感器数据、控制 ESP 输出还是建立双向通信,本指南都将为您提供必要的步骤。

ESP-NOW 协议介绍

使用 ESP-NOW,我们可以在 ESP MCU 设备之间进行单向甚至双向通信,而无需使用 Wi-Fi 网络。它允许低开销的点对点无线数据传输,但数据包较小。最多可传输 250 字节的数据。因此,如果需要传输大量数据,那么使用此协议就没用了。

使用 ESP-NOW,连接协议得到简化,从而降低了功耗,因为数据传输所需的时间更少。此外,ESP-NOWESP-NOW使用与 Wi-Fi 相同的 2.4 GHz 频段,但不需要连接或干扰本地网络连接。它是一种快速、方便的通信协议,适用于传输较小量的数据。

ESP32 ESP-NOW 主要特性

  1. 一对一的数据传输(加密和未加密)。
  2. 支持加密和未加密的对等设备。对于加密设备,工作站模式下最多有 10 个对等点,SoftAP 或两种模式的混合模式下最多有 6 个对等点。但是,对于未加密的设备,最多允许有 19 个设备,且不限制其运行模式。
  3. 最多可以以小数据包的形式传输 250 字节的数据。
  4. 它生成一个回调函数,通知应用层数据是否传输成功。

要初始化 ESP-NOW 连接,我们必须首先配对 ESP32 板。无论任何一块板重新启动,该连接都将保持,并且传输将继续顺利进行。

在本教程中,我们将演示一个 Arduino 草图,通过该草图我们将数据从一个 ESP32 板无线传输到另一块板(单向通信)。在此之前,让我们首先看一下可以使用该通信协议的一些配置。

ESP32 ESP-NOW 单向通信

在单向通信中,一个对等设备充当发送方/主设备,另一台充当接收方/从设备。在这种情况下,我们可以对发送方-接收方进行多种配置。

  • 一块 ESP32 板向另一块 ESP32 板发送数据

如下图所示,一块 ESP32 板充当发送器,另一块板接收数据,因此充当接收器。
用途:发送传感器数据,控制 ESP 输出,包括 LED、继电器、蜂鸣器等。

ESP NOW 单向通信配置1

  • 一块 ESP32 发送器板向其他各种 ESP32 接收器板发送数据

在这种情况下,一个 ESP32 板将充当发送器/主设备,并将数据发送到多个充当接收器/从设备的 ESP32 板。
用途:遥控

ESP NOW 单向通信配置2

  • 一块 ESP32 板从其他各种 ESP32 发送器板接收数据

最后,在这种情况下,一个 ESP32 板(接收器/从机)从多个 ESP32 板(发送器/主机)接收数据。
用途:接收来自各种传感器的传感器数据。

ESP NOW 单向通信配置3

ESP-NOW 双向通信

ESP-NOW 协议还支持对等设备之间的双向通信。在这种情况下,ESP32板可以同时充当发送器(主)和接收器(从)。我们可以有两个 ESP32 板来传输数据,如下所示:

ESP NOW 双向通信配置1

或者甚至有一个 ESP32 板网络传输数据:

ESP NOW 双向通信配置2

ESP-NOW 有用的功能

以下是 ESP-NOW 函数的列表,这些函数在将 ESP-NOW 与 ESP32 结合使用时非常方便且经常使用。要查看更多信息,请查看官方 ESP-NOW 文档。

  • esp_now_init() :该函数将初始化 ESP-NOW 协议。请记住在初始化 ESP-NOW 通信协议之前启动 Wi-Fi。
  • esp_now_add_peer() :此函数将通过将唯一的 MAC 地址作为参数传递到设备内部来连接设备。
  • esp_now_send() :该函数将通过 ESP-NOW 发送
<think>我们正在讨论RK3588开发板上的DMA内存分配与数据传输操作,参考RV1106上的dma_buf_alloc等操作。在Linux内核中,DMA(直接内存访问)操作通常通过DMA引擎API来实现。不同的芯片平台可能有不同的底层实现,但内核提供了统一的接口。 根据引用[1]和引用[3]的提示,我们知道在Rockchip平台上(如RK3288、BL808)有一些特定的配置函数。RK3588作为Rockchip的新一代芯片,其DMA操作应该也有类似的框架。 在RK3588上,DMA内存分配通常需要分配能够被DMA访问的内存。这可以通过多种方式实现,例如使用dma_alloc_coherent、dma_map_single等函数。同时,Rockchip提供了自己的DMA驱动,我们可以通过DMA引擎API来配置和使用DMA传输。 以下步骤可能用于在RK3588上实现DMA内存分配和传输: 1. 分配DMA缓冲区:使用dma_alloc_coherent分配一段一致性的DMA内存。一致性内存在整个传输过程中都是缓存一致的,适用于长期存在的缓冲区。 例如: ```c void *dma_buf; dma_addr_t dma_handle; dma_buf = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL); ``` 2. 如果使用流式DMA映射(适用于一次性传输),则可以使用dma_map_single。但要注意,流式映射需要在使用后及时解除映射。 3. 配置DMA传输:使用DMA引擎API。首先需要申请一个DMA通道,然后配置传输描述符(slave_config),最后提交传输。 例如: ```c struct dma_chan *chan; struct dma_slave_config config = {0}; struct dma_async_tx_descriptor *desc; dma_cookie_t cookie; // 获取DMA通道 chan = dma_request_chan(dev, "dma_channel_name"); // 配置DMA传输参数 config.direction = DMA_MEM_TO_DEV; // 例如,内存到设备 config.dst_addr = dst_addr; // 设备端的物理地址 config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; // 数据宽度 config.dst_maxburst = 8; // 突发传输长度 // 应用配置 dmaengine_slave_config(chan, &config); // 准备传输描述符 desc = dmaengine_prep_slave_single(chan, dma_handle, size, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT); if (!desc) { // 错误处理 } // 设置回调函数(可选) desc->callback = my_dma_callback; desc->callback_param = my_param; // 提交传输 cookie = dmaengine_submit(desc); dma_async_issue_pending(chan); // 等待传输完成(可选) dma_sync_wait(chan, cookie); ``` 4. 传输完成后,释放资源:对于一致性内存,使用dma_free_coherent释放;对于流式映射,使用dma_unmap_single解除映射。同时,释放DMA通道。 注意:具体的DMA通道名称(如"dma_channel_name")和配置参数需要参考RK3588的文档或驱动源码。Rockchip的DMA驱动通常使用"rx"和"tx"来表示接收和发送通道。 另外,引用[3]提到了时钟配置,但DMA传输本身可能不需要直接配置时钟,除非涉及外设(如SPI、I2C)的时钟。如果DMA用于外设数据传输,那么外设的时钟配置也是必要的。 在RK3588上,时钟配置可能通过类似GLB_Set_Display_CLK的函数(在BL808上)进行,但RK3588的时钟配置函数可能不同。需要参考RK3588的时钟树和相应的驱动代码。 由于我们无法直接获取RK3588的完整驱动代码,这里只能提供一般性指导。具体实现时,需要查阅RK3588的SDK或内核驱动代码。 如果用户希望实现类似于RV1106的dma_buf_alloc操作,那么RV1106上可能有一个自定义的dma_buf_alloc函数。在RK3588上,我们通常使用标准的DMA API(如dma_alloc_coherent)来分配DMA内存,而不需要自定义的分配函数。 因此,总结在RK3588上实现DMA内存分配和传输的步骤: - 分配DMA缓冲区(dma_alloc_coherent) - 获取DMA通道(dma_request_chan) - 配置DMA传输参数(dmaengine_slave_config) - 准备并提交传输(dmaengine_prep_slave_single, dmaengine_submit, dma_async_issue_pending) - 等待传输完成(可选,dma_sync_wait) - 释放资源(dma_free_coherent, dma_release_channel) 注意:以上代码为示例,实际使用时需要添加错误处理。 如果用户需要更具体的代码示例,可能需要参考RK3588的SDK中提供的DMA示例或相关外设驱动(如SPI、UART等)中的DMA使用部分。 另外,引用[1]中提到了RK3288的SPI传输函数rockchip_spi_transfer_one,在RK3588的SPI驱动中可能也有类似的函数,它内部可能使用了DMA传输。因此,如果用户是在SPI等总线上使用DMA,那么可以直接使用该总线驱动的DMA接口,而不需要直接操作DMA引擎API。 例如,在SPI传输中,可以这样使用DMA: - 初始化SPI控制器时,使能DMA。 - 在传输结构体spi_transfer中设置tx_dma和rx_dma为DMA缓冲区的物理地址,并设置dma_len。 - 然后使用spi_sync()或spi_async()启动传输。 但是,这需要SPI控制器驱动支持DMA。在Rockchip的SPI驱动中,通常已经实现了DMA支持。 因此,用户可以根据具体使用的外设来选择直接使用DMA引擎还是通过外设驱动间接使用DMA。 由于问题中没有明确是用于外设还是内存到内存的DMA,这里提供了两种情况的思路。 希望以上信息对用户有所帮助。 生成相关问题: 1. 在RK3588上,如何为SPI传输配置DMA? 2. dma_alloc_coherent和dma_map_single有什么区别?在RK3588上应如何选择? 3. 如何检查RK3588的DMA传输是否完成? 4. 在RK3588上,DMA传输出现错误时如何进行调试?</think>在RK3588开发板上实现DMA内存分配及数据传输操作,可参考以下步骤(基于Linux内核DMA引擎API): ### 1. **DMA内存分配** 使用`dma_alloc_coherent`分配一致性DMA缓冲区: ```c #include <linux/dma-mapping.h> void *dma_buf; dma_addr_t dma_handle; // DMA物理地址 dma_buf = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL); if (!dma_buf) { // 错误处理 } ``` - `dev`:设备结构体指针(如`&pdev->dev`) - `size`:缓冲区大小(字节) - 返回:虚拟地址指针(`dma_buf`)和物理地址(`dma_handle`) ### 2. **配置DMA通道** 申请DMA通道并配置传输参数: ```c #include <linux/dmaengine.h> struct dma_chan *chan; struct dma_slave_config config = {0}; // 申请通道(根据设备树命名) chan = dma_request_chan(dev, "dma_channel_name"); // 配置传输方向及地址 config.direction = DMA_MEM_TO_DEV; // 内存→设备 config.dst_addr = 0xfea00000; // 目标外设物理地址(如SPI FIFO) config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; dmaengine_slave_config(chan, &config); // 应用配置 ``` ### 3. **发起DMA传输** ```c struct dma_async_tx_descriptor *desc; dma_cookie_t cookie; desc = dmaengine_prep_slave_single( chan, dma_handle, // 步骤1中的DMA物理地址 size, // 传输长度 DMA_MEM_TO_DEV, // 传输方向 DMA_PREP_INTERRUPT // 启用中断 ); desc->callback = dma_transfer_complete; // 传输完成回调函数 cookie = dmaengine_submit(desc); // 提交传输 dma_async_issue_pending(chan); // 启动传输 ``` ### 4. **时钟配置(如需)** 若涉及外设时钟(如SPI时钟),需配置PLL分频(参考引用[3]): ```c // 示例:配置显示模块时钟(需替换为实际时钟控制函数) GLB_Set_Display_CLK(ENABLE, GLB_DISP_CLK_MIPIPLL_1500M, 29); // 注意:分频值可能被舍入为偶数[^3] ``` ### 5. **释放资源** 传输完成后释放资源: ```c dma_free_coherent(dev, size, dma_buf, dma_handle); // 释放DMA内存 dma_release_channel(chan); // 释放DMA通道 ``` ### 关键注意事项: 1. **内存对齐**:DMA缓冲区需按`dma_get_cache_alignment()`对齐 2. **缓存一致性**:`dma_alloc_coherent`保证CPU与DMA缓存一致 3. **外设寄存器**:目标地址需在驱动中映射外设寄存器基地址(如`devm_ioremap_resource()`) 4. **错误处理**:检查所有DMA API返回值,使用`dmaengine_terminate_sync()`中止传输 > 以上流程基于标准Linux DMA Engine API实现,Rockchip平台相关驱动源码可参考: > - DMA驱动:`drivers/dma/pl330.c`(RK3588使用PL330 DMA控制器) > - SPI传输参考:`drivers/spi/spi-rockchip.c`中的`rockchip_spi_prepare_dma()`[^1] --- ### 相关问题 1. RK3588的DMA控制器PL330与RV1106有何差异? 2. 如何为RK3588的SPI外设配置DMA传输模式? 3. 在DMA传输中如何处理缓存一致性问题? 4. RK3588的时钟树结构中,如何动态调整DMA外设时钟频率?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值