FreeRTOS_信号量之二值信号量

news2024/7/6 18:44:00

目录

1. 信号量简介

2. 二值信号量

2.1 二值信号量简介

2.1.1 二值信号量无效

2.1.2 中断释放信号量

2.1.3 任务获取信号量成功

2.1.4 任务再次进入阻塞态

2.2 创建二值信号量

2.2.1 vSemaphoreCreateBinary()

2.2.2 xSemaphoreCreateBinary()

2.2.3 xSemaphoreCreateBinaryStatic()

2.3 二值信号量创建过程分析

2.4 释放信号量

2.4.1 函数 xSemaphoreGive()

2.4.2 函数 xSemaphoreGiveFromISR()

2.5 获取信号量

2.5.1 函数 xSemaphoreTake()

2.5.2 函数 xSemaphoreTakeFromISR()

3. 二值信号量操作实验

3.1 实验目的

3.2 实验设计

3.3 实验程序

3.3.1 USART.c

3.3.2 main.c


        信号量是操作系统中重要的一部分,信号量一般用于进行资源管理和任务同步,在 FreeRTOS 中,信号量可以分为二值信号量、计数型信号量、互斥信号量和递归互斥信号量。不同的信号量其应用场景不同,但是有些应用场景是可以互换着使用的,从这里开始,我们进行信号量的学习!!!

1. 信号量简介

        信号量常常用于控制对共享资源的访问和任务同步。举个很常见的例子,某个停车场有 100 个停车位,这 100 个停车位大家都可以使用,对于大家来说,这 100 个停车位就是共享资源。现在假设这个停车场正常运行,我们要把车停到这个停车场首先肯定要先看一下现在已经停了多少车?还有没有停车位?当前的停车数量就是一个信号量,具体的停车数量就是这个信号量值,当这个值到达 100 的时候就说明停车场满了。停车场满的时候我们可以等一会看看有没有其他的车开出停车场,当有车开出停车场的时候停车数量就会加一,也就是信号量加一。

        这是一个典型的使用信号量进行共享资源管理的案例,在这个案例中使用的就是计数型信号量。我们再看另外一个,使用公共电话,我们知道一次只能一个人使用电话,这个时候公共电话就只可能有两个状态:使用或未使用,如果用电话的这两个状态作为信号量的话,那么这个就是二值信号量。

        信号量的另一个重要的应用场合是任务同步,用于任务与任务或中断与任务之间的同步在执行中断服务函数的时候可以通过向任务发送信号量来通知任务它所期待的事件发生了,当退出中断服务函数以后在任务调度器的调度下同步的任务就会执行。在编写中断服务函数的时候我们都知道一定要快进快出,中断服务函数里面不能放太多的代码,否则的话就会影响中断的实时性。裸机编写中断服务函数的时候一般都只是在中断服务函数中打个标记,然后在其他地方根据标记来做具体的处理过程。在使用 RTOS 系统的时候我们就可以借助信号量完成此功能,当中断发生的时候就释放信号量,中断服务函数不作具体的处理。具体的处理过程做成一个任务,这个任务会获取信号量,如果获取到信号量就说明中断发生了,那么就开始完成相应的处理,这么做的好处就是中断执行的时间非常短。

这里区别信号量和队列?

        队列是用来传输数据的,而信号量是用来传输状态的;

        信号量表示一种状态:

                如果信号量为 0 和 1,那么此时称为二值信号量;

                如果信号量为 1 2 3 4 5 6 7 8 9 10,那么此时称为计数型信号量;

队列:

        可以容纳多个数据;

        创建队列有两部分内存:队列结构体 + 队列项存储空间;

        写入队列:当队列满时,可阻塞;

        读取队列:当队列为空时,可阻塞;
信号量:

        仅存放计数值,无法存放其他数据;

        创建信号量,只需分配信号量结构体;

        释放信号量:不可阻塞,计数值++;当计数值为最大值时,返回失败;

        获取信号量:计数值--;当没有资源时,可阻塞;

信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问?

        在计算机中,为了合理的进行内存管理,引入共享资源的概念。同步问题就是说,比如我让计算机去算 A + B*C,我们肯定知道要先算 B*C,然后再去算 A + B*C 的结果,但是如果我们不加以限制,计算机不会这样,计算机会先去计算 A + B,然后再去计算乘法,因此,同步就是为了保证任务与任务之间的运行次序,比如,我先给乘法运行的任务一个信号量,乘法运算的任务收到这个信号量,开始在任务中执行乘法运算;然后同样的过程进行加法运算,这样就可以保证乘法运算是在加法运算之前进行的;共享资源就是计算机中的所有任务都可以使用的资源,但是同一时刻只能有一个任务进行使用,你能想象打印机第一页打印 A 文档,第二页打印 B 文档吗?所以同步机制对于操作系统而言是至关重要的!

2. 二值信号量

2.1 二值信号量简介

        二值信号量通常用于互斥访问或同步。二值信号量和互斥信号量非常类似,但是也是存在一些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。因此二值信号量更加适用于同步(任务与任务或任务与中断的同步),而互斥信号量适合于简单的互斥访问

        和队列一样,信号量 API 函数允许设置一个阻塞时间。阻塞时间是当任务获取信号量的时候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一个信号量上的话,那么优先级最高的那个任务优先获得信号量,这样当信号量有效的时候高优先级的任务就会解除阻塞状态。

        二值信号量其实就是一个只有一个队列项的队列(队列长度为 1,该队列就只有空 0 和满 1 两种情况,这就是二值;就像硬币的正反面一样)。这个特殊的队列要么是满的,要么是空的,正如其名,二值?任务与中断使用这个特殊的队列不用在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的即可。可以利用这个机制完成任务与中断之间的同步。

        在实际应用中通常会使用一个任务来处理 MCU 的某个外设,比如网络应用中,一般最简单的方法就是使用一个任务去轮询的查询 MCU 的 ETH(网络相关外设,如 STM32 的以太网 MAC)外设是否有数据,当有数据的时候就处理这个网络数据这样的使用轮询的方式是很浪费 CPU 资源的,而且也阻止了其他任务的运行。最理想的方法就是当没有网络数据的时候网络任务就进入阻塞态,把 CPU 让给其他任务,当有数据的时候网络任务才去执行。现在使用二值信号量就可以实现这样的功能,任务通过获取信号量来判断是否有网络数据,没有的话就进入阻塞态,而网络中断服务函数(大多数的网络外设都有中断功能,比如 STM32 的 MAC 专用 DMA 中断,通过中断可以判断是否接收到了数据)通过释放信号量来通知任务以太网外设接收到了网络数据,网络任务可以去提取处理了。网络任务只是在一直的获取二值信号量,它不会释放信号量,而中断服务函数是一直在释放信号量,它不会获取信号量。在中断服务函数中发送信号量可以使用 xSemaphoreGiveFromISR(),也可以使用任务通知功能来代替二值信号量,而且使用任务通知的话速度更快,代码量更少。

        使用二值信号量来完成中断与任务同步的这个机制中,任务优先级确保了外设能够得到及时的处理,这样做相当于推迟了中断处理过程。也可以使用队列来代替二值信号量,在外设事件的中断服务函数中获取相关数据,并将相关的数据通过队列发送给任务。如果队列无效的话任务就进入阻塞态,直至队列中有数据,任务接收到数据以后就开始相关的处理过程。

2.1.1 二值信号量无效

        上图中任务 Task 通过函数 xSemaphoreTake() 获取信号量,但是此时二值信号量无效,所以任务 Task 进入阻塞态。

2.1.2 中断释放信号量

        此时中断发生了,在中断服务函数中通过函数 xSemaphoreGiveFromISR() 释放信号量,因此信号量变为有效。

2.1.3 任务获取信号量成功

        由于信号量已经有效了,所以任务 Task 获取信号量成功,任务从阻塞态解除,开始执行相关的处理过程。

2.1.4 任务再次进入阻塞态

        由于任务函数一般都是一个大循环,所以在任务做完相关的处理以后就会再次调用函数 xSemaphoreTake() 获取信号量。在执行完第三步以后二值信号量就已经变为无效的了,所以任务将再次进入阻塞态,和第一步一样,直至中断再次发生并且调用函数 xSemaphoreGiveFromISR() 释放信号量。

2.2 创建二值信号量

        和队列一样,要想使用二值信号量就必须先创建二值信号量,二值信号量创建函数如下表所示:

vSemaphoreCreateBinary()                动态创建二值信号量(老版本的 FreeRTOS 创建信号量的 API 函数)

xSemaphoreCreateBinary()                动态创建二值信号量(新版本的 FreeRTOS 创建信号量的 API 函数)

xSemaphoreCreateBinaryStatic()       静态创建二值信号量

2.2.1 vSemaphoreCreateBinary()

        此函数是老版本的 FreeRTOS 中的创建二值信号量的函数,新版本已经不再使用了,这里还保留这个函数是为了兼容那些基于老版本 FreeRTOS 而做的应用层代码。

        此函数是一个宏,具体创建过程由函数 xQueueGenericCreate() 来完成;

void vSemaphoreCreateBinary(SemaphoreHandle_t xSemaphore)

参数:

        xSemaphore:保存创建成功的二值信号量句柄

返回值:

        NULL:二值信号量创建失败。

        其他值:二值信号量创建成功。

#define xSemaphoreCreateBinary()
    xQueueGenericCreate(1,semSEMAPHORE_QUEUE_ITEM_LENGTH,queueQUEUE_TYPE_BINARY_SEMAPHORE)
#define semSEMAPHORE_QUEUE_ITEM_LENGTH    ((uint8_t)0U)

宏定义为 semSEMAPHORE_QUEUE_ITEM_LENGTH 为 0,所以创建的二值信号量队列长度为 0,队列项 1 个

