FreeRTOS_信号量之二值信号量

目录

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 4if(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.mzph.cn/news/124277.shtml

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

相关文章

算法的时间复杂度及空间复杂度

目录 一、前言 二、时间复杂度 1.时间复杂度定义 2.时间复杂度描述方法 三、实例代码 实例1&#xff08;取影响最大的项&#xff09; 实例2&#xff08;舍去系数&#xff09; 实例3&#xff08;不确定大小关系的用max函数取最大&#xff09; 实例4&#xff08;常数次的…

编译运行windows+OpenMVG+OpenMVS+vs2017

安装vcpkg过程需要翻墙&#xff01;&#xff01;&#xff01; github下载代码 git clone https://github.com/microsoft/vcpkg git clone https://github.com/cdcseacave/VCG.git git clone https://github.com/cdcseacave/openMVS.git src安装vcpkg包 cd .\vcpkg .\bootstr…

“第五十五天”

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

【SpringBoot】Docker部署

docker部署是主流的部署方式&#xff0c;极大的方便了开发部署环境&#xff0c;保持了环境的统一&#xff0c;也是实现自动化部署的前提。 1 项目的目录结构 package: 点击打包&#xff0c;生成 xxx-SNAPSHOT.jar target目录: 打包生成目录&#xff0c;生成的jar存放位置Docke…

【网络安全】Seeker内网穿透追踪定位

Seeker追踪定位对方精确位置 前言一、kali安装二、seeker定位1、ngrok平台注册2、获取一次性邮箱地址3、ngrok平台登录4、ngrok下载5、ngrok令牌授权6、seeker下载7、运行seeker定位8、运行隧道开启监听9、伪装链接10、用户点击&#xff08;获取定位成功&#xff09;11、利用经…

iTransformer: INVERTED TRANSFORMERS ARE EFFECTIVE FOR TIME SERIES FORECASTING

#论文题目&#xff1a;ITRANSFORMER: INVERTED TRANSFORMERS ARE EFFECTIVE FOR TIME SERIES FORECASTING #论文地址&#xff1a;https://arxiv.org/abs/2310.06625 #论文源码开源地址&#xff1a;https://github.com/thuml/Time-Series-Library #论文所属会议&#xff1a;Mach…

基于单片机的智能清洁小车设计—控制系统设计

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、研究的主要内容和目标二、总体方案设计2.1智能清洁小车的硬件系统组成2.2智能清洁小车的硬件结构图 三、 小车结构设计5.1基本布局和功能分析5.2小车二维及三维图小车三维图&#xff1a; 四、 原理图程序 五、…

在CentOS 7中手工打造和运行xml文件配置的Servlet,然后使用curl、浏览器、telnet等三种工具各自测试

下载Openjdk并配置环境变量 https://jdk.java.net/java-se-ri/11-MR2是官网下载Openjdk 11的地方。 sudo wget https://download.java.net/openjdk/jdk11.0.0.1/ri/openjdk-11.0.0.1_linux-x64_bin.tar.gz下载openjdk 11。 sudo mkdir -p /usr/openjdk11创建目录&#xff…

UE5 日记(人物连招:蒙太奇动画通知(含视频链接))

教程https://www.youtube.com/watch?vsWpENaVGj2M&listPLiSlOaRBfgkcPAhYpGps16PT_9f28amXi&index10&ppiAQB 相关蓝图 连招逻辑 动画通知类 逻辑分析 1.用户输入 已搭载战斗系统模块,可以收到输入指令 2.连击 第一次攻击&#xff1a; 第一次攻击&#xff0c;…

vulnhub momentum 靶机复盘

环境配置 到学校了 原来的桥接配置要改 这里记录几个点备忘 1.virtualbox 桥接 未指定 重新安装驱动VBoxNetLwf.inf 2.配置完靶机启动失败 手动安装VBoxNetLwf.inf 不要用virtualbox自带的netlwfinstall 3.配置完nmap扫不到 rw init/bin/bash 进去看看网卡配置 信息收集…

C语言_字符串和内存函数

文章目录 前言一. strlen二. strcpy三.strcat四. strcmp &#xff08;字符串比较&#xff09;五. strncpy六. strncmp七. strstr八. strtok九 . strerror perror十. 字符分类函数十一. memcpy (内存拷贝&#xff09;十二. memmove(可以重叠拷贝 也可以实现不重叠的内存拷贝) 前…

VC++程序崩溃时,使用Visual Studio静态分析dump文件

Window环境下的C程序如果发生异常崩溃&#xff0c;首先会和客户联系&#xff0c;让帮忙取特定目录下的dump文件和log文件来分析崩溃的原因。不过发生崩溃的话&#xff0c;从log一般分析不出特定原因&#xff0c;这时候dump文件就起作用了。可以通过Visual Studio和Windbg来静态…

java八股文(基础篇)

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

高效学习工具之Anki新手入门指南(ios端,包括ipad、ihpone设备)————创建、使用、备份、相关资料

文章目录 0 背景0.1 闭环学习0.2 什么是anki 1 开始使用1.1 导入1.2 创建空白组1.3 创建卡片&#xff08;&#xff09;1.3.1 利用anki创建卡片的两种方法1.3.2 复习材料分类 1.4 开启v3算法&#xff0c;设置排程1.4 自定义排程 2 操作卡牌&#xff08;位于卡牌界面中“游览”&a…

C++快餐——C++11(2)

如期待奇迹发生&#xff0c;那唯有不停伸手去抓紧&#xff0c;去把握才行。 文章目录 类成员变量缺省值default关键字delete关键字final关键字可变参数模板STL容器中empalce相关接口函数优点 lambda表达式捕获列表注意&#xff01;&#xff01;&#xff01;底层实现 总结 类成员…

leetcode-栈与队列

C中stack 是容器么&#xff1f; 栈&#xff0c;队列往往不被归类为容器&#xff0c;而被归类为container adapter(容器适配器)。因为由底层的容器实现&#xff0c;同时使用适配器模式的设计模式&#xff0c;封装了一层。 我们使用的stack是属于哪个版本的STL&#xff1f;SGI ST…

电路的电线的拼接

不积跬步无以至千里&#xff0c;今天小编也是复习今天学习的内容&#xff0c;废话不多说&#xff0c;看博客吧&#xff01;&#xff01;&#xff01; 目录 准备条件 操作 成品 准备条件 操作 将定制的套管插入导线当中&#xff0c;24V或者0V是尖端的端子&#xff0c;后面根…

基于SSM的养老院管理系统

基于SSM的养老院管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringSpringMVCMyBatisVUE工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 摘要 养老院管理系统是一个基于SSM&#xff08;Spring、Spring MVC、MyBatis&…

微信定时发圈,快人一步不落索

现在的社交媒体运营已经成为了私域流量获取的重要手段&#xff0c;而微信作为最大的社交平台之一&#xff0c;更是吸引了众多使用者。但是&#xff0c;你是否曾经感叹过每天手动发朋友圈的繁琐&#xff1f;是否希望能够事先设置好定时发送的功能&#xff0c;让你的朋友圈自动更…

Spring Boot 3系列之一(初始化项目)

近期&#xff0c;JDK 21正式发布&#xff0c;而Spring Boot 3也推出已有一段时间。作为这两大技术领域的新一代标杆&#xff0c;它们带来了许多令人振奋的新功能和改进。尽管已有不少博客和文章对此进行了介绍&#xff0c;但对于我们这些身处一线的开发人员来说&#xff0c;有些…