FreeRTOS移植
Keil MDK创建项目
首先在PC上创建FreeRTOS项目文件夹目录树如下,其中:
- Board文件夹用来存放开发板硬件操作的相关C文件,如led、串口等;
- CMSIS文件夹存放CortexM系列微控制器软件接口标准相关C文件;
- FreeRTOS文件夹存放FreeRTOS操作系统相关的C文件;
- Output文件夹存放编译输出的文件;
- Stdlib文件夹存放STM32的标准库C文件;
接下来将STM32L的标准库和FreeRTOS里的相关源码C文件添加相应的项目路径下:
把STM32L151的标准库STM32L1xx_StdPeriph_Lib_V1.3.1\Libraries\STM32L1xx_StdPeriph_Driver里的inc和src两个文件夹拷贝到Stdlib文件夹下;
将STM32L151的标注库STM32L1xx_StdPeriph_Lib_V1.3.1\Libraries\CMSIS\Device\ST\STM32L1xx\Source\Templates\arm\startup_stm32l1xx_md.s文件拷贝到CMSIS文件夹下;
将STM32L151的标准库STM32L1xx_StdPeriph_Lib_V1.3.1\Libraries\CMSIS\Device\ST\STM32L1xx\Include里的stm32l1xx.h、system_stm32l1xx.h等文件拷贝到CMSIS文件夹下;
将STM32L151的标准库STM32L1xx_StdPeriph_Lib_V1.3.1\Libraries\CMSIS\Device\ST\STM32L1xx\Source\Templates\system_stm32l1xx.c文件拷贝到CMSIS文件夹下;
将STM32L151的标准库STM32L1xx_StdPeriph_Lib_V1.3.1\Project\STM32L1xx_StdPeriph_Templates\stm32l1xx_conf.h文件拷贝到CMSIS文件夹下;
将Keil安装路径下的D:\Program Files\Keil\ARM\PACK\ARM\CMSIS\5.4.0\CMSIS\Core\Include里的cmsis_armcc.h、cmsis_compiler.h、cmsis_version.h、core_cm3.h、mpu_armv7.h等文件拷贝到CMSIS文件夹下:
将FreeRTOSv10.1.1\FreeRTOS\Source\include文件夹拷贝FreeRTOS文件夹下;
将FreeRTOSv10.1.1\FreeRTOS\Source中的list.c、queue.c和tasks.c拷贝到FreeRTOS文件夹下;
将FreeRTOSv10.1.1\FreeRTOS\Source\portable\RVDS\ARM_CM3\port.c拷贝到FreeRTOS文件夹下;
将FreeRTOSv10.1.1\FreeRTOS\Source\portable\MemMang\heap_4.c拷贝到FreeRTOS文件夹下;
将FreeRTOSv10.1.1\FreeRTOS\Demo\CORTEX_STM32L152_IAR\FreeRTOSConfig.h拷贝到FreeRTOS文件夹
使用Keil MDK创建STM32L151的FreeRTOS的项目,创建CMSIS、Stdlib、Board、FreeRTOS、Main五个Group并将之前的源码添加到MDK项目的相应组里。
修改启动代码:
SVC(系统服务调用)和 PendSV(可悬起系统调用)多用于在操作系统的软件开发中。 SVC 用于产生系统函数的调用请求,例如,操作系统不让用户程序直接访问硬件,而是通过提供一些系统服务函数,用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。因此,当用户程序想要控制特定的硬件时,它就会产生一个 SVC 异常,然后操作系统提供的 SVC 异常服务例程得到执行,它再调用相关的操作系统函数,后者完成用户程序请求的服务。
另一个相关的异常是 PendSV,他的一个典型使用场合是和SVC协同合作完成不同任务之间切换(上下文切换)。一方面, SVC异常是必须立即得到响应的(若因优先级不比当前正处理的高, 或是其它原因使之无法立即响应, 将上访成硬 fault),应用程序执行 SVC 时都是希望所需的请求立即得到响应。另一方面, PendSV 则不同,它是可以像普通的中断一样被悬起的(不像 SVC 那样会上访)。 OS 可以利用它“缓期执行” 一个异常——直到其它重要的任务完成后才执行动作。
操作系统进行运转的时候,都会需要一个定义时间的系统“滴答”。它会根据“滴答”的节拍来工作,把整个时间段分成很多小小的时间片,每个任务每次只能运行一个“时间片”的时间长度就得退出给别的任务运行,这样可以确保任何一个任务都不会霸占整个系统。此外操作系统所提供的各种定时功能,都与这个系统滴答有关。因此,需要一个定时器用来周期性地产生中断提供定时,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。 ARM CortexM3的内核在设计的时候,专门设计了一个系统定时器外设Systick,内嵌在NVIC中。它是一个24位的向下递减的计数器,计数器每记数一次的时间为1/SYSCLK,当重装载寄存器的值递减到0时系统就会产生Systick中断,以此循环反复进行,这样就可以为操作系统提供系统滴答服务。因为他是CortexM3内核里的一个外设,所以所有基于CortexM3内核的MCU都具有该功能。
因为SVC、PendSV和Systick这三个中断功能对于操作系统的设计非常需要,所以FreeRTOS在port.c文件中分别重新定义了这三个中断服务处理程序,这时我们在启动代码startup_stm32l1xx_md.s中用他们替换STM32标准库里提供的相应函数,即将 SVC_Handler修改改成vPortSVCHandler, 将PendSV_Handler改成xPortPendSVHandler ,将SysTick_Handler改成xPortSysTickHandler:。
修改CMSIS下的startup_stm32l1xx_md.s文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
... ... DCD vPortSVCHandler ; SVCall Handler DCD DebugMon_Handler ; Debug Monitor Handler DCD 0 ; Reserved DCD xPortPendSVHandler ; PendSV Handler DCD xPortSysTickHandler ; SysTick Handler ... ... vPortSVCHandler PROC EXPORT vPortSVCHandler [WEAK] B . ENDP DebugMon_Handler\ PROC EXPORT DebugMon_Handler [WEAK] B . ENDP xPortPendSVHandler PROC EXPORT xPortPendSVHandler [WEAK] B . ENDP xPortSysTickHandler PROC EXPORT xPortSysTickHandler [WEAK] B . ENDP ... ... |
FreeRTOS内核配置
FreeRTOS内核是高度可定制的,使用配置文件FreeRTOSConfig.h进行定制。每个FreeRTOS应用都必须包含这个头文件,用户根据实际应用来裁剪定制FreeRTOS内核。这个配置文件是针对用户程序的,而非内核,因此配置文件一般放在应用程序目录下,不要放在RTOS内核源码目录下。在下载的FreeRTOS文件包中,每个演示例程都有一个FreeRTOSConfig.h文件。有些例程的配置文件是比较旧的版本,可能不会包含所有有效选项。如果没有在配置文件中指定某个选项,那么RTOS内核会使用默认值。之前我们已经将STM32L152_IAR Demon项目中的配置文件拷贝到了项目中,下面是这个典型的FreeRTOSConfig.h配置文件注释说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
#ifndef FREERTOS_CONFIG_H #define FREERTOS_CONFIG_H /*----------------------------------------------------------- * Application specific definitions. * * These definitions should be adjusted for your particular hardware and * application requirements. * * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE. * * See http://www.freertos.org/a00110.html *----------------------------------------------------------*/ /* 为1时RTOS使用抢占式调度器,为0时RTOS使用时间片调度器*/ #define configUSE_PREEMPTION 1 /* 当RTOS调度器开始工作后,为了保证至少有一个任务在运行,空闲任务被自动创建,占用最低优先级。 设置为1使用空闲钩子函数,0忽略空闲钩子函数。 */ #define configUSE_IDLE_HOOK 1 /* 时间片钩子函数可以很方便的实现一个定时器功能。设置为1使用时间片钩子函数,0忽略时间片钩子函数。 */ #define configUSE_TICK_HOOK 0 /* 写入实际的CPU内核时钟频率,也就是CPU指令执行频率,通常称为Fcclk。配置此值是为了正确的配置系统节拍中断周期。 */ #define configCPU_CLOCK_HZ ( 32000000UL ) /* RTOS 系统节拍中断的频率。即一秒中断的次数,每次中断RTOS都会进行任务调度。 */ #define configTICK_RATE_HZ ( ( TickType_t ) 1000 ) /* 配置应用程序有效的优先级数目。任何数量的任务都可以共享一个优先级,使用协程可以单独的给与它们优先权。 在RTOS内核中,每个有效优先级都会消耗一定量的RAM,因此这个值不要超过你的应用实际需要的优先级数目。*/ #define configMAX_PRIORITIES ( 5 ) /* 定义空闲任务使用的堆栈大小,以字而不是字节为单位。通常此值不应小于对应处理器演示例程文件FreeRTOSConfig.h中定义的数值。*/ #define configMINIMAL_STACK_SIZE ( ( unsigned short ) 70 ) /* 指定malloc()分配的堆的大小。 每当创建任务、队列、互斥量、软件定时器或信号量时,RTOS内核会为此分配RAM,这里的RAM都属于堆 */ #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 10 * 1024 ) ) /* 调用任务函数时,需要设置描述任务信息的字符串,这个宏用来定义该字符串的最大长度。这里定义的长度包括字符串结束符’\0’。 */ #define configMAX_TASK_NAME_LEN ( 16 ) /* 设置成1表示启动可视化跟踪调试,会激活一些附加的结构体成员和函数。 */ #define configUSE_TRACE_FACILITY 1 /* 定义系统节拍计数器的变量类型,即定义portTickType是表示16位变量还是32位变量。0表示32位无符号整形,1表示16位无符号整形 */ #define configUSE_16_BIT_TICKS 0 /* 这个参数控制任务在空闲优先级中的行为。 */ #define configIDLE_SHOULD_YIELD 1 /* 设置为1表示使用互斥量,设置成0表示忽略互斥量。 */ #define configUSE_MUTEXES 1 /* 定义可以记录的队列和信号量的最大数目。*/ #define configQUEUE_REGISTRY_SIZE 5 /* 为不同的常量来使用不同堆栈溢出检测机制。 */ #define configCHECK_FOR_STACK_OVERFLOW 2 /* 设置成1表示使用递归互斥量,设置成0表示不使用。 */ #define configUSE_RECURSIVE_MUTEXES 0 /* 该宏设置为1,那么必须定义一个malloc()失败钩子函数,如果宏设置为0,malloc()失败钩子函数不会被调用,即便已经定义了这个函数。*/ #define configUSE_MALLOC_FAILED_HOOK 1 /* 使能任务标签功能 */ #define configUSE_APPLICATION_TASK_TAG 0 /* Co-routine definitions. */ #define configUSE_CO_ROUTINES 0 #define configMAX_CO_ROUTINE_PRIORITIES ( 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 0 #define INCLUDE_vTaskSuspend 1 #define INCLUDE_vTaskDelayUntil 1 #define INCLUDE_vTaskDelay 1 /* Use the system definition, if there is one */ #ifdef __NVIC_PRIO_BITS #define configPRIO_BITS __NVIC_PRIO_BITS #else #define configPRIO_BITS 4 /* 15 priority levels */ #endif #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 /* The lowest priority. */ #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) /* Priority 5, or 95 as only the top four bits are implemented. */ /* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!! See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) /* Prevent the following definitions being included when FreeRTOSConfig.h is included from an asm file. */ #if (configUSE_TICK_HOOK == 1 ) #include "stm32l1xx_tim.h" extern void vConfigureTimerForRunTimeStats( void ); extern unsigned long ulTIM6_OverflowCount; /* Run time stats related macros. */ #define configGENERATE_RUN_TIME_STATS 1 #define configUSE_STATS_FORMATTING_FUNCTIONS 1 #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() vConfigureTimerForRunTimeStats() #define portALT_GET_RUN_TIME_COUNTER_VALUE( ulCountValue ) \ { \ TIM_Cmd( TIM6, DISABLE ); \ ulCountValue = ( ( ulTIM6_OverflowCount << 16UL ) | ( unsigned long ) TIM6->CNT ); \ TIM_Cmd( TIM6, ENABLE ); \ } #endif // (configUSE_TICK_HOOK == 1 ) #endif /* FREERTOS_CONFIG_H */ |
FreeRTOS运行需要的一些钩子函数
如果你在FreeRTOSConfig.h中定义了宏configUSE_MALLOC_FAILED_HOOK,则需要提供malloc失败的钩子函数vApplicationMallocFailedHook();如果定义了宏configUSE_IDLE_HOOK则需要提供钩子函数vApplicationIdleHook(),如果定义了宏configCHECK_FOR_STACK_OVERFLOW则需要提供钩子函数vApplicationStackOverflowHook();如果设置了configUSE_TICK_HOOK=1,则必须编写voidvApplicationTickHook( void )函数,该函数可以利用时间片中断,可以很方便的实现一个定时器功能。
这时我们在Board路径下创建提供这些钩子函数的C文件stm32_vport.c和相应头文件stm32_vport.h提供这些钩子函数,同时在该C文件中也定义prvSetupHardware()函数用来初始化相关硬件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
/**************************************************************************** * 功能描述: FreeRTOS移植所需要的一些钩子函数,由头文件FreeRTOSConfig.h 控制 ****************************************************************************/ #include "stm32l1xx.h" #include "stm32_vport.h" void prvSetupHardware( void ) { init_led_gpio(); stm32_init_printf(USART_PORT1, 115200); } /* FreeRTOSConfig.h 中如果定义了宏 configUSE_MALLOC_FAILED_HOOK 则需要该函数的实现 */ void vApplicationMallocFailedHook( void ) { /* Called if a call to pvPortMalloc() fails because there is insufficient free memory available in the FreeRTOS heap. pvPortMalloc() is called internally by FreeRTOS API functions that create tasks, queues or semaphores. */ for( ;; ); } /* FreeRTOSConfig.h 中如果定义了宏 configUSE_IDLE_HOOK 则需要该函数的实现 */ void vApplicationIdleHook( void ) { /* Called on each iteration of the idle task. In this case the idle task just enters a low(ish) power mode. */ PWR_EnterSleepMode( PWR_Regulator_ON, PWR_SLEEPEntry_WFI ); } /* FreeRTOSConfig.h 中如果定义了宏 configCHECK_FOR_STACK_OVERFLOW 则需要该函数的实现 */ void vApplicationStackOverflowHook( TaskHandle_t pxTask, char *pcTaskName ) { ( void ) pcTaskName; ( void ) pxTask; /* Run time stack overflow checking is performed if configconfigCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook function is called if a stack overflow is detected. */ for( ;; ); } /**************************************************************************** * Description: FreeRTOS移植所需要的一些钩子函数,由头文件FreeRTOSConfig.h 控制 ****************************************************************************/ #ifndef __STM32_VPORT_H_ #define __STM32_VPORT_H_ #include "stm32_led.h" #include "stm32_usart.h" #include "FreeRTOS.h" #include "task.h" void prvSetupHardware( void ); void vApplicationMallocFailedHook( void ); void vApplicationIdleHook( void ); void vApplicationStackOverflowHook( TaskHandle_t pxTask, char *pcTaskName ); #endif |
初始化和操作LED的源码
在Board路径下创建并添加LED初始化和操作的C文件stm32_led.c 和 头文件 stm32_led.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
/**************************************************************************** * 功能描述: STM32L151 初始化和操作LED函数C文件 ****************************************************************************/ #include "stm32l1xx.h" #include "stm32_led.h" /* STM32L151C8T6 CubeMX 开发板只有一个GPIO控制的 Green LED, 红色的LED是电源指示灯 */ led_gpio_t leds_gpio[MAX_LED] = { {LED1, GPIOB, GPIO_Pin_1}, /* LED_Green 用的GPB1 */ }; /* 函数说明: 配置 LED GPIO口和时钟; * 参数说明: 无 * 返回值: 无 */ void init_led_gpio(void) { int i; GPIO_InitTypeDef GPIO_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); /* 使能LED1所在的GPIO分组的时钟 */ /*初始化所有的LED GPIO口和时钟 */ for(i=0; i<MAX_LED; i++) { GPIO_InitStructure.GPIO_Pin = leds_gpio[i].pin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_40MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(leds_gpio[i].group, &GPIO_InitStructure); } } /* 函数说明: 点亮或灭掉相应LED * 参数说明: which: 要操作哪个LED,其值应该为 LED1、LED2 或 LED3? * cmd: 要亮还是灭, 其值对应为 ON 或 OFF * 返回值: 无 */ void turn_led(int which, int cmd) { if(which<0 || which> MAX_LED ) return; if(OFF == cmd) GPIO_SetBits(leds_gpio[which].group, leds_gpio[which].pin); else GPIO_ResetBits(leds_gpio[which].group, leds_gpio[which].pin); } /**************************************************************************** * Description: STM32L151 初始化和操作LED函数头文件 ****************************************************************************/ #ifndef __STM32_LED_H_ #define __STM32_LED_H_ #include "stm32l1xx.h" /* LED操作的 开关宏 */ #define ON 1 #define OFF 0 /* LED的编号定义 */ enum { LED1 = 0, MAX_LED, }; typedef struct led_gpio_s { int num; /* LED编号 */ GPIO_TypeDef *group; /* LED使用的GPIO在哪一组: GPIOB or GPIOD */ uint16_t pin; /* LED使用的GPIO组中的那一个pin: GPIO_Pin_x */ } led_gpio_t; /* 函数说明: 配置 LED GPIO口和时钟; * 参数说明: 无 * 返回值: 无 */ extern void init_led_gpio(void); /* 函数说明: 点亮或灭掉相应LED * 参数说明: which: 要操作哪个LED,其值应该为 LED1、LED2 或 LED3? * cmd: 要亮还是灭, 其值对应为 ON 或 OFF * 返回值: 无 */ extern void turn_led(int which, int cmd); #endif |
初始化调试串口和实现printf的源码
在Board路径下创建并添加串口初始化和printf实现的C文件stm32_usart.c 和 头文件stm32_usart.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
/**************************************************************************** * Description: STM32L151 初始化调试串口(未使用中断)和实现printf的源码C文件 ****************************************************************************/ #include <stdio.h> #include "stm32_usart.h" static USART_TypeDef* debug_usart=USART1; /* 默认使用串口1作为调试串口 */ /* 开发板上的各个串口所使用的串口 管脚、时钟定义 */ stm32_usart_pins_t usart_pins[USART_MAX] = { /* USARTx GPIOx Tx_Pin Rx_Pin Tx_Pin_source Rx_Pin_Source GPIO_AF_USARTx USART_Clock TxRx_GPIO_Clock */ {USART1, GPIOA, GPIO_Pin_9, GPIO_Pin_10, GPIO_PinSource9, GPIO_PinSource10, GPIO_AF_USART1, RCC_APB2Periph_USART1, RCC_AHBPeriph_GPIOA}, {USART2, GPIOA, GPIO_Pin_2, GPIO_Pin_3, GPIO_PinSource2, GPIO_PinSource3, GPIO_AF_USART2, RCC_APB1Periph_USART2, RCC_AHBPeriph_GPIOA}, {USART3, GPIOB, GPIO_Pin_10, GPIO_Pin_11, GPIO_PinSource10, GPIO_PinSource11, GPIO_AF_USART3, RCC_APB1Periph_USART3, RCC_AHBPeriph_GPIOB}, }; /* 函数说明: 配置串口GPIO口和时钟; * 参数说明: which指定要配置的串口,其值应该为 USART_PORT1、USART_PORT2、USART_PORT3,定义在stm32_usart.h中; * 返回值: 无 */ void init_usart_gpio(uint8_t which) { GPIO_InitTypeDef GPIO_InitStructure; if( which >= USART_MAX ) return ; /* Enable USART GPIO and USART clock */ RCC_AHBPeriphClockCmd(usart_pins[which].gpio_clk, ENABLE); if( USART_PORT1 == which ) RCC_APB2PeriphClockCmd(usart_pins[which].usart_clk, ENABLE); else RCC_APB1PeriphClockCmd(usart_pins[which].usart_clk, ENABLE); /* Connect PXx to USARTx_Tx, PXx to USARTx_Rx */ GPIO_PinAFConfig(usart_pins[which].group, usart_pins[which].txpin_src, usart_pins[which].gpio_af); GPIO_PinAFConfig(usart_pins[which].group, usart_pins[which].rxpin_src, usart_pins[which].gpio_af); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_40MHz; /* Configure USART Tx as alternate function push-pull */ GPIO_InitStructure.GPIO_Pin = usart_pins[which].txpin; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(usart_pins[which].group, &GPIO_InitStructure); /* Configure USART1 Rx as input floating */ GPIO_InitStructure.GPIO_Pin = usart_pins[which].rxpin; GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(usart_pins[which].group, &GPIO_InitStructure); } /* 函数说明: 配置串口的波特率、数据位、奇偶校验位、停止位、流控等; * 参数说明: which指定要配置的串口,其值应该为 USART_PORT1、USART_PORT2、USART_PORT3,定义在stm32_usart.h中; * baudrate: 波特率,其值为: 115200,9600,4800,2400,1200.... * 返回值: 无 */ void stm32_init_usart(uint8_t which, uint32_t baudrate) { USART_InitTypeDef USART_InitStructure; if( which >= USART_MAX ) return ; init_usart_gpio(which); /* Configure USART1 */ USART_InitStructure.USART_BaudRate = baudrate; //波特率 USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据位8位 USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位1位 USART_InitStructure.USART_Parity = USART_Parity_No; //无校验位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流控 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式 USART_Init(usart_pins[which].USARTx, &USART_InitStructure); //配置串口参数函数 USART_Cmd(usart_pins[which].USARTx, ENABLE); /* 使能相应的串口 */ } /* 函数说明: 初始化调试串口,并指定相应的串口为printf函数的输出串口 * 参数说明: which指定要配置的串口,其值应该为 USART_PORT1、USART_PORT2、USART_PORT3,定义在stm32_usart.h中; * baudrate: 波特率,其值为: 115200,9600,4800,2400,1200.... * 返回值: 无 */ void stm32_init_printf(uint8_t which, uint32_t baudrate) { if( which >= USART_MAX ) return ; debug_usart = usart_pins[which].USARTx; /* 初始化 USART1的GPIO口 */ stm32_init_usart(which, baudrate); /* 输出一个回车, secureCRT软件操作串口的时候需要 */ USART_PUTCHR(debug_usart, '\n'); } /* 函数说明: printf函数实现的钩子函数 * 参数说明: ch: 要发送的字符 FILE *f: 未使用 * 返回值: 发送的字符 */ int fputc(int ch, FILE *f) { /* windows下换行符是\r\n,如果碰到\n则在前面添加\r */ if('\n' == ch) { USART_PUTCHR(debug_usart, '\r'); } USART_PUTCHR(debug_usart, ch); return ch; } /**************************************************************************** * 功能描述: STM32L151 初始化调试串口(未使用中断)和实现printf的源码头文件 ****************************************************************************/ #ifndef __STM32_USART_H_ #define __STM32_USART_H_ #include "stm32l1xx_usart.h" #define USART_PUTCHR(COM, ch) { USART_SendData(COM, ch); while (USART_GetFlagStatus(COM, USART_FLAG_TC) == RESET) ; } enum { USART_PORT1, USART_PORT2, USART_PORT3, USART_MAX, }; typedef struct stm32_usart_pins_s { USART_TypeDef *USARTx; /* USART1、USART2、USART3 */ GPIO_TypeDef *group; /* GPIOx: GPIOA、GPIOB */ uint16_t txpin; /* GPIO_Pin: GPIO_Pin_2 */ uint16_t rxpin; /* GPIO_Pin: GPIO_Pin_3 */ uint16_t txpin_src; /* GPIO_PinSource: GPIO_PinSource2 */ uint16_t rxpin_src; /* GPIO_PinSource: GPIO_PinSource3 */ uint8_t gpio_af; /* GPIO_AF: GPIO_AF_USART1*/ uint32_t usart_clk; /* USART clock: RCC_APB1Periph_USART1 */ uint16_t gpio_clk; /* USART Tx/Rx Pin Clock: RCC_AHBPeriph_GPIOA */ } stm32_usart_pins_t; /* 函数说明: 配置串口的波特率、数据位、奇偶校验位、停止位、流控等; * 参数说明: which指定要配置的串口,其值应该为 USART_PORT1、USART_PORT2、USART_PORT3,定义在stm32_usart.h中; * baudrate: 波特率,其值为: 115200,9600,4800,2400,1200.... * 返回值: 无 */ extern void stm32_init_usart(uint8_t which, uint32_t baudrate); /* 函数说明: 初始化调试串口,并指定相应的串口为printf函数的输出串口 * 参数说明: which指定要配置的串口,其值应该为 USART_PORT1、USART_PORT2、USART_PORT3,定义在stm32_usart.h中; * baudrate: 波特率,其值为: 115200,9600,4800,2400,1200.... * 返回值: 无 */ extern void stm32_init_printf(uint8_t which, uint32_t baudrate); /* 函数说明: 往调试串口发送字符串 * 参数说明: str 指向要发送的字符串内容 * 返回值: 无 */ extern void usart_puts(const char *str); #endif |
FreeRTOS入口main程序:
在准备好上面的源码文件之后,我们接下来编写整个项目的入口main函数并添加到Keil MDK项目中去,在这里我们首先初始化相应的硬件(主要是LED和调试串口),之后调用xTaskCreate()创建了两个任务vLEDFlashTask和vSerialPutsTask,其中vLEDFlashTask让LED每隔250ms亮灭一次,而vSerialPutsTask则每隔10s在串口上输出一次。具体源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
/**************************************************************************** * 功能描述: FreeRTOS 操作系统入口main()函数 ****************************************************************************/ #include <stdio.h> #include "stm32_vport.h" #define mainFLASH_TASK_PRIORITY ( tskIDLE_PRIORITY + 2 ) #define ledSTACK_SIZE configMINIMAL_STACK_SIZE static void vLEDFlashTask( void * pvParameters ); static void vSerialPutsTask( void *pvParameters ); int main(void) { prvSetupHardware(); printf("STM32L151C8T6A CubeMX Board FreeRTOS Start Running.\n"); printf("Create LED Flash task now.\n"); xTaskCreate( vLEDFlashTask, "LED", ledSTACK_SIZE, ( void * ) NULL, mainFLASH_TASK_PRIORITY, ( TaskHandle_t * ) NULL ); printf("Create Serial puts task now.\n"); xTaskCreate( vSerialPutsTask, "TTY", ledSTACK_SIZE, ( void * ) NULL, mainFLASH_TASK_PRIORITY, ( TaskHandle_t * ) NULL ); printf("FreeRTOS Task Scheduler Start...\n"); vTaskStartScheduler(); /* !!! Program will stuck in vTaskStartScheduler() and never comes here !!!*/ printf("FreeRTOS Task Scheduler End\n"); while(1); } static void vLEDFlashTask( void *pvParameters ) { const TickType_t xFlashRate = 125/ portTICK_PERIOD_MS; for(;;) { vTaskDelay( xFlashRate ); turn_led(LED1, ON); vTaskDelay( xFlashRate ); turn_led(LED1, OFF); } } static void vSerialPutsTask( void *pvParameters ) { const TickType_t xFlashRate = 10000/ portTICK_PERIOD_MS; for(;;) { printf("Serial Puts Task Continue Running...\n"); vTaskDelay( xFlashRate ); } } |
编译运行
项目创建并添加好源码之后,接下来我们需要对项目进行一些配置就可以编译、运行了:
因为使用了printf,所以我们要在项目配置中要使能 “Use MicroLIB”:
选着编译输出的文件到Output文件夹中:
添加两个必须的宏定义STM32L1XX_MD,USE_STDPERIPH_DRIVER和所有的头文件所在的路径
因为现在已经开始运行FreeRTOS操作系统,系统上电后被操作系统接管。这时如果使用ST-Link来烧录的时候会出现“Internal command error”错误,这时我们在Debug里设置Connect时“With Pre-reset”就可以解决ST-Link不能烧录的问题了。
接下来编译整个项目并调试运行,会发现串口上每隔10s收到开发板的打印消息,以及LED每隔250ms各亮灭一次,验证了FreeRTOS的两个任务调度运行成功。
文章评论