2.2.2 xSemaphoreCreateBinary()

        此函数是创建二值信号量的新版本函数,使用此函数来创建二值信号量的话信号量所需的 RAM 是由 FreeRTOS 的内存管理部分来动态分配的此函数创建好的二值信号量默认是空的,也就是说刚创建好的二值信号量使用函数 xSemaphoreTake() 是获取不到的(因为获取二值信号量必须保证整个队列不为空),此函数也是一个宏,具体创建过程是由函数 xQueueGenericCreate() 来完成的,函数原型如下:

SemaphoreHandle_t xSemaphoreCreateBinary(void)

参数:

        无

返回值:

        NULL:二值信号量创建失败。

        其他值:创建成功的二值信号量的句柄。

2.2.3 xSemaphoreCreateBinaryStatic()

        此函数也是用来创建二值信号量的,只不过使用此函数创建二值信号量的话信号量所需要的 RAM 需要由用户来分配,此函数也是一个宏,具体创建过程是通过函数 xQueueGenericCreateStatic() 来完成的,函数原型如下:

SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *pxSemaphoreBuffe)

参数:

        pxSemaphoreBuffer:此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。

返回值:

        NULL:二值信号量创建失败。

        其他值:创建成功的二值信号量句柄。

2.3 二值信号量创建过程分析

        上面我们学习二值信号量的创建函数,两个动态创建函数和一个静态创建函数。

        这里我们着重的看两个动态的创建函数:

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) 
    #define vSemaphoreCreateBinary( xSemaphore )                      \ 
        {                                                             \ 
            ( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1,  \ (1) 
            semSEMAPHORE_QUEUE_ITEM_LENGTH,                           \ 
            queueQUEUE_TYPE_BINARY_SEMAPHORE );                       \ 
            if( ( xSemaphore ) != NULL )                              \ 
            {                                                         \ 
                ( void ) xSemaphoreGive( ( xSemaphore ) );            \ (2) 
            }                                                         \ 
        } 
#endif 

        (1)在上面我们提到了二值信号量是在队列的基础上实现的,所以创建二值信号量其实就是创建队列的过程。这里使用函数 xQueueGenericCreate() 创建了一个队列,队列长度为 1 ,队列项长度为 0,队列类型为 queueQUEUE_TYPE_BINARY_SEMAPHORE,也就是二值信号量。

        (2)当二值信号量创建成功以后立即调用函数 xSemaphoreGive() 释放二值信号量,此时新创建的二值信号量有效。

        紧接着, 我们来看新版本的二值信号量创建函数 xSemaphoreCreateBinary(),函数代码如下:

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) 
    #define xSemaphoreCreateBinary()                 \ 
    xQueueGenericCreate( ( UBaseType_t ) 1,          \ 
    semSEMAPHORE_QUEUE_ITEM_LENGTH,                  \ 
    queueQUEUE_TYPE_BINARY_SEMAPHORE )               \ 
#endif

        通过观察可以发现,新版本和旧版本的创建二值信号量的函数具有相同点,也具有不同点:新版本的二值信号量创建函数也是使用函数 xQueueGenericCreate() 来创建一个类型为 queueQUEUE_TYPE_BINARY_SEMAPHORE、长度为 1 、队列项长度为 0 的队列。这一步和老版本的创建二值信号量的函数一样,唯一不同的是新版本的函数在成功创建二值信号量以后不会立即调用函数 xSemaphoreGive() 释放二值信号量。也就是说新版本函数创建二值信号量默认是无效的,而老版本默认是有效的。 

        注意看,创建的队列的队列项长度为 0 ,也就是说创建的队列是个没有存储区的队列,前面说了使用队列是否为空来表示二值信号量,而队列是否为空可以通过队列结构体的成员变量 uxMessagesWaiting 来判断。

2.4 释放信号量

        释放信号量的函数有两个:

xSemaphoreGive()                                任务级信号量释放函数

xSemaphoreGiveFromISR()                  中断级信号量释放函数

        和队列一样!释放信号量也分为任务级和中断级

注意:

        不管是二值信号量、计数型信号量还是互斥信号量,它们都使用上述的函数释放信号量,但是递归互斥信号量有专用的释放函数!

2.4.1 函数 xSemaphoreGive()

        此函数用于释放二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正释放信号量的过程是由函数 xQueueGenericSend() 来完成的,函数原型如下:

BaseType_t xSemaphoreGive(xSemaphore)

参数:

        xSemaphore        要释放的任务量句柄。

返回值:

        pdPASS                                释放信号量成功。

        errQUEUE_FULL                  释放信号量失败。

        紧接着,我们来看一下函数 xSemaphoreGive() 的具体内容,此函数在文件 semaphr.h 中有如下定义:

#define xSemaphoreGive( xSemaphore )                             \ 
    xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ),         \ 
    NULL,                                                        \ 
    semGIVE_BLOCK_TIME,                                          \ 
    queueSEND_TO_BACK )                                          \ 

