裸机开发感觉实时性不好的时候,就开始要学实时操作系统了,这里讲的是FreeRTOS。
更多人先学的ucos,他资料多。但是ucos不是免费的,所以工作用的更多的是freertos。
freeRTOS的任务在很短的时间内多次切换(1运行1/1000秒,2再运行1/1000秒,循环)使得多个任务几乎是同时进行的,没有一个轮询方式一个进行完了才能进行下一个的问题。
freertos中任务有三种调度方式;抢占式调度,时间片调度,协程式调度
抢占式调度(不同优先级):优先级高的当可以运行的时候,低优先级任务会被抢占;当高优先级任务阻塞了(用了延时函数或等待信号量),高优先级阻塞,运行低优先级任务
时间片调度(同优先级):一个时间片运行任务1,一个时间片运行任务2这样循环进行
任务有四种状态:
运行态:任务正在运行
就绪态:任务可以被运行,但还没运行
阻塞态:任务在延时或在等待外部事件发生
挂起态:类似暂停,用函数实现,vTaskSuspend()挂起函数,vTaskResume()来解除挂起。
可以看到运行态可以变为另外三种状态
但是想变成运行态, 只能通过就绪态来转变。
stm32cubeMX直接生成
直接看图
选择外部时钟并配置时钟树
如果使用外部时钟的话是这个图,如果不是用外部时钟就按你自己的配置
定时器设置
freeRTOS设置
FreeRTOS在使用时总会需要裁剪掉不需要的内容,使资源不被浪费,在stm32CUBEMX中,直接就有各种裁剪的选项
配置参数
下面写一些配置,图片写不开,我写在下面
另外如果只是基本使用,可以直接用默认值就行
箭头这几个都是默认值,注意一下就行
这里按照顺序说一下都是什么
USE_PREEMPTION:用于配置FreeRTOS任务调度器的抢占式行为;如果定义了USE_PREEMPTION宏,则FreeRTOS任务调度器将启用任务抢占。这意味着如果具有更高优先级的任务就绪,它可以中断当前正在运行的任务,并开始执行具有更高优先级的任务。【总之要高优先级的任务抢占低优先级的就得使能他】
TICK_RATE_HZ:用于指定系统的节拍;比如设置1000,系统每秒会产生1000个节拍,这意味着任务等待和定时器超时等操作的最小时间间隔将为1毫秒。【用人话说就是设置的越高,时间片越小,任务之间切换的频率就越高】(调整TICK_RATE_HZ值会影响系统的时间精度和CPU占用率。如果TICK_RATE_HZ设置得过高,将会增加RTOS内部的计算负担和上下文切换开销,从而降低系统的性能和响应速度。相反,如果TICK_RATE_HZ设置得过低,将会降低系统的时间精度和任务响应速度。一般设置100或1000就行)
MAX_PRIORITIES:这个是说任务有多少个优先级
MINIMAL_STACK_SIZE:用于指定FreeRTOS中任务栈的最小大小。【任务栈配置太小的话,任务的空间就不够用,太大就浪费。这里是限制最小堆栈大小】(在FreeRTOS中,每个任务都有一个对应的栈,用于保存任务执行时所需要的所有上下文信息。任务栈的大小直接影响任务能否正常执行,因此必须确保为每个任务分配足够的栈空间。)
MAX_TASK_NAME_LEN:用于设置任务名字的最大长度。这个就看心情设置了,注意设置太大了浪费空间,设置太小了自己读起来麻烦。
IDLE_SHOULD_YIELD:用于指示FreeRTOS空闲任务是否应该让出CPU给其他高优先级的任务执行。如果不使能的话,空闲任务就会一直工作,比较浪费资源。
USE_MUTEXES:用于指示FreeRTOS是否启用互斥量。(互斥量是一种用于保护共享资源的同步机制,可以防止多个任务同时访问共享资源而导致的冲突和数据损坏。在FreeRTOS中,互斥量可以通过xSemaphoreCreateMutex()函数创建,并通过xSemaphoreTake()和xSemaphoreGive()函数进行锁定和解锁。)如果定义了USE_MUTEXES宏,则可以在系统中使用互斥量来保护共享资源。这样可以保证系统的正确性和可靠性,避免出现竞争和冲突。
USE_RECURSIVE_MUTEXES:用于指示FreeRTOS是否启用递归互斥量。递归互斥量是一种特殊的互斥量,允许同一个任务多次对互斥量进行加锁,而不会导致死锁。在FreeRTOS中,递归互斥量可以通过xSemaphoreCreateRecursiveMutex()函数创建,并通过xSemaphoreTakeRecursive()和xSemaphoreGiveRecursive()函数进行锁定和解锁。如果定义了USE_RECURSIVE_MUTEXES宏,则可以在系统中使用递归互斥量来保护共享资源。这样可以方便地实现嵌套临界区,并避免出现死锁等问题。
USE_COUNTING_SEMAPHORES:用于指示FreeRTOS是否启用计数信号量。计数信号量是一种用于同步和通信的机制,它可以允许多个任务同时访问一个共享资源,并且可以控制资源的可用数量。在FreeRTOS中,计数信号量可以通过xSemaphoreCreateCounting()函数创建,并通过xSemaphoreTake()和xSemaphoreGive()函数进行锁定和解锁。如果定义了USE_COUNTING_SEMAPHORES宏,则可以在系统中使用计数信号量来实现任务之间的同步和通信。这可以方便地实现资源共享和协作,并避免出现竞争和冲突等问题。
QUEUE_REGISTRY_SIZE:用于指定系统支持的队列个数。如果将QUEUE_REGISTRY_SIZE设置为20,则表示系统最多可以支持20个队列。这意味着在系统中同时创建和使用的队列个数不能超过20个。如果队列注册表已满,则无法再创建新的队列,从而导致任务间通信的失败。如果设置过大也会浪费资源,所以自己权衡要多大的队列。
USE_APPLICATION_TASK_TAG:用于启用或禁用任务标签功能。任务标签是一种用于标识任务的额外信息,可以用于在调试、跟踪和分析系统性能时识别和区分不同的任务。任务标签可以通过vTaskSetApplicationTaskTag()和xTaskGetApplicationTaskTag()等函数进行设置和获取。
ENABLE_BACKWARD_COMPATIBILITY:用于启用或禁用向后兼容性。【就是让旧版本的程序可以在新版本的程序上运行】
USE_TICKLESS_IDLE:用于启用或禁用低功耗模式。启用节能模式可以通过vTaskSuspendAll()和xTaskResumeAll()等函数来实现。在这种模式下,系统将等待一个固定的时间间隔,然后再次唤醒,并继续执行其他任务。这个时间间隔可以根据系统的实际需求进行配置,以最大程度地降低功耗和温度等指标。
USE_TASK_NOTIFICATIONS:用于启用或禁用任务通知机制。(开启的话每个任务会多消耗8字节)启用任务通知机制可以通过xTaskNotify()和ulTaskNotifyTake()等函数来实现。这些函数可以实现任务之间的通信和同步等功能,从而提高系统的实时性能和可靠性。同时,任务通知机制也可以实现任务之间的互斥和同步等功能,从而避免了资源竞争和死锁等问题。
RECORD_STACK_HIGH_ADDRESS:用于记录任务堆栈的高地址。在FreeRTOS中,每个任务都有自己的堆栈,用于存储任务执行过程中的临时变量和函数调用信息等。由于任务堆栈是从高地址向低地址生长的,因此记录任务堆栈的高地址是非常有用的,可以帮助我们了解当前任务的堆栈使用情况
Memory Allocation:选择内存分配的方式。有静态和动态两种,静态的不方便,但是好,避免内存碎片的问题,而且分配速度快。动态的方便。
TOTAL_HEAP_SIZE:当用动态分配内存的时候才需要。这里是动态分配内存的时候,所用到的内存空间;就是说动态分配出去的内存都是在这里给的。
Memory Management scheme:这个是动态内存分配的方式。不懂,有好几种,大家都用的4.
USE_IDLE_HOOK:是否启用空闲钩子函数(钩子函数就类似与我们裸机上的中断函数)
USE_TICK_HOOK:用于启用或禁用系统滴答定时器钩子函数
USE_MALLOC_FAILED_HOOK:用于启用或禁用动态内存分配失败钩子函数
USE_DAEMON_TASK_STARTUP_HOOK:用于启用或禁用守护任务启动钩子函数
CHECK_FOR_STACK_OVERFLOW:用于启用或禁用栈溢出检查
GENERATE_RUN_TIME_STATS:用于启用或禁用运行时统计信息的支持。FreeRTOS提供了一个名为vTaskGetRunTimeStats()的函数,该函数可以用于获取任务运行时间的统计信息。使用这些统计信息可以了解每个任务运行所占用的时间比例,以及系统中任务的运行情况。这对于系统性能优化和调试非常有用。
USE_TRACE_FACILITY:用于启用或禁用追踪功能。使用追踪功能可以了解系统中任务和中断的运行情况,帮助开发人员优化系统性能和调试问题。可以使用FreeRTOS提供的vTraceEnable()函数来启用追踪功能,并使用vTraceStop()函数来停止追踪。追踪数据将在内存缓冲区中存储,并可以使用FreeRTOS提供的工具来收集和分析追踪数据。 USE_TRACE_FACILITY是简化的,如果要详细配置可以使用configUSE_TRACE_FACILITY宏来指定内存缓冲区的大小。如果指定的缓冲区大小不足以存储追踪数据,则追踪功能将无法正常工作。
USE_STATS_FORMATTING_FUNCTIONS:用于启用或禁用状态统计格式化函数。FreeRTOS提供了一组状态统计函数,可以用于查询系统中任务、队列和定时器的状态信息。这些函数可以用于监控系统性能和诊断问题。使用USE_STATS_FORMATTING_FUNCTIONS宏可以启用额外的状态统计格式化函数,这些函数可以将状态统计信息格式化为可读的字符串,方便用户查看和分析。
USE_CO_ROUTINES:用于启用或禁用协程功能。协程是一种轻量级的任务,可以在单个线程中运行多个协程,从而实现并发执行。
MAX_CO_ROUTINE_PRIORITIES:用于设置协程的优先级数量。
USE_TIMERS:用于指定是否使用FreeRTOS中的软件定时器功能。它可以在任务中创建一个计时器,并设置计时器的超时时间。当计时器超时时,FreeRTOS会自动将任务从阻塞状态唤醒,任务可以在计时器超时时执行一些操作,如发送一个消息、切换到另一个任务等。
TIMER_TASK_PRIORITY:用于指定FreeRTOS软件定时器任务的优先级。
TIMER_QUEUE_LENGTH:指定FreeRTOS系统中可用的软件定时器的最大数量。
TIMER_TASK_STACK_DEPTH:用于指定FreeRTOS软件定时器任务的堆栈深度。
在FreeRTOS中,所有的任务和软件定时器都是在中断中执行的,因此需要使用中断优先级来控制任务和定时器的优先级。中断优先级的值越小,表示优先级越高,因此需要保证任务和定时器的优先级不高于系统中可用的最高优先级中断的优先级。
LIBRARY_LOWEST_INTERRUPT_PRIORITY:表示FreeRTOS中可用的最低优先级中断的优先级值。
LIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY:表示FreeRTOS中可用的最高优先级中断的优先级值
USE_POSIX_ERRNO:用于启用或禁用POSIX标准中定义的errno变量。比如在使用xQueueSend()函数向队列发送数据时,如果队列已满,xQueueSend()函数将返回错误码pdFAIL,此时可以使用errno变量获取错误代码,然后根据错误代码执行相应的处理。
函数的使能
函数就不说了,看自己用什么来自己裁剪
创建任务
然后可以再设置一下生成方式project manager,就不说了
下面就可以进IDE看一下了
在freertos.c里面,有咱们创建的任务,在里面写就行。
hal库生成的freertos,会有一套封装好的函数,像这个osdelay就是封装过的vTaskDelay
自己移植
自己移植的话,先随便创建一个项目,然后把在官网下载的东西复制进去,再改点东西就行
下载freertos
freertos官网下载地址
下面这张图里,红色的是必要的,黑色的如果你没用的可以不添加到项目中,用到了再加,不过黑色这几个一般也能用到,都放进去就行
这里面移植相关文件,有些是不用的,东西很多,我也不大认识,只说用到的三个
这里面,内存管理和内核移植文件里又要选择
其中内存管理中,我们选这个,这个内存管理比较复杂,以后有机会再说,反正现在选了就能用。这里是选择在动态分配内存的时候,内存是如何分配的。
内核移植文件要按照自己使用的芯片内核来选,图上写了两个常用的,其他芯片自己去查查是什么内核
开始移植
移动文件
先在工程中创建两个文件夹Middlewares/FreeRTOS_CORE和Middlewares/FreeRTOS_PORTABLE,用来存放Freertos的文件。
再去项目文件夹中,创建FreeRTOS文件夹,用来存放freertos的文件。并且把上面说的用到的文件复制过来
回到工程文件,把文件夹的东西全都放到Middlewares/FreeRTOS_CORE和Middlewares/FreeRTOS_PORTABLE两个文件夹里
然后添加头文件的路径,这里有两个文件夹里有头文件,include和portable/RVDS/ARM_CMx
这些东西都搞完以后,还有一个用来裁剪FreeRTOS的头文件FreeRTOSconfig.h
这个文件里面就是上面在STM32cubeMX里面设置的那些参数
好像正常是自己写的,但我办不了,我去复制,在demo里
找自己对应的demo,f1就找f1的,f4就找f4的
把里面的FreeRTOSconfig.h复制到一个我们工程能找到的地方,我直接复制到core/inc里
现在文件复制完了
解决各种错误
1
我们直接编译会报一个错,下面是修改一些代码,去掉报错
这个错误是因为我们复制来的FreeRTOSconfig.h是在IAR跑的,看宏定义,在我们Keil里是不工作的。那么我们要做的是让他在Keil里也工作,就修改宏定义的条件
改成
#if defined(__ICCARM__)||defined(__CC_ARM)||defined(__GNUC__)
这样我们第一波错误就解决了
2
然后我们继续编译,会出现第二波错误
test\test.axf: Error: L6200E: Symbol SVC_Handler multiply defined (by port.o and stm32f4xx_it.o).
test\test.axf: Error: L6200E: Symbol PendSV_Handler multiply defined (by port.o and stm32f4xx_it.o).
test\test.axf: Error: L6200E: Symbol SysTick_Handler multiply defined (by port.o and stm32f4xx_it.o).
这是因为Freertos中会重新写SVC_Handler;PendSV_Handler;SysTick_Handler这三个代码,然后和stm32f4xx_it.c里的函数重复了。我们要做的是去stm32f4xx_it里把这三个函数注释掉。
可以crtl+f全局搜索,找到要注释的函数
把stm32f4xx_it.c和.h文件里这三个函数注释掉,这个报错就解决了
3
然后第三波报错
test\test.axf: Error: L6218E: Undefined symbol vApplicationIdleHook (referred from tasks.o).
test\test.axf: Error: L6218E: Undefined symbol vApplicationStackOverflowHook (referred from tasks.o).
test\test.axf: Error: L6218E: Undefined symbol vApplicationTickHook (referred from tasks.o).
test\test.axf: Error: L6218E: Undefined symbol vApplicationMallocFailedHook (referred from heap_4.o).
这是因为我们宏定义开启了钩子函数,但是我们又没写钩子函数。所以我们把宏定义的这四个改成0
第三波报错就解决了,所有的报错也都解决了
写个点灯函数吧
这里我们没有stm32cubemx给生成函数,要自己去写,然后创建
直接在主函数写吧
我们用freertos就要包含FreeRTOS.h;然后我们点灯需要任务,就要包括task.h
#include "FreeRTOS.h"
#include "task.h"
void LED0_Task()
{
for(;;)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);
vTaskDelay(1000);
}
}
void LED1_Task()
{
for(;;)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_10);
vTaskDelay(300);
}
}
任务要执行的函数写完了,要去用动态分配内存的方式创建任务xTaskCreate(),然后启动任务调度器vTaskStartScheduler() 让系统不停运行任务;我们先写,最后介绍创建任务这个函数
xTaskCreate(LED0_Task,"LED0",128,NULL,1,NULL);
xTaskCreate(LED1_Task,"LED1",128,NULL,1,NULL);
vTaskStartScheduler();
这样就结束了。烧录到板子上看看吧。
下面介绍函数
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask);
pxTaskCode:指向任务函数的指针。
pcName:任务的名称,仅用于调试目的。
usStackDepth:任务堆栈的大小(以字节为单位)。
pvParameters:传递给任务函数的参数。没有就写NULL
uxPriority:任务的优先级,数字越高,优先级越高。
pxCreatedTask:指向一个变量的指针,在任务创建成功后用于存储任务句柄。