基于STM32CubeMX创建FreeRTOS—以STM32F429为例

目录

1. 实验任务

2. 使用STM32CubeMX创建基础工程

2.1 使用STM32CubeMX创建项目

2.2 创建新项目

2.3 时钟设置

2.4 时钟配置树

2.5 修改时钟基准,打开串行调试

2.6 配置串口

2.7 配置状态指示灯

2.8 FreeRTOS配置

2.9 配置工程输出项

3. 代码编辑

3.1 printf重映射

3.1.1 使用ARMCC 5编译器时的printf重映射方法

3.1.2 使用ARM 6编译器时的printf重映射方法

3.2 使用FreeRTOS任务编写代码

3.3 main函数中关于裸机和多任务代码一点说明

3.3  运行效果

4 多任务相关知识

4.1 任务基础知识

4.2 任务状态

4.3 任务优先级

4.4 任务实现


1. 实验任务

利用STM32CubeMX,创建MDK工程,STM32CubeMX移植FreeRTOS。

开发环境准备:

Win11、MDK5、STM32CubeMX、STM32F429IGT6开发板

STM32CubeMX v6.10

2. 使用STM32CubeMX创建基础工程

2.1 使用STM32CubeMX创建项目

0a5154f9efce4dddb1b38b7df035c195.png

2.2 创建新项目

MCU型号选择STM32F429IGT6,开始项目。

fbea6d54b74248fe90eb74824f430ffb.png

2.3 时钟设置

选择使用外部高速时钟,时钟源为外部晶振(25MHz),配置系统时钟,根据外部晶振频率,将系统时钟配置到180MHz。

d13fbb6831ac44f7ac0d41d14f6e30d4.png

2.4 时钟配置树

0ca734433eaf46d6ab5fb24549c3bc72.png

2.5 修改时钟基准,打开串行调试

  • FreeRTOS使用systick系统嘀嗒定时器当作心跳,此处须修改HAL库的时钟基准。
  • 由于FreeRTOS和HAL库不能同时使用SysTick定时器,将HAL库的定时器改成TIM4

f03cb0bdd9f94a23852b0f559a1bdec4.png

2.6 配置串口

采用异步通信方式,通讯参数此处为默认值。

4719c78c016249d0889bef61aa31f189.png

2.7 配置状态指示灯

88ed322f4714405aac83ee6d5225e1b7.png

2.8 FreeRTOS配置

STM32CubeMX已经将FreeRTOS集成到工具中,并且将RTOS的接口封装为CMSIS-RTOS V1/V2,相较之于V1版本的CMSIS-RTOS API,V2版本的API的兼容性更高,为了将来的开发和移 植,建议开发者使用V2版本的API。

选择Middleware-FreeRTOS,Interface中选择CMSIS_V2。

ddf2631d34e1474cb265a107ae0a402c.png

选择CMSIS V2接口后,还要进一步配置FreeRTOS的参数和功能。
FreeRTOS的参数包括时基频率、任务堆栈大小、是否使能互斥锁等等,需要开发者根据自己对FreeRTOS的了解以及项目开发的需求,来定制参数。此处使用默认参数不需要修改。
使用STM32CubeMX,可以手工添加任务、队列、信号量、互斥锁、定时器等等。为提高代码的可移植性和灵活性,建议大家不要严重依赖STM32CubeMX,尽量不使用STM32CubeMX来添加这些对象,而是手写代码来使用这些对象。

建立双任务:

任务1:LED指示灯闪烁

任务2:串口打印输出

94da16ba15f9407bb841ad713d153315.png

提示:留意下串口中断的问题,由于FreeRTOS只允许5以上的中断级别,需要中断的话请设置大于等于5的值。

2.9 配置工程输出项

e77c2463f8b0486b9dca41f9c34509b5.png

6e7c0a73545743e8ab3d182dc1114c5b.png

3. 代码编辑

3.1 printf重映射

3.1.1 使用ARMCC 5编译器时的printf重映射方法

printf函数重映射,可以直接使用printf在串口一中打印数据,定位到usart.c文件中。

/* USER CODE BEGIN 0 */
#include "stdio.h"
/* USER CODE END 0 */
/* USER CODE BEGIN 1 */
int fputc(int ch,FILE *f)
{
    HAL_UART_Transmit(&huart1,(uint8_t*)&ch,1,10);
    return(ch);
}
/* USER CODE END 1 */

注意:重写printf后运行代码前一定要勾选Use MicroLiB,否则项目烧录后无法执行。

eca30f0165d74349ba36aa3515d1d3a6.png

提示:Keil 5.37以后的版本,不再集成AC5编译器,安装与配置方法如下:

