0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于睿擎派的工业FOC无刷电机控制系统与WEB推流监看系统| 技术集结

RT-Thread官方账号 2025-11-26 18:25 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

项目背景及功能


在小型自动化调试或简易设备控制场景中,无刷电机的速度调控与运行状态监看存在明显不便:传统方案下,电机转速调节需依赖现场专用工具连接调试,无法远程操作;设备运行画面、电机工作状态的查看也需人员到场,导致运维效率较低。睿擎派嵌入式开发板具备串口外设接口与网络传输能力,可同时承载控制与监看需求,因此本项目基于该硬件展开开发。项目通过串口实现电机速度环控制,精准调节转速;同时集成视频推流功能,将电机运行画面或设备现场视频远程传输,实现状态的实时远程监看。该方案有效解决了传统方式中现场调试繁琐和远程监看缺失的问题,适配了此类轻量场景下的实际需求。


RTT使用情况


本项目基于 RT-Thread开发,充分依托 RTT 嵌入式操作系统的多线程调度、设备驱动框架、FinSH 调试终端、WebNet 组件四大核心能力,实现了 FOC 电机控制与 WEB 视频推流的高效集成,大幅简化开发流程并提升系统稳定性。


3b57f18e-cab2-11f0-8ce9-92fbcf53809c.png


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进行下载。


3b6c4b34-cab2-11f0-8ce9-92fbcf53809c.png


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

3b847cd6-cab2-11f0-8ce9-92fbcf53809c.png


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

3b918e3a-cab2-11f0-8ce9-92fbcf53809c.png


这个就是我们的RTT操作系统了,很容易上手。接着,输入一下。

help

就可以看到所有支持的指令了。

3ba2f92c-cab2-11f0-8ce9-92fbcf53809c.png


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

3bb4cf30-cab2-11f0-8ce9-92fbcf53809c.png


可以看到打印了预期的结果。


开发过程

先去建立一个空的工程,我们先测试一下串口的功能,下面给出串口控制的代码。

