356浏览
查看: 356|回复: 1

[ESP8266/ESP32] FireBeetle 2 ESP32-C5基于ESPNOW的无线通信

[复制链接]
本帖最后由 豆爸 于 2025-10-6 16:19 编辑

一、简介

本项目基于 ESP32 开发板,通过 ESP-NOW 无线通信协议构建了一套 “摇杆指令发送 - 接收 - 显示” 系统。发送端利用 ADC 模块采集摇杆的 X、Y 轴模拟信号,转化为 “上 / 下 / 左 / 右 / 停止”5 种控制指令;接收端通过 ESP-NOW 接收指令后,在 LCD 屏幕上实时显示指令内容,同时在串口输出 sender MAC 地址与指令信息,实现了低延迟、近距离的无线指令交互。
该系统展示了低功耗无线通信技术在物联网设备控制中的应用,适用于遥控小车、智能家居控制等多种场景。

二、硬件

1、硬件清单

硬件名称
数量
功能说明
链接
FireBeetle 2 ESP32-C5 开发板
1
系统核心控制板,支持 ESP-NOW 通信
-
Gravity: JoyStick 摇杆
1
输入设备,提供 X/Y 轴模拟控制信号
https://www.dfrobot.com.cn/goods-117.html
Gravity: I2C OLED-2864 显示屏
1
显示设备,支持 I2C 通信的 OLED 屏幕
https://www.dfrobot.com.cn/goods-1374.html
ESP32编程掌机(ST7735 LCD 显示屏)
1
显示设备,160×128 分辨率 ST7735 驱动 LCD
-
Type-C&Micro二合一USB线
1
用于程序下载与供电
https://www.dfrobot.com.cn/goods-2843.html

2、硬件简介

(1)FireBeetle 2 ESP32-C5 开发板

FireBeetle 2 ESP32-C5基于ESPNOW的无线通信图1


FireBeetle 2 ESP32-C5 是一款搭载乐鑫 ESP32-C5 模组的低功耗 IoT 开发板,面向智能家居和广泛物联网场景,集高性能计算、多协议支持与智能电源管理于一体,为各种部署需求提供高可靠性、高灵活性与长续航的解决方案。
当前试用赠送的Firebeetle 2 ESP32-C5开发板板载ESP32-C5模组为ECO1 版本,ECO1版本的ESP32-C5模组是基于 ESP32-C5 revision v0.1 版本芯片。

(2)Gravity: JoyStick 摇杆

FireBeetle 2 ESP32-C5基于ESPNOW的无线通信图2


DFRobot的JoyStick摇杆采用原装优质金属PS2摇杆电位器制作,具有(X,Y)2轴模拟输出,(Z)1路按钮数字输出。

(3)Gravity: I2C OLED-2864 显示屏

FireBeetle 2 ESP32-C5基于ESPNOW的无线通信图3


Gravity OLED-2864 显示屏是一款无需背景光源,自发光式的显示模块。模块采用蓝色背景,显示尺寸控制在0.96英寸,采用OLED专用驱动芯片SSD1306控制。模块采用Gravity I2C通用接口。

(4)ESP32编程掌机

FireBeetle 2 ESP32-C5基于ESPNOW的无线通信图4


ESP32编程掌机搭载了EESP32-D0WD MCU,并配备1.8寸ST7735 LCD屏幕、6个按键、蜂鸣器、光感器、电机驱动、锂电池,同时支持外接传感器扩展。

  • 按键:上=2,下=13,左=27,右=35,A =34,B= 12
  • LCD屏幕:SPl=2,sck=18,mosi=23,cs=5,dc=4,res=19,bl=None
  • SD卡:SPl=2,sck=18,mosi=23,miso=19,cs=22
  • 蜂鸣器:14,光照:36,热敏电阻:39,
  • I2C:SCL=15,SDA=21
  • 端口1:33,端口2:32,端口3:26,端口4:25,端口5:UART,端口6:I2C

(5)Type-C&Micro二合一USB线

FireBeetle 2 ESP32-C5基于ESPNOW的无线通信图5


Type-C&Micro二合一USB线 ,采用扁平线不易打结,加上硅胶扎带方便收纳,同时两个接口都可以上传程序,一根线解决多种板子使用问题。

三、软件

