个人名片:
🎓作者简介:嵌入式领域优质创作者
🌐个人主页:妄北y📞个人QQ:2061314755
💌个人邮箱:[mailto:2061314755@qq.com]
📱个人微信:Vir2025WBY🖥️个人公众号:科技妄北
🖋️本文为妄北y原创佳作,独家首发于CSDN🎊🎊🎊
💡座右铭:改造世界固然伟大,但改造自我更为可贵。
专栏导航:
妄北y系列专栏导航:
物联网嵌入式开发项目:大学期间的毕业设计,课程设计,大创项目,各种竞赛项目,全面覆盖了需求分析、方案设计、实施与调试、成果展示以及总结反思等关键环节。📚💼💡
QT基础入门学习:对QT的基础图形化页面设计进行了一个简单的学习与认识,利用QT的基础知识进行了翻金币小游戏的制作。🛠️🔧💭
Linux基础编程:初步认识什么是Linux,为什么学Linux,安装环境,进行基础命令的学习,入门级的shell编程。🍻🎉🖥️
深耕Linux应用开发:分享Linux的基本概念、命令行操作、文件系统、用户和权限管理等,网络编程相关知识,TCP/IP 协议、套接字(Socket)编程等,可以实现网络通信功能。常见开源库的二次开发,如libcurl、OpenSSL、json-c、freetype等💐📝💡
Linux驱动开发:Linux驱动开发是Linux系统不可或缺的组成部分,它专注于编写特殊的程序——驱动程序。这些程序承载着硬件设备的详细信息,并扮演着操作系统与硬件间沟通的桥梁角色。驱动开发的核心使命在于确保硬件设备在Linux系统上顺畅运作,同时实现与操作系统的无缝集成,为用户带来流畅稳定的体验。🚀🔧💻
Linux项目开发:Linux基础知识的实践,做项目是最锻炼能力的一个学习方法,这里我们会学习到一些简单基础的项目开发与应用,而且都是毕业设计级别的哦。🤸🌱🚀
非常期待与您一同在这个广阔的互联网天地里,携手探索知识的海洋,互相学习,共同进步。🌐💫🌱 熠熠星光,照亮我们的成长之路
✨✨ 欢迎订阅本专栏,对专栏内容任何问题都可以随时联系博主,共同书写属于我们的精彩篇章!✨✨
文章介绍:
📚本篇文章将深入剖析RTOS学习的精髓与奥秘,与您一同分享相关知识!🎉🎉🎉
若您觉得文章尚可入目,期待您能慷慨地送上点赞、收藏与分享的三连支持!您的每一份鼓励,都是我创作路上源源不断的动力。让我们携手并进,共同奔跑,期待在顶峰相见的那一天,共庆辉煌!🚀🚀🚀
🙏衷心感谢大家的点赞👍、收藏⭐和评论✍️,您的支持是我前进的动力!
目录:
目录:
一、什么是信号量?
1.1 信号量的主要目的:
1.1.1 管理共享资源访问
1.1.2 实现任务间同步:
1.2 常见信号量
1. 二值信号量(Binary Semaphore)
2. 计数型信号量(Counting Semaphore)
3. 互斥信号量(Mutex Semaphore)
4. 递归互斥信号量(Recursive Mutex Semaphore)
二、二值信号量:
2.1 二值信号量无效:
编辑
2.2 中断释放信号量:
2.3 任务获取信号量成功:
2.4 任务再次进入阻塞态
2.5 创建二值信号量
2.5.1 函数vSemaphoreCreateBinary ()
2.5.2 函数xSemaphoreCreateBinary()
2.5.3 函数xSemaphoreCreateBinaryStatic()
2.6 释放信号量
2.6.1 函数xSemaphoreGive()
2.6.2 函数xSemaphoreGiveFromISR()
2.7 获取信号量:
2.7.1 函数xSemaphoreTake()
2.7.2 函数xSemaphoreTakeFromISR ()
三、程序设计
3.1 实验目的:
3.2 具体设计:
3.3 任务设计:
3.4 实验步骤:
3.5 程序设计与分析
3.5.1 任务设置
3.5.2 其他应用函数:
3.5.3 main()函数:
3.5.4 任务函数:
3.6 中断初始化及处理过程:
3.7 程序运行结果分析
一、什么是信号量?
1.1 信号量的主要目的:
在FreeRTOS中,信号量是用来管理共享资源访问和实现任务间同步的关键工具。
1.1.1 管理共享资源访问
1. 使用信号量(计数型信号量)进行共享资源管理的案例:
假设一个停车场有100个停车位,这些停车位对所有车辆都是共享的。在这种情况下,我们可以使用计数型信号量来管理这些停车位。计数型信号量的初始值设为100,代表停车场的全部空位。
车辆进入停车场:
- 当车辆试图进入停车场时,它首先会"请求"(或称"获取")信号量。
- 如果信号量的值大于0(表示有空位),信号量的值减1(即一个停车位被占用),车辆可以进入停车场并占用一个停车位。
- 如果信号量的值为0(无空位),车辆需要等待,直到有其他车辆离开停车场释放一个停车位。
车辆离开停车场:
- 当车辆准备离开停车场时,它会"释放"信号量。
- 释放信号量意味着信号量的值加1(即一个停车位变为空闲状态),这样其他正在等待的车辆就可以进入停车场。
信号量的作用:
- 避免资源冲突:确保每个停车位在同一时间内只被一个车辆占用。
- 同步资源状态:信号量的值随时反映出当前空闲的停车位数量,帮助车辆决定是否可以立即进入停车场。
2. 使用信号量(二值信号量)进行共享资源管理的案例:
二值信号量是一种特殊的信号量,它只有两个状态,通常被用来表示一个资源的占用状态(使用中或未使用)。公共电话就是那个需要被多个用户共享的资源。
电话未被使用时:
- 二值信号量的初始状态设置为1,表示电话是可用的。
- 当一个人想要使用电话时,他或她会尝试获取(或称"请求")这个二值信号量。
- 如果信号量的值为1(电话未使用),信号量将被设置为0(电话正在使用),用户可以开始使用电话。
- 如果信号量的值为0(电话已被占用),用户必须等待,直到当前使用者完成通话。
电话使用完成后:
- 用户完成通话后,需要释放(或称"给出")信号量。
- 释放信号量将其值设置回1,表示电话再次变为可用状态。
- 这样,下一个等待使用电话的人可以获取信号量并开始使用电话。
信号量的作用:
- 互斥访问:确保在任何给定时间内,只有一个人能使用电话,防止多人同时使用同一资源造成的冲突。
- 简单同步:通过信号量的获取和释放操作,用户的行为(使用电话)被同步,确保资源使用的有序性。
1.1.2 实现任务间同步:
信号量的另一个重要的应用场合就是任务同步,用于任务与任务或中断与任务之间的同步。在执行中断服务函数的时候可以通过向任务。
在RTOS环境下,中断服务函数需要快速执行并完成,以保持系统的响应性和实时性。通常,ISR中会进行最小必要的处理,如设置标志或释放信号量,而将时间消耗较长的处理推迟到任务级别执行。
中断发生:
- 当硬件事件触发中断时,对应的ISR被执行。
- ISR中不进行复杂逻辑处理,而是释放一个预先定义的信号量。这个操作通常非常快速,符合ISR的快进快出原则。
任务响应:
- 系统中有一个或多个任务在等待这个信号量。
- 一旦ISR释放信号量,等待该信号量的任务被唤醒。
- 任务获取到信号量后,开始执行更复杂的处理逻辑,如数据处理、用户通知等。
信号量的作用:
- 最小化中断处理时间:通过将复杂处理从ISR转移到任务,减少了ISR的执行时间,从而减少对系统其他部分的干扰。
- 同步机制:信号量作为中断和任务之间的同步机制,确保任务在适当的时机开始执行,即在相关的中断事件发生后。
1.2 常见信号量
1. 二值信号量(Binary Semaphore)
二值信号量只有两个状态:被占用(0)和可用(1)。它通常用于实现任务间的简单同步操作,例如,一个任务可以等待二值信号量释放后才开始执行某个操作。二值信号量也常用于从中断服务例程(ISR)向任务发送信号,表明某个事件已经发生。
2. 计数型信号量(Counting Semaphore)
计数型信号量可以拥有大于1的计数值,用于管理访问数量有限的共享资源。例如,如果有多个资源实例可用(如多个内存缓冲区),计数型信号量的初始计数会设置为可用资源的数量。任务在访问资源前需要获取信号量,每次获取信号量会使计数值减一;释放信号量则会使计数值加一。
3. 互斥信号量(Mutex Semaphore)
互斥信号量是专门设计用于控制对共享资源的互斥访问。与二值信号量不同,互斥信号量提供了优先级继承的机制,这有助于解决优先级反转的问题。当一个低优先级任务持有互斥信号量时,如果一个高优先级任务试图获取这个信号量并因此被阻塞,低优先级任务的优先级会临时提升到高优先级任务的优先级,直到它释放信号量。
4. 递归互斥信号量(Recursive Mutex Semaphore)
递归互斥信号量是一种特殊类型的互斥信号量,允许同一个任务多次获取同一个互斥量而不会导致死锁。这对于在同一个任务中需要多次进入临界区的情况非常有用。每次获取信号量时,内部计数器会增加,任务必须释放相同数量的信号量才能最终释放互斥量。
二、二值信号量:
2.1 二值信号量无效:
图2-1 请求二值信号量
在图 2-1 中任务Task通过函数xSemaphoreTake()获取信号量,但是此时二值信号量无效,所以任务Task进入阻塞态。
2.2 中断释放信号量:
图2-2 发送中断
此时中断发生了,在中断服务函数中通过函数xSemaphoreGiveFromISR()释放信号量,因此信
号量变为有效。
2.3 任务获取信号量成功:
图2-3 任务请求信号量成功
由于信号量已经有效了,所以任务Task获取信号量成功,任务从阻塞态解除,开始执行相关的处理过程。
2.4 任务再次进入阻塞态
由于任务函数一般都是一个大循环,所以在任务做完相关的处理以后就会再次调用函数xSemaphore Take0获取信号量。在执行完第三步以后二值信号量就已经变为无效的了,所以任务
将再次进入阻塞态,和第一步一样,直至中断再次发生并且调用函数xSemaphoreGiveFromISR()
释放信号量。
2.5 创建二值信号量
同队列一样,要想使用二值信号量就必须先创建二值信号量,二值信号量创建函数如表:
图2-4 创建二值信号量
2.5.1 函数vSemaphoreCreateBinary ()
vSemaphoreCreateBinary
是老版本FreeRTOS中的创建二值信号量的函数,已被新版本的xSemaphoreCreateBinary
替代。此函数仍然保留
SemaphoreHandle_t xSemaphoreCreateBinary(void);
以兼容基于老版本FreeRTOS的应用层代码。
void vSemaphoreCreateBinary(SemaphoreHandle_t *xSemaphore);
参数:
- xSemaphore: 保存创建成功的二值信号量句柄。
返回值:
- NULL: 二值信号量创建失败。
- 其他值: 二值信号量创建成功。
2.5.2 函数xSemaphoreCreateBinary()
xSemaphoreCreateBinary
是新版本FreeRTOS中用于创建二值信号量的函数,它替代了老版本中的vSemaphoreCreateBinary
函数。此函数使用FreeRTOS的内存管理部分来动态分配所需的RAM。
SemaphoreHandle_t xSemaphoreCreateBinary(void);
参数
- 无参数。
返回值
- NULL: 如果信号量创建失败。
- 其他值: 返回创建成功的二值信号量的句柄。
2.5.3 函数xSemaphoreCreateBinaryStatic()
xSemaphoreCreateBinaryStatic
是用于创建二值信号量的函数,与xSemaphoreCreateBinary
不同的是,此函数允许用户自行分配信号量所需的RAM,而不是由FreeRTOS的内存管理部分动态分配。
SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *pxSemaphoreBuffer);
参数
- pxSemaphoreBuffer: 此参数指向一个
StaticSemaphore_t
类型的变量,用来保存信号量结构体。
返回值
- NULL: 如果二值信号量创建失败。
- 其他值: 返回创建成功的二值信号量的句柄。
2.6 释放信号量
图2-5 释放信号量
同队列一样,释放信号量也分为任务级和中断级。还有!不管是二值信号量、计数型信号量还是互斥信号量,它们都使用图中的函数释放信号量,递归互斥信号量有专用的释放函数。
2.6.1 函数xSemaphoreGive()
xSemaphoreGive
是FreeRTOS中用于释放(或给出)二值信号量的函数。此函数的实现基于向队列发送消息的机制,但实际上并不发送具体的消息内容。
在semphr.h
文件中,xSemaphoreGive
被定义为一个宏:
#define xSemaphoreGive(xSemaphore) \
xQueueGenericSend((QueueHandle_t)(xSemaphore), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK)
参数
- xSemaphore: 要释放的信号量句柄。
返回值
- pdPASS: 释放信号量成功。
- errQUEUE_FULL: 释放信号量失败,通常表示队列已满。
实现细节
- 队列操作:
xSemaphoreGive
实际上是通过调用xQueueGenericSend
函数来实现的。这个函数尝试将一个条目放入队列,但对于二值信号量,它实际上并不放入任何实际的数据。 - 阻塞时间: 宏
semGIVE_BLOCK_TIME
设置为0,意味着操作不会阻塞。 - 入队方式: 使用
queueSEND_TO_BACK
方式,即将信号量状态放置在队列的末尾。
信号量状态
- uxMessagesWaiting: 在队列结构体中,
uxMessagesWaiting
成员变量用于跟踪队列中的消息数量。对于二值信号量,这个值在信号量被获取时减少,被释放时增加。 - 当
uxMessagesWaiting
为1时,表示信号量有效(可被获取)。 - 当
uxMessagesWaiting
为0时,表示信号量无效(已被获取或未初始化)。
错误处理
- 如果尝试释放一个已满的信号量(即
uxMessagesWaiting
已经是1),xQueueGenericSend
将返回errQUEUE_FULL
,表示操作失败。
2.6.2 函数xSemaphoreGiveFromISR()
xSemaphoreGiveFromISR
是FreeRTOS中用于在中断服务例程(ISR)中释放二值信号量或计数型信号量的函数。此函数是专门设计用于中断环境,不适用于释放互斥信号量,因为互斥信号量涉及到优先级继承问题,而中断不属于任务,无法处理优先级继承。
xSemaphoreGiveFromISR
被定义为一个宏,实际执行的是xQueueGiveFromISR
函数:
#define xSemaphoreGiveFromISR(xSemaphore, pxHigherPriorityTaskWoken) \
xQueueGiveFromISR((QueueHandle_t)(xSemaphore), (pxHigherPriorityTaskWoken))
参数:
- xSemaphore: 要释放的信号量句柄。
- pxHigherPriorityTaskWoken: 指向一个
BaseType_t
变量的指针,该变量在函数执行后会被设置,用来指示是否需要在退出中断时进行任务切换。如果此值被设置为pdTRUE
,则在退出中断服务函数之前应进行一次任务切换。
返回值:
- pdPASS: 释放信号量成功。
- errQUEUE_FULL: 释放信号量失败,通常表示信号量已经是释放状态。
实现细节:
- 函数行为:
xSemaphoreGiveFromISR
通过调用xQueueGiveFromISR
来实现,这个函数是为中断环境优化的,可以安全地在ISR中调用。 - 任务切换: 参数
pxHigherPriorityTaskWoken
用于在中断中决定是否需要进行任务切换。这是因为在中断中释放信号量可能会唤醒一个优先级更高的任务,如果发生这种情况,为了保证系统的响应性,推荐在中断结束前进行任务切换。
2.7 获取信号量:
图2-6 获取信号量
同释放信号量的API函数一样,不管是二值信号量、计数型信号量还是互斥信号量,它们都使用图2-6 中的函数获取信号量。
2.7.1 函数xSemaphoreTake()
xSemaphoreTake
是FreeRTOS中用于获取二值信号量、计数型信号量或互斥信号量的函数。此函数是一个宏,实际的获取信号量过程由xQueueGenericReceive
函数完成。
在semphr.h
文件中,xSemaphoreTake
被定义为一个宏:
#define xSemaphoreTake(xSemaphore, xBlockTime) \
xQueueGenericReceive((QueueHandle_t)(xSemaphore), NULL, (xBlockTime), pdFALSE)
参数:
- xSemaphore: 要获取的信号量句柄。
- xBlockTime: 阻塞时间,表示在获取信号量时,如果信号量不可用,任务可以等待的时间。
返回值:
- pdTRUE: 获取信号量成功。
- pdFALSE: 超时,获取信号量失败。
实现细节:
- 获取信号量的过程:
xSemaphoreTake
的实现实际上是通过调用xQueueGenericReceive
来完成的。获取信号量的过程类似于从队列中读取数据,但并不真正读取队列中的消息。 - 队列状态处理:
- 如果队列为空且
xBlockTime
为0,函数立即返回errQUEUE_EMPTY
,表示获取信号量失败。 - 如果队列为空且
xBlockTime
不为0,任务将被添加到延时列表中以等待信号量的释放。 - 如果队列不为空,函数将从队列中读取数据(尽管并不使用这个数据),并将队列结构体中的
uxMessagesWaiting
成员变量减一,以更新信号量状态。
- 如果队列为空且
2.7.2 函数xSemaphoreTakeFromISR ()
xSemaphoreTakeFromISR
是FreeRTOS中用于在中断服务例程(ISR)中获取二值信号量或计数型信号量的函数。此函数不能用于获取互斥信号量,因为互斥信号量涉及优先级继承问题,而中断服务例程不适合处理这种情况。
xSemaphoreTakeFromISR
是一个宏,实际执行的是xQueueReceiveFromISR
函数:
#define xSemaphoreTakeFromISR(xSemaphore, pxHigherPriorityTaskWoken) \
xQueueReceiveFromISR((QueueHandle_t)(xSemaphore), NULL, (pxHigherPriorityTaskWoken))
参数:
- xSemaphore: 要获取的信号量句柄。
- pxHigherPriorityTaskWoken: 指向一个
BaseType_t
变量的指针,用于标记在函数执行后是否需要进行任务切换。如果此值被设置为pdTRUE
,则在退出中断服务函数之前应进行一次任务切换。
返回值:
- pdPASS: 获取信号量成功。
- pdFAIL: 获取信号量失败,通常表示队列为空,信号量不可用。
实现细节:
- 函数行为:
xSemaphoreTakeFromISR
通过调用xQueueReceiveFromISR
来实现,该函数在中断环境中安全地执行出队操作。获取信号量时,如果队列中有信号量可用,函数将成功获取并更新队列状态。 - 队列状态处理:
- 当队列不为空时,
xQueueReceiveFromISR
函数会减少队列中的uxMessagesWaiting
成员变量,更新信号量状态。 - 如果有任务因信号量阻塞且其优先级较高,函数会设置
pxHigherPriorityTaskWoken
为pdTRUE
,指示需要任务切换。 - 如果队列为空,函数直接返回
pdFAIL
,表示获取信号量失败。
- 当队列不为空时,
三、程序设计
3.1 实验目的:
本设计的目的是使用二值信号量实现中断与任务之间的同步。通过这个设计,我们将学习如何在嵌入式系统中利用二值信号量来实现中断与任务之间的同步控制。
3.2 具体设计:
设计内容是通过串口发送指定指令来控制开发板上的LED1和蜂鸣器(BEEP)的状态。具体指令如下(不区分大小写):
- LED1ON: 打开LED1。
- LED1OFF: 关闭LED1。
- BEEPON: 打开蜂鸣器。
- BEEPOFF: 关闭蜂鸣器。
指令通过串口发送到开发板,开发板使用串口中断接收数据。当接收到数据后,通过释放二值信号量来通知任务处理程序有新的数据到来。任务 DataProcessTask
用于处理这些指令。任务不断尝试获取二值信号量,当成功获取到信号量后,从串口接收缓冲区中提取指令并控制相应的外设。
3.3 任务设计:
本实验设计了三个任务:StartTask
、Task1Task
、DataProcessTask
,它们的功能如下:
- StartTask: 用于创建其他两个任务,即
Task1Task
和DataProcessTask
。 - Task1Task: 控制LED0的闪烁,提示系统正在运行。
- DataProcessTask: 处理串口指令,根据接收到的指令控制不同的外设。
还创建了一个二值信号量 BinarySemaphore
,用于串口中断与任务 DataProcessTask
之间的同步。
3.4 实验步骤:
1. 创建二值信号量: 初始化信号量 BinarySemaphore
,用于任务与中断之间的同步。
2. 创建任务:
StartTask
:用于创建其他任务。Task1Task
:启动后,控制LED0闪烁。DataProcessTask
:不断尝试获取信号量以处理指令。
3. 串口中断服务函数:
- 串口接收到数据后,释放
BinarySemaphore
信号量。
4. 任务处理逻辑:
DataProcessTask
尝试获取BinarySemaphore
信号量。- 成功获取信号量后,从串口接收缓冲区读取指令。
- 根据读取的指令执行相应的操作(打开/关闭 LED1 或蜂鸣器)。
3.5 程序设计与分析
3.5.1 任务设置
//任务优先级
#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
3.5.2 其他应用函数:
//将字符串中的小写字母转换为大写
//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)。
3.5.3 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(); //开启任务调度
}
3.5.4 任务函数:
//开始任务任务函数
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); //获取信号量
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); //将字符串转换为大写
CommandValue=CommandProcess(CommandStr); //命令解析
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) //处理命令
{
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. 获取二值信号量:使用FreeRTOS的xSemaphoreTake
函数来获取信号量。这里使用portMAX_DELAY
意味着任务会一直阻塞,直到信号量可用。
2. 将字符串转换为大写字母:调用LowerToCap
函数来将命令字符串中的小写字母转换为大写字母。假设这个函数已经定义好并可用。
3. 处理命令字符串:调用CommandProcess
函数以处理命令字符串,将其转换为一个命令值。这个命令值可以是一个枚举或整数,用于标识不同的命令。
4. 执行相应的操作:根据不同的命令值执行不同的操作,如控制LED1和蜂鸣器(BEEP)的状态。
3.6 中断初始化及处理过程:
本设计中串口1是通过中断方式来接收数据的,所以需要初始化串口1,不过要注意串口1的中断优先级!因为我们要在串口1的中断服务函数中使用FeeRTOS的API函数,设置串口1的抢占优先级为7,子优先级为0。
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=7 ;//抢占优先级7
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
串口1 的中断服务函数如下:
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
BaseType_t xHigherPriorityTaskWoken;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1); //读取接收到的数据
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);//如果需要的话进行一次任务切换
}
}
当串口接收到数据以后就调用函数xSemaphoreGiveFromISRO释放信号量BinarySemaphore。
3.7 程序运行结果分析
编译并下载实验代码到开发板中,打开串口调试助手,通过串口调试助手发送命令,比如命令“led1ON”,开发板接收到命令以后就会将命令中的小写字母转换为大写,并且显示在LCD上,如图所示:
当命令正确的时候LED1就会亮,同时开发板向串口调试助手发送经过大小写转换后的命令字符串,如图所示:
当命令错误的时候开发板就会向串口调试助手发送命令错误的提示信息,比如我们发送“led1_off”这个命令,串口调试助手显示如图所示:
📝大佬觉得本文有所裨益,不妨轻点一下👍给予鼓励吧!
❤️❤️❤️本人虽努力,但能力尚浅,若有不足之处,恳请各位大佬不吝赐教,您的批评指正将是我进步的动力!😊😊😊
💖💖💖若您认为此篇文章对您有所帮助,烦请点赞👍并收藏🌟,您的支持是我前行的最大动力!
🚀🚀🚀任务在默默中完成,价值在悄然间提升。让我们携手共进,一起加油,迎接更美好的未来!🌈🌈🌈