Keil MDK5.37以上版本自行添加AC5(ARMCC)编译器的方法_armcc下载-CSDN博客文章浏览阅读2.4w次,点赞89次,收藏204次。​【对安装AC5后,编译时提示找不到序列号的错误,文中有提示的解决方法】从MDK5.37开始,AC5(ARMCC)编译器不再默认安装,需要独立安装。路径、字符等安装问题,都可能引起AC5的编译错误。下面给出不用爬坑的方法。下面是安装步骤:下载AC5(ARMCC)编译器:1. 官方页面(可能下载不成功)2.网盘下载百度网盘:链接:https://pan.baidu.com/s/1ND3vKLzqxanWVP304txRtQ ,提取码:idvc..........................._armcc下载https://blog.csdn.net/qcmyqcmy/article/details/125814461 

3.1.2 使用ARM 6编译器时的printf重映射方法

下面两篇博文提供了同时使用AC5 和ARM Clang 6编译器的方法,使用此方法,你可以方便按需选择合适的编译器,代码不再报错。

方法1:阻塞方式

基础篇007. 串行通信(一)--阻塞方式发送接收_串口阻塞-CSDN博客文章浏览阅读1.7k次,点赞2次,收藏10次。在学习C语言时我们经常使用C语言标准函数库输入输出函数,比如printf、scanf、getchar等。为让开发板也支持这些函数需要把USART发送和接收函数添加到这些函数的内部函数内。在C语言HAL库中,fputc函数是printf函数内部的一个函数,功能是将字符ch写入到文件指针f所指向文件的当前写指针位置。fgetc函数与fputc函数非常相似,实现字符读取功能。在使用scanf函数时需要注意字符输入格式。文中引入可使用#if …… #elif …… #endif方式调试代码。_串口阻塞https://blog.csdn.net/qcmyqcmy/article/details/130674914

方法二、中断方式

基础篇007. 串行通信(二)--中断方式接收数据_串口设备通信0xffff-CSDN博客文章浏览阅读2.1k次。目录1. 实验任务2. 硬件原理3. 利用STM32CubeMX创建MDK工程3.1 STM32CubeMX工程创建3.2 配置调试方式3.3 配置时钟电路3.4 配置GPIO3.5 配置串口参数3.6 配置时钟3.7 项目配置4. 串行通信实验4.1 UART串口printf,scanf函数串口重定向4.2 开启中断4.3 中断回调函数4.4 main()函数修改5.调试与验证6.总结利用STM32CubeMX,创建MDK工程,使用中断方式,实现串口接收数据,然后在转发到串口。本实验是串行通信的第二部分,_串口设备通信0xffffhttps://blog.csdn.net/qcmyqcmy/article/details/130716056

3.2 使用FreeRTOS任务编写代码

在freertos.c中的任务函数里面写相应的程序

添加头文件:

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "usart.h"
/* USER CODE END Includes */

MDK截图如下: 

84a54a428adf42bea6fff2f6e7de2841.png

 添加数组:

/* USER CODE BEGIN Variables */
uint8_t USART1_BUF[] = "Hello FreeRTOS\r\n";
/* USER CODE END Variables */

MDK截图如下:  

825a2e34c8a748c19a8ef4aea6a07edd.png

 添加任务一启动代码:

/* USER CODE BEGIN Header_StartTask01 */
/**
  * @brief  Function implementing the myTask01 thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartTask01 */
void StartTask01(void *argument)
{
  /* USER CODE BEGIN StartTask01 */
  /* Infinite loop */
  for(;;)
  {
    HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
    osDelay(1000);
  }
  /* USER CODE END StartTask01 */
}

MDK截图如下:  

0091760d0cff49308bfad957353a4324.png

  添加任务二启动代码:

/* USER CODE BEGIN Header_StartTask02 */
/**
* @brief Function implementing the myTask02 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask02 */
void StartTask02(void *argument)
{
  /* USER CODE BEGIN StartTask02 */
  /* Infinite loop */
  for(;;)
  {
    printf("USART1: %s", USART1_BUF);
    osDelay(1000);
  }
  /* USER CODE END StartTask02 */
}

MDK截图如下:  

40ef596dc8364a5fa6be11b2f5d66979.png

3.3 main函数中关于裸机和多任务代码一点说明

main.c函数中,osKernelStart()是任务调度函数,通过它可以进入多任务操作系统。

注释该语句后,程序就可进入后面的while语句了,可作为裸机程序运行。

  /* Start scheduler */
  osKernelStart();

b71ec3c652d24e98807c85cd39ebb7ea.png

3.3  运行效果

待补充...

4 多任务相关知识

4.1 任务基础知识

单片机裸机(未使用系统)运行时,一般都是在main函数中用循环来处理所有事件,循环调用相应的函数完成事件的处理。有时候也可以通过中断完成一些处理。相对多任务系统而言,这种就是单任务系统,也称作前后台系统,中断服务函数作为前台程序,大循环while(1)作为后台程序。

e7fc79b8aba64298b89ad3f8a1e57c5e.png