/*** 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#defineUART_NAME"uart3"staticrt_device_tserial;staticcharrx_buffer[64];staticrt_size_trx_len =0;staticrt_err_tuart_rx_ind(rt_device_tdev,rt_size_tsize){ charch; 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、驱动轮等领域的首选动力方案。


其次,给出我的硬件接线结构整体图:

3bc2570e-cab2-11f0-8ce9-92fbcf53809c.png


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

3bd572b2-cab2-11f0-8ce9-92fbcf53809c.png


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

3be9688a-cab2-11f0-8ce9-92fbcf53809c.png3bf9c70c-cab2-11f0-8ce9-92fbcf53809c.png


使用10帧格式和crc8的末尾校验格式,为此我们可以在RT中进行编程CRC生成代码。

staticrt_err_tmotor_send_speed(int16_tspeed){ // 若未初始化,先执行初始化 if(motor_inited == RT_FALSE) { if(motor_init() != RT_EOK) { returnRT_ERROR; } } // 构造速度控制指令(对齐CH55x的mot_control_data) speed = speed *10; uint8_tcmd_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); returnRT_EOK; } returnRT_ERROR;}


接着就是电机初始化系统指令控制的代码:


// 功能:执行CH55x中的初始化流程:发送使能指令 → 延时 → 发送速度模式指令staticrt_err_t motor_init(void){ if(motor_inited==RT_TRUE) { rt_kprintf("电机已初始化,无需重复执行!\n"); returnRT_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"); returnRT_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"); returnRT_ERROR; } rt_kprintf("已切换到速度模式!\n"); rt_thread_mdelay(10); // 短延时,确保模式生效 // 3. 初始化标志置位(避免重复执行) motor_inited=RT_TRUE; current_speed=0; // 初始化速度为0 returnRT_EOK;}

然后针对电机特性,创建一个DEMO线程

staticrt_err_tmotor_send_speed(int16_tspeed){ // 若未初始化,先执行初始化 if(motor_inited == RT_FALSE) { if(motor_init() != RT_EOK) { returnRT_ERROR; } } // 构造速度控制指令(对齐CH55x的mot_control_data) speed = speed *10; uint8_tcmd_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); returnRT_EOK; } returnRT_ERROR;}

这样我们的电机就成功驱动了,速度环控制成功。

3c0959f6-cab2-11f0-8ce9-92fbcf53809c.png


电机控制的完整代码:

#include#include// -------------------------- 1. 硬件与协议配置(完全对齐CH55x代码)--------------------------#defineMOTOR_UART_NAME "uart3" // 电机UART设备名#defineMOTOR_ADDR 0x01 // 电机从机地址(与CH55x一致:0x01)// 电机协议指令(从CH55x代码提取)#defineCMD_ENABLE 0xA0 // 使能/模式控制指令码(CH55x中0xA0)#defineSUB_CMD_ENABLE 0x08 // 子指令:电机使能(CH55x中0x08)#defineSUB_CMD_SPEED_MODE 0x02 // 子指令:速度模式(CH55x中0x02,对应sel_2_mode=1)#defineCMD_SPEED_CTRL 0x64 // 速度控制指令码(原代码不变)// 速度参数#defineSPEED_STEP 5 // 速度变化步长#defineSPEED_DELAY 100 // 速度变化间隔(ms)#defineINIT_DELAY 500 // 初始化指令间隔(ms,与CH55x一致)// -------------------------- 2. 全局变量 --------------------------staticrt_device_tmotor_uart = RT_NULL; // 电机UART句柄staticint16_tcurrent_speed =0; // 当前速度(-100~100)staticuint8_tspeed_dir =1; // 速度方向:1=0→100,2=100→-100,3=-100→100staticrt_bool_tmotor_inited = RT_FALSE;// 电机初始化标志(避免重复初始化)// -------------------------- 3. 基础工具函数(位反转+CRC8,原代码不变)--------------------------staticuint8_treverse_bits(uint8_tbyte){ uint8_treversed =0; for(uint8_ti =0; i < 8; i++)    {        reversed <<= 1;        reversed |= (byte & 0x01);        byte >>=1; } returnreversed;}staticuint8_tcrc8_maxim(constuint8_t* data,size_tlen){ uint8_tcrc =0x00; constuint8_tpoly =0x31; for(size_ti =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); returnRT_EOK; } returnRT_ERROR;}// -------------------------- 7. 速度循环线程(原逻辑不变,新增初始化检查)--------------------------staticvoidspeed_loop_thread(void*parameter){ // 线程启动时先检查初始化(双重保障) if(motor_inited == RT_FALSE) { if(motor_init() != RT_EOK) { rt_kprintf("线程启动失败:电机初始化失败!\n"); return; // 初始化失败,线程退出 } } while(1) { // 速度方向逻辑(原代码不变) switch(speed_dir) { case1: // 0→100(递增) current_speed += SPEED_STEP; if(current_speed >=100) { current_speed =100; speed_dir =2; } break; case2: // 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配置+线程创建)--------------------------staticintmotor_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); returnRT_ERROR; } // 打开UART(读+写:需读取电机响应并丢弃,所以用RDWR模式) if(rt_device_open(motor_uart, RT_DEVICE_FLAG_RDWR) != RT_EOK) { rt_kprintf("打开UART失败:%s\n", MOTOR_UART_NAME); returnRT_ERROR; } // 配置UART参数(波特率38400,8N1,与CH55x一致) structserial_configureconfig = 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 returnRT_ERROR; } // 3. 创建并启动速度循环线程 rt_thread_ttid =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"); returnRT_ERROR; } returnRT_EOK;}// -------------------------- 9. 导出Msh命令(启动+停止)--------------------------MSH_CMD_EXPORT(motor_speed_loop_init, 启动电机速度环(含初始化+速度模式切换));// 新增:一键停止(发送0速度+删除线程)staticintmotor_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_ttid =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; } returnRT_EOK;}MSH_CMD_EXPORT(motor_speed_loop_stop, 停止电机速度环(停电机+删线程))


下面进行推流的设置

3c177126-cab2-11f0-8ce9-92fbcf53809c.png


配一下ip地址,然后使用FTP传输web代码到RT睿擎派上面

下面给出web代码:

html> <2;title>RT-Thread 视频流控制中心title>