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

描述

项目背景及功能


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


 

RTT使用情况


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


 

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


 

Web


 

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

Web


 

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

Web


 

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

  •  

help

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

Web


 

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

Web


 

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


 

开发过程

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

  •  
  •  
  •  
  •  
  •  

/** * 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、驱动轮等领域的首选动力方案。


 

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

Web


 

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

Web


 

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

WebWeb


 

使用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;}

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

Web


 

电机控制的完整代码:

  •  
  •  
  •  
  •  
  •  

#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, 停止电机速度环(停电机+删线程))


 

下面进行推流的设置

Web


 

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

下面给出web代码:

  •  
  •  
  •  
  •  
  •  

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