项目背景及功能
在小型自动化调试或简易设备控制场景中,无刷电机的速度调控与运行状态监看存在明显不便:传统方案下,电机转速调节需依赖现场专用工具连接调试,无法远程操作;设备运行画面、电机工作状态的查看也需人员到场,导致运维效率较低。睿擎派嵌入式开发板具备串口外设接口与网络传输能力,可同时承载控制与监看需求,因此本项目基于该硬件展开开发。项目通过串口实现电机速度环控制,精准调节转速;同时集成视频推流功能,将电机运行画面或设备现场视频远程传输,实现状态的实时远程监看。该方案有效解决了传统方式中现场调试繁琐和远程监看缺失的问题,适配了此类轻量场景下的实际需求。
RTT使用情况
本项目基于 RT-Thread开发,充分依托 RTT 嵌入式操作系统的多线程调度、设备驱动框架、FinSH 调试终端、WebNet 组件四大核心能力,实现了 FOC 电机控制与 WEB 视频推流的高效集成,大幅简化开发流程并提升系统稳定性。

1、UART 串口(电机控制)依托 RTT 的串口设备驱动,通过rt_device_find(“uart3”)查找电机连接的 UART3 设备,rt_device_open(serial, RT_DEVICE_FLAG_RDWR)打开设备并配置为 “读写模式”;后续通过rt_device_write()发送电机控制指令(含 CRC 校验),rt_device_read()丢弃电机响应数据,全程无需操作寄存器,适配效率提升 80%。
2、UVC 摄像头(视频推流)项目中 UVC 摄像头通过 RTT 的usbh_uvc驱动适配,直接调用rt_device_find(“uvc”)获取设备句柄,rt_device_control()设置视频帧回调函数(video_frame_callback),实现 “摄像头采集→帧数据回调→WEB 推流” 的端到端流程,底层 USB 枚举、数据传输等逻辑完全由 RTT 驱动封装,开发者仅需关注应用层数据处理。
3、网络设备(WEB 服务)无需手动配置网卡,RTT 通过sal(套接字抽象层)自动适配以太网 / 无线网卡,WebNet 组件直接基于lwIP协议栈创建 TCP 监听(80 端口),通过webnet_start()一键启动 WEB 服务,简化网络编程复杂度。
项目的 WEB 视频推流功能完全基于 RTT 官方webnet组件实现,无需从零开发 HTTP 服务:
1、WEB 服务启动:通过webnet_start(80, “/webroot”)启动 80 端口 WEB 服务,指定网页资源根目录为/webroot(FTP 上传的 HTML 页面存于此目录);
2、视频推流 CGI 注册:通过webnet_cgi_register(“mjpeg_stream”, cgi_mjpeg_stream_handler)注册推流接口,当浏览器访问mjpeg_stream时,自动触发cgi_mjpeg_stream_handler回调;
3、流数据传输:在回调函数中,通过webnet_session_printf()设置 HTTP 响应头(multipart/x-mixed-replace格式),webnet_session_write()发送 JPEG 视频帧,实现 “一帧一响应” 的 MJPEG 推流,浏览器无需插件即可实时播放。
技术路径
开箱上手
首先是到手的开箱,有一个电源和一个主板,经过验证,便宜的DAP-Link无法使用,因此该DAP-Link用作串口调试,烧录就使用USB进行下载。

接着安装RTT的工业开发平台,没有什么需要注意的,直接一直下一步就好了。

新建项目就好了,但是要注意jtag和daplink的区分即可,接着就使用RKtool刷一下系统升级,这个太简单了,就跳过。然后使用moba就可以进入系统了。

这个就是我们的RTT操作系统了,很容易上手。接着,输入一下。
help
就可以看到所有支持的指令了。

接着把RTT的历程烧写进去,体验一下多线程的系统效果。

