DAC,FreeRTOS的任务,临界区和信号量,FreeRTOS之消息队列,定时器和事件标志组

342 阅读9分钟

DAC,FreeRTOS的任务,临界区和信号量

练习:

使用ADC读出光敏电阻电路的电压值

三十一.DAC

1.概念和特性

DAC就是将数字信号转换成模拟信号,程序给定数字值,通过DAC输出模拟信号。

0

0

可以选择PA4 PA5输出模拟电压

0

0

2.库函数的编程实现

需要添加库函数源文件

0

(1)使能GPIO和DAC时钟

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);

(2)GPIOA4配置为模拟功能

GPIO_Init(...);

(3)初始化DAC

void DAC_Init(uint32_t DAC_Channel, DAC_InitTypeDef* DAC_InitStruct);
参数:
    DAC_Channel - 哪个通道 DAC_Channel_1
    DAC_InitStruct - DAC初始化结构
   
typedef struct
{
  uint32_t DAC_Trigger;                      /*!< 外部触发选择 @ref DAC_trigger_selection */uint32_t DAC_WaveGeneration;               /*!< 产生波形配置 @ref DAC_wave_generation */uint32_t DAC_LFSRUnmask_TriangleAmplitude; /*!< 噪声波形相关 @ref DAC_lfsrunmask_triangleamplitude */uint32_t DAC_OutputBuffer;                 /*!< 输出缓冲开关 @ref DAC_output_buffer */
}DAC_InitTypeDef;     

(4)使能DAC的转换通道

DAC_Cmd(DAC_Channel_1, ENABLE);

(5)设置DAC的输出值

void DAC_SetChannel1Data(uint32_t DAC_Align, uint16_t Data);
参数:
    DAC_Align - 输出位数和对齐方式 DAC_Align_12b_R
    Data - 输出值

三十二.FreeRTOS

1.介绍

FreeRTOS的名字由两部分组成:Free和RTOS。Free就是免费的,自由的,不受约束的意思;RTOS指的是Real Time Operating System,叫做实时操作系统。因此FreeRTOS就是一个免费的实时操作系统。实时操作系统指的是具有实时性地操作系统,所谓实时性表示操作会在指定时间内完成。比如UCOS,FreeRTOS,RT-Thread,RTX等都是实时性的操作系统。

FreeRTOS每个任务都有一个优先级,任务调度器根据优先级来决定下一刻应该执行哪个任务。FreeRTOS十分小巧,可以在资源优先的微控制器中运行。

FreeRTOS属于抢占式内核,任务之间可以互相抢占。FreeRTOS本身只包含操作系统的核心功能,外部扩展非常方便。

2.将FreeRTOS移植到M4开发板运行

3.Freertos源码介绍

1.基础硬件的初始化
2.创建第一个任务
3.在第一个任务重完成其他所有硬件的初始化,创建所需的所有任务
4.第一个任务退出

4.FreeRTOS的任务机制

任务的操作包括任务的创建,删除,挂起和恢复等,其系统启动过程大体如下:

1.基础硬件的初始化
2.创建第一个任务
3.在第一个任务重完成其他所有硬件的初始化,创建所需的所有任务
4.第一个任务退出

(1)任务的创建

BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,//任务函数
    const char * const pcName,//任务名
    const configSTACK_DEPTH_TYPE usStackDepth,//任务栈的大小
    void * const pvParameters,//任务函数的参数
    UBaseType_t uxPriority,//任务优先级
    TaskHandle_t * const pxCreatedTask);//任务控制块指针//开启任务调度        
vTaskStartScheduler();    

(2)任务的删除

//在任务函数中调用 vTaskDelete(NULL);

(3)任务优先级

数字越大,优先级越高,0表示最低优先级
高优先级的任务可以抢占低优先级的任务,优先级相同的任务可以互相抢占
调度器总是在所有可运行的任务重选择优先级最高的任务来运行
调度器在一个时间片时间内选择一次,时间片时间由系统心跳时钟来决定
    #define configTICK_RATE_HZ ((TickType_t)1000) //1ms

(4)任务函数中的延时

任务执行时通常会有延时,作用是让出CPU,让其他低优先级的任务有机会运行
    vTaskDelay(200);//200ms

练习:

