1. 基础知识
注意:在RTOS中是优先值越高则优先级越高(和ucos/linux的相反)
在移植的时候,主要裁剪FreeRTOS/Source/portable文件夹,该文件夹用来针对不同MCU做的一些处理,如下图所示,我们只需要使用:

1.1配置工程时,选择memMang时,一般使用heap_4.c

  • heap_4: 优点在于可以有效的利用内存碎片来合并为一个大内存.缺点在于只能用来一个ram里.
  • heap_5: 一般针对有外部RAM才用到,优点在于可以同时利用内部ram和外部ram来进行内存碎片合并.

最终添加的库文件有:

然后我们在分配释放内存的时候,就尽量使用RTOS带的函数来实现,分配/释放函数如下所示:

void *pvPortMalloc( size_t xWantedSize );
void vPortFree( void *pv );

1.2 添加头文件路径

  • 添加FreeRTOS\include
  • 添加FreeRTOS\portable\RVDS\ARM_CM3
  • 并将原子中的FreeRTOSConfig.h也复制到我们项目的FreeRTOS\include中(用来配置RTOS系统)

2. FreeRTOSConfig.h配置介绍
一般会写configXXXXX或者INCLUDE_XXXX类似的宏,这两个宏区别在于:

  • configXXXXX

用来实现不同功能,比如定义configUSE_COUNTING_SEMAPHORES为1时,表示使用计数信号量

  • INCLUDE_XXXX

用来是否将某个API函数编译进程序中.
比如定义INCLUDE_xTaskGetSchedulerState 为1 时,则将会编译xTaskGetSchedulerState()函数,如下图所示:

3. FreeRTOS任务状态

3.1 运行态
指当前任务正在运行.
3.2 就绪态
指当前任务正在等待调度,因为有个高优先级/同优先级的任务正在运行中
3.3 阻塞态
当前任务处于等待外部事件通知或通过vTaskDelay()函数进入休眠了,外部事件通知常见有信号量、等待队列、事件标志组、任务通知.
3.4 挂起态
类似于暂停,表示不会再参与任务调度了,通过vTaskSuspend()实现,重新恢复调度则使用xTaskResume()

4. FreeRTOS中断配置

4.1 回忆stm32 NVIC中断
Stm32可以设置NVIC中断组数为0~4,其中0~4区别在于如下图所示:、

比如我们设置为NVIC_PriorityGroup_4时:
表示抢占优先级为4bit(即为2^4,为0~15个抢占优先级),副优先级为0bit(表示没有).

4.2 抢占优先级和副优先级的区别:

  • 1. 抢占优先级和副优先级的值越低,则优先级越高
  • 2. 高的抢占优先级的中断可以直接打断低的抢占优先级的中断
  • 3. 高的副优先级的中断不可以打断低的副优先级的中断(只是两个相同抢占优先级的中断同时来的时候,只会优先选择高的副优先级)

4.3 FreeRTOS中断配置宏

  • configKERNEL_INTERRUPT_PRIORITY

用来配置中断最低抢占优先级,也就是可以FreeRTOS可以管理的最小抢占优先级,所以使用FreeRTOS时,我们尽量设置stm32为NVIC_PriorityGroup_4,这样就可以管理16个优先级了.

  • configMAX_SYSCALL_INTERRUPT_PRIORITY

用来配置FreeRTOS能够安全管理的的最高优先级.比如原子的FreeRTOSConfig.h里就设置为5,而0~4的优先级中断就不会被FreeRTOS因为开关中断而禁止掉(一直都会有),并且不能调用RTOS中的”FromISR”结尾的API函数.比如喂看门狗中断函数就需要设置为0~4

  • 如下图所示(来自原子手册):

4.3 FreeRTOS中断开关函数

portENABLE_INTERRUPTS();
//开中断,将configMAX_SYSCALL_INTERRUPT_PRIORITY至 configKERNEL_INTERRUPT_PRIORITY之间的优先级中断打开 portDISABLE_INTERRUPTS();
//关中断,将configMAX_SYSCALL_INTERRUPT_PRIORITY至 configKERNEL_INTERRUPT_PRIORITY之间的优先级中断禁止掉

5.任务常用API函数

5.1 xTaskCreate创建任务函数
定义如下:

