FreeRTOS 信号量(一)

news2024/11/19 19:44:11

文章目录

  • 一、信号量简介
  • 二、二值信号量
    • 1. 二值信号量简介
    • 2. 创建二值信号量
      • ①函数 vSemaphoreCreateBinary ()
      • ②函数 xSemaphoreCreateBinary()
      • ③函数 xSemaphoreCreateBinaryStatic()
    • 3. 二值信号量创建过程分析
    • 4. 释放信号量
      • ①函数 xSemaphoreGive()
      • ②函数 xSemaphoreGiveFromISR()
    • 5. 获取信号量
      • ①函数 xSemaphoreTake()
      • ②函数 xSemaphoreTakeFromISR ()
  • 三、二值信号量操作实验


一、信号量简介

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

信号量进行共享资源管理的案例,在这个案例中使用的就是计数型信号量。再看另外一个案例:使用公共电话,我们知道一次只能一个人使用电话,这个时候公共电话就只可能有两个状态:使用或未使用,如果用电话的这两个状态作为信号量的话,那么这个就是二值信号量。信号量用于控制共享资源访问的场景相当于一个上锁机制,代码只有获得了这个锁的钥匙才能够执行。

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

FreeRTOS 中还有一些其他特殊类型的信号量,比如互斥信号量和递归互斥信号量,这些具体遇到的时候在讲解。有关信号量的知识在 FreeRTOS 的官网上都有详细的讲解,包括二值信号量、计数型信号量、互斥信号量和递归互斥信号量,我们下面要讲解的这些涉及到理论性的知识都是翻译自 FreeRTOS 官方资料,感兴趣的可以去官网看原版的英文资料。

二、二值信号量

1. 二值信号量简介

二值信号量通常用于互斥访问或同步,二值信号量和互斥信号量非常类似,但是还是有一些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。因此二值信号另更适合用于同步(任务与任务或任务与中断的同步),而互斥信号量适合用于简单的互斥访问,有关互斥信号量的内容后面会专门讲解,本节只讲解二值信号量在同步中的应用。

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

二值信号量其实就是一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空的,这不正好就是二值的吗? 任务和中断使用这个特殊队列不用在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的。可以利用这个机制来完成任务与中断之间的同步。

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

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

1、二值信号量无效
在这里插入图片描述
在上图中任务 Task 通过函数 xSemaphoreTake()获取信号量,但是此时二值信号量无
效,所以任务 Task 进入阻塞态。

2、中断释放信号量

在这里插入图片描述
此时中断发生了,在中断服务函数中通过函数 xSemaphoreGiveFromISR()释放信号量,因此信号量变为有效。

3、任务获取信号量成功
在这里插入图片描述
由于信号量已经有效了,所以任务 Task 获取信号量成功,任务从阻塞态解除,开始执行相
关的处理过程。

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

2. 创建二值信号量

同队列一样,要想使用二值信号量就必须先创建二值信号量,二值信号量创建函数如下表所示:
在这里插入图片描述

①函数 vSemaphoreCreateBinary ()

此函数是老版本 FreeRTOS 中的创建二值信号量函数,新版本已经不再使用了,新版本的FreeRTOS 使用 xSemaphoreCreateBinary()来替代此函数,这里还保留这个函数是为了兼容那些基于老版本 FreeRTOS 而做的应用层代码。此函数是个宏,具体创建过程是由函数xQueueGenericCreate()来完成的,在文件 semphr.h 中有如下定义:

void vSemaphoreCreateBinary( SemaphoreHandle_t xSemaphore )

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

返回值:
NULL: 二值信号量创建失败。
其他值: 二值信号量创建成功。

②函数 xSemaphoreCreateBinary()

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

SemaphoreHandle_t xSemaphoreCreateBinary( void )

参数:

返回值:
NULL: 二值信号量创建失败。
其他值: 创建成功的二值信号量的句柄。

③函数 xSemaphoreCreateBinaryStatic()

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

SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer )

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

返回值:
NULL: 二值信号量创建失败。
其他值: 创建成功的二值信号量句柄。

3. 二值信号量创建过程分析

