FreeRTOS学习笔记——二值型信号量

1.前言

在嵌入式操作系统中二值型信号量是任务间、任务与中断间同步的重要手段。FreeRTOS的二值型信号量简单易用,下面结合一个具体例子说明FreeRTOS中的二值型信号量如何使用。

【相关博文】

FreeRTOS STM32移植笔记

FreeRTOS学习笔记——任务间使用队列同步数据

如何在FreeRTOS下实现低功耗——MSP430F5438平台

代码链接】——示例代码存于百度网盘

2.特别说明    

    二值型信号量的使用方法见图1所示,二值型信号量可以理解为任务与中断间或者两个任务间的标志,该标志非“满”即“空”。Send操作相当把该标志置“满”,Receive操作相关与把该标志取"空",经过send和receive操作实现任务与中断间或者两任务的操作同步。

图1 二值型信号量使用示意图

【特别说明】

V7.X版本和V8.X的信号量操作存在少许区别

V7.X版本中使用vSemaphoreCreateBinary函数,使用该函数创建的信号量初始值为“满”,receive操作立刻有返回。相关代码见文章末尾补充代码1,从补充代码1中可以发现,创建信号量之后立刻调用xSemaphoreGive函数,使得信号量由“空”变“满”。

V8.X版本中使用xSemaphoreCreateBinary函数,使用该函数创建的信号量初始值为“空”,receive操作不会立刻返回。

3.参考代码

示例代码具有一个128字节的串口接收缓冲区,在串口中断中把接收到的字符存入缓冲区中,一旦接收到回车换行符(\r\n),便通过xSemaphoreGiveFromISR把信号量置“满”,打印任务中使用xSemaphoreTake实现于中断接收函数的同步,xSemaphoreTake把任务挂起,一旦查询到信号量为“满”,通过串口打印结束到的内容,并清空缓冲区。

【示例代码】

/* Standard includes. */
#include <stdio.h>
#include <string.h>

/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

/* Library includes. */
#include "stm32f10x.h"

#define LED0_ON()   GPIO_SetBits(GPIOB,GPIO_Pin_5);
#define LED0_OFF()  GPIO_ResetBits(GPIOB,GPIO_Pin_5);

static void Setup(void);
static void PrintTask(void *pvParameters);

void LedInit(void);
void UART1Init(void);

uint8_t RxBuffer[128];
__IO uint8_t RxCounter = 0;

SemaphoreHandle_t xSemaphore;

int main(void)
{
    /* 初始化硬件平台 */
    Setup();

    /* 创建信号量 */
    xSemaphore = xSemaphoreCreateBinary();
    /* 建立Print任务 */
    xTaskCreate(PrintTask, "Print Task", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY+4, NULL);
    /* 启动OS */
    vTaskStartScheduler();

    return 0;
}

void PrintTask(void *pvParameters)
{
    for(;;)
    {
        if( xSemaphoreTake( xSemaphore, portMAX_DELAY ) == pdTRUE )
        {
            printf("receive:%s", RxBuffer);
            memset(RxBuffer, 0x00, 128);
            RxCounter = 0;
        }
    }
}

static void Setup( void )
{
    LedInit();
    UART1Init();
}

void LedInit( void )
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );
    /*LED0 @ GPIOB.5*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init( GPIOB, &GPIO_InitStructure );
}

void UART1Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    /* 第1步:打开GPIO和USART时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);

    /* 第2步:将USART1 [email protected]的GPIO配置为推挽复用模式 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 第3步:将USART1 [email protected]的GPIO配置为浮空输入模式 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 第4步:配置USART1参数
    波特率   = 9600
    数据长度 = 8
    停止位   = 1
    校验位   = No
    禁止硬件流控(即禁止RTS和CTS)
    使能接收和发送
    */
    USART_InitStructure.USART_BaudRate = 9600;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);

    /* 第5步:使能 USART1, 配置完毕 */
    USART_Cmd(USART1, ENABLE);

    /* 清除发送完成标志 */
    USART_ClearFlag(USART1, USART_FLAG_TC);

    /* 使能USART1发送中断和接收中断,并设置优先级 */
    NVIC_InitTypeDef NVIC_InitStructure;
    /* 设定USART1 中断优先级 */
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = configLIBRARY_KERNEL_INTERRUPT_PRIORITY;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    /* 使能接收中断 */
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}

