1. 背景
对于一些简单的单片机项目,没必要非得跑RTOS,因此,很多项目都是在“裸奔”(指纯循环加上中断的机制)。所以,开发出一套好用的裸机框架是非常有必要的,本文章带你手把手实现裸机中的“多任务”调度。
2. 基本知识
需要掌握的基本知识并不多,也都是最基本的知识,总结如下
Systick 定时器
函数指针
结构体数组
3. 代码实现
整个系统的代码由 Systick 中断发动,Systick 就像是一个发动机,每隔一定的周期发动一次中断,利用这一个中断的时间去对指定任务的时间片减1,在主循环中一直遍历系统中所有任务的时间片,如果时间片耗尽了,则执行该任务。
首先创建一个任务对象,经过上述分析,首先该任务对象至少有个时间片来决定该任务到底多久执行一次,其次该任务对象得有个任务,这个任务就是一个函数,比如创建一个LED灯的任务,每隔1秒钟执行一次。相关代码如下,该机制完全可以通过一个链表给链起来,可以有,但没必要,裸机代码简单易懂是关键。
typedef struct _task
{
void (*task)(void);
uint32_t tasktick;
}task_t;
通过task_t这个结构体去实例化一个对象,如下:
task_t led_task;//实例化出led_task对象
led_task.tasktick = 1000;
led_task.task = xxxx_func();
这样就将一个led任务的对象给创建出来了,但是系统中不可能仅存在一个任务,因此使用一个结构体数组将全部任务集中到一起,如下:
task_t task[] = {
{led_task,500},
{tim_capture_task,20},
{muliti_button_task,5},
{mpu6050_task,10},
{mpu6050_display_task,100},
{srf_05_task,30}
};
这个Task 数组中集合了该系统中的所有任务,就只剩下每个时钟周期( systick 中断 )减去每个任务的tick即可。这里涉及到一个Systick 定时器的中断周期问题,但一般都是1ms一次中断,Systick 配置如下:
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
/* 配置systick中断周期为1ms */
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000);
/* 系统滴答定时器时钟源 */
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
/* 系统滴答定时器中断优先级配置 */
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);//提高systick优先级为最高
}
直接将配置systick相关代码加到系统时钟初始化函数中即可,需要注意的是将Systick 中断优先级调至系统最高,这样其他中断来临时,不会打断正在执行的Systick中断。
接下来就就需要在SysTick_Handler(void)服务函数中递减每个任务的时间片了,代码如下:
void SysTick_Handler(void)
{
uint8_t i;
for(i = 0 ; i < MAX_TASK ; i ++) //MAX_TASK 系统中最大任务个数,可指定
{
if(task_timer[i]) //任务定时器
task_timer[i]--;
}
}
上述代码中的 task_timer需要经过 Task[] 这个结构体数组初始化,如下:
void task_init(void) // 系统任务初始化
{
uint8_t i = 0;
for(i = 0; i < sizeof(task)/sizeof(task[0]); i++)
task_timer[i] = task[i].tasktick;
}
经过以上简单的几步整个系统这时候就能够跑起来了,不过还需最后一步,也就是在 while(1) 中添加任务运行相关的代码,如下:
void task_run(void)
{
uint8_t i = 0;
for(i = 0; i < sizeof(task)/sizeof(task[0]); i++)
{
if(task_timer[i] == 0)
{
task_timer[i] = task[i].tasktick; //重新给当前任务定时器付上初值,这里还可以再给对象添加一个周期任务与单次任务,需要的自行添加即可。
(task[i].task)();//调用任务函数
}
}
}
整个框架的核心步骤就如上所述。整个 main函数中也是非常精简,可谓是看似精简,实则内部波涛汹涌。
int main(void)
{
board_init();//硬件相关初始化与 task_init
while (1)
{
task_run(); // 上述 task_run函数
}
}
赶快用起来吧!!!
————————————————
版权声明:本文为CSDN博主「EINT」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_56548489/article/details/124333366
|