这里分析一下这两个动态的创建函数,静态创建函数和动态的类似,就不做分析了。
首先来看一下老版本的二值信号量动态创建函数 vSemaphoreCreateBinary(),函数代码如下:

#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()释放二值信号量。也就是说新版函数创建的二值信号量默认是无效的,而老版本是有效的。

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

4. 释放信号量

释放信号量的函数有两个,如上表所示:
在这里插入图片描述
同队列一样,释放信号量也分为任务级和中断级。还有!不管是二值信号量、计数型信号
量还是互斥信号量,它们都使用上表中的函数释放信号量,递归互斥信号量有专用的释放函数。

①函数 xSemaphoreGive()

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

BaseType_t xSemaphoreGive( xSemaphore )

参数:
xSemaphore: 要释放的信号量句柄。

返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败。

再来看一下函数 xSemaphoreGive()的具体内容,此函数在文件 semphr.h 中有如下定义:

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

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

②函数 xSemaphoreGiveFromISR()

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

BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
 								  BaseType_t * pxHigherPriorityTaskWoken)

参数:
xSemaphore: 要释放的信号量句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。

返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败。

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

5. 获取信号量

获取信号量也有两个函数,如下表所示:
在这里插入图片描述
同释放信号量的 API 函数一样,不管是二值信号量、计数型信号量还是互斥信号量,它们
都使用上表中的函数获取信号量

①函数 xSemaphoreTake()

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

BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,
						  TickType_t xBlockTime)

参数:
xSemaphore: 要获取的信号量句柄。
xBlockTime: 阻塞时间。

返回值:
pdTRUE: 获取信号量成功。
pdFALSE: 超时,获取信号量失败。

再来看一下函数 xSemaphoreTake ()的具体内容,此函数在文件 semphr.h 中有如下定义:

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

获取信号量的过程其实就是读取队列的过程,只是这里并不是为了读取队列中的消息。如果队列为空并且阻塞时间为 0 的话就立即返回 errQUEUE_EMPTY,表示队列满。如果队列为空并且阻塞时间不为 0 的话就将任务添加到延时列表中。如果队列不为空的话就从队列中读取数据(获取信号量不执行这一步),数据读取完成以后还需要将队列结构体成员变量 uxMessagesWaiting 减一,然后解除某些因为入队而阻塞的任务,最后返回 pdPASS 表示出对成功。互斥信号量涉及到优先级继承,处理方式不同,后面讲解互斥信号量的时候在详细的讲解。

②函数 xSemaphoreTakeFromISR ()

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

BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,
 								 BaseType_t * pxHigherPriorityTaskWoken)

参数:
xSemaphore: 要获取的信号量句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。

返回值:
pdPASS: 获取信号量成功。
pdFALSE: 获取信号量失败。

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

三、二值信号量操作实验

1、实验目的
二值信号量的使命就是同步,完成任务与任务或中断与任务之间的同步。大多数情况下都是中断与任务之间的同步。本节就学习一下如何使用二值信号量来完成中断与任务之间的同步。

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

LED1ON:打开 LED1。
LED1OFF:关闭 LED1。
BEEPON:打开蜂鸣器。
BEEPOFF:关闭蜂鸣器。

这些指令通过串口发送给开发板,指令是不分大小写的!开发板使用中断接收,当接收到
数据以后就释放二值信号量。任务 DataProcess_task()用于处理这些指令,任务会一直尝试获取二值信号量,当获取到信号量就会从串口接收缓冲区中提取这些指令,然后根据指令控制相应的外设。

本实验设计三个任务:start_task、task1_task 、DataProcess_task 这三个任务的任务功能如下:
start_task:用来创建其他 2 个任务。
task1_task :控制 LED0 闪烁,提示系统正在运行。
DataProcess_task :指令处理任务,根据接收到的指令来控制不同的外设。
实验中还创建了 一个二值信号量 BinarySemaphore 用 于 完 成 串 口 中 断 和 任 务
DataProcess_task 之间的同步。

3、实验程序与分析

●任务设置

#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 256 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define TASK1_TASK_PRIO 2 //任务优先级
#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

● 其他应用函数

//将字符串中的小写字母转换为大写
//str:要转换的字符串
//len:字符串长度
void LowerToCap(u8 *str,u8 len)
{
	u8 i;
	for(i=0;i<len;i++)
	{
		if((96<str[i])&&(str[i]<123)) //小写字母
		str[i]=str[i]-32; //转换为大写
	}
}

//命令处理函数,将字符串命令转换成命令值
//str:命令
//返回值: 0XFF,命令错误;其他值,命令值
u8 CommandProcess(u8 *str)
{
	u8 CommandValue=COMMANDERR;
	if(strcmp((char*)str,"LED1ON")==0) CommandValue=LED1ON;
	else if(strcmp((char*)str,"LED1OFF")==0) CommandValue=LED1OFF;
	else if(strcmp((char*)str,"BEEPON")==0) CommandValue=BEEPON;
	else if(strcmp((char*)str,"BEEPOFF")==0) CommandValue=BEEPOFF;
	return CommandValue;
}

函数 LowerToCap()用于将串口发送过来的命令中的小写字母统一转换成大写字母,这样就
可以在发送命令的时候不用区分大小写,因为开发板会统一转换成大写。函数CommandProcess()用于将接收到的命令字符串转换成命令值,比如命令“LED1ON”转换成命令值就是 0(宏LED1ON 为 0)。

● main()函数

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组 4
	delay_init(); //延时函数初始化
	uart_init(115200); //初始化串口
	LED_Init(); //初始化 LED
	KEY_Init(); //初始化按键
	BEEP_Init(); //初始化蜂鸣器
	LCD_Init(); //初始化 LCD
	my_mem_init(SRAMIN); //初始化内部内存池
	 
	POINT_COLOR=RED;
	 LCD_ShowString(10,10,200,16,16,"ATK STM32F103/407");
	LCD_ShowString(10,30,200,16,16,"FreeRTOS Examp 14-1");
	LCD_ShowString(10,50,200,16,16,"Binary Semap");
	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(); //进入临界区
	//创建二值信号量
	BinarySemaphore=xSemaphoreCreateBinary();
	 //创建 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* )"keyprocess_task", 
	 (uint16_t )DATAPROCESS_STK_SIZE,
	 (void* )NULL,
	 (UBaseType_t )DATAPROCESS_TASK_PRIO,
	 (TaskHandle_t* )&DataProcess_Handler); 
	 vTaskDelete(StartTask_Handler); //删除开始任务
	 taskEXIT_CRITICAL(); //退出临界区
}

//task1 任务函数
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);//获取信号量 (1)
			if(err==pdTRUE) //获取信号量成功
			{
				len=USART_RX_STA&0x3fff; //得到此次接收到的数据长度
				CommandStr=mymalloc(SRAMIN,len+1); //申请内存
				sprintf((char*)CommandStr,"%s",USART_RX_BUF);
				CommandStr[len]='\0'; //加上字符串结尾符号
				LowerToCap(CommandStr,len); //将字符串转换为大写 (2)
				CommandValue=CommandProcess(CommandStr); //命令解析 (3)
				if(CommandValue!=COMMANDERR) //接收到正确的命令
				{
					LCD_Fill(10,90,210,110,WHITE); //清除显示区域
					LCD_ShowString(10,90,200,16,16,CommandStr);//在 LCD 上显示命令
					printf("命令为:%s\r\n",CommandStr);
					switch(CommandValue) //处理命令 (4)
					{
					case LED1ON: 
						LED1=0;
						break;
					case LED1OFF:
						LED1=1;
						break;
					case BEEPON:
						BEEP=1;
						break;
					case BEEPOFF:
						BEEP=0;
						break;
					}
			}
			else
			{
				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 个时钟节拍
		}
	}
}
	 

(1) 、 使 用 函 数 xSemaphoreTake() 获 取 二 值 信 号 量 BinarySemaphore , 延 时 时 间 为portMAX_DELAY。

(2)、调用函数 LowerToCap()将命令字符串中的小写字母转换成大写的。

(3)、调用函数 CommandProcess()处理命令字符串,其实就是将命令字符串转换为命令值。

(4)、根据不同的命令值执行不同的操作,如开关 LED1,开关 BEEP。

●中断初始化及处理过程
本实验中串口 1 是通过中断方式来接收数据的,所以需要初始化串口 1,串口的初始化很
简单,前面已经讲了很多次了。不过要注意串口 1 的中断优先级!因为我们要在串口 1 的中断服务函数中使用 FeeRTOS 的 API 函数,本实验设置串口 1 的抢占优先级为 7,子优先级为 0。

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

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

相关文章

UFD203A101 3BHE019361R0101电 工理论、电子技术、信息处理、控制理论、电力系统分析

UFD203A101 3BHE019361R0101电 工理论、电子技术、信息处理、控制理论、电力系统分析 作为电气工程及其自动化专业的大学生都会关心电气工程及其自动化就业方向是什么&#xff1f;电气工程专业就业方向怎样&#xff1f;自动化专业就业方向怎样&#xff1f; 对于很多本专业的在校…

实验06:哈夫曼编码

1.实验目的&#xff1a; 理解贪心算法的思想&#xff0c;掌握哈夫曼编码的技术和图像编解码算法的基本。 2.实验内容&#xff1a; 统计图像像素灰度值的分布特性&#xff0c;利用哈夫曼编码构造码表&#xff0c;实现对图像的编码和解码。 3.实验要求&#xff1a; 首先完成…

《JavaEE》InputStream, OutputStream 的用法

目录 File类 路径 绝对路径 相对路径 InputStream和OutputStream的使用 InputStream基本用法 OutputStream基本用法 功能实现 我们先来尝试着使用一些File类完成一些基本操作 我们查看这个文本是否存在 如果不存在我们创建一个新的文本出来 在当前文件夹中创建一个新…

MATLAB函数封装1:生成QT可以调用的.dll动态链接库

在进行相关算法的开发和设计过程中&#xff0c;MATLAB具有特别的优势&#xff0c;尤其是对于矩阵运算的处理&#xff0c;具有很多现成的方法和函数可以进行调用&#xff0c;同时MATLAB支持把函数封装成不同的语言方便完成算法的集成。 这里记录利用MATLAB封装成C动态链接库&…

git 自学笔记

git 自学笔记 git 是一个开源的分布式版本控制软件&#xff0c;可以敏捷的处理任何大小项目。 git 的工作流程大体如下&#xff1a; 首先克隆一个git资源作为工作目录 在克隆的资源上添加或者修改文件 如果其他人也修改了&#xff0c;就要对资源进行更新 在提交时也要查看有没有…

通过使用生成对抗市场模型改进基于强化学习的交易的泛化

Improving Generalization in Reinforcement Learning–Based Trading by Using a Generative Adversarial Market Model | IEEE Journals & Magazine | IEEE Xplore Improving Generalization in Reinforcement Learning–Based Trading by Using a Generative Adversaria…

ASEMI代理ADG736BRMZ-REEL7原装ADI车规级ADG736BRMZ-REEL7

编辑&#xff1a;ll ASEMI代理ADG736BRMZ-REEL7原装ADI车规级ADG736BRMZ-REEL7 型号&#xff1a;ADG736BRMZ-REEL7 品牌&#xff1a;ADI /亚德诺 封装&#xff1a;MSOP-10 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;10 类型&#xff1…

c/c++:指针p+p-p*p/,数组a+1,指针减指针,指针实现strlen函数,指针的比较运算,指针数组,多级指针

c&#xff1a; 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;此时学会c的话&#xff0c; 我所知道的周边的会c的同学&#xff0c;可手握10多个offer&#xff0c;随心所欲&#xff0c;而找啥算法岗的&#xff0c;基本gg 提…

IMX6ULLPRO单独编译kernel+dtb内核模块以及uboot

目录 为什么编译驱动程序之前要先编译内核&#xff1f; 驱动程序要用到内核文件&#xff1a; 编译内核 编译安装内核模块 编译内核模块 安装内核模块到 Ubuntu 某个目录下备用 安装内核和模块到开发板上 Bootloader 介绍 编译 u-boot 镜像 为什么编译驱动程序之前要先编…

4.4 使用分组聚合进行组内计算

4.4 使用分组聚合进行组内计算 4.4.1 使用groupby方法拆分数据groupby方法的参数及其说明&#xff1a;groupby对象常用的描述性统计方法如下&#xff1a; 4.4.2 使用agg方法聚合数据agg函数和aggregate函数的参数说明1、使用agg求出当前数据对应的统计量2、使用agg分别求字段的…

