术语
GCC
GCC : GNU C Compiler .顾名思义,GUN C 编译器。自从面世以后逐渐发展,现在不仅支持C语言,还支持很多语言,如C++、Ada、Java等。因此GCC得意思被定义为GUN Compiler Collection,即GNU 编译器套件。 其中GNU是一个自由软件组织。
Measure comlexity of C source
分析C编程函数的复杂程度的计算方法。
MISRA coding standard guidelines
MISRA: THE Motor Industry Software reliability Assosition 汽车工业软件可靠性联会。位于英国的也有一个跨国汽车工业协会。其成员包括了大部分欧美汽车生产商。
这个标准包括了127条C语言编码标准。代码编写遵循度越高,代码越容易维护和管理。
堆(heap)和堆栈(stack)
FreeRTOS port 端口
定义:每种支持的编译器和处理器组合被认为是一个端口。FreeRTOS 自持很多种编译器和处理器。
每个端口使用一个定时器来生成定期的滴答中断。许多端口使用其他中断来管理执行任务切换。RTOS端口所需的中断由提供的RTOS端口源文件提供服务。
Thread
线程: 运算调度的最小单位。
钩子函数(hook结尾)
作用:定义一些操作,函数名称确定,只需要补全函数体。钩子函数由事件触发。函数指针指向钩子函数,调用钩子函数的过程,称为挂钩子。
C语言宏定义中##符号
作用:连接。
示例1:
#define CONN(X,Y) X##Y
##表示连接,例long n=CONN(123,456),n=123456。
示例2:如果使用osThreadDef(first,1,1,1,1),那么就生成了 os_thread_def_first
#if defined (osObjectsExternal) // object is external
#define osThreadDef(name, thread, priority, instances, stacksz) \
extern const osThreadDef_t os_thread_def_##name
#else // define the object
hard and soft real-time requirements 硬、软实时需求
软实时需求: 即使违反了时间要求,也不会导致系统失效,例如按键的响应太慢会导致系统不响应按键,但是不会导致系统没用。
硬实时需求: 违反了时间要求,会导致系统的失败。例如驾驶员气囊系统,碰撞传感器响应太慢对导致很大的问题。
one core one thread
一个核在任何时间智能执行一个线程。核通过线程的优先级来判断当前该执行哪一个任务。硬实时需求分配较高优先级,软实时需求分配较低优先级。在FreeRTOS中,把thread 通常称为task,避免线程的概念和其他领域中的概念混淆。
FreeRTOS 的适用场景、特点
适用于较为复杂的系统,任务优先级确保程序的满足处理期限。
特点:
- Very flexible task priority assignment 灵活的任务优先级设置
- Flexible, fast and light weight task notification mechanism 灵活、快速、轻量级的任务通知机制
- Queues 队列
- Binary semaphores 二进制信号量
- Counting semaphores 计数信号量
- mutexes 互斥信号量
- Recursive Mutexes 递归信号量
- Software timers 软件定时器
- event groups 时间组
- Tick hook functions 时间钩子函数
- Idle hook functions 空闲钩子函数
- Stack overflow checking 堆栈溢出检查
- Trace recording 跟踪记录
- Task run-time statistics gathering 任务运行时间攻击
- Full interrupt nesting model (for some architectures) 完整的终端嵌套模型
- A tick-less capability for extreme low power applications 没有时间的极低功耗
- Software managed interrupt stack when appropriate (this can help save RAM) 软件管理中断堆栈
FreeRTOSConfig.h 配置文件
根据需要修改其中的参数。
task.c and list
所有的FreeRTOS 接口中共有的文件。
queue.c 队列文件
提供队列和信号服务。几乎总是需要这个文件。
timers.c
提供软件定时器功能。
event_groups.c
提供事件组功能。
croutine.c
co-routine 协程,适用于较小的核,用的较少。官方已经不打算更新了。
heap memory management 堆内存管理
低于FreeRTOS 9.0的版本必须使用heap memory manager。或者是打开了 configSUPPORT_DYNAMIC_ALLOCATION 。 使用的是heap_1 to heap_5 文件。
数据类型
针对端口类型,有两个指定的数据类型定义文件,分别为 TickType_t 和 BaseType_t .
TickType_t : 滴答时间计时。根据MCU 位数定义,16或32位。
BaseType_t: 基本数据类型,通常和MCU的位数一致。
变量名
注意前缀。会指明变量的标准类型或非标准类型。以及指针类型。
函数名
注意前缀,前缀有两层,一层是返回值,一层是文件地址。
private function 前缀 是prv.
宏定义
宏定义名大写字母,前缀小写字母,指明文件位置。
注意:semaphore API 信号接口几乎是用宏来定义的,但是使用函数的命名规则来写的。
动态内存分配
内核对象: tasks queue 、semaphore and event group.
对象不是在创建时分配的,在运行时动态分配的。创建对象时分配RAM 空间,删除对象时删除RAM空间。这样的目的是减少RAM的使用空间。
使用函数: malloc() 、free(). 缺点是占用代码空间大,不适用于小型的MCU 。在FreeRTOS 中用 pvPortMalloc() 、vPortFree()代替。
函数位置 heap_1 ~ heap_5。
- heap_1: 只能创建任务
- heap_2: 能够删除任务,填空式重新创建任务。是不确定性的,但是速度比标准库要快。
- heap_3: 使用malloc()、free()标准函数。通过挂起调度器来保证分配安全。
- heap_4: 静态声明。合并空闲块避免碎片,适合重复分配和释放RAM。是不确定性的,但是速度比标准库要快。可以定义堆的起始地址。
- heap_5: 和heap_4算法类似。不限于单个静态arry 分配内存,可以从多个单独的内存空间分配内存。vPortDefineHeapRegions() 必须实现声明。需要创建起始地址和 RAM 大小。
堆相关函数
size_t xPortGetFreeHeapSize();
当函数被调用时,返回堆字节数量。可以用来优化堆的大小。
size_t xPortGetMinimumEverFreeHeapSize( void );
返回最小未分配的堆的字节数。作用:指示距离空间消耗完毕有多接近。
void vApplicationMallocFailedHook( void );
钩子函数,解决堆分配失败的的问题,指明接下来怎么做。
注意事项
[Cortex-M users: Information on installing interrupt handers is provided in the “The application I created compiles, but does not run” FAQ]
翻译:[Cortex-M用户:“我创建的应用程序已编译但无法运行”常见问题解答中提供了有关安装中断处理程序的信息。
解决问题网址:https://www.freertos.org/FAQHelp.html
Task management 任务管理★
任务特点及任务运行状态
0、不退出,无限循环。
1、断点恢复执行。
2、任务切入、切出的名词:switch in \ switch out .只有任务调度器才能执行切换动作。
3、空闲任务自动创建当 scheduler 开始,保证任何时刻都有一个任务可以执行。
event-driven 时间驱动任务:只有在事件触发之后,才能让 task 进入运行状态,然后开始执行。意义是避免多优先级任务下,其他任务饿死(不执行),高优先级不进入 运行状态,调度器就不能调度他们,从而选择低优先级的、进入运行状态的任务。
Not running state:
- the block state : 等待 event 的 task 被称为Blcoked state;event 分两种,时间类型(vTaskDelay())和同步类型(FreeRTOS队列,二进制信号量,计数信号量,互斥量,递归互斥量,事件组以及直接到任务的通知都可以用来创建同步事件)。
- the suspended state :进入Suspended状态的唯一方法是通过调用vTaskSuspend()API函数,唯一的方法是通过调用vTaskResume()或xTaskResumeFromISR()API函数。 大多数应用程序不使用Suspended状态。
- the ready state:未被挂起或者阻止的任务,处于可以执行的状态。
时间分配
任务选择
优先级影响系统行为
任务状态
task 应用方法
创建 task 实例
1、 函数原型
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char * const pcName,
uint16_t usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask );
任务创建失败的原因: 堆存储空间不够。
2、task 创建位置:main(); 或者另一个task 中。
使用任务参数
使用任务函数的意义:能够有效的提取、总结函数的核心功能,将一系列操作归类为一类操作。
创建、改变任务的优先级
优先级的设置方法: 有两种,任务开始阶段创建(设定优先级参数uxPriorty),或者调用vTaskPrioritySet(); 函数。
相关函数:
void vTaskPrioritySet( TaskHandle_t pxTask, UBaseType_t uxNewPriority ); // 设置任务优先级
UBaseType_t uxTaskPriorityGet( TaskHandle_t pxTask ); // 查询任务优先级,需要事先配置 INCLUDE_uxTaskPriorityGet 为1.
注意:函数名 pxTask 设置为 null 后,返回的是该 task 的优先级。
相关参数:configMAX_PRIORITIES : 定义的最大优先级数。
优先级和数字:小数字代表低优先级,0为最低优先级。(0 - configMAX_PRIORITIES - 1).
任务调度方法: genetic method \ architecture optimized method 两种。 注意优先级数不要设置太高,否则会浪费RAM。???两种方法选择的依据?
Time Measurement and the Tick interrupt 时间测量和滴答中断
time slicing 时间片: 针对 task 优先级相同的场景。每个任务对应一个时间片,时间片开始时进入运行状态,时间片结束时推出运行状态。
时间片原理: 使用 滴答中断 实现周期性中断。
相关参数:
- configTICK_RATE_HZ ,滴答频率。两个滴答中断之间的时间称为滴答周期(tick period)。不建议直接用滴答来指定时间。使用 pdMS_TO_TICKS() 来指定时间比较好。
- tick count 滴答计数:记录自程序运行以来的滴答中断数。
任务延迟
1、使用软件计数延时是粗糙的做法,效率低下。
2、建议的方法:vTaskDelay(). 该函数将函数blocked指定的时间来将函数延迟。注意:事先需要将 INCLUDE_vTaskDelay 设置为1.
void vTaskDelay(TickType_t xTicksToDelay);
该函数的参数是需要延迟的时间,以Ticks 为最小单位。如果需要以ms为单位,那么就结合 pdMS_TO_TICKS() 宏来转换为ms. 如 vTaskDelay(pdMS_TO_TICKS(100)), 就制定了100ms的延时。
void vTaskDelayUntil( TickType_t * pxPreviousWakeTime, TickType_t xTimeIncrement );
该函数式精准延时函数。可以指定起始点。
Idle task 应用方法
意义: 1、保证时刻有一个任务处于可执行状态。2、测量备用处理能力。3、降低功耗(有一定效果)。
特点:优先级最低。
参数:
- configIDLE_SHOULD_YIELD :
- configUSE_IDLE_HOOK :设置为1,才可以使用idle hook function.
应用限制:
- idle task hook function 不能被挂起。
- 删除任务后,idle task hook function 应尽快被调用,清理资源。
void vApplicationIdleHook( void );
删除任务
意义:可以删除自身或者其他任务。被删除的任务不再存在,不能再进入Running state.
相关参数: INCLUDE_vTaskDelete :设置为1 ,才可以使用删除函数。
相关函数:
注意:idle task 的清除作用。
void vTaskDelete( TaskHandle_t pxTaskToDelete ); // 参数是NUll 时,删除的是自己。
配置调度算法
Round Robin Scheduling: 相关任务同优先级循环切换,共享处理时间。
Prioritized Pre-emptive Scheduling with Time Slicing 基于时间片的优先级调度算法:
Prioritized Pre-emptive Scheduling (without Time Slicing) 无时间片优先抢占调用: 难度大,有经验的高手使用。
Co-operative Scheduling 合作调度: 只有当前运行任务进入block状态或者使用 taskYIELD() 函数强制推迟才会切换任务。 任务不会被抢占,所以无法使用时间片。参数配置如下。
相关参数:
configUSE_PREEMPTION :设置为1 ,打开抢占
configUSE_TIME_SLICING:设置为1,打开时间片
configUSE_TICKLESS_IDLE:设置为1,关闭 tick interrupt ,节省功耗。
configIDLE_SHOULD_YIELD: 空闲 task 退让函数。设置为0,面对和 idle task 相同优先级的任务,不退让。 设置为1,退让。下图是退让的结果。
queue management 队列管理
queue 提供了 task to task \ task to interrupt \ interrupt to interrupt 的通信机制。
如何创建队列以及队列的信息
QUEUE 的 length 和 size : length 是 items 大小。size 是 队列里可以存储的 Item 的数量。
作用:FIFO buffer.
应用手段:直接复制队列内容到另一个队列。或者使用队列的指针。
特点:
- Access by mutiple task 多任务读写:可以被多个任务读写。
- Block on Queue Reads 读阻塞 : 可以阻塞 task 读取,并且可以指定读阻塞时间。多 task 的读取顺序取决于queue 的优先级,或者同优先级下的等待时间
- Blocking on queue writes 读阻塞。可以阻塞 task 写入,并且可以指定写阻塞时间。多 task 的写的优先级取决于 task 的优先级或者同优先级下等待的时间。
- 队列编组。队列可以编组, task 可以等待一组queue 的数据。
使用队列
1、创建一个队列.
先创建,再使用。通过 handle 引用,类型是 QueueHandle_t。
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
2、发送队列
BaseType_t xQueueSendToFront( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait ); // 发送数据到队首
xQueueSendToFrontFromISR(); // ISR 函数;
BaseType_t xQueueSendToBack( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait ); // 发送数据到队尾
xQueueSendToBackFromISR(); // ISR 函数
3、接收队列
注意:有ISR版本
BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait );
4、查询队列
注意:有ISR版本
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );
处理大型或可变类型的数据
1、处理办法: 使用结构体指针,将数据源 和 数据 编制为结构体,实现多种数据源、数据类型的传递。
- 注意指针的拥有者。不要同时修改内存。
- 指向的RAM仍然有效。(不是很明白)。不要轻易尝试使用被分配过的RAM。
queue sets 队列集
对队列限制数据来源和数据类型。
不简洁、效率不高对比单个queue 接收,所以不是必须的话不要用。
相关参数:
configUSE_QUEUE_SETS:使用队列集要打开这个使能位。
相关函数:
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength ); // 创建队列
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet ); // queue 添加到集
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,
const TickType_t xTicksToWait ); // 从队列集中选择一个queue
实现步骤
- 创建queue
- 创建queue set
- 添加queue 到 queue set
使用步骤
- 选取queue从queue set; 使用这个函数,xQueueSelectFromSet(); 如果返回值不是NULL,说明queue里有数据了,返回值就是该queue 的句柄。
- 读取queue
mailbox 邮箱
邮箱指代 length = 1 的 queue。一种特殊用法。
主要有两种情况:
- 使用queue 实现两个 task 之间传递数据;
- 使用queue 存储数据,发送方始终重复写,接收方读取数据但是不删除queue 中的数据。
相关函数
BaseType_t xQueueOverwrite( QueueHandle_t xQueue, const void * pvItemToQueue ); // 覆盖写queue.类似 xQueueSendToBack() ,差别在于queue full 之后,会覆盖已有的数据。
xQueueOverwriteFromISR(); // ISR 版本
BaseType_t xQueuePeek( QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait ); // 从队列中读取数据,类似于xQueueReceive(),差别在于不会删除 queue 中的函数。
xQueuePeekFromISR(); // ISR版本
software Timer management 软件定时器
定义: 周期性的调用函数,执行函数被称为 callback 函数。实现依靠 FreeRTOS 核,不需要硬件定时器支持。除非callback 函数在执行,否则不会消耗时间。只能在创建的函数中运行。
相关配置:
- timer.c
- congfigUSE_TIMERS = 1;
software timer callback functions 软件定时器函数
callback函数应用要求:
- 保持短小;
- 不能进入 Blocked 状态。不要调用FreeRTOS API函数,避免进入Block 状态。
函数原型
void ATimerCallback( TimerHandle_t xTimer );
attribute and states 属性和状态
period of software timer: creat – executing ,创建到执行时间。
one shot and auto-reload timer 单发和自动重装定时器: 单发只能执行一次,自动重装会周期运行。
状态: dormant 休眠 、 running 两种。
软件定时器的内容
The RTOS Daemon (Timer Service) Task: callback 函数在此运行。自动创建。禁止调用API 函数的原因是会导致 deamon 函数进入BLOCKED 状态。
- configTIMER_TASK_PRIORITY:优先级
- configTIMER_TASK_STACK_DEPTH:堆栈大小
The Timer Command Queue: 作用是传递命令。command 传递 从timer task to daemon task ,自动创建,长度参数 configTIMER_QUEUE_LENGTH 。conmand 可以使用 time stamp 时间戳,设定传递时间。
软件定时器和任务的特点对比
RTOS demon task 守护程序任务
The timer command queue 定时器命令队列
one shot timer 和 periodic timer 的不同
如何去创建、开始、复位和修改软件定时器
创建步骤:
1、声明 TimerHandle_t 型变量。
2、使用函数创建定时器。
相关函数:
TimerHandle_t xTimerCreate( const char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * pvTimerID,
TimerCallbackFunction_t pxCallbackFunction ); // 创建 TIMER
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait ); // 定时器起始,有ISR 版本
The Timer ID 标签
相关函数:
void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID ); // 设置ID,也可以创建时设定。
void *pvTimerGetTimerID( TimerHandle_t xTimer ); // 获取ID 标签。
队列怎么管理信息
如何发送数据到队列
如何接收数据到队列
如何阻止多队列
如何重写队列
如何清除队列
任务优先级对队列写入的影响
interrupt management 中断管理
不同来源的事件必须给出不同的处理方法。中断就是处理特殊事件的方式。task 是软件特色,和硬件无关,而中断和硬件有关。
1、画出 task 的优先级,和中断的优先级。
相关的宏
portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); // context switch
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken ); // 和上函数一样
注意:不同的PORT 不同的放置位置,大多数用在任何位置,小部分只能用在ISR的结尾。
ISR中可以 使用的API函数
部分API 函数不能再ISR中执行,可能导致任务进入blocked 状态。在ISR中使用"FromISR"版本。可以是代码更高效,相比将ISR和task结合起来的方案,减少判断条件。
xHigherPriorityTaskWoken 高优先级唤醒参数
高优先唤醒参数,如果在ISR中可能会发生因为优先级变化而发生的 switch ,那么用这个参数记录。不需要可以设置为NULL;
不要 switch 的函数场景
1、避免不必要的切换。
2、避免不可预测切换。
3、portability 可移植的
4、不要超过一个转换的请求。
5、避免 tick interrupt.
deferred interrupt processing 延迟中断处理
中断中记录原因,并且清理中断标志,需要大量操作的内容放到task 中,这样的操作被称为延迟中断处理。好处是1、减少中断处理时间。2、使用所有API函数。
1、一般来说,保证ISR尽可能短。原因如下:
- ISR可能破坏任务的开始时间和执行时间(增加“抖动”)。
- 在执行ISR时,可能无法接受任何新的中断或至少新中断的子集。
- 应用程序编写者需要考虑任务和ISR同时访问的资源(例如变量,外围设备和内存缓冲区)的后果并加以防范。
- 中断时间越短,嵌套的可能性就越小。一些FreeRTOS端口允许中断嵌套,但是中断嵌套会增加复杂性并降低可预测性。 。
2、适合延迟 ISR 的情况。 - 中断所必需的处理复杂的。 例如,如果中断只是存储模数转换的结果,则几乎可以肯定,这最好在ISR内部执行,但是如果转换结果也必须通过软件过滤器传递,则可能是 最好在任务中执行过滤器。
- ISR内部无法执行的操作,例如写入控制台或分配内存,这很方便。
- 不确定性处理,意味着事先不知道处理将花费多长时间。
使用 FromISR 函数的缺点???
没看懂
methods of deferring interrupt processing to a task 中断到task 的方法。
deffer interrupt processs; 大部分操作放到 task 中,记录操作放在 ISR 中。
binary semaphore use for synchronization 二进制信号量用于同步
二进制信号量用于传达事件。队列用来传达事件、传递数据。
1、二进制信号量用于“defer (推迟)” ISR 处理到task 中。
2、二进制信号量的构成:长度为1的 queue,状态始终为empty 或者 FULL,
3、“taking” 和 “give” 是信号的两种操作。
4、除了mutexes , 都可以使用xSemaphoreTake();来获取semaphore。不能用在ISR中。
创建和使用信号量 counting semaphore
相关函数:
SemaphoreHandle_t xSemaphoreCreateBinary( void ); // 创建信号
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );// 获取信号,不能用在ISR中。 一直处于BLOCKED 状态,如果没有信号量返回。
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken ); // 给予 semaphore;有ISR版本。
counting semaphore 计数信号量
长度不为1的信号量。item 的数量就是 信号 count 的量。
使用场景:
- counting event : 用来计数event;事件发生一次计数加一,为 “give”; task 每处理一个时间,计数减一,为“take”;
- resource management : 指示可用的resource ;为了获取资源,那么必须获取信号,即信号计数减1。ISR “give” the semaphore,即信号加1;
相关参数:
configUSE_COUNTING_SEMAPHORES :使用计数信号量
相关函数:
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount ); // 创建计数信号
binary semaphore 和 counting semaphore 的不同
ISR中使用 Queue 传递数据输入、输出
队列用来传达事件、传递数据。ISR中的特殊队列API用来ISR之间传递事件和数据。队列提供了ISR到task 传递数据的方式,但是如果数据的频率很高,那么不适合用 queue, 有更适合的DMA方式。
中断嵌套
- numeric priority 数字优先级
- logical priority 逻辑优先级
数字优先级是分配优先级的数字大小,逻辑优先级是真实逻辑执行顺序。数字优先级和逻辑优先级的关系和硬件相关,有的数字打优先级低,有的数字小优先级低。
相关函数:
configMAX_SYSCALL_INTERRUPT_PRIORITY
configMAX_API_CALL_INTERRUPT_PRIORITY
Free RTOS 快速开始
The easiest way to use FreeRTOS is to start with one of the pre-configured demo application projects (found in the FreeRTOS/Demo directory). That way you will have the correct FreeRTOS source files included, and the correct include paths configured. Once a demo application is building and executing you can remove the demo application file, and start to add in your own application source files.
翻译:使用 demo 工程,快速开始。省的配置头文件路径什么的。
ARMCC 和 ARMCLANG 编译器
都是ARM的编译器,ARMCC支持到ARMv7 架构,ARMCLANG 可以支持到ARMv6、ARMv7、ARMv8以及以后的新处理器。
创建一个新的 FreeRTOS工程
文件数量:以下源文件是必须的。
FreeRTOS/Source/tasks.c
FreeRTOS/Source/queue.c
FreeRTOS/Source/list.c
FreeRTOS/Source/portable/[compiler]/[architecture]/port.c.
FreeRTOS/Source/portable/MemMang/heap_x.c where ‘x’ is 1, 2, 3, 4 or 5.
其余文件需要什么添加什么,例如需要定时器功能,那么就添加一个timer.c文件。
从现有demo改编一个 FreeRTOS 工程
1. **准备工作** 保证任务代码可以正确执行。
2. vTaskStartScheduler() 在主函数中调用这个函数,开始RTOS application.
3. 在必须的源文件基础上,选择需要的功能文件,如timer.c.
4. 头文件放到指定的文件夹中,保证路径正确。
5. 配置文件。配置文件将内核适配应用程序。因为配置文件是应用于应用程序而不是RTOS,所以它应该位于应用程序目录中,而不是RTOS内核源代码中。 堆和堆栈的大小设置,参考官方示例。
6. 中断向量。
FreeRTOS学习实录
疑问
0 middleware 的含义?
1. Middleware 下 interface 里面的选项 “CMSIS_V1” 和“CMSIS_V2”代表了什么。
2 头文件中 #define osObjectExternal 作用,除了打开
0 安装开发MDK
0 安装STM32CubeMX
1 安装STM32F4芯片包
2 安装STM32F4芯片程序包
可能会中断多次,请耐心得多尝试几遍。
1 使用STM32创建基于STM32F4的RTOS工程
1 在"middleware"下的FREERTOS 打开CMSIS_V1,CMSIS_V2.里面的参数暂时不需要配置。等着接下来了解了参数的定义之后,再开始针对性的配置。
2 时钟配置。选择合适的时钟源和时钟频率。
3 工程配置。配置工程名称、工程目录、IDE等。工程路径中不要有中文字符,否则会报错。
4 点击页面右上角“GENERATE CODE”按钮,生成代码。会提示RTOS时钟问题,这时需要在“SYS”中把 Timebase Source 时钟基准由滴答时钟改变为定时器时钟。
FREE RTOS 代码阅读
宏定义和条件编译结合,实现不同代码不同配置。
文件解读
cmsis_os.c
这个文件不是FreeRTOS 的源文件,属于移植必须的文件。由ST 的团队开发。是内核与RTOS 的接口文件。由此来调用FreeRTOS的文件。
代码解读
名称约定和头文件修改
All definitions are prefixed with \b os to give an unique name space for CMSIS-RTOS functions.
翻译:CMSIS-RTOS 文件中函数的名称, 前缀 都是 os。
Definitions that are prefixed \b os_ are not used in the application code but local to this header file.
翻译:只在本地使用,未在应用程序中使用的,前缀都是os_
All definitions and functions that belong to a module are grouped and have a common prefix, i.e. \b osThread.
翻译:相同前缀的,属于同一模块
Definitions that are marked with <b>CAN BE CHANGED</b> can be adapted towards the needs of the actual CMSIS-RTOS implementation.
These definitions can be specific to the underlying RTOS kernel.
翻译:标记为 CAN BE CHANGED ,可以根据需要被改写。
Definitions that are marked with <b>MUST REMAIN UNCHANGED</b> cannot be altered. Otherwise the CMSIS-RTOS implementation is no longer
compliant to the standard. Note that some functions are optional and need not to be provided by every CMSIS-RTOS implementation.
翻译:标记为 MUST REMAIN UNCHANGED 的,不能修改。
<b>Function calls from interrupt service routines</b>
函数调用。有些函数能被中断服务函数调用,有些不可以。不能被调用的函数被调用后会报错,有些可以被多个ISR调用。可以被调用的函数如下。
- \ref osSignalSet
- \ref osSemaphoreRelease
- \ref osPoolAlloc, \ref osPoolCAlloc, \ref osPoolFree
- \ref osMessagePut, \ref osMessageGet
- \ref osMailAlloc, \ref osMailCAlloc, \ref osMailGet, \ref osMailPut, \ref osMailFree
Idle task
空闲任务。
变量、宏、函数的前缀解释
FreeRTOS referrence manual P395. 有详细介绍。
projdef.h 文件
定义了一些状态。
API应用限制
1 函数名以FromISR结尾,才可以用在ISR中。
2 有些情况下以FromISR 结尾的函数,也不能用在ISR中。即函数优先级高于系统调用优先级。reference manual P19.
3
context switch
是啥?
scheduler is suspend
是啥
critical section
是啥
resource
是啥
aceess a resource.
resource managerment 资源管理
意义: 实现task 对资源的控制,不被抢占。
资源管理的必要性
资源访问不合理,会导致任务错误。多任务系统中,一个任务未完成对资源的访问,缺从运行状态中转换。导致资源的状态不确定,后续任务访问可能导致数据出错。
举例:写LCD。任务A要写hello World,任务B要写abort,retry,fail? 任务A的字符串写了一半被任务B给抢占了,写了字符串B的内容。导致最后显示的字符串是错误的。Hello wabort retry,fail?orld.
basic critical sections 关键区域
意义:保证重要代码完整执行。多用在代码初始化阶段,保护不能被打断的代码。
方式:是更改中断使能状态的唯一合法方式。中断场景中的函数
- taskENTER_CRITICAL(); // 普通场景,无返回值。
- taskEXIT_CRITICAL(); // 普通场景
- taskENTER_CRITICAL_FROM_ISR() ; // 中断场景函数 ,允许中断嵌套,有返回值。
- taskEXIT_CRITICAL_FROM_ISR() ; // 中断场景函数,允许中断嵌套。
示例:taskENTER_CRITICAL(); taskEXIT_CRITICAL();两个函数包围着printf( “%s”, pcString );,保证它的执行。这是一种粗糙的方法,实现原理是通过关闭中断。
suspending(lock) the scheduler 挂起调度器
定义:挂起调度器。只能保护不被 task 抢占,中断依然能够抢占。保护能力没有 basic critical 强。
函数:
void vTaskSuspendAll( void );
BaseType_t xTaskResumeAll( void );
特点:挂起调度器或恢复调度器需要花费较长时间。因此必须想好这这种方法的使用场景。
Mutexes (and Binary Semaphores) 互斥和二进制信号量
- 定义:特殊类型的二进制信号量,用于控制多个任务之间资源的共享。顾名思义,任务之间互斥,任务A没完成对资源X的使用,其他任务不能用。会使用到堆内存,堆内存不足时不能创建。
- 使用方法:FreeRTOSConfig.h中将configUSE_MUTEXES设置为1,互斥体才可用。
1) 创建互斥信号量。使用互斥信号量之前需要创建。根据函数返回值判断是否创建成功。
SemaphoreHandle_t xSemaphoreCreateMutex( void );
2)使用互斥信号量。在应用程序中使用互斥信号量。详情见reference manual P248.
3. (不理解):
- 必须始终返回用于互斥的信号量。
- 用于同步的信号量通常会被丢弃而不返回。
- 注意事项:禁止使用在ISR中。(不是很理解)
priority inversion 优先级反转
定义:高优先级等待低优先级执行完成才能执行,被称为优先级反转。属于互斥锁的陷阱。
Priority Inheritance 优先继承
定义:优先继承。能够让优先级反转负面影响最小化的方案。它不会修复优先级反转,只是通过确保优先级反转受到时间限制而减小优先级反转带来的负面影响。
实现原理:正在运行的、具有互斥量的、低优先级的任务,优先级提高到高优先级,优先级和尝试获取同一互斥量的任务的优先级相等(继承高优先级任务的优先级)。任务执行完成之后,优先级恢复至原来优先级。
缺点:让系统时序复杂,并且依靠它来保证正确的系统操作不是一个好的习惯。
实现方法:!!!待补充
Deadlock (or Deadly Embrace) 死锁(或致命拥抱)
定义:两个任务 A\B 都需要两个互斥量X\Y,而两个互斥量被两个任务各拿走一个,这时两个任务都抱着自己的互斥量,无法获得对方的互斥量进入了blocked状态。
解决办法:设计时避免这种情况的发生。
Recursive Mutexes 递归互斥
定义:一个任务自身也可能会发生死锁现象。任务多次使用同一个互斥量而不返回,就会发生死锁。为避免这种情况就设计了递归互斥。
使用方法:获取多少次地柜互斥,就要释放多少次递归互斥,见示例。P255.
使用xSemaphoreCreateMutex()创建标准互斥锁。
使用xSemaphoreCreateRecursiveMutex()创建递归互斥。这两个API函数具有相同的原型。
使用xSemaphoreTake()“获取”标准互斥锁。
使用xSemaphoreTakeRecursive()来“获取”递归互斥。这两个API函数具有相同的原型。
使用xSemaphoreGive()“释放”标准互斥锁。
使用xSemaphoreGiveRecursive()释放的递归互斥。这两个API函数具有相同的原型。
Mutexes and Task Scheduling 互斥量和任务调用
定义:如果任务1和任务2具有相同的优先级,并且任务1处于“已阻止”状态以等待任务2持有的互斥量,则当任务2“给予”互斥量时,任务1不会抢占任务2。而是,任务2将保持在“运行”状态,而任务1将仅从“阻止”状态移至“就绪”状态。
gatekeeper task 守门人任务
意义:解决资源管理的问题,而且不会带来 priority inversion 优先级反转以及 deadlock 死锁。
event groups 事件组
意义:同步 task ,是queue 、semaphore的综合应用。
特点:
- 组合几个事件,任务可以等待事件组合的发生而发生。
- 事件组能解锁所有的 等待同一个 事件的 task 。(broadcast?)
- 代替二进制信号量?
event groups 、event flag an event bits: ‘flag’是事件发生的标志,布尔值。groups 是一系列的‘flag’,保存在一个变量中,类型为 EventBits_t,每一位代表什么,作者决定。
相关函数:
EventGroupHandle_t xEventGroupCreate( void ); // 创建事件组
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet ); // 事件组置位,有ISR 版本
注意:ISR版本不直接用在 ISR 中,用在 daemon task 中。具体原因没理解。
EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait ); // 等待位
事件组的实际用途
事件组的优缺点
事件组中置位
等待事件组置位
使用一个事件组同步一系列任务
task notification 任务通知
意义:小数据量的传递,简单场景,方便。
Notification Value: 通知接收变量;
状态: Pending or Not-Pending;
相关参数:
configUSE_TASK_NOTIFICATIONS // 使用任务参数
优点相比 通信中间件
1、优点
速度: 发送一个事件、字节非常快,相比其他通信中间件。
RAM: 节省RAM ,因为其他通信中间件需要都需要事先创建,占用大量RAM空间。
2、局限性 部分场景不合适。就是通信中间件的长处。比如缓存大数据量,使能多个任务,向ISR发生数据,在 blocked 中等待。
函数
give 和 take 函数是 xTaskNotify() 和xTaskNotifyWait() 的简化版。
xTaskNotify() :可以模仿二进制信号量,可以模仿简单版本,可以模仿event groups,可以传具体数字。
xTaskNotifyWait;ulBitsToClearOnExit 和 ulBitsToClearOnEntry 的区别。
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); // 发送 notification,有ISR 版本。
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ); // 在blocked 状态中等待获取 notification
轮询的坏处:浪费时间,CPU只能处理一个任务,低级别的任务无法执行。
解决方法:中断和block状态结合。使用信号量将任务置于 blocked 状态。