//该函数总共4个参数
//第一个参数信号量句柄
//第二个参数发送的消息,在信号量中都是针对的计数值,所以为NULL
//第三个参数是阻塞时间,这里设置为0,也就是释放信号量计数值++,永远不会阻塞
//第四个参数表示队列的入队方式,这里是后向入队

        可以看出任务级释放信号量就是向队列发送消息的过程,只是这里并没有发送具体的消息,阻塞时间为 0 (宏 semGIVE_BLOCK_TIME),入队方式采用的后向入队。入队的时候队列结构体成员变量 uxMessagesWaiting 会加一,对于二值信号量通过判断 uxMessagesWaiting 就可以知道信号量是否有效了,当 uxMessagesWaiting 为 1 的话说明二值信号量有效,为 0 就无效。如果队列满的话就返回错误值 errQUEUE_FULL,提示队列满,入队失败!

2.4.2 函数 xSemaphoreGiveFromISR()

        此函数用于在中断中释放信号量,此函数只能用来释放二值信号量和计数型信号量,绝对不能用来在中断服务函数中释放互斥信号量!此函数是一个宏,真正执行的是函数 xQueueGiveFromISR() ,此函数原型如下:

BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t    xSemaphore,
                                 BaseType_t*          pxHigherPriorityTaskWoken)

参数:

        xSemaphore                        要释放的信号量句柄

        pxHigherPriorityTaskWoken                标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置,用户不用进

                                                                    行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退

                                                                    出中断服务函数之前一定要进行一次任务切换。

返回值:

        pdPASS                                释放信号量成功。

        errQUEUE_FULL                 释放信号量失败。

        在中断中释放信号量真正使用的是函数 xQueueGiveFromISR(),此函数和中断级通用入队函数 xQueueGenericSendFromISR() 极其类似!只是针对信号量做了微小的改动。函数 xSemaphoreGiveFromISR() 不能用于中断中释放互斥信号量,因为互斥信号量涉及到优先级继承的问题,而中断不属于任务,没法处理中断优先级继承。

2.5 获取信号量

        获取信号量也有两个函数,如下表所示:

xSemaphoreTake()                                        任务级获取信号量函数

xSemaphoreTakeFromISR()                          中断级获取信号量函数

        同释放信号量的 API 函数一样,不管是二值信号量、计数型信号量还是互斥信号量,它们都使用上述的函数获取信号量。

2.5.1 函数 xSemaphoreTake()

        此函数用于获取二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正获取信号量的过程是由函数 xQueueGenericReceive() 来完成的,函数原型如下:

BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,
                           TickType_t        xBlockTime)

参数:

        xSemaphore:要获取的信号量句柄

        xBlockTime:阻塞时间。

返回值:

        pdTRUE:获取信号量成功。

        pdFALSE:超时,获取信号量失败。

        紧接着,来看函数 xSemaphoreTake() 的具体内容,此函数在文件 semaphr.h 中如下定义:

#define xSemaphoreTake( xSemaphore, xBlockTime )                     \ 
    xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ),          \ 
    NULL,                                                            \ 
    ( xBlockTime ),                                                  \ 
    pdFALSE )                                                        \

        获取信号量的过程其实就是读取队列的过程,只是这里并不是为了读取队列中的消息。在对队列学习时,我们知道函数 xQueueGenericReceive() 的时候说过如果队列为空并且阻塞时间为 0 的话就立即返回 errQUEUE_EMPTY,表示队列满。如果队列为空并且阻塞时间不为 0 的话就将任务添加到延时列表中。如果队列不为空的话就从队列中读取数据,数据读取完成以后还需将队列结构体成员变量 uxMessagesWaiting 减一,然后解除某些因为入队而阻塞的任务,最后返回 pdPASS 表示出队成功。

2.5.2 函数 xSemaphoreTakeFromISR()

        此函数用于在中断服务函数中获取信号量,此函数用于获取二值信号量和计数型信号量,绝对不能使用此函数来获取互斥信号量!此函数是一个宏,真正执行的是函数 xQueueReceiveFromISR(),此函数原型如下:

BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t  xSemaphore,
                                 BaseType_t*        pxHigherPriorityTaskWoken)

参数:

        xSemaphore:要获取的信号量句柄。

        pxHigherPriorityTaskWoken                标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置,用户不用进

                                                                    行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退

                                                                    出中断服务函数之前一定要进行一次任务切换。

返回值:

        pdPASS:获取信号量成功。

        pdFALSE:获取信号量失败。

        在中断中获取信号量真正使用的是函数 xQueueReceiveFromISR(),这个函数就是中断级出队函数!当队列不为空的时候就拷贝队列中的数据,然后将队列结构体中的成员变量 uxMessagesWaiting 减一,如果有任务因为入队而阻塞的话就解除阻塞态,当解除阻塞的任务拥有更高优先级的话就将参数 pxHigherPriorityTaskWoken 设置为 pdTRUE,最后返回 pdPASS 表示出队成功。如果队列为空的话就直接返回 pdFAIL 表示出队失败!