1、开发环境:Thonny IDE
Thonny是一款专为Python初学者设计的集成开发环境(IDE),提供简洁直观的界面和易用特性,支持代码即时语法高亮显示、内置教程及逐步指导等功能,帮助用户专注于编程学习。

FireBeetle 2 ESP32-C5基于ESPNOW的无线通信图6


下载地址:https://github.com/thonny/thonny/releases/download/v4.1.7/thonny-4.1.7.exe

2、固件

(1)FireBeetle 2 ESP32-C5ESP32 MicroPython 固件


(2)游戏掌机MicroPython 固件

ESP32_GENERIC-SPIRAM-20250911-v1.26.1.bin

3、依赖库

(1)显示驱动库:st7735_buf.py(ST7735 LCD 屏幕底层驱动)
(2)显示工具库:easydisplay.py(简化 LCD 文本显示的封装库)
(3)系统内置库:network(网络配置)、espnow(ESP-NOW 通信)、machine(硬件引脚控制)、ubinascii(MAC 地址格式转换)
三、制作过程
1、硬件接线

(1)发送端接线(Gravity: JoyStick 摇杆FireBeetle 2 ESP32-C5

VCC
3.3V
摇杆电源(不可接 5V,避免烧毁电位器)
GND
GND
接地
X 轴(VRx)
GPIO2
X 轴模拟信号输入(接 ADC 引脚)
Y 轴(VRy)
GPIO3
Y 轴模拟信号输入(接 ADC 引脚)
SW(按键)
不接线
本项目暂不使用摇杆按键功能

(2)接收端(ESP32 、ST7735 LCD)

ST7735 LCD
ESP32
功能说明
VCC
3.3V
屏幕电源
GND
GND
接地
SCK(时钟)
GPIO18
SPI 通信时钟线
SDA(数据)
GPIO23
SPI 通信数据线(MOSI)
CS(片选)
GPIO5
屏幕片选信号(低电平有效)
DC(数据 / 命令)
GPIO4
区分 SPI 传输的是命令还是数据
RES(复位)
GPIO19
屏幕复位信号(低电平复位)
BL(背光)
不接线
本项目暂不控制背光亮度

2、烧录固件

(1)Firebeetle 2 ESP32-C5

打开ESP系列芯片烧录工具,按下面步骤进行操作:

  • 选择正确的芯片类型,以Firebeetle 2 ESP32-C5 ver ECO1,选择esp32c5。
  • 选择“使用内置MicroPython固件”。
  • 点击“开始烧录”按钮。
  • “确认”对话框中,点击“是”按钮,进入烧录程序。
  • 当出现“固件烧录完成”,即完成了固件烧录,如下图所示


(2)ESP32编程掌机

打开ESP系列芯片烧录工具,按下面步骤进行操作:

  • 选择正确的芯片类型,这里选择esp32。
  • 选择“使用外置MicroPython固件”。
  • 点击“浏览”按钮,选择固件文件,这里选择ESP32_GENERIC-SPIRAM-20250911-v1.26.1.bin。
  • 输入正确的地址,这里使用0x1000。
  • 点击“开始烧录”按钮。
  • “确认”对话框中,点击“是”按钮,进入烧录程序。
  • 当出现“固件烧录完成”,即完成了固件烧录

FireBeetle 2 ESP32-C5基于ESPNOW的无线通信图7




3、依赖库上传

打开Thonny,选择“运行”->“配置解释器”,然后按下图所示进行设置。

FireBeetle 2 ESP32-C5基于ESPNOW的无线通信图8


选择菜单“视图”->“文件”,打开文件视图。在文件视图中找到要上传的库文件,右键弹出菜单选择“上传到/”,分别将ssd1306.pys上传到 FireBeetle 2 ESP32-C5,将t7735_buf.py和easydisplay.py上传到ESP32编程掌机。

FireBeetle 2 ESP32-C5基于ESPNOW的无线通信图9


4、编写程序

新建文件,复制 “发送端代码”,
修改peer_mac为接收端的实际 MAC 地址,保存为main.py并上传到发送端 FireBeetle 2 ESP32-C5。新建文件,复制 “接收端代码”,保存为main.py并上传到ESP32编程掌机。

(1)发送端代码(main.py)

  1. import network
  2. import espnow
  3. import time
  4. import machine
  5. import ubinascii
  6. from machine import I2C, Pin
  7. from ssd1306 import SSD1306_I2C
  8. # ----------------------------
  9. # 常量定义 - 集中管理配置参数
  10. # ----------------------------
  11. # ADC引脚配置 (连接摇杆)
  12. ADC_X_PIN = 3
  13. ADC_Y_PIN = 2
  14. # 摇杆校准参数 (根据实际硬件调整)
  15. JOYSTICK_CALIB = {
  16.     'x_min': 1978,
  17.     'x_center': 3634,
  18.     'x_max': 4095,
  19.     'y_min': 1974,
  20.     'y_center': 3600,
  21.     'y_max': 4095,
  22.     'threshold': 50  # 死区阈值
  23. }
  24. # OLED屏幕配置
  25. OLED_WIDTH = 128
  26. OLED_HEIGHT = 64
  27. OLED_I2C_SCL = 10
  28. OLED_I2C_SDA = 9
  29. # ESP-NOW配置
  30. TARGET_MAC = b'\xac\x67\xb2\x44\xba\x8c'  # 目标设备MAC地址
  31. LOOP_DELAY = 0.1  # 主循环延迟(秒)
  32. # 方向指令常量
  33. DIRECTIONS = {
  34.     'stop': 'Stop',
  35.     'forward': 'Forward',
  36.     'backward': 'Backward',
  37.     'left': 'Left',
  38.     'right': 'Right'
  39. }
  40. # ----------------------------
  41. # 硬件初始化函数
  42. # ----------------------------
  43. def init_adc():
  44.     """初始化ADC引脚用于读取摇杆值"""
  45.     adc_x = machine.ADC(machine.Pin(ADC_X_PIN))
  46.     adc_y = machine.ADC(machine.Pin(ADC_Y_PIN))
  47.     # 设置衰减以支持0-3.3V范围
  48.     adc_x.atten(machine.ADC.ATTN_11DB)
  49.     adc_y.atten(machine.ADC.ATTN_11DB)
  50.     return adc_x, adc_y
  51. def init_oled():
  52.     """初始化OLED屏幕"""
  53.     i2c = I2C(0, scl=Pin(OLED_I2C_SCL), sda=Pin(OLED_I2C_SDA), freq=400000)
  54.     oled = SSD1306_I2C(OLED_WIDTH, OLED_HEIGHT, i2c)
  55.     oled.fill(0)  # 清屏
  56.     return oled
  57. def init_espnow():
  58.     """初始化ESP-NOW通信"""
  59.     # 初始化WiFi为STA模式
  60.     sta = network.WLAN(network.STA_IF)
  61.     sta.active(True)
  62.    
  63.     # 显示本机MAC地址
  64.     mac_bytes = sta.config('mac')
  65.     mac_str = ubinascii.hexlify(mac_bytes, ':').decode()
  66.     print(f"本机MAC地址: {mac_str}")
  67.     sta.disconnect()  # 不需要连接到AP
  68.    
  69.     # 初始化ESP-NOW
  70.     esp_now = espnow.ESPNow()
  71.     esp_now.active(True)
  72.    
  73.     # 添加目标设备
  74.     esp_now.add_peer(TARGET_MAC)
  75.     target_mac_str = ubinascii.hexlify(TARGET_MAC, ':').decode()
  76.     print(f"已添加目标设备MAC: {target_mac_str}")
  77.    
  78.     return esp_now
  79. # ----------------------------
  80. # 功能函数
  81. # ----------------------------
  82. def read_joystick_values(adc_x, adc_y):
  83.     """读取摇杆原始ADC值"""
  84.     return adc_x.read(), adc_y.read()
  85. def calculate_direction(x, y):
  86.     """根据摇杆位置计算方向指令"""
  87.     x_deviation = x - JOYSTICK_CALIB['x_center']
  88.     y_deviation = y - JOYSTICK_CALIB['y_center']
  89.    
  90.     # 判断是否在死区内(停止状态)
  91.     if abs(x_deviation) < JOYSTICK_CALIB['threshold'] and \
  92.        abs(y_deviation) < JOYSTICK_CALIB['threshold']:
  93.         return DIRECTIONS['stop']
  94.    
  95.     # 判断X/Y方向优先级
  96.     if abs(x_deviation) > abs(y_deviation):
  97.         return DIRECTIONS['right'] if x_deviation > 0 else DIRECTIONS['left']
  98.     else:
  99.         return DIRECTIONS['forward'] if y_deviation > 0 else DIRECTIONS['backward']
  100. def calculate_analog_values(x, y):
  101.     """
  102.     计算摇杆的模拟量值(-127~+127)
  103.     返回: (x_analog, y_analog)
  104.     """
  105.     x_deviation = x - JOYSTICK_CALIB['x_center']
  106.     y_deviation = y - JOYSTICK_CALIB['y_center']
  107.    
  108.     # 如果在死区内,返回0
  109.     if abs(x_deviation) < JOYSTICK_CALIB['threshold'] and \
  110.        abs(y_deviation) < JOYSTICK_CALIB['threshold']:
  111.         return 0, 0
  112.    
  113.     # 计算X轴模拟量
  114.     if x_deviation > 0:
  115.         # 右方向:从中心到最大值映射到0~127
  116.         x_range = JOYSTICK_CALIB['x_max'] - JOYSTICK_CALIB['x_center']
  117.         x_analog = int((x_deviation / x_range) * 127)
  118.     else:
  119.         # 左方向:从最小值到中心映射到-127~0
  120.         x_range = JOYSTICK_CALIB['x_center'] - JOYSTICK_CALIB['x_min']
  121.         x_analog = int((x_deviation / x_range) * 127)
  122.    
  123.     # 计算Y轴模拟量
  124.     if y_deviation > 0:
  125.         # 前方向:从中心到最大值映射到0~127
  126.         y_range = JOYSTICK_CALIB['y_max'] - JOYSTICK_CALIB['y_center']
  127.         y_analog = int((y_deviation / y_range) * 127)
  128.     else:
  129.         # 后方向:从最小值到中心映射到-127~0
  130.         y_range = JOYSTICK_CALIB['y_center'] - JOYSTICK_CALIB['y_min']
  131.         y_analog = int((y_deviation / y_range) * 127)
  132.    
  133.     # 限制在-127~127范围内
  134.     x_analog = max(-127, min(127, x_analog))
  135.     y_analog = max(-127, min(127, y_analog))
  136.    
  137.     return x_analog, y_analog
  138. def send_analog_values(esp_now, x_analog, y_analog):
  139.     """
  140.     发送模拟量值到目标设备
  141.     格式: "ANALOG:X:Y" 例如: "ANALOG:100:-50"
  142.     """
  143.     analog_msg = f"ANALOG:{x_analog}:{y_analog}"
  144.     return esp_now.send(TARGET_MAC, analog_msg, True)
  145. def get_centered_position(text, font_width=8, font_height=8):
  146.     """计算文字在OLED屏幕上的居中位置"""
  147.     text_width = len(text) * font_width
  148.     x = (OLED_WIDTH - text_width) // 2
  149.     y = (OLED_HEIGHT - font_height) // 2 + 8  # 垂直居中偏上一点
  150.     return x, y
  151. # ----------------------------
  152. # 主程序
  153. # ----------------------------
  154. def main():
  155.     # 初始化硬件
  156.     adc_x, adc_y = init_adc()
  157.     #oled = init_oled()
  158.     esp_now = init_espnow()
  159.    
  160.     # 显示目标设备信息
  161.     target_mac_str = ubinascii.hexlify(TARGET_MAC, ':').decode()
  162.     target_mac_str_no_colon = ubinascii.hexlify(TARGET_MAC).decode()
  163.     oled.text(f"To:{target_mac_str_no_colon}", 0, 0)
  164.     oled.show()
  165.    
  166.     last_direction = None
  167.     last_x_analog = 0
  168.     last_y_analog = 0
  169.     analog_mode = False  # 模拟量模式开关
  170.    
  171.     print("控制器启动,开始监控摇杆...")
  172.    
  173.     try:
  174.         while True:
  175.             # 读取并计算方向
  176.             x, y = read_joystick_values(adc_x, adc_y)
  177.             current_direction = calculate_direction(x, y)
  178.             x_analog, y_analog = calculate_analog_values(x, y)         
  179.             # 模拟量模式
  180.             if x_analog != last_x_analog or y_analog != last_y_analog:
  181.                 send_result = send_analog_values(esp_now, x_analog, y_analog)
  182.                 if send_result:
  183.                     print(f"发送模拟量: X={x_analog:4d}, Y={y_analog:4d}")
  184.                 else:
  185.                     print(f"发送失败: X={x_analog:4d}, Y={y_analog:4d}")
  186.                
  187.                 # 更新OLED显示
  188.                 oled.fill_rect(0, 16, OLED_WIDTH, OLED_HEIGHT-16, 0)
  189.                 oled.text("Analog Mode", 0, 16)
  190.                 oled.text(f"X:{x_analog:4d}", 0, 32)
  191.                 oled.text(f"Y:{y_analog:4d}", 0, 48)
  192.                 oled.show()
  193.                
  194.                 last_x_analog = x_analog
  195.                 last_y_analog = y_analog
  196.             
  197.             time.sleep(LOOP_DELAY)
  198.             
  199.     except KeyboardInterrupt:
  200.         print("程序被用户终止")
  201.     finally:
  202.         # 清理资源
  203.         oled.fill(0)
  204.         oled.text("Stopped", 40, 32)
  205.         oled.show()
  206.         esp_now.send(TARGET_MAC, DIRECTIONS['stop'], True)
  207.         print("已发送停止指令,程序退出")
  208. if __name__ == "__main__":
  209.     main()
复制代码


(2)接收端代码(main.py)

  1. # 导入必要库:网络、ESP-NOW通信、硬件控制及LCD驱动
  2. import network
  3. import espnow
  4. import time
  5. from time import sleep_ms
  6. from machine import SPI, Pin
  7. from driver import st7735_buf  # ST7735 LCD底层驱动
  8. from driver import drivers         # 电机、光线传感器、温度传感器驱动
  9. from lib.easydisplay import EasyDisplay  # 简化LCD显示操作
  10. import ubinascii
  11. # ----------------------------------
  12. # 1. 定义“计算居中坐标”的函数
  13. # ----------------------------------
  14. def get_center_pos(text, font_width=8, font_height=8):
  15.     """
  16.     计算文字居中时的起始坐标 (x, y)
  17.     text: 要显示的文字(如 "forward")
  18.     font_width: 字体宽度(默认 8 像素)
  19.     font_height: 字体高度(默认 8 像素)
  20.     """
  21.     # 计算文字总宽度(字符数 × 字体宽度)
  22.     text_total_width = len(text) * font_width
  23.     # X 轴:屏幕水平中心 - 文字总宽度的一半
  24.     x = (160 - text_total_width) // 2
  25.     # Y 轴:屏幕垂直中心 - 字体高度的一半
  26.     y = (128 - font_height) // 2 + 8
  27.     return x, y
  28. # 初始化
  29. spi = SPI(2, baudrate=20000000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(23))
  30. dp = st7735_buf.ST7735(width=160, height=128, spi=spi, cs=5, dc=4, res=19, rotate=1, bl=None,invert=False, rgb=True)
  31. ed = EasyDisplay(display=dp, font="/font/text_lite_16px_2312.v3.bmf", show=True, color=0xFFFF, clear=True,color_type="RGB565")
  32. hd = drivers.HardwareDrivers()       # 创建硬件驱动实例
  33. # 初始化ESP-NOW通信
  34. # 初始化sta
  35. sta = network.WLAN(network.STA_IF)
  36. sta.active(True)
  37. # 获取MAC地址(以字节形式)
  38. mac_bytes = sta.config('mac')
  39. # 将字节转换为人类可读的十六进制字符串
  40. mac_str = ubinascii.hexlify(mac_bytes, ':').decode()
  41. print(f"MAC地址: {mac_str}")
  42. sta.disconnect()
  43. ed.text(mac_str, 10, 10)
  44. e = espnow.ESPNow()
  45. e.active(True)  # 启用ESP-NOW
  46. def receive_messages():
  47.     """接收ESP-NOW消息,处理后在LCD和串口显示"""
  48.     while True:
  49.         try:
  50.             host, msg = e.recv(0)  # 非阻塞接收消息
  51.             
  52.             if msg:
  53.                 # 解码消息
  54.                 command = msg.decode('utf-8').strip() if isinstance(msg, bytes) else str(msg).strip()
  55.                 # 格式化发送端MAC
  56.                 sender_mac = ':'.join(['%02x' % b for b in host])
  57.                 print(f"来自 {sender_mac} 的指令: {command}")
  58.                               
  59.                 # LCD显示指令
  60.                 # ed.fill(0x0000)  # 清屏(黑色)
  61.                 dir_x, dir_y = get_center_pos(command)
  62.                 ed.text(command, dir_x, dir_y)
  63.                 # 首先判断是方向指令还是模拟量指令
  64.                 if command.startswith("ANALOG:"):
  65.                     # 模拟量指令 - 解析X和Y值
  66.                     try:
  67.                         parts = command.split(":")
  68.                         x_analog = int(parts[1])
  69.                         y_analog = int(parts[2])
  70.                         
  71.                         # 死区处理 - 如果摇杆接近中心位置,则停止
  72.                         if abs(x_analog) < 10 and abs(y_analog) < 10:
  73.                             hd.motor_stop("ALL")
  74.                             print("车辆停止")
  75.                            
  76.                         else:
  77.                             # 基础速度计算(基于Y轴)
  78.                             base_speed = abs(y_analog) * 2  # 转换为0-255范围
  79.                            
  80.                             # 转向系数(基于X轴)
  81.                             turn_factor = x_analog / 127.0  # -1.0 到 +1.0
  82.                            
  83.                             if y_analog > 10:  # 前进
  84.                                 # 差速转向:一个轮子快,一个轮子慢
  85.                                 left_speed = int(base_speed * (1 - turn_factor))
  86.                                 right_speed = int(base_speed * (1 + turn_factor))
  87.                                 
  88.                                 # 限制速度在0-255范围内
  89.                                 left_speed = max(0, min(255, left_speed))
  90.                                 right_speed = max(0, min(255, right_speed))
  91.                                 
  92.                                 hd.motor_run(1, "CW", right_speed)  # 右轮
  93.                                 hd.motor_run(2, "CW", left_speed)   # 左轮
  94.                                 print(f"前进 - 左轮: {left_speed}, 右轮: {right_speed}")
  95.                                 
  96.                             elif y_analog < -10:  # 后退
  97.                                 # 差速转向:一个轮子快,一个轮子慢
  98.                                 left_speed = int(base_speed * (1 + turn_factor))
  99.                                 right_speed = int(base_speed * (1 - turn_factor))
  100.                                 
  101.                                 # 限制速度在0-255范围内
  102.                                 left_speed = max(0, min(255, left_speed))
  103.                                 right_speed = max(0, min(255, right_speed))
  104.                                 
  105.                                 hd.motor_run(1, "CCW", right_speed)  # 右轮
  106.                                 hd.motor_run(2, "CCW", left_speed)   # 左轮
  107.                                 print(f"后退 - 左轮: {left_speed}, 右轮: {right_speed}")
  108.                                 
  109.                             else:  # 原地转向(只有X轴输入)
  110.                                 turn_speed = abs(x_analog) * 2
  111.                                 if x_analog > 10:  # 原地右转
  112.                                     hd.motor_run(1, "CCW", min(turn_speed, 255))
  113.                                     hd.motor_run(2, "CW", min(turn_speed, 255))
  114.                                     print(f"原地右转 - 速度: {turn_speed}")
  115.                                 elif x_analog < -10:  # 原地左转
  116.                                     hd.motor_run(1, "CW", min(turn_speed, 255))
  117.                                     hd.motor_run(2, "CCW", min(turn_speed, 255))
  118.                                     print(f"原地左转 - 速度: {turn_speed}")
  119.                                     
  120.                     except (ValueError, IndexError):
  121.                         print(f"模拟量指令解析错误: {command}")
  122.                         
  123.                 else:
  124.                     # 方向指令 - 原有的逻辑保持不变
  125.                     if command == "Forward":
  126.                         hd.motor_run(1, "CW", 255)
  127.                         hd.motor_run(2, "CW", 255)
  128.                         print("车辆前进")
  129.                     elif command == "Backward":
  130.                         hd.motor_run(1, "CCW", 255)
  131.                         hd.motor_run(2, "CCW", 255)
  132.                         print("车辆后退")
  133.                     elif command == "Left":
  134.                         hd.motor_run(1, "CW", 255)
  135.                         hd.motor_run(2, "CCW", 255)
  136.                         print("车辆左转")
  137.                     elif command == "Right":
  138.                         hd.motor_run(1, "CCW", 255)
  139.                         hd.motor_run(2, "CW", 255)
  140.                         print("车辆右转")
  141.                     elif command == "Stop":
  142.                         hd.motor_stop("ALL")
  143.                         print("车辆停止")
  144.             sleep_ms(10)
  145.         except Exception as ex:
  146.             print(f"接收错误: {ex}")
  147.             sleep_ms(100)
  148. def main():
  149.     """主函数:初始化系统并启动消息接收"""
  150.     try:
  151.         # ed.fill(0x0000)
  152.         receive_messages()  # 启动接收循环
  153.     except KeyboardInterrupt:
  154.         print("程序被中断")
  155.     except Exception as ex:
  156.         print(f"错误: {ex}")
  157.     finally:
  158.         # 清理资源
  159.         e.active(False)
  160.         sta.active(False)
  161.         ed.fill(0x0000)
  162.         ed.text("已停止", 50, 60)
  163. if __name__ == "__main__":
  164.     main()
复制代码



5、系统调试

FireBeetle 2 ESP32-C5基于ESPNOW的无线通信图10

(1)分别给发送端、接收端上电,打开 Thonny 的 “串口监视器”(波特率默认 115200)。

(2)发送端串口会打印自身 MAC 地址和 “准备发送指令” 提示;接收端会打印自身 MAC 地址和 “等待指令” 提示,LCD 屏幕显示 “ESPNow 接收端”“状态:运行中”。

FireBeetle 2 ESP32-C5基于ESPNOW的无线通信图11


(3)FireBeetle 2 ESP32-C5(发送端)拨动摇杆,发送ESPNOW指令,OLED屏幕显示“Forward/Backward/Left/Right”,串口打印 “发送指令:Forward/Backward/Left/Right/Stop”。

FireBeetle 2 ESP32-C5基于ESPNOW的无线通信图13


(4)ESP32掌机(接收端)串口打印 “来自 [发送端 MAC] 的指令:Forward/Backward/Left/Right/Stop”,且 LCD 屏幕实时显示“Forward/Backward/Left/Right/Stop”。




FireBeetle 2 ESP32-C5基于ESPNOW的无线通信图12


四、技术原理

1、ESP-NOW 通信

ESP-NOW 是一种由乐鑫公司定义的无连接 Wi-Fi 通信协议。在 ESP-NOW 中,应用程序数据被封装在各个供应商的动作帧中,然后在无连接的情况下,从一个 Wi-Fi 设备传输到另一个 Wi-Fi 设备。特点是低延迟(毫秒级)、低功耗、无需 IP 地址,适合物联网设备间的短数据传输(如指令、传感器数据)。

(1)配对逻辑:发送端需先将接收端的 MAC 地址添加为 “peer(对等设备)”,才能向其发送数据;接收端无需主动添加,可被动接收已配对设备的消息。

(2)数据传输:本项目中发送端通过e.send(peer_mac, dir, True)发送指令(True表示等待接收端确认),接收端通过e.recv(0)非阻塞接收消息(0表示不等待,立即返回)。

2、ADC 摇杆信号采集

ESP32 的 ADC 模块(模拟 - 数字转换器)可将摇杆的模拟电压信号(0~3.3V)转换为 0~4095 的数字值(12 位精度)。

(1)信号校准:通过X_CENTER, Y_CENTER = 3634, 3600设置摇杆 “中立位置” 的基准值(需根据实际摇杆校准,避免漂移)。

(2)阈值过滤:通过THRESHOLD = 50设置 “死区阈值”,当摇杆偏移量小于 50 时,判定为 “stop”,避免轻微晃动导致误触发。

(3)方向判断:比较 X 轴(xd)和 Y 轴(yd)的偏移量绝对值,绝对值大的轴为 “有效方向”,再根据正负判断具体方向(如 xd>0 则为 “right”)。

3、LCD 屏幕显示

通过EasyDisplay封装库简化 ST7735 屏幕控制。

(1)初始化:先通过st7735_buf.ST7735配置 SPI 引脚、屏幕分辨率、旋转方向等底层参数,再通过EasyDisplay设置字体、默认颜色、清屏模式。

(2)文本显示:调用ed.text(内容, x坐标, y坐标)在指定位置显示文本,ed.fill(0x0000)实现清屏(0x0000 为 RGB565 格式的黑色)。

附件:程序、固件、工具

木子哦  管理员

发表于 2025-9-24 09:29:13

先占个坑
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail