当前位置: 首页 > news >正文

如何使用ROS 2与STM32进行串口通信,并实现通过键盘按键‘1’来控制LED灯开关

系统架构概述

本项目的目标是构建一个分布式控制系统:一台运行ROS 2的计算机(上位机)通过串口向一颗STM32微控制器(下位机)发送指令,从而控制连接在STM32上的LED灯。键盘作为用户输入设备,由ROS 2节点监听,最终将按键事件转化为通过串口发送的指令。

整个系统的数据流可以清晰地用下图表示:


核心组件

  • ROS2节点​ (keyboard_control_node):负责监听键盘事件,并通过串口发送控制指令。

  • 串口通信:基于UART协议,是ROS2与STM32之间的物理和数据链路。

  • STM32固件:负责解析指令并直接控制GPIO引脚。

硬件准备与连接

  1. 所需硬件

    • STM32开发板(如STM32F103C8T6)

    • USB转TTL串口模块(如CH340、CP2102)

    • LED灯及合适阻值的限流电阻(如220Ω)

    • 跳线、面包板若干

    • 运行Ubuntu和ROS2(Humble或更新版本)的电脑

  2. 硬件连接至关重要,务必准确

    • USB转TTL模块的TX​ 接 STM32的RX(例如PA10)

    • USB转TTL模块的RX​ 接 STM32的TX(例如PA9)

    • USB转TTL模块的GND​ 接 STM32的GND必须共地

    • LED正极通过电阻接STM32的GPIO引脚(例如PA8)

    • LED负极GND

    接线示意图如下:

STM32下位机程序实现

STM32端的任务是初始化串口和GPIO,并不断监听来自串口的指令,根据指令控制LED。

  1. 核心代码逻辑(主循环)

    以下代码展示了一个简单的STM32程序框架,它持续检查串口是否收到数据,并根据收到的字符控制LED。

    #include "stm32f10x.h"
    #include "stdio.h"
    #include "string.h"// 定义接收缓冲区和状态
    #define RX_BUF_SIZE 64
    char uart_rx_buf[RX_BUF_SIZE];
    uint8_t rx_index = 0;
    uint8_t cmd_ready = 0;int main(void) {// 初始化系统时钟、LED GPIO(推挽输出)、串口(波特率115200,8N1)// ... 硬件初始化代码 (基于HAL库或标准库) ...while(1) {// 检查是否收到一行完整的命令(以换行符'\\n'结束)if(cmd_ready) {cmd_ready = 0;process_command(uart_rx_buf); // 处理命令rx_index = 0; // 重置缓冲区索引}// 其他主循环任务...}
    }// 串口中断服务函数
    void USART1_IRQHandler(void) {if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {char received_char = USART_ReceiveData(USART1);// 简单协议:以换行符'\\n'作为一帧命令的结束if(received_char == '\\n') {if(rx_index > 0) {uart_rx_buf[rx_index] = '\\0'; // 添加字符串结束符cmd_ready = 1;}} else {// 将字符存入缓冲区,防止溢出if(rx_index < RX_BUF_SIZE - 1) {uart_rx_buf[rx_index++] = received_char;}}}
    }// 命令处理函数
    void process_command(char* cmd) {if(strcmp(cmd, "LED_ON") == 0) {GPIO_ResetBits(GPIOA, GPIO_Pin_8); // 点亮LED} else if(strcmp(cmd, "LED_OFF") == 0) {GPIO_SetBits(GPIOA, GPIO_Pin_8);   // 熄灭LED}// 可以添加更多命令,如"LED_TOGGLE"
    }
  2. 串口与GPIO初始化

    关键步骤包括使能时钟、配置GPIO为复用功能、设置串口参数(波特率115200、8位数据位、无校验位、1位停止位)以及使能中断。

ROS 2上位机程序实现

ROS 2节点负责监听键盘事件,并通过串口发送约定的指令。

  1. 创建功能包

    cd ~/ros2_ws/src
    ros2 pkg create --build-type ament_python stm32_keyboard_control --dependencies rclpy

  2. 核心代码逻辑(键盘控制节点)

    创建一个Python文件(如 keyboard_control_node.py)。这个节点使用 pyserial库进行串口通信,并监听键盘输入。

    #!/usr/bin/env python3
    import serial
    import rclpy
    from rclpy.node import Node
    import termios
    import tty
    import sys
    import select
    import threading
    import timeclass KeyboardLEDControl(Node):def __init__(self):super().__init__('keyboard_led_control')# 参数声明self.declare_parameter('serial_port', '/dev/ttyUSB0')self.declare_parameter('baudrate', 115200)serial_port = self.get_parameter('serial_port').valuebaudrate = self.get_parameter('baudrate').value# 串口初始化try:self.serial_conn = serial.Serial(serial_port, baudrate, timeout=1)time.sleep(2)  # 等待串口稳定self.get_logger().info(f'Successfully connected to {serial_port}')except Exception as e:self.get_logger().error(f'Could not open serial port: {e}')raise e# 保存原始终端设置,并设置为原始模式以立即捕获按键self.old_settings = termios.tcgetattr(sys.stdin)tty.setraw(sys.stdin.fileno())# 状态变量self.led_state = Falseself.is_running = True# 在独立线程中启动键盘监听,避免阻塞ROS2self.keyboard_thread = threading.Thread(target=self._keyboard_listener)self.keyboard_thread.daemon = Trueself.keyboard_thread.start()self.get_logger().info('Keyboard listener started. Press "1" to toggle LED, "Esc" to exit.')def _keyboard_listener(self):"""核心键盘监听循环"""while self.is_running and rclpy.ok():# 使用select非阻塞读取rlist, _, _ = select.select([sys.stdin], [], [], 0.1)if rlist:key = sys.stdin.read(1)self._handle_key_press(key)def _handle_key_press(self, key):"""处理按键"""if key == '1':self.toggle_led()elif key == '\\x1b':  # ESC键self.get_logger().info("ESC pressed, shutting down...")self.is_running = False# 清理工作可在destroy_node中完成def toggle_led(self):"""切换LED状态并发送命令"""if self.led_state:command = "LED_OFF"self.led_state = Falseself.get_logger().info("Turning LED OFF")else:command = "LED_ON"self.led_state = Trueself.get_logger().info("Turning LED ON")self._send_command(command)def _send_command(self, command):"""通过串口发送命令"""try:full_command = command + '\\n'self.serial_conn.write(full_command.encode('utf-8'))self.serial_conn.flush()self.get_logger().debug(f'Sent: {command}')except Exception as e:self.get_logger().error(f'Failed to send command: {e}')def destroy_node(self):"""节点清理"""self.is_running = Falsetermios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings) # 恢复终端if hasattr(self, 'serial_conn') and self.serial_conn.is_open:self.serial_conn.close()super().destroy_node()def main(args=None):rclpy.init(args=args)node = KeyboardLEDControl()try:rclpy.spin(node)except KeyboardInterrupt:passfinally:node.destroy_node()rclpy.shutdown()if __name__ == '__main__':main()
  3. 配置、编译与运行

    • 修改 setup.py:确保 entry_points中的 console_scripts指向正确的节点。

    • 设置串口权限:每次重新插拔USB设备后可能需要执行 sudo chmod 666 /dev/ttyUSB0,或将用户加入 dialout组永久解决 (sudo usermod -a -G dialout $USER,需重新登录)。

    • 编译功能包colcon build --packages-select stm32_keyboard_control

    • 运行节点ros2 run stm32_keyboard_control keyboard_control_node --ros-args -p serial_port:=/dev/ttyUSB0

通信协议设计

一个简单可靠的协议至关重要。本项目采用文本协议,优点是可读性好,便于调试。

  • 指令格式"LED_ON\n"和 "LED_OFF\n"。换行符 \n作为帧结束符,帮助STM32判断一条指令是否接收完整。

  • 一致性:务必保证ROS 2节点发送的指令字符串与STM32端解析的字符串完全一致。

调试技巧与常见问题排查

  1. STM32端独立测试

    • 烧录程序后,使用串口调试助手(如minicompicocomcutecom)直接向STM32发送LED_ONLED_OFF字符串,验证STM32硬件和固件是否正常工作。

    • 在STM32端添加"回声"功能,将收到的数据原样发回,验证数据通路。

  2. ROS 2节点调试

    • 在 _send_command函数中添加详细日志,确认指令是否已生成并尝试发送。

    • 使用 ros2 topic list和 ros2 topic echo命令检查节点是否正常运行。

  3. 常见问题

    • LED无反应:检查硬件连接(TX/RX是否接反、共地)、串口权限、波特率是否一致。

    • 按键无响应:确保运行节点的终端窗口处于焦点状态。本方案使用原始终端模式,不依赖图形界面,可靠性更高。

    • 数据错误:检查协议一致性,确保STM32端正确解析换行符。

总结与扩展

通过本项目,我们成功搭建了一个典型的ROS 2与微控制器通信的框架。这个框架可以轻松扩展:

  • 控制更多设备:如电机、舵机、传感器等。

  • 实现双向通信:让STM32将传感器数据(如温度、距离)发送回ROS 2,并在ROS 2中发布为话题。

  • 引入更复杂的协议:如加入校验和确保数据可靠性,或使用二进制协议提高传输效率。

http://www.dtcms.com/a/586048.html

相关文章:

  • jQuery 入门学习教程,从入门到精通, jQuery在HTML5中的应用(16)
  • Flutter TweenAnimationBuilder 使用指南
  • 快速网站搭建百度推广介绍
  • 婚纱摄影网站源码自己制作app的应用程序
  • 【经验分享】Spring Authorization Server 实现详解:构建企业级OAuth2认证中心(一)
  • 北京电脑培训网站备案号查询网站网址
  • 建可收费的网站一个网站策划需要多少钱
  • 吴恩达新课程:Agentic AI(笔记7)
  • 记录一次给Dell 10代cpu 重装系统遇到的BitLocker锁相关问题处理
  • Arbess CICD实践(2) - 使用Arbess+GitLab+PostIn实现Go项目构建/主机部署及接口自动化测试
  • 家具用品:撑起家的骨架与温度
  • 响应式网站建设的未来发展6滕州网站建设助企网络
  • 仿网站模板乐清网站制作公司招聘
  • .NET异步编程中内存泄漏的终极解决方案
  • 精读《JavaScript 高级程序设计 第4版》第14章 DOM(一)
  • Tr0ll 1 (VulnHub)做题笔记
  • 南宁建设银行官网招聘网站建设集团是做什么的
  • 【瑞芯微】【rk3128】【移植 qt 5.12.9】
  • 第十章 VLAN间通信
  • 苹果公司基于Transformer架构的蛋白质折叠开源模型SimpleFold-安装体验与比较
  • 网站制作的一般步骤中国建设人才网络学院
  • 长沙哪家做网站设计好网站设计制作的服务和质量
  • ENSP Pro Lab笔记:配置STP/RSTP/MSTP(5)
  • html5网站检测网站标签是什么
  • Java 内存模型(JMM)与 volatile、synchronized 可见性原理
  • 怎么开网站合肥做app公司
  • git本地分支创建
  • 操作系统(11)进程描述与控制--5种IO状态(1)
  • 整体设计 全面梳理复盘 之23 九宫格框架搭建与一体化开发环境设计 编程 之2
  • xm-软件测试工程师面经准备