3. 二值信号量操作实验

3.1 实验目的

        二值信号量的使命就是同步,完成任务与任务或中断与任务之间的同步。大多数情况下都是中断与任务之间的同步。

3.2 实验设计

        这里我们设置一个通过串口发送指定的指令来控制开发板上的的 LED1 和 BEEP 开关的实验,指令如下(不区分大小写):

        LED1ON:打开 LED1。

        LED1OFF:关闭 LED1。

        BEEPON:打开蜂鸣器。

        BEEPOFF:关闭蜂鸣器。

        这些指令通过串口发送给开发板,指令是不区分大小写的!开发板使用中断接收,当接收到数据以后就释放二值信号量

        任务 DataProcess_task() 用于处理这些指令,任务会一直尝试获取二值信号量,当获取到信号量就会从串口接收缓冲区中提取这些指令,然后根据指令控制相应的外设。(简单来说就是:串口将指令发送给开发板,开发板通过中断来接收,在中断中释放二值信号量;然后任务 DataProcess_task 处理这些指令,并且一直获取中断释放的二值信号量,一旦获取到信号量就从串口接收缓冲区中提取这些指令,然后根据指令控制相关的外设)

        本实验设计三个任务:start_task、task1_task、DataProcess_task 这三个任务的任务功能如下:

        start_task:用于创建其他两个任务。

        task1_task:控制 LED0 闪烁,提示系统正在运行。

        DataProcess_task:指令处理任务,根据接收到的指令来控制不同的外设。

        实验中还创建了一个二值信号量 BinarySemaphore 用于完成串口中断和任务 DataProcess_task 之间的同步

3.3 实验程序

3.3.1 USART.c

extern SemaphoreHandle_t BinarySemaphore;  //信息队列句柄 
//QueueHandle_t queue.h 中定义

void USART1_IRQHandler(void)                	//串口1中断服务程序
{
	u8 Res;
    //xHigherPriorityTaskWoken:用来标记退出此函数以后是否进行任务切换,这个变量的值由三个函数来设置,用户不再进行设置
                                 //用户只需要提供一个变量来保存这个值就可以了。
                                 //但是切记要注意:当此值为 pdTURE 的时候在退出中断服务函数之前一定要进行一次任务切换。
    
    BaseType_t xHigherPriorityTaskWoken;  //BaseType_t 也在 queue.h 中定义
    
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
	{
		Res =USART_ReceiveData(USART1);//(USART1->DR);	//读取接收到的数据
		
		if((USART_RX_STA&0x8000)==0)//接收未完成
		{
			if(USART_RX_STA&0x4000)//接收到了0x0d
			{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
			}
			else //还没收到0X0D
			{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
				{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res;
					USART_RX_STA++;
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
				}		 
			}
		} 
    }
    //释放二值信号量
    //指令通过串口发送给开发板,串口中断用来释放二值信号量,任务用来不断获取信号量
    //任务一旦获取到信号量,就会从串口接收缓冲区中提取这些指令,然后根据这些指令控制相应的外设
    if((USART_RX_STA&0x8000)&&(BinarySemaphore!=NULL)) //串口接收到数据,并且二值信号量不为空,也就表示二值信号量是有效的
    {
        xSemaphoreGiveFromISR(BinarySemaphore,&xHigherPriorityTaskWoken);  //调用在中断中释放二值信号量函数
        //函数第一个参数:释放二值信号量句柄
        //函数第二个参数:标记是否需要进行任务切换
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken); //如果需要的话进行一次任务切换
    }   //二值信号量用来实现同步的意思就是说:保证中断先释放信号量,然后任务在获取信号量;
}

3.3.2 main.c

#include "stm32f4xx.h"  
#include "FreeRTOS.h" //这里注意必须先引用FreeRTOS的头文件,然后再引用task.h
#include "task.h"     //存在一个先后的关系
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"
#include "beep.h"
#include "malloc.h"
#include "timer.h"
#include "queue.h"
#include "semphr.h"


//任务优先级
#define START_TASK_PRIO     1     //用于创建其他两个任务
//任务堆栈大小
#define START_STK_SIZE      256
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define TASK1_TASK_PRIO     2   //控制 LED0 闪烁,提示系统正在运行
//任务堆栈大小
#define TASK1_STK_SIZE      256
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);

//任务优先级
#define DATAPROCESS_TASK_PRIO 3  //指令处理函数
//任务堆栈大小	
#define DATAPROCESS_STK_SIZE  256 
//任务句柄
TaskHandle_t DataProcess_Handler;
//任务函数
void DataProcess_task(void *pvParameters);

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

//用于命令解析用的命令值
#define LED1ON  1
#define LED1OFF 2
#define BEEPON  3
#define BEEPOFF 4
#define COMMANDERR  0xFF

//函数 LowerToCap 用于将串口发送过来的命令中的小写字母统一转换成大写字母,
//这样就可以在发送命令的时候不用区分大小写,因为开发板会统一转换成大写。
//将字符串中的小写字母转换为大写
//str:要转换的字符串
//len:字符串长度
void LowerToCap(u8 *str,u8 len)
{
    u8 i;
    for(i=0;i<len;i++)
    {
        //判断字符串的ASCII码是否位于96到123之间
        if((96<str[i])&&(str[i]<123))  //小写字母
        {
            //ASCII码是一种用于表示字符的编码系统。在ASCII码中,每个字符都被赋予一个唯一的整数值。
            //大写字母的ASCII码值是65到90
            //小写字母的ASCII码值是97到122   所以一旦确定ASCII码值位于小写字母的范畴内,只需要将ASCII码值减去32即可转换为大写
            str[i] = str[i] - 32;  //转换为大写
        }
    }
}

//函数 CommandProcess 用于将接收到的命令字符串转换成命令值,比如说命令“LED1ON”转换成命令值就是 0(宏LED1ON为 0)
//命令处理函数,将字符串命令转换成命令值
//str:命令
//返回值:0xFF,命令错误;其他值,命令值
u8 CommandProcess(u8 *str)
{
    u8 CommandValue = COMMANDERR;
    if(strcmp((char*)str,"LED1ON")==0) //strcmp 字符串比较函数
        //这个函数会比较两个参数;比较时,会以字符的ASCII值进行比较
        //如果str1的ASCII码值小于str2,返回一个负数;反之,返回一个正数;
        //如果str1的ASCII码值等于str2,返回 0,此时,if判断语句成立
        CommandValue = LED1ON; //设置的LED1ON的宏为1,也就是在串口输入1,if判断语句成立
    else if(strcmp((char*)str,"LED1OFF")==0)
        CommandValue = LED1OFF; //在串口输入2,if判断语句成立
    else if(strcmp((char*)str,"BEEPON")==0)
        CommandValue = BEEPON; //在串口输入3,if判断语句成立
    else if(strcmp((char*)str,"BEEPOFF")==0)
        CommandValue = BEEPOFF; //在串口输入4,if判断语句成立
    return CommandValue;
}

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //设置系统中断优先级
    delay_init(168);
    uart_init(115200);
    LED_Init();
    KEY_Init();
    BEEP_Init();
    LCD_Init();
    my_mem_init(SRAMIN);    //初始化内部内存池
    
    POINT_COLOR=RED;
    LCD_ShowString(10,10,200,16,16,"ATK STM32F407");
    LCD_ShowString(10,30,200,16,16,"FreeRTOS Example");
    LCD_ShowString(10,50,200,16,16,"Binary Semaphore");
    LCD_ShowString(10,70,200,16,16,"Command Data:");
    
    //创建开始任务
    xTaskCreate((TaskFunction_t )start_task,            //任务函数
                (const char*    )"start_task",          //任务名称
                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )START_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
    vTaskStartScheduler();          //开启任务调度
}

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();    //进入临界区
    
    //创建二值信号量,也就是创建一个长度为1的队列
    BinarySemaphore = xSemaphoreCreateBinary();   //xSemaphoreCreateBinary函数为动态创建二值信号量函数
    //返回 NULL,二值信号量创建失败;返回其他值,表示创建成功的二值信号量的句柄;
    //所以BinarySemaphore表示创建成功的二值信号量的句柄;
    
    //创建Task1任务
    xTaskCreate((TaskFunction_t )task1_task,            //任务函数
                (const char*    )"task1_task",          //任务名称
                (uint16_t       )TASK1_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )TASK1_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&Task1Task_Handler);   //任务句柄  
    //创建Task2任务
    xTaskCreate((TaskFunction_t )DataProcess_task,            //任务函数
                (const char*    )"DataProcess_task",          //任务名称
                (uint16_t       )DATAPROCESS_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )DATAPROCESS_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&DataProcess_Handler);   //任务句柄  
                                  
    vTaskDelete(StartTask_Handler);  //删除开始任务
    taskEXIT_CRITICAL();          //退出临界区
}

//Task1任务
//控制 LED0 闪烁,提示系统正在运行
void task1_task(void *pvParameters)
{
    while(1)
    {
        LED0=!LED0;
        vTaskDelay(500);        //延时500ms,也就是500个时钟节拍
    }
}

//DataProcess_task函数
//指令处理任务,根据接收到的指令来控制不同的外设
void DataProcess_task(void *pvParameters)
{
    u8 len=0;
    u8 CommandValue=COMMANDERR;
    BaseType_t err=pdFALSE;
    
    u8 *CommandStr;
    POINT_COLOR=BLUE;
    while(1)
    {
        if(BinarySemaphore!=NULL) //二值信号量不为空,表明接收到的数据是有效的
        {
            err=xSemaphoreTake(BinarySemaphore,portMAX_DELAY); //获取信号量函数;返回值pdTURE,获取信号量成功;pdFALSE,获取信号量失败;
            //第一个参数,要获取的信号量句柄
            //第二个参数,阻塞时间,这里设置为portMAX_DEALY,译为无限等待,直至获得信号量
            if(err==pdTRUE) //获取信号量成功
            {
                len=USART_RX_STA&0x3fff;  //得到此次接收到的数据长度
                //接收状态
                //bit15,	接收完成标志
                //bit14,	接收到0x0d
                //bit13~0,	接收到的有效字节数目
                CommandStr=mymalloc(SRAMIN,len+1);  //申请内存 指针指向申请内存的首地址
                sprintf((char*)CommandStr,"%s",USART_RX_BUF);  //打印接收缓存区,把接收缓存区的数据保存到CommandStr中
                CommandStr[len]='\0';   //加上字符串结尾符号
                //CommandStr 是个指针,长度为len,数组是从下角标 0 开始的,所以len就表示数组的最后一个
                LowerToCap(CommandStr,len);  //将字符串转换成大写
                CommandValue=CommandProcess(CommandStr);  //命令解析,也就是获取上面定义的宏 1 2 3 4
                if(CommandValue!=COMMANDERR)//if判断语句成立,表示CommandValue不等于0xFF,那也就是 LED1ON、LED1OFF、BEEPON、BEEPOFF 其中一个指令
                {
                    LCD_Fill(10,90,210,110,WHITE);  //清除显示区域
                    LCD_ShowString(10,90,200,16,16,CommandStr);  //在LCD上显示命令
                    printf("命令为:%s\r\n",CommandStr);   
                    switch(CommandValue)
                    {
                        case LED1ON:
                            LED1=0;
                            break;
                        case LED1OFF:
                            LED1=1;
                            break;
                        case BEEPON:
                            BEEP=1;
                            break;
                        case BEEPOFF:
                            BEEP=0;
                            break;
                    }
                }
                else
                {//当命令错误的时候开发板会向串口调试助手发送命令错误的提示信息
                    //比如我们发送 LED1_off 这个命令,串口助手会显示:无效的命令,请重新输入!!
                    printf("无效的命令,请重新输入!!\r\n");
                }
                USART_RX_STA = 0;
                memset(USART_RX_BUF,0,USART_REC_LEN);  //串口接收缓冲区清零
                myfree(SRAMIN,CommandStr);             //释放内存
            }
        }
        else if(err==pdFALSE)
        {
            vTaskDelay(10);   //延时10ms,也就是10个时钟节拍
        }
    }
}





本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1151080.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

初学编程需要什么基础,零基础学编程系统化编程课程之编程工具箱文件名称操作构件属性

初学编程需要什么基础&#xff0c;零基础学编程系统化编程课程之编程工具箱文件名称操作构件属性 上图为 该编程工具构件的基本属性和方法 编程系统化课程总目录及明细&#xff0c;零基础学中文编程视频教程&#xff0c;点击进入了解详情。 https://blog.csdn.net/qq_29129627/…

2023年阿里云双11有什么优惠活动?详细攻略来了!

随着双十一的临近&#xff0c;阿里云也正式开启了双11大促&#xff0c;推出了“金秋云创季”活动&#xff0c;那么&#xff0c;2023年阿里云双11的优惠活动究竟有哪些呢&#xff1f;本文将为大家详细介绍。 一、阿里云双11活动时间 1、2023年10月27日-2023年10月31日&#xff…

基于蝠鲼觅食算法的无人机航迹规划-附代码

基于蝠鲼觅食算法的无人机航迹规划 文章目录 基于蝠鲼觅食算法的无人机航迹规划1.蝠鲼觅食搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用蝠鲼觅食算法来优化无人机航迹规划。 …

前端HTML

文章目录 一、什么是前端前端后端 前端三剑客1.什么是HTML2.编写前端的步骤1.编写服务端2.浏览器充当客户端访问服务端​ 3.浏览器无法正常展示服务端内容(因为服务端的数据没有遵循标准)4.HTTP协议>>>:最主要的内容就是规定了浏览器与服务端之间数据交互的格式 3. 前…

“第五十五天”

定点数&#xff1a; 原码的乘法&#xff1a; 乘法的符号位是单独处理的&#xff08;通过对被乘数和乘数的符号位进行异或实现&#xff09;&#xff0c;数值位去绝对值进行运算。这里的乘法实际上是通过多次加法实现的。 这里被乘数是放在x寄存器&#xff0c;乘数放在MQ寄存器…

数据结构线性表——顺序表

前言&#xff1a;小伙伴们好久不见&#xff0c;从这篇文章开始&#xff0c;我们就要正式进入数据结构的学习啦。 学习的难度也将逐步上升&#xff0c;希望小伙伴们能够和博主一起坚持&#xff0c;一起加油&#xff01;&#xff01;&#xff01; 目录 一.什么是线性表 二.什么…

便利店超市怎么做一个实用的微信小程序?