xTaskCreate(    TaskFunction_t pxTaskCode,     //任务函数,用来供给函数指针调用的
const char * const pcName,            //任务的字符串别名
const uint16_t usStackDepth,          //任务堆栈深度,实际申请到的堆栈是该参数的4倍
void * const pvParameters,          //函数参数,用来供给指针调用的
UBaseType_t uxPriority,             //优先级,越高优先级高,范围为0~configMAX_PRIORITIES-1
                            //注意优先级0会创建为空闲任务, 优先级configMAX_PRIORITIES-1会创建一个软件定时器服务任务(管理定时器的)
TaskHandle_t * const pxCreatedTask ); //任务句柄,该句柄可以用于挂起/恢复/删除对应的任务 //返回值 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(-1):表示创建任务堆空间不足pdPASS(1):创建成功

5.2 taskENTER_CRITICAL()和taskEXIT_CRITICAL()
用于任务中进入/退出临界区,调用taskENTER_CRITICAL()主要会关闭其他任务调度.而taskEXIT_CRITICAL()则会恢复任务调度,一般用于初始化外设等.

5.3 taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()
用于在中断函数中进入/退出临界区,作用和上面一样

5.4 挂起/恢复/删除任务函数

void vTaskSuspend( TaskHandle_t xTaskToSuspend );        //挂起一个任务,参数为挂起任务的句柄,如果为NULL则表示挂起自身任务

void vTaskResume( TaskHandle_t xTaskToResume );       //恢复一个任务

BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume);//从中断函数中恢复一个任务,返回1表示恢复成功

void vTaskDelete( TaskHandle_t xTaskToDelete );      //删除一个任务,如果从任务函数中退出的话,则需要调用vTaskDelete(NULL)来删除自身任务

5.5 vTaskDelay()延时函数

void vTaskDelay( const TickType_t xTicksToDelay );    //参数表示延时的系统滴答数

比如延时500ms可以写为: vTaskDelay( 500/portTICK_RATE_MS );
portTICK_RATE_MS是个宏,表示当前系统的1个滴答需要多少ms,而500/portTICK_RATE_MS则表示当前500ms需要多少个系统滴答数.

6. 队列
6.1简介
队列用于任务与任务或者任务与中断之间的通信.比如key任务检测到按键按下时,则可以通过队列向lcd显示任务发送信息,使得lcd切换界面.
队列采用先进先出存储机制.队列发送数据可以有两种方式:浅拷贝、深拷贝.

  • 数据量不大的情况下,都使用深拷贝(会分配新的空间,并进行数据拷贝,缺点在于耗时)
  • 数据量大的情况下,都使用浅拷贝(通过指针方式,前提是要发送的数据必须不会被释放的)

6.2队列的优点
队列可以通过任何任务或者中断进行访问,可以随时存取数据消息.
并且出入队的时候可以进行任务阻塞,比如某个任务进行读消息出队时,如果没有消息,则可以实现进入休眠状态,直到有消息才唤醒任务.

6.3队列创建删除相关API

QueueHandle_t xQueueCreate( uxQueueLength, uxItemSize );
//动态创建队列,内存会交给RTOS自动分配
// uxQueueLength:队列长度(表示队列中最大多少条消息),uxItemSize:每个队列消息的长度(以字节为单位)
//返回值: NULL(0, 表示分配失败),非0(表示返回该队列分配好的地址)
//注意:使用自动分配时,需要配置configSUPPORT_DYNAMIC_ALLOCATION宏为1,否则只能由用户来分配. QueueHandle_t xQueueCreateStatic( uxQueueLength, uxItemSize, pucQueueStorage, pxQueueBuffer );
//静态创建队列,内存需要由用户事先分配好
// uxQueueLength:队列长度(表示队列中最大多少条消息),uxItemSize:每个队列消息的长度(以字节为单位)
// pucQueueStorage:指向用户事先分配好的存储区内存(必须为uint8_t型)
// pxQueueBuffer:指向队列结构体,用来提供给RTOS初始化.然后给用户使用
//返回值: NULL(0, 表示分配失败),非0(表示返回该队列分配好的地址) vQueueDelete( QueueHandle_t xQueue );
//删除队列,并释放空间 xQueueReset( xQueue );
//将队列里的消息清空一次,也就是恢复初始状态

6.4队列出入队相关API

