STM32自学笔记30-操作系统的入门

在进入电机控制的工程之后,我开始入门单片机的操作系统。当工程越来越复杂,代码里的函数越来越多时,如果还是单线程的使用main函数和定时器中断的方式,分析下会有以下几点不足:

  1. 任务执行顺序固定,必须等上一个任务(也就是函数执行完毕)才能执行下一个任务,在处理一个任务的过程中不能响应其他任务,即无法多线程工作;
  2. 由于上一个特性,使得总的任务执行时间长。在最坏的情况下,任务的响应时间是所有任务循环一次的时间,这就很夸张了;
  3. 还有一个问题,使用中断来控制任务执行时,中断的执行时间不能超过定时器的中断时间,这就需要更理解底层的代码执行时间,而我其实是不想去深入底层的;
  4. 这种结构以函数为单位,函数又会经常被各个任务之间互相调用,构建复杂系统的时候会头大,特别是找bug时,会找到崩溃,懂得都懂。

看这样一个例子,这其实是之前大部分项目里常有的结构:

void main()
{
   while(1)
   {
	  Func1();
	  Func2();
	  ...
	  
   }
}

执行完这个main函数的时间就是把所有Func()都跑一遍,很多Func()还有delay函数,就更耗费时间了。这样看下来是不是效率很低?其实简单的项目里这些问题都无所谓,但是对于大型项目来说就不行了,这也就是为什么现在很多复杂的项目都直接上Linux

单片机的操作系统概念和计算机的应该一致,我们在操作计算机时是不是可以多任务?在代码编译的过程中可以打开浏览器看新闻,就是说在一个函数没有执行完时可以跳转到另一个函数。我们不需要关心系统是怎么进行调用或者操作的,只需要把任务按照功能写进几个进程,每一个功能用一个函数实现,操作系统要做的就是决定什么时候跳转到哪一个进程运行。

以下为我搜索到的操作系统信息

操作系统分为实时操作系统(Real Time Operating System, RTOS)和通用操作系统(General Purpose Operating System, GPOS)
前者的代表是uCOS, FreeRTOS, RT-Thread等,后者就是耳熟能详的Windows, MAC, Linux了。

FreeRTOS是比较主流针对STM32的片上操作系统,学习资源较多,我也选取了FreeRTOS作为学习对象。最主要的原因是STM32CubeMX里集成了FreeRTOS,而且正如它的名字Free,是完全免费并且开源的。

为了快速学习,我用cubemx建一个项目来简单实施FreeRTOS,功能是这样的:

1. 串口每隔1s输出
2. 按key1后LED1闪烁
3. LED2每隔2s闪灭一次

具体来实施,首先是常规的cubemx上设置各个时钟、串口、gpio等,这里不做赘述。在左侧Middleware选择FREERTOS,进入设置页面,如图
STM32自学笔记30-操作系统的入门_第1张图片

Interface选择CMSIS_V1,选V2也可以,我的开发板是STM32F1的,V1完全够用了。关于V1和V2的区别,可以去看CMSIS ARM的官方定义。

下半部分就有很多参数可以设置,这里只关注Tasks and Queues选项卡,其他到后面慢慢学。点击Add,这里新加一个LED闪烁的Task
STM32自学笔记30-操作系统的入门_第2张图片

这几个选项的意义:

- Task Name: 设置任务的名称,会根据这个建立一个指向任务的句柄(handle)
- Priority: 任务优先级,顾名思义,这里就都搞成Normal
- Stack Size: 任务栈的大小,用于储存任务相关变量,这个我想后面再系统学习下,现在就用默认的值128个字(words)
- Entry_Function: 设置任务的入口函数,比较重要,就是函数的名字
- Code Generation Option和Parameter,都选择默认
- Allocation: 设置任务在堆还是在栈(动态和静态区),堆栈的知识也需要深入学习,现在就先选默认(Dynamic)

Queues的选项先不做任何操作。
设置完之后Generate Code。我在Clion里面打开这个工程后,可以看到之前设置的几个Task的函数都在freertos.c里等待被定义。分别补充完整。
首先是第一个任务,串口每隔一秒输出一句话

void Task_Comm(void const * argument)
{
  /* USER CODE BEGIN Task_Comm */
  /* Infinite loop */
  for(;;)
  {
	printf("This is Task1! \r\n");
    osDelay(1000);
  }
  /* USER CODE END Task_Comm */
}

然后是第二个任务,按key1后LED1闪烁

void Task_Key(void const * argument)
{
  /* USER CODE BEGIN Task_Key */
  /* Infinite loop */
  for(;;)
  {
	if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET) {
        HAL_Delay(50);
        printf("Key pressed! \n");
        HAL_GPIO_TogglePin(D2_GPIO_Port, D2_Pin);
	}
   }
  /* USER CODE END Task_Key */
}

第三个任务,简单的LED闪灭

void Task_Blink(void const * argument)
{
  /* USER CODE BEGIN Task_Blink */
  /* Infinite loop */
  for(;;)
  {
      HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
    osDelay(1000);
  }
  /* USER CODE END Task_Blink */
}

这几个函数的代码在前面都学过。项目编译调试都没问题,也实现了预想的3个功能。
然后看main.c里的代码,有这两句:

  /* Call init function for freertos objects (in freertos.c) */
  MX_FREERTOS_Init();
  /* Start scheduler */
  osKernelStart();

其中第一个是FreeRTOS的初始化,就是我们在CubeMX里面的设置,另一个是os核心,是task.c里定义的vTaskStartScheduler()函数,后面再来学习。

main.c里面的代码非常少,while(1)循环不需要任何语句项目就跑起来了,这三个任务都独立运行,至于系统怎么调用,我不太关心,但是关于堆栈、task、queue的知识,我还是要继续学习。

你可能感兴趣的:(stm32,笔记,嵌入式硬件,c语言,arm,单片机)