近年来&#xff0c;微信小程序商城越来越受到商家的青睐&#xff0c;因为它不仅提供了便捷的在线购物体验&#xff0c;而且不需要安装额外的应用。对于零编程经验的初学者&#xff0c;制作一个小程序商城可能会感到有些困难&#xff0c;但不用担心&#xff0c;本文将引导你一步…

Netty复习:(2)IdleStateHandler的用法

一、handler定义&#xff1a; package handler;import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter;public class MyChatServerHandler3 extends ChannelInboundHandlerAdapter {Overridepublic void userEventTriggered(…

【实用教程】MySQL内置函数

1 背景 在MySQL查询等操作过程中&#xff0c;我们需要根据实际情况&#xff0c;使用其提供的内置函数。今天我们就来一起来学习下这些函数&#xff0c;在之后的使用过程中更加得心应手。 2 MySQL函数 2.1 字符串函数 常用的函数如下&#xff1a; concat(s1,s2,…sn)字符串…

Ansible的安装和部署

目录 1.Ansible的安装 2.构建Ansible清单 直接书写受管主机名或ip 设定受管主机的组[组名称] 主机规格的范围化操作 指定其他清单文件 ansible命令指定清单的正则表达式 3.Ansible配置文件参数详解 配置文件的分类与优先级 常用配置参数 4.构建用户级Ansible操作环…

女神联盟2攻略,女神联盟2-GM红将全免

女神联盟2攻略&#xff1a;前期过渡阵容建议&#xff1a; 在女神联盟2-GM红将全免的初期玩法中&#xff0c;资源的获取是有限的&#xff0c;因此玩家需要合理运用搭配&#xff0c;避免浪费。同时在有限的资源中与敌人竞争资源需要进行头脑对决。首先&#xff0c;游戏中的硬通货…

Linux学习第27天:Platform设备驱动开发: 专注与分散

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 专注与分散是我在题目中着重说明的一个内容。这是今天我们要学习分离与分层概念的延伸。专注是说我们要专注某层驱动的开发&#xff0c;而对于其他层则是芯片厂商…

【QT】鼠标常用事件

新建项目 加标签控件 当鼠标进去&#xff0c;显示【鼠标进入】&#xff0c;离开时显示【鼠标离开】 将QLable提升成自己的控件&#xff0c;然后再去捕获 添加文件 改继承的类名 提升类 同一个父类&#xff0c;可以提升 效果 现在代码就和Qlabel对应起来了。 在.h中声明&…

1992-2021年全国各地级市经过矫正的夜间灯光数据(GNLD、VIIRS)

1992-2021年全国各地级市经过矫正的夜间灯光数据&#xff08;GNLD、VIIRS&#xff09; 1、时间&#xff1a;1992-2021年3月&#xff0c;其中1992-2013年为年度数据&#xff0c;2013-2021年3月为月度数据 2、来源&#xff1a;DMSP、VIIRS 3、范围&#xff1a;分区域汇总&…

短期经济波动:均衡国民收入决定理论(一

宏观经济学讲义 10 短期经济波动&#xff1a;均衡国民收入决定理论(一) 文章目录 10 短期经济波动&#xff1a;均衡国民收入决定理论(一)[toc]1 均衡国民收入决定1.1 均衡国民收入决定的不同理论1.2 两部门经济&#xff1a;有效需求原理和框架1.2.1 模型假设1.2.2 模型推导1.2…

正点原子嵌入式linux驱动开发——Linux 音频驱动

音频是最常用到的功能&#xff0c;音频也是linux和安卓的重点应用场合。STM32MP1带有SAI接口&#xff0c;正点原子的STM32MP1开发板通过此接口外接了一个CS42L51音频DAC芯片&#xff0c;本章就来学习一下如何使能CS42L51驱动&#xff0c;并且CS42L51通过芯片来完成音乐播放与录…

java八股文(基础篇)

面向过程和面向对象的区别 面向过程&#xff1a;在解决问题时&#xff0c;特别自定义函数编写一步一步的步骤解决问题。 面向对象&#xff1a;其特点就是 继承&#xff0c;多态&#xff0c;继承&#xff0c;在解决问题时&#xff0c;不再注重函数的编写&#xff0c;而在于注重…

EPLAN_012#自定义导航器

关键字&#xff1a;设备导航器、端子排导航器、电缆导航器、设备列表导航器&#xff0c;树形结构&#xff08;导航器&#xff09; 正常情况下&#xff0c;eplan中的设备导航器是这个模样 如何在导航器中显示更多内容或者进行规划。&#xff08;比如下图中&#xff0c;可以显示其…

买车软件有哪些,买车软件哪个好

买车软件是指为购买汽车提供便利的手机应用程序&#xff0c;可以帮助消费者找到心仪的汽车型号、比较不同车型的价格、了解车辆的详细参数和配置、预约试驾、办理贷款、购车保险等一系列服务。 买车软件可以让用户更加便捷地了解汽车信息、比较不同车型的价格和配置、预约试驾…

【STL】:vector的模拟实现

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关vector的模拟实现&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数…