单页面 网站 模板,室内设计网站平面案例,衡水电商网站建设,企业网站模板cms目录前言A. 使用RTOSB.裸机多任务并发前言
在学习各种MCU的时候#xff0c;都是用在main函数里写一个while(1){/* 执行代码 */}#xff0c;这种方式只能一个函数运行完以后再运行另一个函数。 假设需求控制多个模块#xff0c;如显示屏幕信息的同时控制电机#xff0c;还要…
目录前言A. 使用RTOSB.裸机多任务并发前言
在学习各种MCU的时候都是用在main函数里写一个while(1){/* 执行代码 */}这种方式只能一个函数运行完以后再运行另一个函数。 假设需求控制多个模块如显示屏幕信息的同时控制电机还要一边接收按键输入。如果用上面的方式每个模块要排队等待CPU运行就会显的很卡。 那有没有办法每个模块运行固定的时间时间到了运行下一个模块这样单个模块即使特别耗时也不影响其他模块的运行这个方法叫时间片轮转。 想到这个办法很容易但要怎么编写代码呢
A. 使用RTOS
根据不要重复造轮子的理论能用现有的开源代码当然是最好的了。如类STM32常用的操作系统有uCOS, RT-thread(国产)FreeRTOS。 用现成的去官网等地方搜移植教程本文不再详述。 用这些开源代码的问题是如果MCU RAM或ROM太小删减起来就不太方便了这时候手搓一个多任务并发系统的作用来了。
B.裸机多任务并发
时间片轮转的基本思路就是通过定时器(最好是硬件定时器)将CPU的运行时间切片成一个个时间片代码里叫tick然后一个任务每运行一个时间片tick计数加1当前任务运行的tick数已经达到分配给任务的tick数就不再执行当前任务执行下一个任务。
先定义一个最任务结构体至少包括任务主体函数分配给任务的时间片tick数和当前运行已经消耗的时间片tick计数。用结构体就是为了方便扩展用的还可以增加参数比如任务使能任务偏移量等。这样就使任务执行更加灵活。
typedef void (*Func)(void);
typedef struct task_info_t
{Func func; /* 任务主体函数 */uint16_t task_tick; /* 分配给任务的时间片tick数 */uint16_t tast_tick_cnt; /* 当前运行已经消耗的时间片tick计数 */
} TaskInfo_t;初学者 typedef void (*Func)(void); 看不懂这个是定义一种函数类型这种函数类型是void (*)(void)型的给他取个别名叫Func. 相当于下面这种写法 不清楚的看这篇文章typedef void *(Func)(void)用法
typedef struct task_info_t
{void (*func)(void); /* 任务主体函数 */uint16_t task_tick; /* 分配给任务的时间片tick数 */uint16_t tast_tick_cnt; /* 当前运行已经消耗的时间片tick计数 */
} TaskInfo_t;定义好结构体后创建一个结构体数组在数组里初始化任务的参数结构体数组不清楚的看这里结构体数组
TaskInfo_t TaskInfoArray[] {{IdleTask, 10, 0},{LedTask, 20, 0},{KeyTask, 5, 0},{MotorTask, 50, 0},/*任务名 执行时间 运行计数*/
}然后要开启一个定时器中断(我是STM32的用这种方法其他单片机可以用其他方法)比如将时间片定为1ms. 用STM32CubeMX直接配置一个1ms中断的定时器也可以去问ChatGPT。我这里是开了一个定时器TIM3.
/*** brief TIM3 Initialization Function* param None* retval None*/
static void MX_TIM3_Init(void)
{TIM_MasterConfigTypeDef sMasterConfig {0};TIM_OC_InitTypeDef sConfigOC {0};htim3.Instance TIM3;htim3.Init.Prescaler 850;htim3.Init.CounterMode TIM_COUNTERMODE_UP;htim3.Init.Period 65535;htim3.Init.ClockDivision TIM_CLOCKDIVISION_DIV1;htim3.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_DISABLE;if (HAL_TIM_OC_Init(htim3) ! HAL_OK){Error_Handler();}sMasterConfig.MasterOutputTrigger TIM_TRGO_RESET;sMasterConfig.MasterSlaveMode TIM_MASTERSLAVEMODE_DISABLE;if (HAL_TIMEx_MasterConfigSynchronization(htim3, sMasterConfig) ! HAL_OK){Error_Handler();}sConfigOC.OCMode TIM_OCMODE_TOGGLE;sConfigOC.Pulse 0;sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH;sConfigOC.OCFastMode TIM_OCFAST_DISABLE;if (HAL_TIM_OC_ConfigChannel(htim3, sConfigOC, TIM_CHANNEL_3) ! HAL_OK){Error_Handler();}HAL_TIM_MspPostInit(htim3);
}然后设置一个任务运行1ms的标志位定义成全局变量 task1ms_Flag定时器里把这个标志位至1.
/*** brief This function handles TIM3 global interrupt.*/
uint8_t task1ms_Flag 0;
void TIM3_IRQHandler(void)
{task1ms_Flag 1;HAL_TIM_IRQHandler(htim3);
}然后在while(1){…}里面写整个任务切换的程序看懂逻辑不复杂
/*** brief The application entry point.* retval int*/
int main(void)
{/*初始化代码*/uint8_t i;TaskInfo_t *p_task;while(1){if (0 task1ms_Flag)return; //当前运行结束1ms没到不需要切换任务elsetask1ms_Flag 0; //当task1ms_Flag为1时运行先重置为0for (i 0; i sizeof(TaskInfoArray) / sizeof(TaskInfo_t); i) //sizeof(TaskInfoArray) / sizeof(TaskInfo_t)为任务数每1ms时间片轮转一遍{p_task TaskInfoArray[i]; //从任务0开始任务轮转if( p_task-task_tick_cnt tsk_ptr-tsk_tick ) //当前任务已经消耗tick大于分配的tick{tsk_ptr-func(); //执行当前任务i的函数主体tsk_ptr-tst_tick_cnt tsk_ptr-tst_tick_cnt % tsk_ptr-tsk_tick; //当前任务已经消耗tick减去分配的tick,相当于清零。}else //如果当前任务已经消耗tick小于分配的tick则当前任务已经消耗tick加一{tsk_ptr-tst_tick_cnt; }}}
}完成这些配置后就可以写每个任务的执行函数了和RT-thread不一样这些任务函数里不要写while(1)
void IdleTask(void)
{
}void LedTask(void)
{
}void KeyTask(void)
{
}void MotorTask(void)
{
}整个系统的逻辑就是时间片轮转执行Task程序如IdleTask执行10msLedTask执行20msKeyTask执行5msMotorTask执行50ms。
注意每个任务里如果要写延时函数注意延时的总时间不要大于分配的时间片。