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. js 原型链的介绍

    对象的原型链:一个对象所拥有的属性不仅仅是它本身拥有的属性,他还会从其他对象中继承一些属性.当js在一个对象中找不到需要的属性时,它会到这个对象的父对象上去找,以此类催,这就构成了对象的原型链. 下面 ...

  2. CVE-2018-20129:DedeCMS V5.7 SP2前台文件上传漏洞

    一.漏洞摘要 漏洞名称: DedeCMS V5.7 SP2前台文件上传漏洞上报日期: 2018-12-11漏洞发现者: 陈灿华产品首页: http://www.dedecms.com/软件链接: ht ...

  3. 怎么过滤 &nbsp;

    replace(str, " ", ""); 就是这么简单

  4. Rewrite JSON project with Fetch

    上传 JSON 数据 使用fetch()来发布json编码的数据. var url = 'https://example.com/profile'; var data = {username: 'ex ...

  5. JSON File Parse

    1.write a json file base on website(在网站上写一个json文件) json文件网址:https://raw.githubusercontent.com/DJOSIM ...

  6. 学用HBuilder开发App的看过来

    自己的呕心沥血之作吧,花了一年时间,系统介绍HTML5 App开发的相关技术. 越来越多的公司采用HTML5来快速开发移动跨平台App,它支持当前市场流行的移动设备. 本书主要介绍了HTML5在移动A ...

  7. Hadoop源码分析:Hadoop编程思想

    60页的ppt讲述Hadoop的编程思想 下载地址 http://download.csdn.net/detail/popsuper1982/9544904

  8. RabbitMQ 集群原理和完善

    一.RabbitMQ集群方案的原理 RabbitMQ这款消息队列中间件产品本身是基于Erlang编写,Erlang语言天生具备分布式特性(通过同步Erlang集群各节点的magic cookie来实现 ...

  9. emWin智能家居主界面设计,含uCOS-III和FreeRTOS两个版本

    第6期:智能家居主界面设计配套例子:V6-910_STemWin提高篇实验_智能家居主界面设计(uCOS-III)V6-911_STemWin提高篇实验_智能家居主界面设计(FreeRTOS) 例程下 ...

  10. idea之debug

    [转载]原文地址:https://www.cnblogs.com/nihaorz/p/7613967.html 在Intellij IDEA中使用Debug Debug用来追踪代码的运行流程,通常在程 ...