背景
先说说背景:
1,我需要控制电机的运行,串口屏点击“运行”时,电机开始运行;串口屏点击“停止”时,电机停止运行。
因为这个电机是大功率的高速电机,需要通过与变频器进行通信来控制电机的转速,按照一定的加减速曲线来改变转速。
这个算法是我们单片机这边来实现的,我用了一个for循环,放在Run_Fun()和Stop_Fun()中。
2,为了实现电机停止的实时性,把Run_Fun()和Stop_Fun()放在了不同的线程中。
void Lcd_Display_Task_Func(void *argument)
{
......
while(1)
{
......
//执行停机逻辑
Stop_Fun(countdown_timer);
vTaskDelay(pdMS_TO_TICKS(50)); // 周期50ms
}
}
void Ctrl_Task_Func(void *argument)
{
......
while(1)
{
......
//控制函数:电机运行
ctrl_driver.Ctrl(countdown_timer);
vTaskDelay(pdMS_TO_TICKS(100)); // 周期100ms
}
}
问题
好的,问题来了:
问题1
调试时发现,有时候点击“停止”时,电机的实时转速会出现一会增加、一会减小的现象。
分析了一下,应该是这样:
如果当前电机在加速过程中(即:在Run_Fun()里加速的for循环中),我们点击了“停止”,Stop_Fun()会开始减速。
此时就会出现:一会执行Stop_Fun()中减速的for循环,一会又会执行Run_Fun()中加速的for循环。
嗯,应该是这样。
解决1
怎么解决呢?
我们想要实现的目标是:一旦Stop_Fun()开始执行,Run_Fun()就停止执行,哪怕是正在for循环中。
这个简单呀,执行Stop_Fun()时,直接把Run_Fun()所在的任务挂起,到Stop_Fun()执行完之后,再恢复Run_Fun()所在的任务。
代码如下:
//执行停机逻辑
static void Stop_Fun(TimerHandle_t xTimer)
{
/* 等待停止的信号量 */
if (g_flag.stop)
{
g_flag.stop = 0;
g_flag.run = 0;
// 挂起 Ctrl_Task
vTaskSuspend(Ctrl_TaskHandle);
//停机操作
......
// 恢复 Ctrl_Task
vTaskResume(Ctrl_TaskHandle);
}
}
问题2
按照上述方法,问题又来了:
每次电机完全停止之后,又会自动开始加速。
好奇怪,找了好久的原因,触发条件一个也没有满足啊
解决
终于,在多次调试之后,发现每次好像都是从上一次加速的速度又开始加速了。
每次都这样,应该是代码的问题吧,执行停机操作没啥问题,执行完之后,恢复了 Ctrl_Task。
哎? 不会是任务又从上一次挂起时的状态接着运行了吧。
果然,查了查资料:FreeRTOS中,vTaskSuspend 会挂起任务,但任务的状态和局部变量依然保留。这意味着,当你调用 vTaskResume 恢复任务时,任务会从上次停止的地方继续执行。
我们想要的是Ctrl_Task 从头开始运行,而不是恢复上次暂停的位置。那不如直接杀死并重新创建任务。
//执行停机逻辑
static void Stop_Fun(TimerHandle_t xTimer)
{
/* 等待停止的信号量 */
if (g_flag.stop)
{
g_flag.stop = 0;
g_flag.run = 0;
// 删除任务
vTaskDelete(Ctrl_TaskHandle);
//停机操作
......
// 重新创建任务
osThreadAttr_t Ctrl_Task_attributes = {
.name = "Ctrl_Task",
.stack_size = 256 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
Ctrl_TaskHandle = osThreadNew(Ctrl_Task_Func, NULL, &Ctrl_Task_attributes);
}
}
果然,问题解决,其实只是对FreeRTOS中API的理解不够透彻。可能之前学习过,但没有实际踩坑就不算会了。
实践出真知啊。