一、项目介绍
本项目基于兆易创新 GD32H759IMT6 微控制器(Cortex-M7 600MHz)构建了 LVGL v8.3.5 嵌入式图形交互终端,与 Linux PC 端 Genesis 物理仿真引擎通过 ROS 话题通信形成全链路闭环——在 GD32 触控屏上实现包含欢迎动画、六自由度关节控制、笛卡尔坐标显示与急停联动的六屏 GUI 系统,经 115200bps 串口协议上传关节增量与急停事件,由 Python TCP 网关解析并转换为标准 ROS 消息驱动 Genesis 中 YHRG S1 六自由度仿真机械臂执行 PD 位置控制(100Hz 仿真步进),同时将末端位姿与关节状态以 10Hz 频率回传至嵌入式终端实时显示,急停状态在全链路中同步传播并触发硬件 LED 指示与仿真引擎冻结,最终打通了“触控输入 → 串口传输 → ROS 消息转换 → 物理仿真执行 → 状态反馈回显”的端到端技术闭环,形成一套可复用的嵌入式前端 + 具身智能仿真后端的机械臂原型验证框架。本项目 Linux PC 部分的代码及后续优化工作将在 https://github.com/yjhsh/s1_genesis 同步开源。
任务方向:教育与创意互动——机械臂仿真控制演示系统(自命题)。

二、系统设计
2.1 整体架构
系统采用"嵌入式前端—协议转换层—仿真控制后端"的三层架构模式。

2.2 嵌入式前端(硬件部分)
本项目以兆易创新 GD32H759I-EVAL 评估板 为嵌入式核心硬件平台,搭载 GD32H759IMT6 微控制器(ARM Cortex-M7 内核,主频 600MHz,内置 1MB SRAM 与 2MB Flash),通过 EXMC 总线外扩 32MB SDRAM 作为 LVGL 帧缓冲,驱动 4.3 寸 TFT LCD(480×272 RGB565)经 TLI 接口实现硬件图层叠加显示;人机交互采用 I2C 接口的 GT911 电容触摸 IC 捕获多点触控事件,USART0(115200bps)负责与上位机的全双工串行通信,PA6 引脚配置为推挽输出驱动急停指示 LED,构成一套独立运行的嵌入式人机交互终端。
引脚名 | 功能 | 连接至 |
|---|---|---|
PA9 | USART0_TX | USB-TTL RX |
PA10 | USART0_RX | USB-TTL TX |
PA6 | LED2 (GPIO Output) | 急停指示LED |
PB6 | I2C_SCL | GT911 触摸模块 |
PB7 | I2C_SDA | GT911 触摸模块 |
2.3 协议转换层
异构系统间通过 ROS Noetic 作为分布式通信中间件,定义 /real_arm/joint_angles 、 /real_arm/robot_state 和 /real_arm/robot_status 三个标准话题,所有消息以 std_msgs/String 承载 JSON 结构化数据,实现 GD32 桥接节点( gd32_ros_bridge )与仿真控制节点( yhrg_s1_controller)间的发布/订阅解耦。串口接入采用 socat 构建虚拟串口桥接 —— 将远端 GD32 设备经 TCP 端口传入的串口数据流映射为Linux电脑本地 /dev/ttyVUSB0 伪终端设备,使得 GD32-ROS 桥接节点可通过标准 pyserial 接口透明访问远程物理硬件,消除了嵌入式设备与 Linux 主机间的物理连接距离约束,配合一键启动脚本 start_s1_system.sh 完成 roscore → socat → gd32_ros_control → main_mvc 四进程的自动化启动。

2.4 仿真控制后端
仿真后端采用 Genesis —— 新一代 GPU 加速物理仿真引擎,被广泛部署于具身智能与机器人策略学习领域。Genesis 内置高精度刚体动力学求解器与 PD 位置控制器,直接支持 URDF 标准模型描述文件加载,本项目中以 gs.init(backend=gs.gpu) 启用 GPU 后端,加载 YHRG S1 六自由度机械臂 URDF 模型,逐关节配置 PD 增益,以 0.01s 仿真时间步长执行 100Hz 物理步进;工程采用 MVC 架构 + 代理模式 将其封装为 ISimulatorProxy 抽象接口下的 GenesisSimulatorProxy 实现,实现仿真引擎与控制逻辑的完全解耦,便于替换或扩展。每 10 个仿真步发布一次机器人状态(位置、速度、力矩、末端位姿),形成 10Hz 的状态反馈流,将物理仿真运行结果实时回流至嵌入式终端。
Genesis仿真器的在线文档:https://genesis-world.readthedocs.io/zh-cn/latest/
三、活动指定厂家器件使用情况
3.1 元器件信息及用途
本项目使用了1个由Infineon生产的2N7002 N-MOS(产品链接 https://www.mouser.cn/ProductDetail/726-2N7002H6327XTSA2)。它的漏源极击穿电压 60 V,连续漏极电流 300 mA,栅源极阈值电压 1.5 V。它被用于搭建LED指示灯驱动电路,在系统的急停模式激活后,使 LED 处于最大亮度。
3.2 原理图、PCB和外壳设计
本项目使用立创EDA设计了一个 2N7002 N-MOS 驱动电路来控制 LED 。GD32 PA6 引脚连接模块的 EN 引脚,输入高电平,使 LED 处于最大亮度。此外,本项目使用立创EDA为模块设计了一个塑料外壳(PCB设计图中的绿线是3D外壳的边框),起到美观的作用。壳体上下两部分可直接合上,不需要借助工具或固定件。

四、嵌入式前端软件设计
4.1 整体介绍
本项目使用 Keil MDK-ARM (ARMCC v6) 工具链,目标芯片 GD32H759IMT6,使用 GUI-Guider 构建基于 LVGL v8.3.5 的图形界面,使用 TRAE 完成图形界面的内容定制。其中,LVGL 框架的移植参考 https://bbs.eeworld.com.cn/thread-1285869-1-1.html 的 Keil 工程代码,在此向该项目的作者表示感谢。
4.2 软件流程图

4.3 关键代码介绍
- 机械臂 UI 全局状态:
robot_ui_t。
typedef struct {
scrWelcome_ui_t scrWelcome; // 欢迎页UI对象
scrMonitor_ui_t scrMonitor; // 监控页UI对象
scrPos_ui_t scrPos; // 坐标页UI对象
scrShutdown_ui_t scrShutdown; // 关机页UI对象(代码保留,界面入口已移除)
scrLock_ui_t scrLock; // 锁屏页UI对象
scrStop_ui_t scrStop; // 急停页UI对象
float joint_angles[7]; // 7个关节角度/位移 [J1-J6:度, J7:mm]
float pos_x, pos_y, pos_z; // 末端执行器位置 (mm)
float quat_w, quat_x, quat_y, quat_z; // 四元数姿态
int32_t temperature; // 电机温度 (°C)(预留)
bool teach_mode_active; // 示教模式激活标志(预留)
} robot_ui_t;
- 关节控制组件 (
create_joint_control):每个关节(J1-J7)包含标签、增减按钮、进度条与数值显示。
元素 | 类型 | 功能 |
|---|---|---|
label | lv_label | 显示关节名 "J1"~"J7" |
btnMinus | lv_btn | 减少按钮 "-",步进1° |
bar | lv_bar | 进度条 (0~100) |
btnPlus | lv_btn | 增加按钮 "+",步进1° |
labelValue | lv_label | 显示角度值,保留2位小数 |
static void create_joint_control(lv_obj_t *parent, int joint_num, int y_offset,
lv_obj_t **bar, lv_obj_t **label,
lv_obj_t **btnMinus, lv_obj_t **btnPlus,
lv_obj_t **labelValue)
{
char buf[16];
sprintf(buf, "J%d", joint_num);
*label = lv_label_create(parent);
lv_label_set_text(*label, buf);
lv_obj_set_pos(*label, 5, y_offset + 5);
lv_obj_set_style_text_color(*label, lv_color_hex(0x2f3243), LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_text_font(*label, &lv_font_montserratMedium_14, LV_PART_MAIN|LV_STATE_DEFAULT);
*btnMinus = lv_btn_create(parent);
lv_obj_set_pos(*btnMinus, 35, y_offset);
lv_obj_set_size(*btnMinus, 28, 28);
lv_obj_set_style_bg_color(*btnMinus, lv_color_hex(0x2f3243), LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_radius(*btnMinus, 5, LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_t *labelMinus = lv_label_create(*btnMinus);
lv_label_set_text(labelMinus, "-");
lv_obj_center(labelMinus);
lv_obj_set_style_text_color(labelMinus, lv_color_hex(0xffffff), LV_PART_MAIN|LV_STATE_DEFAULT);
*bar = lv_bar_create(parent);
lv_bar_set_range(*bar, 0, 100);
lv_bar_set_value(*bar, 50, LV_ANIM_OFF);
lv_obj_set_pos(*bar, 68, y_offset + 6);
lv_obj_set_size(*bar, 64, 16);
lv_obj_set_style_bg_color(*bar, lv_color_hex(0xe0e0e0), LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_bg_color(*bar, lv_color_hex(0x2ad3ff), LV_PART_INDICATOR|LV_STATE_DEFAULT);
lv_obj_set_style_radius(*bar, 5, LV_PART_MAIN|LV_STATE_DEFAULT);
*btnPlus = lv_btn_create(parent);
lv_obj_set_pos(*btnPlus, 137, y_offset);
lv_obj_set_size(*btnPlus, 28, 28);
lv_obj_set_style_bg_color(*btnPlus, lv_color_hex(0x2f3243), LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_radius(*btnPlus, 5, LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_t *labelPlus = lv_label_create(*btnPlus);
lv_label_set_text(labelPlus, "+");
lv_obj_center(labelPlus);
lv_obj_set_style_text_color(labelPlus, lv_color_hex(0xffffff), LV_PART_MAIN|LV_STATE_DEFAULT);
*labelValue = lv_label_create(parent);
sprintf(buf, "%.2f", 0.0f);
lv_label_set_text(*labelValue, buf);
lv_obj_set_pos(*labelValue, 170, y_offset + 5);
lv_obj_set_style_text_color(*labelValue, lv_color_hex(0x2f3243), LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_text_font(*labelValue, &lv_font_montserratMedium_14, LV_PART_MAIN|LV_STATE_DEFAULT);
- 关节控制事件回调函数:以J1为例,按下“+”或“-”按键后,发送J1关节角度加减1的指令。
static void btn_j1_minus_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED || code == LV_EVENT_LONG_PRESSED_REPEAT) {
lv_event_stop_bubbling(e);
send_joint_increment_cmd("J1", -1);
}
}
static void btn_j1_plus_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED || code == LV_EVENT_LONG_PRESSED_REPEAT) {
lv_event_stop_bubbling(e);
send_joint_increment_cmd("J1", +1);
}
}
- 上位机与MCU间的串口通信指令协议详见附录。
五、上位机 Linux 系统 ROS 消息转换与 Genesis 仿真控制
5.1 整体介绍
上位机系统采用 MVC(Model-View-Controller)架构,实现了从 GD32 微控制器硬件到 Genesis 物理仿真引擎的完整数据闭环。系统通过 ROS 作为中间件进行进程间通信,通过 socat 虚拟串口桥接实现硬件与软件的解耦。
一键启动脚本 start_s1_system.sh 按以下顺序启动4个核心进程:
序号 | 进程 | 终端 | 功能描述 |
|---|---|---|---|
1 |
| xterm | ROS Master 节点,提供话题注册与通信基础设施 |
2 |
| 后台 | 虚拟串口桥接,将远程 TCP 串口映射为本地 PTY 设备 |
3 |
| xterm | GD32-ROS 桥接节点,串口协议解析与 ROS 话题转发 |
4 |
| xterm | Genesis 仿真主程序,MVC 架构的仿真控制循环 |
5.2 核心类设计
类名 | 设计模式 | 核心职责 |
|---|---|---|
SerialHandler | 单例模式 | 管理 GD32 串口通信连接,后台线程读取数据,支持自动重连 |
ProtocolParser | — | 解析 GD32 上行通知(关节增量、急停、示教模式),正则匹配 |
ROSInterface | — | ROS 话题发布/订阅,GD32-ROS 双向数据转发 |
GD32ROSControl | 协调者 | 协调串口通信、协议解析和 ROS 发布/订阅,关节步长 0.01 rad |
SimulationApplication | MVC-Controller | 仿真应用主类,管理 Model-View-Controller 三层 |
ROSController | 观察者 | 为仿真主程序提供 ROS 话题接口,支持 Real ROS / Mock 双模式 |
GenesisSimulatorProxy | 代理模式 | 封装 Genesis 物理引擎 API,提供统一仿真器操作接口 |
5.3 ROS 节点详细说明
节点 | 源文件 | 话题方向 | 话题名 | 消息类型 | 频率 |
|---|---|---|---|---|---|
gd32_ros_bridge | gd32_ros_control.py | 发布 |
| std_msgs/String | 事件驱动 |
gd32_ros_bridge | gd32_ros_control.py | 发布 |
| std_msgs/String | 事件驱动 |
gd32_ros_bridge | gd32_ros_control.py | 订阅 |
| std_msgs/String | 100Hz |
yhrg_s1_controller | ros_controller.py | 订阅 |
| std_msgs/String | 事件驱动 |
yhrg_s1_controller | ros_controller.py | 订阅 |
| std_msgs/String | 事件驱动 |
yhrg_s1_controller | ros_controller.py | 发布 |
| std_msgs/String | ~10Hz |
ROS 话题消息格式详见附录。
5.4 数据处理与环境交互机制
- 在 Linux 安装 socat 工具后,启动脚本通过它建立虚拟串口桥接,将远程 GD32 设备的 TCP 串口映射为本地 PTY 设备:
sudo socat pty,link=/dev/ttyVUSB0,raw,echo=0 tcp:$SOCAT_IP:7797 &
参数 | 值 | 说明 |
|---|---|---|
| — | 创建伪终端设备 |
|
| 符号链接路径 |
| — | 原始数据模式 |
|
| 禁用回显 |
|
| 远端 TCP 地址和端口 |
- GD32 到 Genesis 仿真器的关节命令流

- Genesis 仿真器到 GD32 的状态反馈流

- 急停状态传播

5.5 仿真控制循环
在main_mvc.py中,程序以约 100Hz(time.sleep(0.01))的频率持续运行。每次循环首先检查急停状态:若未急停,则通过 target_lock 线程安全地读取 ROS 回调更新的最新目标关节角度,并同步写入 RobotState 和 FlowPipeline;无论是否急停,都会执行 FlowPipeline.step() 驱动 Genesis 物理仿真前进一步(急停时目标位置冻结,当前指令仍可完成)。随后从仿真器读取当前关节位置、速度、力矩以及末端执行器的位置和四元数姿态,并以不同频率向外输出:每 10 步(~10Hz)通过 ros_controller.update_robot_state() 将机器人状态发布到 /real_arm/robot_state ROS 话题供 GD32 桥接端接收;每 100 步(~1Hz)记录末端位姿日志,并在距上次打印超过 1 秒时在终端刷新显示命令计数、平均延迟和关节位置等统计信息。循环通过 Ctrl+C 中断后,在 finally 块中安全停止 ROS 控制器,确保资源释放。
try:
while True:
if not self._estop_active:
with target_lock:
target = latest_target.copy()
self._robot_state.set_target_positions(target)
self._flow_pipeline.set_target_position(target)
self._flow_pipeline.step()
positions = self._simulator_proxy.get_joint_positions(self._robot, self._motor_dof_idx)
velocities = self._simulator_proxy.get_joint_velocities(self._robot, self._motor_dof_idx)
efforts = self._simulator_proxy.get_joint_efforts(self._robot, self._motor_dof_idx)
ee_position = self._simulator_proxy.get_end_effector_position(
self._robot, self._end_effector_link_name
)
ee_quaternion = self._simulator_proxy.get_end_effector_quaternion(
self._robot, self._end_effector_link_name
)
if step_count % 100 == 0:
logger.info(f"EE position: {ee_position}, EE quaternion: {ee_quaternion}")
if step_count % 10 == 0:
ros_controller.update_robot_state(
positions=positions,
velocities=velocities,
efforts=efforts,
end_effector_position=ee_position,
end_effector_quaternion=ee_quaternion,
)
if step_count % 100 == 0:
current_time = time.time()
if current_time - last_stats_time >= 1.0:
stats = ros_controller.get_statistics()
print(f"\r[ROS] Commands: {stats['command_count']} | "
f"Latency: {stats['average_latency_ms']:.1f}ms | "
f"Position: {positions[0]:.3f}...", end="", flush=True)
last_stats_time = current_time
step_count += 1
time.sleep(0.01)
except KeyboardInterrupt:
logger.info("ROS simulation interrupted by user")
finally:
ros_controller.stop()
logger.info("ROS simulation ended")
六、功能展示
- 嵌入式前端可视化界面



- 嵌入式终端与 Genesis 仿真环境交互

七、项目总结
本项目采用三层解耦架构(嵌入式前端 / 协议转换层 / 仿真控制后端),应用 MVC + 代理模式封装 Genesis API,通过 socat 虚拟串口桥接突破物理连接距离限制,完美解决原先必须前往服务器所在场地进行系统联调的麻烦,实现了急停事件从硬件 LED 指示到 ROS 话题传播再到仿真引擎冻结的全链路联动。项目验证了“嵌入式 GUI 终端 + 具身智能仿真平台”这一异构系统集成方案的工程可行性,为机器人控制原型开发与算法验证提供了一套可复用的参考框架。
感谢电子森林和贸泽电子对本项目的支持,期待下一次的M-Design活动!
附录
A. 上位机与MCU间的指令协议表
下行指令(上位机 → MCU):
指令 | 格式 | 参数类型 | 返回格式 | 功能描述 |
|---|---|---|---|---|
|
| float |
| 设置关节1角度(-170°~170°) |
|
| float |
| 设置关节2角度(0°~180°) |
|
| float |
| 设置关节3角度(0°~180°) |
|
| float |
| 设置关节4角度(-90°~87°) |
|
| float |
| 设置关节5角度(-90°~90°) |
|
| float |
| 设置关节6角度(-90°~90°) |
|
| float |
| 设置关节7位移(0~0.05mm) |
|
| 7×float |
| 批量设置7个关节 |
|
| 3×float |
| 设置末端位置(mm) |
|
| 4×float |
| 设置四元数姿态 |
|
| int |
| 设置电机温度(°C),暂未使用 |
|
| 无 | 多行文本 | 查询全部状态 |
|
| 无 | 多行文本 | 显示帮助信息 |
上行事件(MCU → 上位机,主动上报):
事件标识 | 输出格式 | 触发条件 | PA6 急停指示指示灯 |
|---|---|---|---|
急停触发 |
| 急停按钮点击 | 高亮度(高电平) |
急停释放 |
| 解锁按钮点击 | 低亮度 (低电平) |
关节增量 |
| 关节+/-按钮 | 不变 |
B. ROS 话题消息格式
/real_arm/joint_angles 消息格式:
{
"joint_names": ["1joint", "2joint", "3joint", "4joint",
"5joint", "6joint", "7joint", "8joint"],
"positions": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
"velocities": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
"timestamp": 1234567890.123
}
/real_arm/robot_state 消息格式:
{
"name": "ros_publisher",
"joint_names": ["1joint", "2joint", "3joint", "4joint",
"5joint", "6joint", "7joint", "8joint"],
"positions": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
"velocities": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
"efforts": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
"end_effector_position": [0.5, 0.0, 0.8],
"end_effector_quaternion": [1.0, 0.0, 0.0, 0.0],
"timestamp": 1234567890.123
}
/real_arm/robot_status 消息格式:
{
"estop": "TRIGGERED",
"teach_mode": "EXIT",
"timestamp": 1234567890.123
}

