2.1 准备工作
首先准备一个简单的基础工作;选用基础例程中的跑马灯来作为基础工程
FreeRTOS的源码使用V9.0.0版本
2.2 FreeRTOS移植
2.2.1向工程中添加相应的文件
在基础工程中新建一个FreeRTOS的文件夹。将FreeRTOSv9.0.0\FreeRTOS\Source下的所有文件复制到该文件夹下面。完成后如下图所示:
再打开portable文件夹。只保留keil、MemMang、RVDS三个文件夹。将其他的内容均删除掉。完成后portable文件夹如下图所示
接下来打开工程,新建两个分组。分别为FreeRTOS_CORE和FreeRTOS_PORTABLE。向FreeRTOS_CORE分组中添加FreeRTOS里面的所有文件。向FreeRTOS_PORTABLE分组中添加portable/MemMang/heap_4.c和portable/RVDS/ARM_CM3/port.c
然后在工程中添加FreeRTOS源码的头文件路径
到目前为止,我们还缺少一个FreeRTOSConfig.h的文件。这个文件有3中解决办法,1、自己创建,2、在FreeRTOS的官方移植工程中拷贝,3、从正点原子教程的Demo中拷贝。我选择第三种。
到此工程可以正常编译。工程所需要的文件均已添加。
2.2.2 修改SYSTEM文件
- 在sys.h文件中将将宏 SYSTEM_SUPPORT_OS 改为 1
- usart.c文件中添加FreeRTOS.h头文件。另在删除掉串口1的中断函数中的OSIntEnter()和 OSIntExit()
delay.c文件直接重写。delay.h中增加对void delay_xms(u32 nms);的声明
#include "delay.h" #include "sys.h" ////////////////////////////////////////////////////////////////////////////////// //如果需要使用OS,则包括下面的头文件即可. #if SYSTEM_SUPPORT_OS #include "FreeRTOS.h" //FreeRTOS使用 #include "task.h" #endif static u8 fac_us=0; //us延时倍乘数 static u16 fac_ms=0; //ms延时倍乘数,在ucos下,代表每个节拍的ms数 extern void xPortSysTickHandler(void); //systick中断服务函数,使用ucos时用到 void SysTick_Handler(void) { if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行 { xPortSysTickHandler(); } } //初始化延迟函数 //SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/8 //这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率! //SYSCLK:系统时钟频率 void delay_init() { u32 reload; SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);//选择外部时钟 HCLK fac_us=SystemCoreClock/1000000; //不论是否使用OS,fac_us都需要使用 reload=SystemCoreClock/1000000; //每秒钟的计数次数 单位为M reload*=1000000/configTICK_RATE_HZ; //根据configTICK_RATE_HZ设定溢出时间 //reload为24位寄存器,最大值:16777216,在72M下,约合0.233s左右 fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位 SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断 SysTick->LOAD=reload; //每1/configTICK_RATE_HZ秒中断一次 SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK } //延时nus //nus:要延时的us数. //nus:0~204522252(最大值即2^32/fac_us@fac_us=168) void delay_us(u32 nus) { u32 ticks; u32 told,tnow,tcnt=0; u32 reload=SysTick->LOAD; //LOAD的值 ticks=nus*fac_us; //需要的节拍数 told=SysTick->VAL; //刚进入时的计数器值 while(1) { tnow=SysTick->VAL; if(tnow!=told) { if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了. else tcnt+=reload-tnow+told; told=tnow; if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出. } }; } //延时nms //nms:要延时的ms数 //nms:0~65535 void delay_ms(u32 nms) { if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行 { if(nms>=fac_ms) //延时的时间大于OS的最少时间周期 { vTaskDelay(nms/fac_ms); //FreeRTOS延时 } nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时 } delay_us((u32)(nms*1000)); //普通方式延时 } //延时nms,不会引起任务调度 //nms:要延时的ms数 void delay_xms(u32 nms) { u32 i; for(i=0;i<nms;i++) delay_us(1000); }
最后可以看到三个报错信息,提示SysTick_Handler()、 SVC_Handler()和 PendSV_Handler()在项目中存在多出定义。
SysTick_Handler()滴答定时器中断在delay.c中实现。需要屏蔽掉stm32f10x_it.c中的实现。这个中断函数主要是为FreeRTOS系统提供系统节拍
SVC_Handler()是一种特殊得到软件中断,SVC中断是由软件指令触发的,通常用于系统调用。他的中断函数由FreeRTOS系统接管。在port.c中已实现。需要在stm32f10x_it.c中屏蔽掉重复的实现函数。SVC中断是操作系统与用户程序之间交互的桥梁。通过SVC中断,用户程序可以请求操作系统执行各种服务,如文件操作、进程管理、内存分配等。
PendSV_Handler(),主要职责是在任务切换时保存当前任务的上下文,并恢复下一个要运行的任务的上下文。具体来说,PendSV_Handler()可能会执行以下操作:1.保存当前任务的上下文;2.更新任务状态;3.恢复下一个任务的上下文;4.返回。他的中断函数由FreeRTOS系统接管。在port.c中已实现。需要在stm32f10x_it.c中屏蔽掉重复的实现函数。2.3 移植验证实验
2.3.1 实验程序设计
在实验工程中启动4个任务。分别为start_task()、 led0_task ()、 led1_task ();
- start_task():用来创建其他三个任务
- led0_task ():控制 LED0 的闪烁,提示系统正在运行
led1_task ():控制 LED1 的闪烁。
#include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" #include "FreeRTOS.h" #include "task.h" #define START_TASK_PRIO 1 //任务优先级 #define START_STK_SIZE 128 //任务堆栈大小 TaskHandle_t StartTask_Handler; //任务句柄 void start_task(void *pvParameters); //任务函数声明 #define LED0_TASK_PRIO 2 //任务优先级 #define LED0_STK_SIZE 50 //任务堆栈大小 TaskHandle_t LED0Task_Handler; //任务句柄 void led0_task(void *pvParameters); //任务函数声明 #define LED1_TASK_PRIO 3 //任务优先级 #define LED1_STK_SIZE 50 //任务堆栈大小 TaskHandle_t LED1Task_Handler; //任务句柄 void led1_task(void *pvParameters); //任务函数声明 int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4 delay_init(); //延时函数初始化 uart_init(115200); //初始化串口 LED_Init(); //LED外设初始化 //创建开始任务 xTaskCreate( (TaskFunction_t)start_task, //任务函数 (const char*)"start_task", //任务名称 (uint16_t)START_STK_SIZE, //任务堆栈大小 (void*)NULL, //传递给任务函数的参数 (UBaseType_t)START_TASK_PRIO, //任务优先级 (TaskHandle_t)&StartTask_Handler //任务句柄 ); vTaskStartScheduler(); //开启任务调度 } //开始任务 任务函数 void start_task(void *pvParameters) { taskENTER_CRITICAL(); //进入临界区 //创建LED0任务 xTaskCreate( (TaskFunction_t)led0_task, //任务函数 (const char*)"led0_task", //任务名称 (uint16_t)LED0_STK_SIZE, //任务堆栈大小 (void*)NULL, //传递给任务函数的参数 (UBaseType_t)LED0_TASK_PRIO, //任务优先级 (TaskHandle_t)&LED0Task_Handler //任务句柄 ); //创建LED1任务 xTaskCreate( (TaskFunction_t)led1_task, //任务函数 (const char*)"led1_task", //任务名称 (uint16_t)LED1_STK_SIZE, //任务堆栈大小 (void*)NULL, //传递给任务函数的参数 (UBaseType_t)LED1_TASK_PRIO, //任务优先级 (TaskHandle_t)&LED1Task_Handler //任务句柄 ); vTaskDelete(StartTask_Handler); //删除开始任务 taskEXIT_CRITICAL(); //退出临界区 } //LED0任务函数 void led0_task(void *pvParameters) { while(1){ LED0 = ~LED0; vTaskDelay(500); } } //LED1任务函数 void led1_task(void *pvParameters) { while(1){ LED1 = 0; vTaskDelay(200); LED1 = 1; vTaskDelay(800); } }
2.3.2 实验程序运行结果分析
LED0 和 LED1 开始闪烁。LED0 均匀闪烁,LED1 亮的时间短,灭的时间长。
评论 (0)