可以看到打印了预期的结果。
开发过程
先去建立一个空的工程,我们先测试一下串口的功能,下面给出串口控制的代码。
/** * RT-Thread RuiChing * * COPYRIGHT (C) 2024-2025 Shanghai Real-Thread Electronic Technology Co., Ltd. * All rights reserved. * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution. */#include #include #define UART_NAME "uart3"static rt_device_t serial;static char rx_buffer[64];static rt_size_t rx_len = 0;static rt_err_t uart_rx_ind(rt_device_t dev, rt_size_t size){ char ch; while (rt_device_read(dev, 0, &ch, 1) == 1) { if (rx_len < sizeof(rx_buffer) - 1) { rx_buffer[rx_len++] = ch; // 将接收到的数据存储到 rx_buffer 中 if (ch == '\n') // 接收到 '\n' 停止接收 { rx_buffer[rx_len] = '\0'; rt_kprintf("Received: %s", rx_buffer); // 打印接收到的数据 rt_device_write(dev, 0, rx_buffer, rx_len); // 回显数据,发送回 uart3 rt_device_set_rx_indicate(dev, RT_NULL); // 注销接收回调,只回显一次 rt_memset(rx_buffer, 0, sizeof(rx_buffer)); // 清空接收缓冲,防止残留 rx_len = 0; rt_kprintf("Echo completed.\n"); } } } return RT_EOK;}static int uart_example(void){ char str[] = "Hello RT-Thread!\r\n"; char tmp = 0; serial = rt_device_find(UART_NAME); // 查找设备名称为 uart3 的串口设备 if (rt_device_open(serial, RT_DEVICE_FLAG_INT_RX) != RT_EOK) // 打开串口设备,并设置为接收中断模式 { rt_kprintf("Open %s failed!\n", UART_NAME); return -RT_ERROR; } while (rt_device_read(serial, 0, &tmp, 1) == 1); // 清空串口缓冲区,防止历史数据触发错误回调 rt_device_set_rx_indicate(serial, uart_rx_ind); // 注册接收中断回调函数 rt_device_write(serial, 0, str, (sizeof(str) - 1)); // 通过 uart3 主动发送 "Hello RT-Thread!\r\n" rt_kprintf("UART echo started.\n"); return RT_EOK;}MSH_CMD_EXPORT(uart_example, serial loopback);
就是一个简单的数据发送与接受,测试ok后进入下一个阶段,进行FOC的开发。
首先,介绍一下次使用的电机:本末科技的m0603a
M0603A电机具有静音、结构紧凑、安装简便、运行稳定等特点,采用双驱动轮设计提升智能操控体验,已成功应用于追觅智能洗地机等产品,并成为家用机器人、AGV、驱动轮等领域的首选动力方案。
其次,给出我的硬件接线结构整体图:

接着我们看一下电机的控制参数:

可看到是LIN转到UART,38400的速度,接着我们来看协议


