4、线程优先级设计:FOC算法中的优先级分配策略

好,咱们接着聊。前面几章我们把FOC算法的基本框架和实时性瓶颈都梳理了一遍。这一章,我打算重点聊聊线程优先级的设计。说白了,就是决定哪个任务先跑、哪个任务后跑,以及谁可以打断谁。

你想想看,FOC算法里任务那么多:电流采样、坐标变换、PI调节、SVPWM生成、速度环、位置环……如果优先级分配不合理,轻则控制抖动,重则电机直接飞车。我在项目中就吃过这个亏,后面会细说。

4.1 为什么优先级分配这么重要?

RT-Thread是一个抢占式实时内核。这意味着高优先级的线程可以随时打断低优先级的线程。如果FOC算法中,一个关键的计算任务被低优先级的任务阻塞了,那电流环的响应时间就会变长,电机控制精度就会下降。

我个人的习惯是,先把FOC算法中的任务按“实时性要求”分个类。你可以参考下面这个表格:

任务类型 实时性要求 典型周期 举例
电流环 最高 10-100 μs ADC采样、Clark/Park变换、PI调节、SVPWM
速度环 100 μs - 1 ms 速度计算、速度PI调节
位置环 中等 1-10 ms 位置计算、位置PI调节
通信/监控 10-100 ms CAN通信、UART日志、状态上报

嗯,这里要注意。电流环的优先级必须是最高的。为什么呢?因为电流环直接决定了电机的转矩响应。如果电流环被延迟了,电机就会产生明显的转矩脉动,甚至失控。

4.2 我的优先级分配策略

基于上面的分类,我一般会采用“分层抢占”的策略。说白了,就是让高实时性的任务拥有最高的优先级,低实时性的任务优先级低一些。这样既能保证关键任务的实时性,又能让非关键任务在空闲时运行。

具体来说,我会这样分配:

  1. 电流环线程:优先级最高(比如 RT_THREAD_PRIORITY_MAX - 2)
    • 这个线程负责执行电流采样、坐标变换、PI调节和SVPWM生成。
    • 它必须在一个PWM周期内完成所有计算。我一般会把它放在定时器中断服务函数中,或者用一个高优先级的线程来轮询。
    • 我曾经在一个项目中,把电流环的优先级设得不够高,结果被一个通信线程打断了。电机在低速时抖动得非常厉害。后来我把电流环的优先级提到最高,问题就解决了。
  2. 速度环线程:优先级次之(比如 RT_THREAD_PRIORITY_MAX - 5)
    • 速度环的周期比电流环长,但实时性要求依然很高。
    • 它可以在电流环空闲的时候运行。但要注意,速度环不能阻塞电流环。
  3. 位置环线程:优先级再次(比如 RT_THREAD_PRIORITY_MAX - 8)
    • 位置环的实时性要求相对较低,可以容忍一些延迟。
    • 我建议把位置环放在一个单独的线程中,避免它影响速度环和电流环。
  4. 通信/监控线程:优先级最低(比如 RT_THREAD_PRIORITY_MAX - 10)
    • 这些任务对实时性要求不高,可以在系统空闲时运行。
    • 但要注意,如果通信数据量很大,可能会影响其他任务的执行。我一般会使用消息队列或邮箱来异步处理通信数据。

核心原则:高优先级的任务不能被低优先级的任务阻塞。如果低优先级的任务持有高优先级任务需要的资源,就会发生优先级反转。RT-Thread提供了优先级继承机制,但最好还是从设计上避免这种情况。

4.3 避坑指南:优先级反转

优先级反转是实时系统中一个非常经典的问题。我来给你讲个我亲身经历的例子。

我曾经在一个伺服驱动项目中,电流环和速度环共享了一个全局变量(比如电机角度)。电流环的优先级高,速度环的优先级低。正常情况下没问题。但有一次,速度环在更新这个全局变量时,被一个中等优先级的通信线程打断了。通信线程运行了很久,导致电流环无法读取最新的角度值。结果电机在高速运行时出现了明显的抖动。

为什么会这样?因为速度环(低优先级)持有资源,电流环(高优先级)在等待资源,而通信线程(中优先级)在运行,导致电流环被间接阻塞了。

解决这个问题的方法有几种:

  • 使用互斥量:RT-Thread的互斥量支持优先级继承。当高优先级线程等待低优先级线程持有的互斥量时,低优先级线程会临时提升到高优先级,从而避免被中优先级线程打断。
  • 使用临界区:如果共享资源的访问时间很短,可以直接关闭中断或使用调度器锁。但要注意,关闭中断的时间不能太长,否则会影响其他中断的响应。
  • 避免共享资源:最好的办法是尽量避免高优先级和低优先级任务共享资源。比如,电流环和速度环可以使用不同的变量,或者通过消息队列传递数据。

警告:千万不要在电流环中使用信号量或互斥量来等待一个长时间的操作。比如,不要在电流环中等待一个CAN消息。这会导致电流环被阻塞,电机失控。

4.4 实战:RT-Thread中的优先级配置

在RT-Thread中,配置线程优先级非常简单。你只需要在创建线程时指定优先级即可。下面是一个示例:

#include <rtthread.h>

/* 电流环线程 */
static void current_loop_entry(void *parameter)
{
    while (1)
    {
        /* 执行电流采样、坐标变换、PI调节、SVPWM */
        /* ... */
        rt_thread_mdelay(1); /* 假设周期为1ms */
    }
}

/* 速度环线程 */
static void speed_loop_entry(void *parameter)
{
    while (1)
    {
        /* 执行速度计算、速度PI调节 */
        /* ... */
        rt_thread_mdelay(10); /* 假设周期为10ms */
    }
}

int motor_control_init(void)
{
    rt_thread_t current_thread;
    rt_thread_t speed_thread;

    /* 创建电流环线程,优先级最高 */
    current_thread = rt_thread_create("current",
                                      current_loop_entry,
                                      RT_NULL,
                                      1024,
                                      RT_THREAD_PRIORITY_MAX - 2,
                                      10);
    if (current_thread != RT_NULL)
    {
        rt_thread_startup(current_thread);
    }

    /* 创建速度环线程,优先级次之 */
    speed_thread = rt_thread_create("speed",
                                    speed_loop_entry,
                                    RT_NULL,
                                    1024,
                                    RT_THREAD_PRIORITY_MAX - 5,
                                    10);
    if (speed_thread != RT_NULL)
    {
        rt_thread_startup(speed_thread);
    }

    return 0;
}

嗯,这里要注意。RT-Thread的优先级数值越小,优先级越高。所以 RT_THREAD_PRIORITY_MAX - 2RT_THREAD_PRIORITY_MAX - 5 的优先级高。

4.5 总结与建议

好了,这一章的内容就到这里。我再来总结一下优先级分配的几个要点:

  • 电流环优先级最高:这是铁律,不能动摇。
  • 速度环次之,位置环再次:根据实时性要求逐级降低。
  • 通信/监控优先级最低:别让它们干扰核心控制。
  • 警惕优先级反转:使用互斥量或临界区来保护共享资源。
  • 避免在关键路径上使用阻塞操作:比如在电流环中等待信号量。

我个人建议,在项目初期就规划好优先级分配方案。不要等到调试时才发现问题。我曾经在一个项目中,因为优先级分配不合理,导致电机在高速时频繁抖动。后来花了整整两天时间才定位到问题。嗯,从那以后,我再也不敢轻视优先级设计了。

小技巧:你可以使用RT-Thread的 rt_thread_control() 函数在运行时动态调整线程优先级。这在调试阶段非常有用。比如,你可以临时降低某个线程的优先级,看看是否会影响系统性能。

下一章,我会聊聊中断与线程的协同设计。这是FOC算法中另一个容易出问题的地方。咱们到时候见。