xQueueSend( xQueue, pvItemToQueue, xTicksToWait );
//插入队尾,和xQueueSendToBack函数效果一致
// xQueue:队列句柄
//PvItemToQueue:消息数据,会通过数据拷贝到队列中,如果想使用浅拷贝,则可以发送一个变量来存储要真正发送的缓冲区地址即可.
// xTicksToWait:阻塞时间,单位为RTOS时钟滴答值,如果configTICK_RATE_HZ是1000,则填入的值表示阻塞的是多少ms,否则的话需要通过X/portTICK_RATE_MS来转换一下,才能实现阻塞Xms.
//xTicksToWait==0:表示入队满了,则直接退出该函数
// xTicksToWait==portMAX_DELAY:表示一直阻塞,直到队列有空位为止.
//注意: INCLUDE_vTaskSuspend宏必须为1,否则任务无法进入休眠状态实现阻塞效果.
//返回值: errQUEUE_FULL(队列已满) pdPASS(通过) xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait );
//插入队头,参数和上面描述一致 xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait );
//插入队尾,参数和上面描述一致 xQueueOverwrite( xQueue, pvItemToQueue );
//将之前未出队的旧数据全部清空,然后再入队,该函数适用于长度为1的队列 xQueueReceive( xQueue, pvBuffer, xTicksToWait );
//从队列头部读出一个消息,并且这个消息会出队(删除掉) xQueuePeek( xQueue, pvBuffer, xTicksToWait );
//从队列头部读出一个消息,但是这个消息不会出队(不会删除)

PS:这些API函数只能用于任务里调用,如果要在中断服务函数中调用,则在函数名后添加FromQueue即可,比如xQueueSendFromQueue()函数

6.5 中断发送/读取消息队列时,要注意的事情

使用中断相关的读写队列相关的API时,第3个参数是不一样的,比如xQueueSendFromISR():

  • PxHigherPriorityTaskWoken

用来标记退出该函数后是否需要进行任务切换,因为我们发送队列时,有可能会将某个阻塞任务退出阻塞态,而此时又在中断中,所以当PxHigherPriorityTaskWoken为pdTRUE时,我们则必须进行一次任务切换.

可以通过portYIELD_FROM_ISR()来进行任务切换,并且我们不需要去判断PxHigherPriorityTaskWoken是否为pdTRUE,因为该函数内部有判断的,如下图所示: 

来个中断函数发送队列示例:

extern QueueHandle_t Message_Queue;                          //信息队列句柄

void USART1_IRQHandler(void)                       //串口1中断服务程序
{
BaseType_t xHigherPriorityTaskWoken; //定义任务切换标志位
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
//处理中断接收数据
} if (Message_Queue!=NULL) //判断Message_Queue是否已创建
{
xQueueSendFromISR(Message_Queue, RX_BUF,&xHigherPriorityTaskWoken);
//向队列Message_Queue中发送RX_BUF
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
//通过portYIELD_FROM_ISR()判断是否需要切换任务
}
}

PS:尽量将portYIELD_FROM_ISR()写在中断函数末尾处

6.6示例-任务之间的伪代码

按键任务向打印任务发送按键消息队列,代码如下:

QueueHandle_t Key_Queue; //按键值消息队列句柄

int main()
{
//...省略N行代码 Key_Queue=xQueueCreate(,sizeof(u8)); //创建消息Key_Queue,长度为1

//创建两个任务:key_task()、print_task()
//...省略N行代码
}
key_task() //获取按键值
{
  while()
  {
    key=KEY_Scan(); //扫描按键
    if((Key_Queue!=NULL)&&(key)) //消息队列Key_Queue创建成功,并且按键被按下
    {
      err=xQueueSend(Key_Queue,&key,);
      if(err==errQUEUE_FULL) //发送按键值
      {
        printf("队列Key_Queue已满,数据发送失败!\r\n");
      }
    }
    vTaskDelay(); //延时10个时钟节拍
  }
} print_task() //打印按键值
{
  u8 key;
  while()
  {
    if(Key_Queue!=NULL)
    {
      if(xQueueReceive(Key_Queue,&key,portMAX_DELAY))//请求消息Key_Queue
      {
        printf("key=%d\r\n",key);
      }
    }
    vTaskDelay(); //延时10个时钟节拍
  }
}

7. RTOS软件定时器

7.1简介
在之前的任务创建的时候有讲到过,RTOS会自动创建一个优先级configMAX_PRIORITIES-1的软件定时器服务任务(管理定时器的).
所以我们写一个定时器回调函数时,则会被该定时器服务任务调用,所以在我们软件定时器函数中不能使用vTaskDelay()阻塞之类的API函数,否则会将系统中的定时器服务函数给阻塞掉.