int fputc(int ch, FILE *f)
{
    /* 写一个字节到USART1 */
    USART_SendData(USART1, (uint8_t) ch);
    /* 等待发送结束 */
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)
    {}
    return ch;
}

void USART1_IRQHandler(void)
{
    static BaseType_t xHigherPriorityTaskWoken;

    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        RxBuffer[RxCounter++] = USART_ReceiveData(USART1);
        if (RxCounter > 2 && RxBuffer[RxCounter-2] == ‘\r‘ && RxBuffer[RxCounter-1] == ‘\n‘) {
            // 在中断中发送信号量
            xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );
        }
    }

    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

4.简单说明

SemaphoreHandle_t xSemaphore;

信号量句柄,二值型信号量、数值型信号量和互斥型信号量均使用SemaphoreHandle_t类型声明

xSemaphore = xSemaphoreCreateBinary();

创建信号量,V8.X版本中新增加函数,创建信号量时初值为“空”。

xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );

在中断中发送信号量,以FromISR结尾的函数具有保护功能,如果在任务中发送信号量可使用xSemaphoreGive。

xSemaphoreTake( xSemaphore, portMAX_DELAY );

等待信号量,等待时间为最大等待时间,如果信号量为“空”任务会处于挂起状态。

5.在中断中使用RTOS API注意点

【FromISR】

应使用xSemaphoreGiveFromISR,而不是xSemaphoreGive。

【中断优先级设置】

串口中断的优先级应该低于configMAX_SYSCALL_INTERRUPT_PRIORITY(191,从另一个角度可以理解为11)设置的最高优先级,本文UART的响应优先级为configLIBRARY_KERNEL_INTERRUPT_PRIORITY(该宏的具体值为15,数值越大优先级越低)。

【main.c】

/* 使能USART1发送中断和接收中断,并设置优先级 */

NVIC_InitTypeDef NVIC_InitStructure;

/* 设定USART1 中断优先级 */

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = configLIBRARY_KERNEL_INTERRUPT_PRIORITY;

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStructure);

【FreeRTOSConfig.h】

/* This is the raw value as per the Cortex-M3 NVIC.  Values can be 255

(lowest) to 0 (1?) (highest). */

#define configKERNEL_INTERRUPT_PRIORITY     255

#define configMAX_SYSCALL_INTERRUPT_PRIORITY  191 /* equivalent to 0xb0, or priority 11. */

/* This is the value being used as per the ST library which permits 16

priority values, 0 to 15.  This must correspond to the

configKERNEL_INTERRUPT_PRIORITY setting.  Here 15 corresponds to the lowest

NVIC value of 255. */

#define configLIBRARY_KERNEL_INTERRUPT_PRIORITY 15

鉴于Cortex M3的NVIC的特性,请详细参考——【在Cortex M3平台上运行FreeRTOS

5.总结

【1】V8.X中使用xSemaphoreCreateBinary()创建的信号量初始值为"空"。

【2】中断中发送信号量尽量使用XXXXFromISR。

【3】某中断的优先级数值应大于configMAX_SYSCALL_INTERRUPT_PRIORITY。

【补充代码1】——vSemaphoreCreateBinary函数实现代码

#define vSemaphoreCreateBinary( xSemaphore )     {         ( xSemaphore ) = xQueueGenericCreate( ( unsigned portBASE_TYPE ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE );         if( ( xSemaphore ) != NULL )         {             ( void ) xSemaphoreGive( ( xSemaphore ) );         }     }

6.参考资料

【 STM32与FreeRTOS学习备忘,xSemaphoreGiveFromISR

时间: 2024-10-12 00:51:19

FreeRTOS学习笔记——二值型信号量的相关文章

【Unity 3D】学习笔记二十八:unity工具类

unity为开发者提供了很多方便开发的工具,他们都是由系统封装的一些功能和方法.比如说:实现时间的time类,获取随机数的Random.Range( )方法等等. 时间类 time类,主要用来获取当前的系统时间. using UnityEngine; using System.Collections; public class Script_04_13 : MonoBehaviour { void OnGUI() { GUILayout.Label("当前游戏时间:" + Time.t

Swift学习笔记(二)参数类型

关于参数类型,在以前的编程过程中,很多时间都忽视了形参与实参的区别.通过这两天的学习,算是捡回了漏掉的知识. 在swift中,参数有形参和实参之分,形参即只能在函数内部调用的参数,默认是不能修改的,如果想要修改就需要在参数前添加var声明. 但这样的声明过后,仍旧不会改变实参的值,这样就要用到inout了,传递给inout的参数类型必须是var类型的,不能是let类型或者字面类型,(字面类型是在swift中常提的一个术语,个人认为就是赋值语句,也不能修改)而且在传递过程中,要用传值符号"&

Go语言学习笔记(二) [变量、类型、关键字]

日期:2014年7月19日 1.Go 在语法上有着类 C 的感觉.如果你希望将两个(或更多)语句放在一行书写,它们 必须用分号分隔.一般情况下,你不需要分号. 2.Go 同其他语言不同的地方在于变量的类型在变量名的后面.例如:不是,int a,而是 a int.当定义了一个变量,它默认赋值为其类型的 null 值.这意味着,在 var a int后,a 的 值为 0.而 var s string,意味着 s 被赋值为零长度字符串,也就是 "". 3.Go语言的变量声明和赋值 在Go中使

Java学习笔记二:数据类型

Java学习笔记二:数据类型 1. 整型:没有小数部分,允许为负数,Java整型分4种:int short long byte 1.1 Int最为常用,一个Int类型变量在内存中占用4个字节,取值范围从-2 147 483 6至2 147 483 647 超过20亿,如果用来存储大于20亿的值,最好使用long型. 1.2  int 与Integer: Java中的数据类型分为基本数据类型和复杂数据类型.Int为前者,integer为后者. Integer是int的封装类,提供了很多转换方法,当

AJax 学习笔记二(onreadystatechange的作用)

AJax 学习笔记二(onreadystatechange的作用) 当发送一个请求后,客户端无法确定什么时候会完成这个请求,所以需要用事件机制来捕获请求的状态XMLHttpRequest对象提供了onreadyStateChange事件实现这一功能.这类似于回调函数的做法.onreadyStateChange事件可指定一个事件处理函数来处理XMLHttpRequest对象的执行结果,如: 复制代码 代码如下: ajaxObj=createAjaxObject(); var url="/MyTod

2. 蛤蟆Python脚本学习笔记二基本命令畅玩

2. 蛤蟆Python脚本学习笔记二基本命令畅玩 本篇名言:"成功源于发现细节,没有细节就没有机遇,留心细节意味着创造机遇.一件司空见惯的小事或许就可能是打开机遇宝库的钥匙!" 下班回家,咱先来看下一些常用的基本命令. 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/48092873 1.  数字和表达式 看下图1一就能说明很多问题: 加法,整除,浮点除,取模,幂乘方等.是不是很直接也很粗暴. 关于上限,蛤蟆不太清楚

小猪的数据结构学习笔记(二)

小猪的数据结构学习笔记(二) 线性表中的顺序表 本节引言: 在上个章节中,我们对数据结构与算法的相关概念进行了了解,知道数据结构的 逻辑结构与物理结构的区别,算法的特性以及设计要求;还学了如何去衡量一个算法 的好坏,以及时间复杂度的计算!在本节中我们将接触第一个数据结构--线性表; 而线性表有两种表现形式,分别是顺序表和链表;学好这一章很重要,是学习后面的基石; 这一节我们会重点学习下顺序表,在这里给大家一个忠告,学编程切忌眼高手低,看懂不代表自己 写得出来,给出的实现代码,自己要理解思路,自己

JavaScript--基于对象的脚本语言学习笔记(二)

第二部分:DOM编程 1.文档象模型(DOM)提供了访问结构化文档的一种方式,很多语言自己的DOM解析器. DOM解析器就是完成结构化文档和DOM树之间的转换关系. DOM解析器解析结构化文档:将磁盘上的结构化文档转换成内存中的DOM树 从DOM树输出结构化文档:将内存中的DOM树转换成磁盘上的结构化文档 2.DOM模型扩展了HTML元素,为几乎所有的HTML元素都新增了innerHTML属性,该属性代表该元素的"内容",即返回的某个元素的开始标签.结束标签之间的字符串内容(不包含其它

angular学习笔记(二十八)-$http(6)-使用ngResource模块构建RESTful架构

ngResource模块是angular专门为RESTful架构而设计的一个模块,它提供了'$resource'模块,$resource模块是基于$http的一个封装.下面来看看它的详细用法 1.引入angular-resource.min.js文件 2.在模块中依赖ngResourece,在服务中注入$resource var HttpREST = angular.module('HttpREST',['ngResource']); HttpREST.factory('cardResource