使用10帧格式和crc8的末尾校验格式,为此我们可以在RT中进行编程CRC生成代码。
static rt_err_t motor_send_speed(int16_t speed){ // 若未初始化,先执行初始化 if (motor_inited == RT_FALSE) { if (motor_init() != RT_EOK) { return RT_ERROR; } } // 构造速度控制指令(对齐CH55x的mot_control_data) speed = speed * 10; uint8_t cmd_speed[9] = { MOTOR_ADDR, // 第1字节:设备地址 CMD_SPEED_CTRL, // 第2字节:速度指令码(0x64) (speed >> 8) & 0xFF, // 第3字节:速度高8位 speed & 0xFF, // 第4字节:速度低8位 0x00, 0x00, 0x00, 0x00, 0x00 // 保留字节 }; // 发送速度指令(复用通用指令发送函数) if (motor_send_cmd(cmd_speed) == RT_EOK) { rt_kprintf("当前速度:%d\n", speed); return RT_EOK; } return RT_ERROR;}
接着就是电机初始化系统指令控制的代码:
// 功能:执行CH55x中的初始化流程:发送使能指令 → 延时 → 发送速度模式指令static rt_err_t motor_init(void){ if (motor_inited == RT_TRUE) { rt_kprintf("电机已初始化,无需重复执行!\n"); return RT_EOK; } // 1. 构造“电机使能指令”(对齐CH55x的enable数组:0x01,0xA0,0x08,...) uint8_t cmd_enable[9] = { MOTOR_ADDR, // 第1字节:设备地址 CMD_ENABLE, // 第2字节:控制指令码(0xA0) SUB_CMD_ENABLE,// 第3字节:子指令(使能:0x08) 0x00, 0x00, // 第4-5字节:保留 0x00, 0x00, // 第6-7字节:保留 0x00, 0x00 // 第8-9字节:保留 }; // 发送使能指令 if (motor_send_cmd(cmd_enable) != RT_EOK) { rt_kprintf("电机使能失败!\n"); return RT_ERROR; } rt_kprintf("电机使能成功!\n"); rt_thread_mdelay(INIT_DELAY); // 延时500ms(与CH55x一致,等待使能完成) // 2. 构造“速度模式指令”(对齐CH55x的position_mode数组:0x01,0xA0,0x02,...) uint8_t cmd_speed_mode[9] = { MOTOR_ADDR, // 第1字节:设备地址 CMD_ENABLE, // 第2字节:控制指令码(0xA0) SUB_CMD_SPEED_MODE,// 第3字节:子指令(速度模式:0x02) 0x00, 0x00, // 第4-5字节:保留 0x00, 0x00, // 第6-7字节:保留 0x00, 0x00 // 第8-9字节:保留 }; // 发送速度模式指令 if (motor_send_cmd(cmd_speed_mode) != RT_EOK) { rt_kprintf("切换速度模式失败!\n"); return RT_ERROR; } rt_kprintf("已切换到速度模式!\n"); rt_thread_mdelay(10); // 短延时,确保模式生效 // 3. 初始化标志置位(避免重复执行) motor_inited = RT_TRUE; current_speed = 0; // 初始化速度为0 return RT_EOK;}
然后针对电机特性,创建一个DEMO线程
static rt_err_t motor_send_speed(int16_t speed){ // 若未初始化,先执行初始化 if (motor_inited == RT_FALSE) { if (motor_init() != RT_EOK) { return RT_ERROR; } } // 构造速度控制指令(对齐CH55x的mot_control_data) speed = speed * 10; uint8_t cmd_speed[9] = { MOTOR_ADDR, // 第1字节:设备地址 CMD_SPEED_CTRL, // 第2字节:速度指令码(0x64) (speed >> 8) & 0xFF, // 第3字节:速度高8位 speed & 0xFF, // 第4字节:速度低8位 0x00, 0x00, 0x00, 0x00, 0x00 // 保留字节 }; // 发送速度指令(复用通用指令发送函数) if (motor_send_cmd(cmd_speed) == RT_EOK) { rt_kprintf("当前速度:%d\n", speed); return RT_EOK; } return RT_ERROR;}
这样我们的电机就成功驱动了,速度环控制成功。

电机控制的完整代码:
#include #include // -------------------------- 1. 硬件与协议配置(完全对齐CH55x代码)--------------------------#define MOTOR_UART_NAME "uart3" // 电机UART设备名#define MOTOR_ADDR 0x01 // 电机从机地址(与CH55x一致:0x01)// 电机协议指令(从CH55x代码提取)#define CMD_ENABLE 0xA0 // 使能/模式控制指令码(CH55x中0xA0)#define SUB_CMD_ENABLE 0x08 // 子指令:电机使能(CH55x中0x08)#define SUB_CMD_SPEED_MODE 0x02 // 子指令:速度模式(CH55x中0x02,对应sel_2_mode=1)#define CMD_SPEED_CTRL 0x64 // 速度控制指令码(原代码不变)// 速度参数#define SPEED_STEP 5 // 速度变化步长#define SPEED_DELAY 100 // 速度变化间隔(ms)#define INIT_DELAY 500 // 初始化指令间隔(ms,与CH55x一致)// -------------------------- 2. 全局变量 --------------------------static rt_device_t motor_uart = RT_NULL; // 电机UART句柄static int16_t current_speed = 0; // 当前速度(-100~100)static uint8_t speed_dir = 1; // 速度方向:1=0→100,2=100→-100,3=-100→100static rt_bool_t motor_inited = RT_FALSE; // 电机初始化标志(避免重复初始化)// -------------------------- 3. 基础工具函数(位反转+CRC8,原代码不变)--------------------------static uint8_t reverse_bits(uint8_t byte){ uint8_t reversed = 0; for (uint8_t i = 0; i < 8; i++) { reversed <<= 1; reversed |= (byte & 0x01); byte >>= 1; } return reversed;}static uint8_t crc8_maxim(const uint8_t* data, size_t len){ uint8_t crc = 0x00; const uint8_t poly = 0x31; for (size_t i = 0; i < len; i++) { uint8_t reversed_byte = reverse_bits(data[i]); crc ^= reversed_byte; for (uint8_t j = 0; j < 8; j++) { crc = (crc & 0x80) ? ((crc << 1) ^ poly) : (crc << 1); crc &= 0xFF; } } crc = reverse_bits(crc); return crc;}// -------------------------- 4. 新增:电机指令发送通用函数(复用逻辑)--------------------------// 功能:发送任意9字节电机指令(含CRC),并丢弃返回数据(对齐CH55x的“读响应丢弃”逻辑)static rt_err_t motor_send_cmd(const uint8_t* cmd){ if (motor_uart == RT_NULL) { rt_kprintf("电机UART未初始化!\n"); return RT_ERROR; } // 1. 计算CRC(9字节指令+1字节CRC) uint8_t crc = crc8_maxim(cmd, 9); // 2. 发送指令(9字节指令) rt_size_t send_len1 = rt_device_write(motor_uart, 0, cmd, 9); // 3. 发送CRC(1字节) rt_size_t send_len2 = rt_device_write(motor_uart, 0, &crc, 1); if (send_len1 != 9 || send_len2 != 1) { rt_kprintf("指令发送失败!\n"); return RT_ERROR; } // 4. 丢弃电机返回的响应数据(对齐CH55x的while(Serial0_available()){Serial0_read()}) rt_thread_mdelay(10); // 等待响应数据返回 uint8_t dummy; while (rt_device_read(motor_uart, 0, &dummy, 1) == 1); // 读取并丢弃所有响应 return RT_EOK;}// -------------------------- 5. 新增:电机初始化(使能+速度模式设置)--------------------------// 功能:执行CH55x中的初始化流程:发送使能指令 → 延时 → 发送速度模式指令static rt_err_t motor_init(void){ if (motor_inited == RT_TRUE) { rt_kprintf("电机已初始化,无需重复执行!\n"); return RT_EOK; } // 1. 构造“电机使能指令”(对齐CH55x的enable数组:0x01,0xA0,0x08,...) uint8_t cmd_enable[9] = { MOTOR_ADDR, // 第1字节:设备地址 CMD_ENABLE, // 第2字节:控制指令码(0xA0) SUB_CMD_ENABLE,// 第3字节:子指令(使能:0x08) 0x00, 0x00, // 第4-5字节:保留 0x00, 0x00, // 第6-7字节:保留 0x00, 0x00 // 第8-9字节:保留 }; // 发送使能指令 if (motor_send_cmd(cmd_enable) != RT_EOK) { rt_kprintf("电机使能失败!\n"); return RT_ERROR; } rt_kprintf("电机使能成功!\n"); rt_thread_mdelay(INIT_DELAY); // 延时500ms(与CH55x一致,等待使能完成) // 2. 构造“速度模式指令”(对齐CH55x的position_mode数组:0x01,0xA0,0x02,...) uint8_t cmd_speed_mode[9] = { MOTOR_ADDR, // 第1字节:设备地址 CMD_ENABLE, // 第2字节:控制指令码(0xA0) SUB_CMD_SPEED_MODE,// 第3字节:子指令(速度模式:0x02) 0x00, 0x00, // 第4-5字节:保留 0x00, 0x00, // 第6-7字节:保留 0x00, 0x00 // 第8-9字节:保留 }; // 发送速度模式指令 if (motor_send_cmd(cmd_speed_mode) != RT_EOK) { rt_kprintf("切换速度模式失败!\n"); return RT_ERROR; } rt_kprintf("已切换到速度模式!\n"); rt_thread_mdelay(10); // 短延时,确保模式生效 // 3. 初始化标志置位(避免重复执行) motor_inited = RT_TRUE; current_speed = 0; // 初始化速度为0 return RT_EOK;}// -------------------------- 6. 速度发送函数(复用motor_send_cmd,逻辑更简洁)--------------------------static rt_err_t motor_send_speed(int16_t speed){ // 若未初始化,先执行初始化 if (motor_inited == RT_FALSE) { if (motor_init() != RT_EOK) { return RT_ERROR; } } // 构造速度控制指令(对齐CH55x的mot_control_data) speed = speed * 10; uint8_t cmd_speed[9] = { MOTOR_ADDR, // 第1字节:设备地址 CMD_SPEED_CTRL, // 第2字节:速度指令码(0x64) (speed >> 8) & 0xFF, // 第3字节:速度高8位 speed & 0xFF, // 第4字节:速度低8位 0x00, 0x00, 0x00, 0x00, 0x00 // 保留字节 }; // 发送速度指令(复用通用指令发送函数) if (motor_send_cmd(cmd_speed) == RT_EOK) { rt_kprintf("当前速度:%d\n", speed); return RT_EOK; } return RT_ERROR;}// -------------------------- 7. 速度循环线程(原逻辑不变,新增初始化检查)--------------------------static void speed_loop_thread(void *parameter){ // 线程启动时先检查初始化(双重保障) if (motor_inited == RT_FALSE) { if (motor_init() != RT_EOK) { rt_kprintf("线程启动失败:电机初始化失败!\n"); return; // 初始化失败,线程退出 } } while (1) { // 速度方向逻辑(原代码不变) switch (speed_dir) { case 1: // 0→100(递增) current_speed += SPEED_STEP; if (current_speed >= 100) { current_speed = 100; speed_dir = 2; } break; case 2: // 100→-100(递减) current_speed -= SPEED_STEP; if (current_speed <= -100) { current_speed = -100; speed_dir = 3; } break; case 3: // -100→100(递增) current_speed += SPEED_STEP; if (current_speed >= 100) { current_speed = 100; speed_dir = 2; } break; default: speed_dir = 1; current_speed = 0; break; } // 发送速度(此时电机已初始化完成) motor_send_speed(current_speed); rt_thread_mdelay(SPEED_DELAY); }}// -------------------------- 8. 主初始化函数(UART配置+线程创建)--------------------------static int motor_speed_loop_init(void){ // 1. 配置电机UART(原逻辑不变) motor_uart = rt_device_find(MOTOR_UART_NAME); if (motor_uart == RT_NULL) { rt_kprintf("未找到UART设备:%s\n", MOTOR_UART_NAME); return RT_ERROR; } // 打开UART(读+写:需读取电机响应并丢弃,所以用RDWR模式) if (rt_device_open(motor_uart, RT_DEVICE_FLAG_RDWR) != RT_EOK) { rt_kprintf("打开UART失败:%s\n", MOTOR_UART_NAME); return RT_ERROR; } // 配置UART参数(波特率38400,8N1,与CH55x一致) struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; config.baud_rate = BAUD_RATE_38400; config.data_bits = DATA_BITS_8; config.stop_bits = STOP_BITS_1; config.parity = PARITY_NONE; rt_device_control(motor_uart, RT_DEVICE_CTRL_CONFIG, &config); // 2. 先执行电机初始化(使能+速度模式) if (motor_init() != RT_EOK) { rt_kprintf("电机初始化失败,无法启动线程!\n"); rt_device_close(motor_uart); // 初始化失败,关闭UART return RT_ERROR; } // 3. 创建并启动速度循环线程 rt_thread_t tid = rt_thread_create( "speed_loop", speed_loop_thread, RT_NULL, 4096, 25, 10 ); if (tid != RT_NULL) { rt_thread_startup(tid); rt_kprintf("电机速度环线程启动成功!\n"); } else { rt_kprintf("线程创建失败!\n"); return RT_ERROR; } return RT_EOK;}// -------------------------- 9. 导出Msh命令(启动+停止)--------------------------MSH_CMD_EXPORT(motor_speed_loop_init, 启动电机速度环(含初始化+速度模式切换));// 新增:一键停止(发送0速度+删除线程)static int motor_speed_loop_stop(void){ // 1. 发送0速度停电机 if (motor_uart != RT_NULL && motor_inited == RT_TRUE) { motor_send_speed(0); rt_kprintf("电机已停转(速度0)\n"); } // 2. 查找并删除速度环线程 rt_thread_t tid = rt_thread_find("speed_loop"); if (tid != RT_NULL) { rt_thread_delete(tid); rt_kprintf("速度环线程已删除\n"); } // 3. 重置状态(下次启动可重新初始化) motor_inited = RT_FALSE; if (motor_uart != RT_NULL) { rt_device_close(motor_uart); motor_uart = RT_NULL; } return RT_EOK;}MSH_CMD_EXPORT(motor_speed_loop_stop, 停止电机速度环(停电机+删线程))
下面进行推流的设置

配一下ip地址,然后使用FTP传输web代码到RT睿擎派上面
下面给出web代码:
html>
<2;title>RT-Thread 视频流控制中心title>