赞
踩
该篇环境为:STM32F103ZET6、Keil 库函数版本
F4、F7、H7移植同理。
FreeRTOS源码:
链接:https://pan.baidu.com/s/10l8TmseEJKkFdwFY3qZc1Q?pwd=8uqw
提取码:8uqw
①修改 sys.h 文件
在 sys.h 文件里面用宏 SYSTEM_SUPPORT_OS 来定义是否使用 OS,
我们使用了 FreeRTOS,所以应该将宏 SYSTEM_SUPPORT_OS 改为 1。
//0,不支持 os
//1,支持 os
#define SYSTEM_SUPPORT_OS 1 //定义系统文件夹是否支持 OS
②修改 usart.c 文件
usart.c 文件有两部分要修改,一个是添加 FreeRTOS.h 头文件,
默认是添加的 UCOS 中的 includes.h 头文件,修改以后如下:
//如果使用 os,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "FreeRTOS.h" //os 使用
#endif
另外一个就是 USART1 的中断服务函数,在使用 UCOS 的时候进出中断的时候需要添加OSIntEnter()和 OSIntExit(),使用 FreeRTOS 的话就不需要了,所以将这两行代码删除掉,修改以后如下:
void USART1_IRQHandler(void) //串口 1 中断服务程序 { u8 Res; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { Res =USART_ReceiveData(USART1); //读取接收到的数据 if((USART_RX_STA&0x8000)==0) //接收未完成 { if(USART_RX_STA&0x4000) //接收到了 0x0d { if(Res!=0x0a)USART_RX_STA=0; //接收错误,重新开始 else USART_RX_STA|=0x8000; //接收完成了 } else //还没收到 0X0D { if(Res==0x0d)USART_RX_STA|=0x4000; else { USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ; USART_RX_STA++; if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0; } } } } }
③修改 delay.c 文件
delay.c 文件修改的就比较大,因为涉及到 FreeRTOS 的系统时钟,delay.c 文件里面有 4个函数,先来看一下函数 SysTick_Handler(),此函数是滴答定时器的中断服务函数,代码如下:
extern void xPortSysTickHandler(void);
//systick 中断服务函数,使用 OS 时用到
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
{
xPortSysTickHandler();
}
}
FreeRTOS 的心跳就是由滴答定时器产生的,根据 FreeRTOS 的系统时钟节拍设置好滴答定时器的周期,这样就会周期触发滴答定时器中断了。在滴答定时器中断服务函数中调用FreeRTOS 的 API 函数 xPortSysTickHandler()。
delay_init()是用来初始化滴答定时器和延时函数,代码如下:
//初始化延迟函数 //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 }
前面我们说了 FreeRTOS 的系统时钟是由滴答定时器提供的,那么肯定要根据 FreeRTOS 的系统时钟节拍来初始化滴答定时器了,delay_init()就是来完成这个功能的。FreeRTOS 的系统时钟节拍由宏 configTICK_RATE_HZ 来设置,这个值我们可以自由设置,但是一旦设置好以后我们就要根据这个值来初始化滴答定时器,其实就是设置滴答定时器的中断周期。在基础例程中
滴答定时器的时钟频率设置的是 AHB 的 1/8,这里为了兼容 FreeRTOS 将滴答定时器的时钟频率改为了 AHB,也就是 72MHz!这一点一定要注意!
接下来的三个函数都是延时的,代码如下:
//延时 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) { //这里注意一下 SYSTICK 是一个递减的计数器就可以了. if(tnow<told)tcnt+=told-tnow; 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); }
delay_us()是 us 级延时函数,delay_ms 和 delay_xms()都是 ms 级的延时函数,delay_us()和delay_xms()不会导致任务切换。delay_ms()其实就是对 FreeRTOS 中的延时函数 vTaskDelay()的简单封装,所以在使用 delay_ms()的时候就会导致任务切换。
delay.c 修改完成以后编译一下,会提示如下图所示错误:
上图的错误提示表示在 port.c、delay.c 和 stm32f10x_it.c 中三个重复定义的函数:SysTick_Handler()、SVC_Handler()和 PendSV_Handler(),这三个函数分别为滴答定时器中断服务函数、SVC 中断服务函数和 PendSV 中断服务函数,将 stm32f10x_it.c 中的三个函数屏蔽掉,如下图所示:
至此,SYSTEM文件夹就修改完成了。
本实验设计四个任务:start_task()、led0_task ()、led1_task ()和 float_task(),这四个任务的任务功能如下:
● 任务设置
#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 *p_arg); //任务函数 #define LED1_TASK_PRIO 3 //任务优先级 #define LED1_STK_SIZE 50 //任务堆栈大小 TaskHandle_t LED1Task_Handler; //任务句柄 void led1_task(void *p_arg); //任务函数
● main()函数
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); } }
测试代码中创建了 3 个任务:LED0 测试任务、LED1 测试任务和浮点测试任务,它们的任务函数分别为:led0_task()、led1_task()。led0_task()和 led1_task()任务很简单,就是让 LED0 和LED1 周期性闪烁。
链接:https://pan.baidu.com/s/15LhxGXTk2i4gcQ2ElNk-qQ?pwd=xck4
提取码:xck4
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。