X509证书中的Subject Public Key Info

SubjectPublicKeyInfo在TBSCertificate的第七项: 对于ECC id-ecPublicKey OBJECT IDENTIFIER :: { iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 } id-ecPublicKey 是必须的 ECParameters :: CHOICE {namedCurve OBJECT IDENTIFIER -- implicitCurve NULL -…

java day10

第10章 创建Swing界面 10.1 Swing的特性10.1.1 标准对话框1. 确认对话框2. 输入对话框3.消息对话框4. 选项对话框 10.1.2 使用对话框10.1.3 滑块10.1.4 滚动窗格10.1.5 工具栏10.1.6 进度条10.1.7 菜单10.1.8 选项卡式窗格 10.1 Swing的特性 10.1.1 标准对话框 JOptionPane类…

HTML5 <rt> 标签、HTML5 <ruby> 标签

HTML5 <rt> 标签 实例 HTML5 <rt>标签用于表示为<ruby>标签中的注释内容。 一个 ruby 注释&#xff1a; <ruby> 漢 <rt> ㄏㄢˋ </rt> </ruby> 尝试一下 浏览器支持 IE 9、Firefox、Opera、Chrome 和 Safari 支持 <rt> 标…

增广拍卖——二跳页下的拍卖机制探索

1. 引言 本文提出的方案已被WSDM 2023接收&#xff0c;论文&#xff1a;Boosting Advertising Space: Designing Ad Auctions for Augment Advertising&#xff0c; 下载&#xff1a;https://dl.acm.org/doi/abs/10.1145/3539597.3570381 信息流产品为了保障用户体验通常会严格…

关于python爬虫解析的问题

在进行Python爬虫解析时&#xff0c;需要注意以下事项&#xff1a; 1、良好的网站使用协议&#xff1a;需要遵守网站的robots.txt文件&#xff0c;以确保你的爬虫程序不会将网站拦截下来。 2、编码问题&#xff1a;需要正确设置HTTP头和解析器的编码&#xff0c;以确保爬虫程…

低代码开发重要工具:jvs-logic(逻辑引擎)可视化设计要素

逻辑引擎可视化的交互 可视化的服务编排是逻辑引擎的核心功能&#xff0c;逻辑引擎的界面可视化设计是为了方便用户使用和操作逻辑引擎而设计的。一个好的界面设计能够提高用户的工作效率和使用体验&#xff0c;同时也能增加软件的可靠性和可维护性。 以下是逻辑引擎界面可视化…

python数据分析综合案列--星巴克门店数据分析及可视化

本实训针对一组关于全球星巴克门店的统计数据&#xff0c;分析了在不同国家和地区以及中国不同城市的星巴克门店数量。 获取数据&#xff0c;数据放在directory.csv 这个案例主要分为以下几个部分&#xff1a; 数据清洗和预处理&#xff1a;使用 Pandas 进行数据清洗和预处理&…

windows下安装emscripten

Qt系列文章目录 文章目录 Qt系列文章目录前言一、Emscripten SDK介绍二、Emscripten SDK安装Get the emsdk repoEnter that directory 前言 由于Web端需要处理大量图像&#xff0c;大量图片的分辨率8k*8k&#xff0c;使用Canvas API&#xff08;画布&#xff09;是在HTML5中新…

(二)app自动化脚本录制回放

上一篇&#xff1a;(一)app自动化测试环境搭建&#xff08;maciosairtest &#xff09;_airtest环境搭建_要开朗的spookypop的博客-CSDN博客 注&#xff1a;后续都是用IOS设备来介绍自动化测试&#xff0c;安卓就不赘述了。 接上一篇&#xff0c;搭建好自动化测试环境后&#…

如何去阅读Java源码,我总结了18条心法

大家好&#xff0c;我是Martin。 这篇文章我准备来聊一聊如何去阅读开源项目的源码。 在聊如何去阅读源码之前&#xff0c;先来简单说一下为什么要去阅读源码&#xff0c;大致可分为以下几点原因&#xff1a; 最直接的原因&#xff0c;就是面试需要&#xff0c;面试喜欢问源码…