STM32-FreeRTOS快速学习之总结1

发布者:HarmoniousSoul最新更新时间:2020-07-07 来源: eefocus关键字:STM32  FreeRTOS  快速学习 手机看文章 扫描二维码
随时随地手机看文章

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 添加头文件路径

  • 添加FreeRTOSinclude

  • 添加FreeRTOSportableRVDSARM_CM3

  • 并将原子中的FreeRTOSConfig.h也复制到我们项目的FreeRTOSinclude中(用来配置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(1,sizeof(u8)); //创建消息Key_Queue,长度为1

   

   //创建两个任务:key_task()、print_task()

   //...省略N行代码

}


key_task()    //获取按键值

{

  while(1)

  {

    key=KEY_Scan(0); //扫描按键

    if((Key_Queue!=NULL)&&(key))    //消息队列Key_Queue创建成功,并且按键被按下

    {

      err=xQueueSend(Key_Queue,&key,10);

      if(err==errQUEUE_FULL) //发送按键值

      {

        printf("队列Key_Queue已满,数据发送失败!rn");

      }

    }

    vTaskDelay(10); //延时10个时钟节拍

  }

}


print_task()    //打印按键值

{

  u8 key;

  while(1)

  {

    if(Key_Queue!=NULL)

    {

      if(xQueueReceive(Key_Queue,&key,portMAX_DELAY))//请求消息Key_Queue

      {

        printf("key=%drn",key);

      }

    } 

    vTaskDelay(10); //延时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();            //创建二值信号量

[1] [2]
关键字:STM32  FreeRTOS  快速学习 引用地址:STM32-FreeRTOS快速学习之总结1

上一篇:LIS3DH三轴加速度计-实现欧拉角(俯仰角,横滚角)
下一篇:STM32-正弦波可调(50HZ~20KHZ可调、峰峰值0~3.3V可调)

推荐阅读最新更新时间:2024-11-13 20:28

Stm32 debug停留在"BKPT 0xAB"或者"SWI 0xAB"的解决办法
一、、背景:   曾经在工作中接触过STM32一段时间,但没有深入的去学习,只是用前辈搭建好的模型来实现一些功能罢了,俗话说的好,大树底下好乘凉,开发确实轻松了,可是不深究点,又觉着心里不踏实,然而也一直没花时间去深究。刚好,最近需要重新使用STM32,完全自己开发,没想到今天一上来就让我碰上个不小的问题,废话不多说,进入正题。 二、正文:   在使用串口的时候,代码可以正常编译,没有报任何错误,烧录进MCU内,就是看不到程序正常运行的现象,而把串口部分注释掉就没问题。进入调试模式,发现代码停在 BKPT  0xAB 这里,并不是死循环,按下全速运行键“F5”,代码会立马在该段被终止,不会继续往下跑,这里说明了main函数都没
[单片机]
<font color='red'>Stm32</font> debug停留在
stm32 的RTC 时钟程序
前些日子做了stm32 RTC时钟的程序,现在把它记录下来。 首先配置RTC,,使用外部时钟32.768KHz。其中配置了秒中断。 RTCFirstConfigure()程序是第一次配置RTC,如果配置后以后上电不需要重新配置,如果RTC时钟快了,可内部校准。 void RTCFirstConfigure() //first ini { RCC_BackupResetCmd(ENABLE); RCC_BackupResetCmd(DISABLE); RCC_LSEConfig(RCC_LSE_ON); //enable LSE clock 32.768K while (RCC_GetFlagSta
[单片机]
STM32 USB HID 自定义设备 bulk 传输
ST(意法半导体公司)为STM32系列处理器编写了外设USB的库,并提供了很好的参考例程,本文就是参考ST提供的例程,在STM32F4 discovery板子上实现usb bulk传输。Host端是在linux平台上利用libusb库函数写的读写USB应用。 本次实现在STM32 USB例程中的Device HID 鼠标例程基础上添加bulk传输端点修改而来。 usb_conf.h 文件中添加 bulk传输端点 /* * endpoint 0x80 and 0x00 are used for enumerating device. * endpoint 0x81 and 0x80 are used for cont
[单片机]
stm32 的3种下载程序方式
个人记录: 了解这些,自己多多总结,也算是对开发板硬件接口的了解。 没总结完, 【1】ISP下载 这里类似51. boot1拨到0, 就行了 下载需要来回拨动拨码开关 【2】J-LINK 可下载 可仿真 (分为SW和JTAG模式) JTAG 或 SW 方式,在KEI的编程选项里面设定,选择好仿真器后,在相关的SETTINGS设置项里面会有 JTAG和SWD方式的选择。 只需要修改KEI环境配置,和硬件设置,这个SWD只需要5个,一个电源,一个地 和仅需要三根调试线SWDO(可选的跟踪),SWDIO(data I/O),SWDCLK(时钟针) 如果用jtag模式下载的话,需要接:jlink的
[单片机]
STM32学习笔记--------GPIO
1、概述 GPIO,即通用I/O(输入/输出)端口,是STM32可控制的引脚。STM32芯片的GPIO引脚与外部设备连接起来,可实现与外部通讯、控制外部硬件或者采集外部硬件数据的功能。 STM32F103有7组IO。分别为GPIOA~GPIOG,每组IO有16个IO口,共有112个IO口。通常称为 PAx、PBx、PCx、PDx、PEx、PFx、PGx,其中x为0-15,F1系列是基于Cortex-M3内核 2、GPIO工作模式(暂时不做深入研究先用) ─ 输入浮空 ─ 输入上拉 ─ 输入下拉 ─ 模拟输入 ─ 开漏输出 ─ 推挽式输出 ─ 推挽式复用功能 ─ 开漏复用功能 3、GPIO使用 使用步骤 1.硬件时钟
[单片机]
<font color='red'>STM32</font><font color='red'>学习</font>笔记--------GPIO
基于STM32F103系统的FreeRTOS程序移植
1 FreeRTOS移植及配置 在程序中,移植了正点原子的基于STM32的FreeRTOS程序,编写了自己的内存管理程序malloc.c,程序主要结构如下: FreeRTOS_CORE中是FreeRTOS的核心文件,包括与协程有关的croutine.c,与事件组有关的event_groups.c,与列表有关的list.c,与队列有关的queue.c,与任务有关的tasks.c,与定时器时钟有关的timers.c。 FreeRTOS_PORTABLE中是与FreeRTOS内存管理有关的文件,包括port.c和heap_4.c,port.c中主要包含一些与中断有关的函数,heap_4.c上一篇文章有详细介绍,与内存分配释放
[单片机]
基于STM32F103系统的<font color='red'>FreeRTOS</font>程序移植
深度解析STM32外设配置冲突问题
近日有客户反映,他在在使用STM32F103C8T6的时候遇到如下问题: I2C1使用PB6和PB7口,定时器TIM3使用PB0PB1PB4PB5做4路PWM。但在使用的过程中,如果只初始化定时器就没有任何问题,但是一旦初始化I2C1,那么定时器的通道2(PB5)就不能产生PWM波,而是保持高电平。 客户查阅手册得知PB5的默认复用功能是I2C1的SMBA引脚,但是它的I2C1是初始化为I2C模式的,并不是初始化为SMBAS模式,而且同样的方式在F0上测试是可用的。它本来用的是标准库开发的,然后尝试使用STM32CubeMx进行硬件配置,使用HAL库新建工程,还是存在同样的问题。 就上面的问题,查看了其有关I2C1和TIM
[单片机]
深度解析<font color='red'>STM32</font>外设配置冲突问题
工程师STM32单片机学习基础手记(4):用PWM实现荧火虫灯(四)
补充一些硬件知识      SEGGER 给出的Jlink引脚图      开发板上的连接图      标准的JTAG连接图,供对照参考。   调试方式既可以用JTAG,也可以用SW。   以下是转载:   SWD 仿真模式概念简述   先所说 SWD 和传统的调试方式有什么不一样:   首先给大家介绍下经验之谈:   (一): SWD 模式比 JTAG 在高速模式下面更加可靠。 在大数据量的情况下面 JTAG 下载程序会失败, 但是 SWD 发生的几率会小很多。 基本使用 JTAG 仿真模式的情况下是可以直接使用 SWD 模式的, 只要你的仿真器支持。 所以推荐大家使用这个模式。   (二): 在大家
[模拟电子]
工程师<font color='red'>STM32</font>单片机<font color='red'>学习</font>基础手记(4):用PWM实现荧火虫灯(四)
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved