FreeRTOS基础入门——FreeRTOS信号量及二值信号量(十三)

news2024/9/23 21:25:42

 个人名片:

🎓作者简介:嵌入式领域优质创作者
🌐个人主页:妄北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成员变量,更新信号量状态。
    • 如果有任务因信号量阻塞且其优先级较高,函数会设置pxHigherPriorityTaskWokenpdTRUE,指示需要任务切换。
    • 如果队列为空,函数直接返回pdFAIL,表示获取信号量失败。

三、程序设计

3.1 实验目的:

本设计的目的是使用二值信号量实现中断与任务之间的同步。通过这个设计,我们将学习如何在嵌入式系统中利用二值信号量来实现中断与任务之间的同步控制。

3.2 具体设计:

设计内容是通过串口发送指定指令来控制开发板上的LED1和蜂鸣器(BEEP)的状态。具体指令如下(不区分大小写):

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

指令通过串口发送到开发板,开发板使用串口中断接收数据。当接收到数据后,通过释放二值信号量来通知任务处理程序有新的数据到来。任务 DataProcessTask 用于处理这些指令。任务不断尝试获取二值信号量,当成功获取到信号量后,从串口接收缓冲区中提取指令并控制相应的外设。

3.3 任务设计:

本实验设计了三个任务:StartTaskTask1TaskDataProcessTask它们的功能如下:

  • StartTask: 用于创建其他两个任务,即Task1TaskDataProcessTask
  • 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”这个命令,串口调试助手显示如图所示:

📝大佬觉得本文有所裨益,不妨轻点一下👍给予鼓励吧!

❤️❤️❤️本人虽努力,但能力尚浅,若有不足之处,恳请各位大佬不吝赐教,您的批评指正将是我进步的动力!😊😊😊

💖💖💖若您认为此篇文章对您有所帮助,烦请点赞👍并收藏🌟,您的支持是我前行的最大动力!

🚀🚀🚀任务在默默中完成,价值在悄然间提升。让我们携手共进,一起加油,迎接更美好的未来!🌈🌈🌈

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

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

相关文章

智联云采 SRM2.0 autologin 身份认证绕过漏洞复现

0x01 产品简介 智联云采是一款针对企业供应链管理难题及智能化转型升级需求而设计的解决方案,针对企业供应链管理难题,及智能化转型升级需求,智联云采依托人工智能、物联网、大数据、云等技术,通过软硬件系统化方案,帮助企业实现供应商关系管理和采购线上化、移动化、智能…

【王树森】RNN模型与NLP应用(4/9):LSTM模型(个人向笔记)

前言 LSTM是对Simple RNN的改进&#xff0c;可以避免梯度消失的问题&#xff0c;能够有更长的记忆力。 LSTM 1. LSTM:Conveyor Belt 过去的信息 C t − 1 C_{t-1} Ct−1​ 通过一个传输带直接输送到下一个状态 C t C_t Ct​&#xff0c;不会发生太大的变化&#xff0c;由此…

激活函数 Sigmod 及其导数

文章目录 1. Sigmod 函数2. 取值3. 图像4. 导数 1. Sigmod 函数 Sigmod 函数是神经网络中最常用的激活函数之一&#xff0c;其形式如下&#xff1a; sigmod ( x ) f ( x ) 1 1 e − x . \text{sigmod}(x) f(x) \frac{1}{1 e^{-x}}. sigmod(x)f(x)1e−x1​. 2. 取值 分…

大模型理论基础

大模型理论基础与学习路径 1.大模型的理论基础&#xff0c;包括深度学习、预训练语言模型和大语言模型。 2.学习大模型开发的路径&#xff0c;包括理论学习、实践操作和项目应用。 3.如何通过理论学习提升上限&#xff0c;为深入学习大模型奠定基础。 GPT模型家族技术发展 1.GP…

数据结构——堆排序

目录 引言 堆排序 1.算法思想 2.算法步骤 3.代码实现 3.1 构建堆 (1)小堆 (2)大堆 3.2 交换与调整 3.3 重复上述过程 4.复杂度分析 5.完整代码 5.1算法实现代码 5.2 示例 6.堆排序的优势 结束语 引言 本篇博客&#xff0c;我们将利用堆结构实现的高效排序算法…

版本控制的核心:Git中的哈希与默克尔树解析

Git是最常用的代码版本控制工具。它帮助我们跟踪代码的更改、管理代码版本&#xff0c;同时保证代码库的完整性和安全性。我们知道 Git 中有一些基本的操作&#xff0c;比如commit、merge、rebase等&#xff0c;但这些操作的底层机制是如何实现的呢&#xff1f;哈希函数和默克尔…

深度学习|模型推理:端到端任务处理

文章目录 引言端到端的能力任务与模型简介手写数字识别数据准备训练集与测试集模型介绍 推理过程前向传播权重参数推理与评估 结语 引言 通过前文「深度学习&#xff5c;感知机&#xff1a;神经网络之始」中 XOR Gate 的示例&#xff0c;我们知道叠加层可以增强感知机的表达能…

单向链表排序及双向链表

单向链表的优缺点 优点&#xff1a;存储空间没有上限&#xff0c;插入删除效率高 缺点&#xff1a;修改和查找效率低&#xff0c;只能单向的向后遍历后续节点&#xff0c;不能向前遍历前驱节点 单向链表快慢指针法查找&#xff1a; 链表的排序 双向链表 由于单向链表只能通…

Linux 性能调优:策略与实践

引言 随着云计算和虚拟化技术的发展&#xff0c;Linux 已经成为企业和个人用户的首选操作系统。Linux 性能调优不仅有助于提高系统资源利用率&#xff0c;还能确保应用程序的高效运行。本文将探讨 Linux 性能调优的基本原则、常用工具和方法&#xff0c;以及实际案例分析。 一…

go中的并发处理

. Goroutines 概念&#xff1a; Goroutines 是 Go 的核心并发机制。它们是由 Go 运行时管理的轻量级线程&#xff0c;具有比操作系统线程更少的开销。每个 goroutine 只需少量的内存&#xff08;大约 2KB&#xff09;&#xff0c;并且由 Go 运行时负责调度和管理,哪怕是java发…

哈希表与统计——594、350、554、609、454(2简3中)

594. 最长和谐子序列&#xff08;简单&#xff09; 和谐数组是指一个数组里元素的最大值和最小值之间的差别 正好是 1 。 现在&#xff0c;给你一个整数数组 nums &#xff0c;请你在所有可能的子序列中找到最长的和谐子序列的长度。 数组的子序列是一个由数组派生出来的序列&a…

华大HC32F460移植FreeRTOS

参考&#xff1a; 关于MCU M4内核移植FreeRTOS的笔记 主要参考这位大佬的&#xff0c;照做就行了&#xff0c;用的也是IAR HC32F460 freeRTOS移植 这位是用Keil的 MCU&#xff1a;华大HC32F460 库版本&#xff1a;hc32f460_ddl_Rev2.2.0 IDE&#xff1a; IAR FreeRTOS版本&…

【多线程】概述

进程与线程 概述 一个正在运行过程中的程序&#xff0c;就是一个进程&#xff0c;也称一个任务。进程是一个重要的软件资源&#xff0c;是由操作系统内核管理和组织的。 并行&#xff1a;微观上同一个时刻&#xff0c;两个核心上的进程&#xff0c;就是同时进行的。 并发&…

遗传算法Github初学

遗传算法的理论是根据达尔文进化论而设计出来的算法&#xff1a;人类是朝着好的方向&#xff08;最优解&#xff09;进化&#xff0c;进化过程中&#xff0c;会自动选择优良基因&#xff0c;淘汰劣等基因 遗传算法&#xff08;genetic algorithm——GA&#xff09;是计算数学中…

【JavaScript】LeetCode:11-15

文章目录 11 最长连续序列12 移动零13 盛最多水的容器14 三数之和15 接雨水 11 最长连续序列 数组排序、去重遍历数组中的元素&#xff0c;当元素连续时&#xff0c;长度加1&#xff0c;当不连续时&#xff0c;长度设置为1。 /*** param {number[]} nums* return {number}*/ v…

AI科学家:自动化科研的未来之路

随着人工智能&#xff08;AI&#xff09;技术的不断进步&#xff0c;AI已经在众多领域中展现了强大的潜力&#xff0c;尤其是在科研方面的应用正在引起广泛关注。最近&#xff0c;Sakana AI与牛津大学和不列颠哥伦比亚大学联合推出了一款被称为“AI科学家”的自动化科研工具&am…

第4章-01-学会从Chrome浏览器中Network

🏆作者简介,黑夜开发者,CSDN领军人物,全栈领域优质创作者✌,CSDN博客专家,阿里云社区专家博主,2023年CSDN全站百大博主。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 🏆本文已收录于专栏:Web爬虫入门与实战精讲,后续完整更新内容如下。 文章…

【Canvas与纹饰】环形小蜜蜂纹饰

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>环形小蜜蜂纹饰</title><style type"text/css"&g…

Mysql基础练习题 1378.使用唯一标识符替换员工ID (力扣)

1378. 展示每位用户的 唯一标识码&#xff08;unique ID &#xff09;&#xff1b;如果某位员工没有唯一标识码&#xff0c;使用 null 填充即可。 你可以以任意顺序返回结果表。 题目链接&#xff1a; https://leetcode.cn/problems/replace-employee-id-with-the-unique-i…

k8s调度、污点、容忍、不可调度、排水、数据卷挂载

一、Kubernetes的list-watch机制 1、List-watch K8S集群中&#xff0c;通过List-watch机制进行每个组件的协作&#xff0c;保持数据同步。这种设计可以实现每个组件之间的解耦 kubectl配置文件&#xff0c;统一向集群内部apiserver发送命令——通过apiserver把命令发送到各个…