7.2 FreeRTOSConfig.h相关的定时器配置

#define configUSE_TIMERS 1              //为1时启用软件定时器

#define configTIMER_TASK_PRIORITY    31      //设置软件定时器优先级可设置的值范围为0~31

#define configTIMER_QUEUE_LENGTH    5       //软件定时器队列长度

#define configTIMER_TASK_STACK_DEPTH    200    //设置每个软件定时器任务堆栈大小

7.3定时创建相关API

TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,    //定时器字符串别名
                    const TickType_t xTimerPeriodInTicks,          
                   //需要定时的周期值,比如通过200/ portTICK_RATE_MS来转换实现定时200毫秒
                    const UBaseType_t uxAutoReload,        
                   //是否重载(周期性/单次性),若为pdTRUE(1)表示为周期性,为pdFALSE(0)表示为单次
                    void * const pvTimerID,
                    //定时器ID号,一般用于多个定时器共用一个定时器回调函数,否则填0即可
                    TimerCallbackFunction_t pxCallbackFunction);//定时器回调函数
xTimerDelete( xTimer, xTicksToWait );
//删除定时器
//xTicksToWait:指定该定时器在多少时钟节拍数之前删除掉,为0则立即删除,一般设为100(如果设为0,则如果在该操作之前还有其它设置定时器操作的话,则不会进行阻塞等待,从而返回false)

7.4 定时器其它常用API

xTimerChangePeriod( xTimer, xNewPeriod, xTicksToWait );
//修改定时器周期,在中断中则使用xTimerChangePeriodFromISR()
// xNewPeriod:要修改的周期值
//xTicksToWait:指定该定时器在多少时钟节拍数之前修改好,为0则立即删除
//xTimerReset( xTimer, xTicksToWait );
//复位定时器,让定时器重新计数,在中断中则使用xTimerResetFromISR()
// xTicksToWait:和上面内容类似 xTimerStart( xTimer, xTicksToWait );
//启动定时器,如果定时器正在运行的话调用该函数的结果和xTimerReset()一样, 在中断中则使用xTimerResetFromISR () xTimerStop( xTimer, xTicksToWait );
//停止定时器, 在中断中则使用xTimerStopFromISR ()

PS:中断中使用定时器API时,同样和队列一样,也需要在函数末尾通过portYIELD_FROM_ISR()进行一次任务切换判断

8. 信号量

在项目中我们一般用二值信号量,用来同步数据的.

比如任务A要向任务B发送一个很大的数据buf,而用队列的话会进行复制拷贝,从而占用大量时间.

此时我们不妨定义一个全局数据buf,任务A修改这个buf,发送一个信号量给任务B,任务B就去读取这个全局数据buf即可.从而省去了队列复制拷贝的时间.

8.1定义信号量举例

SemaphoreHandle_t BinarySemaphore;       //二值信号量句柄

BinarySemaphore=xSemaphoreCreateBinary();            //创建二值信号量

 8.2在中断中发送信号量过程

BaseType_t xHigherPriorityTaskWoken;

xSemaphoreGiveFromISR(BinarySemaphore,&xHigherPriorityTaskWoken);
//发送二值信号量 portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换

 8.3在任务中发送信号量过程

xSemaphoreGive(BinarySemaphore);
//返回值: pdPASS(0, 表示发送成功,如果信号量一直未处理,则会返回值失败FULL)

8.4 在任务中接收信号量过程

err=xSemaphoreTake(BinarySemaphore,portMAX_DELAY);          //获取信号量
// portMAX_DELAY:进入阻塞态一直等待获取
//返回值为pdTRUE(OK) pdFALSE(err)

STM32-FreeRTOS快速学习之总结1的更多相关文章

  1. stm32寄存器版学习笔记07 ADC

    STM32F103RCT有3个ADC,12位主逼近型模拟数字转换器,有18个通道,可测量16个外部和2个内部信号源.各通道的A/D转换可以单次.连续.扫描或间断模式执行. 1.通道选择 stm32把A ...

  2. 【STM32系列汇总】小白博主的STM32实战快速进阶之路(持续更新)

    我把之前在学习和工作中使用STM32进行嵌入式开发的经验和教程等相关整理到这里,方便查阅学习,如果能帮助到您,请帮忙点个赞: 本文的宗旨 STM32 只是一个硬件平台,同样地他可以换成MSP430,N ...

  3. 60分钟Python快速学习(给发哥一个交代)

    60分钟Python快速学习 之前和同事谈到Python,每次下班后跑步都是在听他说,例如Python属于“胶水语言啦”,属于“解释型语言啦!”,是“面向对象的语言啦!”,另外没有数据类型,逻辑全靠空 ...

  4. LinqPad工具:帮你快速学习Linq

    LinqPad工具:帮你快速学习Linq 参考: http://www.cnblogs.com/li-peng/p/3441729.html ★:linqPad下载地址:http://www.linq ...

  5. 快速学习C语言一: Hello World

    估计不会写C语言的同学也都听过C语言,从头开始快速学一下吧,以后肯定能用的上. 如果使用过其它类C的语言,如JAVA,C#等,学C的语法应该挺快的. 先快速学习并练习一些基本的语言要素,基本类型,表达 ...

  6. 【Java线程池快速学习教程】

    1. Java线程池 线程池:顾名思义,用一个池子装载多个线程,使用池子去管理多个线程. 问题来源:应用大量通过new Thread()方法创建执行时间短的线程,较大的消耗系统资源并且系统的响应速度变 ...

  7. 【Java的JNI快速学习教程】

    1. JNI简介 JNI是Java Native Interface的英文缩写,意为Java本地接口. 问题来源:由于Java编写底层的应用较难实现,在一些实时性要求非常高的部分Java较难胜任(实时 ...

  8. 快速学习bootstrap前台框架

    W3c里的解释 使用bootstrap需要注意事项 1.  在html文件第一行要加上<!doctype html>[s1] 2.  导入bootstrap.min.css文件 3.  导 ...

  9. C#快速学习笔记(译)

    下面是通过代码快速学习C#的例子. 1.学习任何语言都必定会学到的hello,world! using System; public class HelloWorld { public static ...

  10. Dapper快速学习

    Dapper快速学习 我们都知道ORM全称叫做Object Relationship Mapper,也就是可以用object来map我们的db,而且市面上的orm框架有很多,其中有一个框架 叫做dap ...

随机推荐

  1. git常用方法整理

    Git是什么? Git是目前世界上最先进的分布式版本控制系统(没有之一). Git有什么特点?简单来说就是:高端大气上档次! 初始化本地仓库 mkdir xxx cd xxx git init 创建本 ...

  2. Bulk API

    承接上文,使用Java High Level REST Client操作elasticsearch Bulk API 高级客户端提供了批量处理器以协助批量请求 Bulk Request BulkReq ...

  3. 设置mysql InnoDB存储引擎下取消自动提交事务

    mysql 存储引擎中最长用的有两种,MyISAM 存储引擎和InnoDB存储引擎. 1.MyISAM 存储引擎 不支持事务,不支持外键,优势是访问速度快: 2.InnoDB存储引擎 支持事务,一般项 ...

  4. Java基础实训

  5. partial 的随笔

    partial class Dmeos { public int Ager { get; set; } public void Run() { Console.WriteLine(Ager); } } ...

  6. 算法与数据结构(四) 图的物理存储结构与深搜、广搜(Swift版)

    开门见山,本篇博客就介绍图相关的东西.图其实就是树结构的升级版.上篇博客我们聊了树的一种,在后边的博客中我们还会介绍其他类型的树,比如红黑树,B树等等,以及这些树结构的应用.本篇博客我们就讲图的存储结 ...

  7. NeuChar 平台使用及开发教程(二):设置平台账号

    在上一篇<NeuChar 平台使用及开发教程(一):开始使用 NeuChar>中我们了解了 NeuChar 的角色和大体功能框架,并进行了注册,本文将介绍如何设置多账号,以便让 NeuCh ...

  8. zuul进阶学习(二)

    1. zuul进阶学习(二) 1.1. zuul对接apollo 1.1.1. Netflix Archaius 1.1.2. 定期拉 1.2. zuul生产管理实践 1.2.1. zuul网关参考部 ...

  9. 知识扩展--if...else...与switch...case...的执行原理

    一.简述 编程语言中的条件分支结构有两种:if-else和switch-case,这两种条件分支之间可以相互转换,但是也存在一些区别,那么什么时候该用if-else,什么时候该用switch-case ...

  10. 重磅推出TabLayout高级窗口组件

    TabLayout是在APICloud现有窗口系统基础上升级而来的高级窗口组件,符合Material Design规范,可通过简单的配置为窗口实现原生的导航栏和TabBar,它将帮助您节省30%以上的 ...