之前挖了图解freeRTOS的坑,挖了坑就得填。今天就从使用开始,先把freeRTOS用起来。先聊一聊在STM32F407上的如何电灯,如果只用freeRTOS点灯,无疑有点杀鸡用牛刀的感觉。但是想用freeRTOS做产品,先照类似步骤则可以验证最小系统,验证freeRTOS是否能正确跑起来,再在此基础上开始做应用开发,就把任务分解了。本文主要梳理一下如何利用官方移植例子,创建一个自己demo工程,对于没用过的有点参考价值。
注:本文基于IAR for ARM 8.22.1(个人喜欢IAR),硬件采用正点原子的STM32F407探索者开发板,STLink V2。 准备工作 去官网https://www./a00104.html下载: 按图中所示下载,目前最新为202104.00,这个带多种移植好的例子。解压出来,是这样的: tools为AWS iot配置工具,quick start例子以及cmake FreeRTOS-Plus包含了MQTT,TCP,FAT等很多例子,先不去管 FreeRTOS为真正的操作系统源码以及多平台移植例子,如下: 使用开源软件,记得看看README,一般会有些介绍信息。 分别介绍一下该文件夹下的大体内容: Source:内核代码,包含硬件独立文件与可移植文件 license:MIT开源协议,可以直接商业应用。 建立工程 选择空工程(Empty project),命名为demo保存。 右键选择Options,进入设置界面设置单片机为STM32F407VG Library Configuration使能CMSIS 4.1 新建freeRTOS文件夹4.2 添加内核文件,在工程文件夹下新建freeRTOS文件夹,将下列文件拷贝过来 4.4 将freeRTOS下的include文件夹以及portable文件夹拷贝到当前工程下 4.5 portable文件夹下保留下面这几个文件夹 注: 全保留也可以,就是其他的用不到,会使工程文件夹变的很大,如果用版本控制软件管理,会增加仓库的大小。 ARM_CM4F_MPU文件夹暂时没用,这个留做备用。 4.7 在IAR工程中新建portable文件夹,并加入相应的文件 4.8 将下面几个文件从官方例子中拷贝过来,并加入工程 FreeRTOSConfig.h是内核配置文件,本文配置如下: #define configUSE_PREEMPTION 1 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 #define configCPU_CLOCK_HZ ( SystemCoreClock ) #define configTICK_RATE_HZ ( ( TickType_t ) 1000 ) #define configMAX_PRIORITIES ( 5 ) #define configMINIMAL_STACK_SIZE ( ( unsigned short ) 130 ) #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 75 * 1024 ) ) #define configMAX_TASK_NAME_LEN ( 10 ) #define configUSE_TRACE_FACILITY 1 #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 1 #define configUSE_MUTEXES 1 #define configQUEUE_REGISTRY_SIZE 8 #define configCHECK_FOR_STACK_OVERFLOW 0 #define configUSE_RECURSIVE_MUTEXES 1 #define configUSE_MALLOC_FAILED_HOOK 0 #define configUSE_APPLICATION_TASK_TAG 0 #define configUSE_COUNTING_SEMAPHORES 1 #define configGENERATE_RUN_TIME_STATS 0 /* Co-routine definitions. */ #define configUSE_CO_ROUTINES 0 #define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) /* Software timer definitions. */ #define configUSE_TIMERS 1 #define configTIMER_TASK_PRIORITY ( 2 ) #define configTIMER_QUEUE_LENGTH 10 #define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 ) /* Set the following definitions to 1 to include the API function, or zero to exclude the API function. */ #define INCLUDE_vTaskPrioritySet 1 #define INCLUDE_uxTaskPriorityGet 1 #define INCLUDE_vTaskDelete 1 #define INCLUDE_vTaskCleanUpResources 1 #define INCLUDE_vTaskSuspend 1 #define INCLUDE_vTaskDelayUntil 1 #define INCLUDE_vTaskDelay 1
将IDLE_HOOK/TICK_HOOK禁止了,先不去关注这两个功能。 正点原子的板子,有两个LED,我们就建立两个任务来闪灯吧,相当于hello world,容易理解。 /* 操作系统头文件. */#include 'FreeRTOS.h' #include 'task.h' #include 'timers.h' #include 'semphr.h' #include 'stm32f4xx.h' //LED管脚#define LED1_PIN GPIO_Pin_9 #define LED1_GPIO_PORT GPIOF #define LED1_GPIO_CLK RCC_AHB1Periph_GPIOF #define LED2_PIN GPIO_Pin_10 #define LED2_GPIO_PORT GPIOF #define LED2_GPIO_CLK RCC_AHB1Periph_GPIOF static void prvLedInitialise () { GPIO_InitTypeDef GPIO_InitStructure; /* LED1时能时钟 */ RCC_AHB1PeriphClockCmd(LED1_GPIO_CLK, ENABLE); /* LED1 GPIO 配置为输出 */ GPIO_InitStructure.GPIO_Pin = LED1_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure); /* LED2时能时钟 */ RCC_AHB1PeriphClockCmd(LED2_GPIO_CLK, ENABLE); /* LED2 GPIO配置为输出 */ GPIO_InitStructure.GPIO_Pin = LED2_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure); } static void prvSetupHardware( void ) { /* 配置时钟,PLL,FLASH */ SystemInit(); /* 配置NVIC优先级. */ NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 ); /*LED GPIO配置*/ prvLedInitialise(); } //500ms LED1灯状态翻转一次 static void led1Task( void * pvParameters ) { while (1) { LED1_GPIO_PORT->ODR ^= LED1_PIN; vTaskDelay(500); } } //1000ms LED2灯状态翻转一次 static void led2Task( void * pvParameters ) { while (1) { LED2_GPIO_PORT->ODR ^= LED2_PIN; vTaskDelay(1000); } } int main(void) { prvSetupHardware(); xTaskCreate(led1Task,'LED1' ,256,NULL,100,(TaskHandle_t *)NULL); xTaskCreate(led2Task,'LED2' ,256,NULL,101,(TaskHandle_t *)NULL); /* 启动任务调度器. */ vTaskStartScheduler(); } 5.1 右键打开options,设置C/C++编译器包含路径 $PROJECT_DIR$是一个变量,表示当前路径,不要设成绝对路径,否则拷贝到不同路径就无法编译了。 $PROJ_DIR$\ $PROJ_DIR$\hal\CMSIS\Device\ST\STM32F4xx\Include $PROJ_DIR$\hal\STM32F4xx_StdPeriph_Driver\inc $PROJ_DIR$\freeRTOS\include $PROJ_DIR$\freeRTOS\portable\IAR\ARM_CM4F $PROJ_DIR$\Common\include示当前工程目录,利用这个变量就可以设置和工程相关的路径了,不要设置为绝 对路径,否则如果工程拷贝到其他路径,就无法正确编译了。
USE_STDPERIPH_DRIVER STM32F4XX 注:如果遇到下面的错误,应该是你使用了老版本的startup_stm32f4xx.s Warning[25 ]: Label 'CAN2_SCE_IRQHandler' is defined pubweak in a section implicitly declared root
官方例子中下面路径的startup_stm32f4xx.s为老版本 CMSIS\Device\ST\STM32F4xx\Source\Templates\iar https://www./knowledge/support/technical-notes/assembler/warning25-label-xxxxx-is-defined-pubweak-in-a-section-implicitly-declared-root/ 编译运行 设置ST-Link
总结一下 官方的例子,要直接拿来开发产品有很多不需要的文件,可以按照本文的步骤做些删减,先建立这样一个基本工程,然后就可以结合自己的实际硬件以及需求,开始做应用开发了。 作者:逸珺