开启三个任务,分别200ms 300ms 500ms运行一次,测试不同优先级情况下的执行情况

5.FreeRTOS中的互斥和同步机制

(1)临界区

临界区用于保护不能被中断的代码,比如某些硬件的初始化/看门狗喂狗,进入临界区后,不在发生任务调度,临界区是否处理中断可以配置。

0

临界区的用法:

//进入临界区
taskENTER_CRITICAL();
​
//......//退出临界区
taskEXIT_CRITICAL();

临界区用于保护代码不被打断,常用于保护关键的代码片段,临界区代码执行的时间尽量短,长时间处于临界区会影响系统的正常运行,临界区代码不允许睡眠。

临界区也可以用于中断,需要使用以下接口:

//进入临界区
taskENTER_CRITICAL_FROM_ISR();
​
//......//退出临界区
taskEXIT_CRITICAL_FROM_ISR(ulReturn);

(2)FreeRTOS中的时基

系统中所有的软件时钟都来自于系统心跳时钟(tick),软件定时器和系统任务调度的最小时间单位就是一个心跳时钟周期,可以通过FreeRTOSConfig.h文件中的宏configTICK_RATE_HZ来配置

#define configTICK_RATE_HZ ((TickType_t)1000) //时基1ms//意味着系统心跳时钟的周期为1ms,系统延时的最小单位就是1ms,低于1ms无法实现
//如果希望使用小于系统时基的延时,需要直接使用系统定时器
//在延时时注意对系统心跳时钟的关闭和开启

在实际开发中,如果产品是固定电源供电,且没有功耗的要求,该值可以往大调

如果是移动电源供电或者产品有功耗的要求,此时在满足要求的前提下尽量调小

(3)信号量

FreeRTOS中的信号量和Linux中的信号量的机制是一样的,也是一个计数器,用于控制共享资源的互斥和同步,使用时先设置初始值,在访问和释放贡献资源时分别进行P操作(-1)和V操作(+1),当计数值为0时不再允许P操作,直到计数值重新大于0位置。

相关函数:

1)创建信号量

SemaphoreHandle_t/QueueHandle_t sem_m = xSemaphoreCreateMutex();//互斥型信号量用于互斥
SemaphoreHandle_t/QueueHandle_t sem_b = xSemaphoreCreateBinary();//二值型信号量用于同步

2)获取信号量(P操作)

//获取互斥性/二值型信号量
xSemaphoreTake(sem_m/sem_b,阻塞时间);//阻塞时间给0立即返回,否则等待指定的心跳数

3)释放信号量(V操作)

xSemaphoreGive(sem_m/sem_b);
​
//在中断中释放信号量要使用
xSemaphoreGiveFromISR(sem_m/sem_b,&token);

互斥/二值型信号量可以用于任务之间的互斥和同步。

互斥指的是多个任务访问共享资源的互斥,访问时获取信号量,访问结束后释放信号量。

同步表示按顺序操作,一个任务获取信号量,一个任务释放信号量。

FreeRTOS之消息队列,定时器和事件标志组

title: 'DAY13-FreeRTOS之消息队列,定时器和事件标志组'
categories: STM32
abbrlink: b706114e
date: 2022-07-28 23:47:13

(4)消息队列

消息队列是用于 中断/任务 向任务传输信息,同时也实现了同步操作。中断和任务都可以发送消息,但是只能在任务中接收消息,在中断中和任务中发送消息的接口不同,发送消息的方式有多种。

1)创建消息队列

QueueHandle_t xQueueCreate(const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize);
参数:
    uxQueueLength - 消息队列中存储消息的最大个数
    uxItemSize - 单个消息的长度
返回值:
    返回NULL表示失败,成功返回消息队列的句柄    

2)发送消息

BaseType_t xQueueSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait);
参数:
    xQueue - 消息队列的句柄
    pvItemToQueue - 发送消息的内容
    xTicksToWait - 等待时间(tick)
返回值:
    返回pdPASS表示发送成功,返回errQUEUE_FULL表示消息队列已满    
    
//发送到队列头
BaseType_t xQueueSendToFront( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait);   
//中断中发送消息
BaseType_t xQueueSendFromISR( QueueHandle_t xQueue, const void * const pvItemToQueue, BaseType_t * const pxHigherPriorityTaskWoken); 