多任务系统会把大问题划成很多的小问题,逐个将小问题解决掉,大问题也就会随之解决。这些小问题是并发处理的,并不是说同一时刻一起执行很多任务,而是由于每个任务执行的时间很短,看起来像同一时刻执行了很多任务。通过FreeRTOS里面的任务调度器完成任务的先后执行。在FreeRTOS中是一个抢占式的实时多任务系统,其任务调度器也是抢占式的。

20f9db76ef6446468682c7daa3a07bb1.png

高优先级的任务可以打断低优先级任务的运行而取得CPU的使用权,这样就保证了那些紧急任务的运行。高优先级的任务执行完成以后重新把CPU的使用权归还给低优先级的任务,这个就是抢占式多任务系统的基本原理。

4.2 任务状态

FreeRTOS中的任务永远只有运行态、就绪态、阻塞态、挂起态。任务状态图切换如下:

095c5619c6e14cf2a3e937f7513ac29a.png

(1)运行态

当一个任务正在运行时,那么就说这个任务处于运行态,处于运行态的任务就是当前正在使用处理器的任务。如果使用的是单核处理器的话那么不管在任何时刻永远都只有一个任务处于运行态。

(2)就绪态

处于就绪态的任务是那些已经准备就绪(这些任务没有被阻塞或者挂起),可以运行的任务,但是处于就绪态的任务还没有运行,因为有一个同优先级或者更高优先级的任务正在运行!

(3)阻塞态

如果一个任务当前正在等待某个外部事件的话就说它处于阻塞态,比如说如果某个任务调用了函数vTaskDelay0的话就会进入阻塞态,直到延时周期完成。任务在等待队列、信号量、事件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个超时时间任务就会退出阻塞态,即使所等待的事件还没有来临!

(4)挂起态

像阻塞态一样,任务进入挂起态以后也不能被调度器调用进入运行态,但是进入挂起态的任务没有超时时间。任务进入和退出挂起态通过调用函数vTaskSuspend()和xTaskResume()。

4.3 任务优先级

每个任务都可以分配一个从0~(configMAX_PRIORITIES-1)的优先级,configMAX_PRIORITIES在文件FreeRTOSConfig.h中有定义。如果所使用的硬件平台支持类似计算前导零这样的指令(可以通过该指令选择下一个要运行的任务,Cortex-M处理器是支持该指令的),并且宏configUSE_PORT_OPTIMISED_TASK_SELECTION也设置为了1,那么宏configMAX_PRIORITIES不能超过32!也就是优先级不能超过32级。其他情况下宏configMAX_PRIORITIES可以为任意值,但是考虑到RAM的消耗,宏configMAX_PRIORITIES最好设置为一个满足应用的最小值。

优先级数字越低表示任务优先级越低,0的优先级最低,configMAX_PRIORITIES-1的优先级最高。空闲任务的优先级最低,为0。

FreeRTOS调度器确保处于就绪态或运行态的高优先级的任务获取处理器使用权,换句话说就是处于就绪态的最高优先级的任务才会运行。当宏configUSE_TIME_SLICING定义为1的时候多个任务可以共用一个优先级,数量不限。默认情况下宏configUSE_TIME_SLICING在文件FreeRTOS.h中已经定义为1。此时处于就绪态的优先级相同的任务就会使用时间片轮转调度器获取运行时间。

4.4 任务实现

在使用FreeRTOS的过程中,需要使用xTaskCreat()或者xTaskCreatStatic()来创建任务,这两个函数的第一个参数为pxTaskCode,就是任务函数本体。任务函数就是完成本任务工作的函数。FreeRTOS官方给出的任务函数模板为:

void vATaskFunction(void *pvParameters){        (1)
    for(;;){                                    (2)
       --任务应用程序--                          (3)
       vTaskDelay();                            (4)
}
    VTaskDelete(NULL);                          (5)
}

(1)、任务函数本质也是函数,所以肯定有任务名什么的,不过这里要注意:任务函数的返回类型一定要为void类型,也就是无返回值,而且任务的参数也是void指针类型的!任务函数名可以根据实际情况定义。

(2)、任务的具体执行过程是一个大循环,for(;;)就代表一个循环,作用和while(1)一样,笔者习惯用while(1)。

(3)、循环里面就是真正的任务代码了,此任务具体要干的活就在这里实现!

(4)、FreeRTOS的延时函数,此处不一定要用延时函数,其他只要能让FreeRTOS发生任务切换的API函数都可以,比如请求信号量、队列等,甚至直接调用任务调度器。只不过最常用的就是FreeRTOS的延时函数。

(5)、任务函数一般不允许跳出循环,如果一定要跳出循环的话在跳出循环以后一定要调用函数vTaskDelete(NULL)删除此任务!FreeRTOS的任务函数和UCOS的任务函数模式基本相同的,不止FreeRTOS,其他RTOS的任务函数基本也是这种方式的。

  • 20
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值