3)接收消息

BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait );
参数:
    xQueue - 消息队列的句柄
    pvBuffer - 接收消息的缓冲区
    xTicksToWait - 等待时间(tick)
返回值:
    返回pdPASS表示接收成功,返回errQUEUE_EMPTY表示消息队列为空,没收到数据    
    
//接收消息但是不把消息从队列中移除
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait ); 
//中断中不能使用以上函数,xQueueReceiveFromISR

4)查询消息队列中消息个数

UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );

练习:

使用消息队列,实现串口命令的操作

(5)软件定时器

定时器可以说是每个MCU必有的外设,但是不同的MCU的定时器功能和应用有所区别,需要硬件的支持。FreeRTOS提供软件定时器,软件定时器以系统心跳(tick)作为时基,精度不如硬件定时器,但是在精度要求不高的周期性操作可以使用。

1)定时器相关配置

/* Software timer definitions. */
#define configUSE_TIMERS   1//使用软件定时器
#define configTIMER_TASK_PRIORITY   ( 2 )//定时器任务的优先级
#define configTIMER_QUEUE_LENGTH   10//定时器命令队列长度
#define configTIMER_TASK_STACK_DEPTH  ( configMINIMAL_STACK_SIZE * 2 )//定时器任务堆栈长度

定时器的使用分为两种模式,单次定时器和周期定时器,单次定时器超时执行一次之后就停止,可以通过代码再次启动,周期定时器超时之后自动重新启动,超时函数就会周期性执行。

2)创建软件定时器

TimerHandle_t xTimerCreate( const char * const pcTimerName, //定时器名称     
    const TickType_t xTimerPeriodInTicks,//定时器周期,单位是ticks
    const UBaseType_t uxAutoReload,//周期/单次定时器选择 pdTRUE/pdFALSE
    void * const pvTimerID,//定时器ID,区分使用同一个超时函数的不同定时器
    TimerCallbackFunction_t pxCallbackFunction );//定时器超时处理函数//返回创建的定时器的句柄        

3)启动定时器

BaseType_t xTimerStart( TimerHandle_t xTimer, const TickType_t xTicksToWait );
参数:
    xTimer - 定时器句柄
    xTicksToWait - 发送消息的等待时间
返回pdPASS表示成功
​
//中断中启动定时器 ----- xTimerStartFromISR     

4)停止定时器

BaseType_t xTimerStop( TimerHandle_t xTimer, const TickType_t xTicksToWait );
参数:
    xTimer - 定时器句柄
    xTicksToWait - 发送消息的等待时间
返回pdPASS表示成功

练习:

使用软件定时器实现LED的闪烁和读取ADC电压,800ms循环一次

(6)事件标志组

1)概念

事件标志组本质上是一个多位二进制数据,用于一对多的同步,也就是一个任务去等待多个条件的机制,一对多的同步分为与同步和或同步。

与同步需要等待多个事件标志都符合才能获取事件标志成功

或同步只要等待多个事件标志中其中一个符合就能获取事件标志成功

2)相关的宏

#define configUSE_16_BIT_TICKS   0
//为0表示24位标志位,为1表示16位标志位

3)创建事件标志组

EventGroupHandle_t xEventGroupCreate(void);
//返回事件标志组句柄,返回NULL表示失败

4)设置事件标志位

EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear );
参数:
    xEventGroup - 事件标志组的句柄
    uxBitsToClear - 要清0的位(为1的位清0)
返回清零之前的标志组的值
​
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet );
//将哪些位置1//可以在中断中设置标志位,但是函数名要加FromISR
//获取标志组的值将Set改为Get              

5)等待指定的事件标志位

EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,//事件标志组的句柄
    const EventBits_t uxBitsToWaitFor,//等待哪些位(等待的位给1)
    const BaseType_t xClearOnExit,//pdTRUE退出后清0,pdFALSE就不变
    const BaseType_t xWaitForAllBits,//pdTRUE表示与同步,pdFALSE就是或同步
    TickType_t xTicksToWait );//等待时间
    
//返回改变后的值    

练习:

把SR04的代码加入到FreeRTOS工程中,通过串口命令触发超声波测量

作业:

添加串口读取温湿度的命令,并显示到OLED