基于STM32F407的FreeRTOS学习笔记(9)——想要多少个定时器就要多少个就要多少个
在嵌入式编程中,定时器是一个非常重要且强大的功能,用来帮我们定时性的调用中断服务函数来帮助我们处理程序。定时器通常是用硬件来实现的,例如STM32F407就有8个硬件定时器。 而在FreeRTOS中则可以用软件实现定时器。大大的扩展了定时器的数量。 阅读FreeRTOS开发者文档我们可以知道,软件定时器的回调函数会在定时器服务函数中执行。 同时,软件定时器的回调函数中不能调用然后可以导致阻塞的函数例如vTaskDelay()等等,包括等待信号量的函数这些也会造成阻塞。 FreeRTOS会使用队列向定时器服务任务发送命令,这个队列就是定时器命令队列。 创建软件定时器 #FreeRTOS# 在API引用文档中,我们可以查询有关创建软件定时器创建的函数。内容有许多,但是还是总归是分三个步骤:包含相关头文件、启动相关的宏、配置软件定时器。 软件定时器配置时也有三个参数需要注意,一个定时器的定时时间,一个是设置定时器重复使用还是一次项,最后需要注意的是传入调用函数的句柄。 定义相关的回调函数与定时器句柄。Timer_Handler = xTimerCreate
( (const char * const) "xTimerCreate",
(const TickType_t) 500,//500ms一次
(const UBaseType_t) pdTRUE,//重复
(void * const) 1,//定时器ID
(TimerCallbackFunction_t) CallBacl );//中断服务函数句柄
接着我们定义一个轮询函数,当按键按下时,我们就开启定时器void Low_Task(void * pvParameters)//参数为 void * pvParameters
{
while(1)
{
if(KEY_Scan(0)==1)
{
xTimerStart(Timer_Handler,100);//开始定时器传入句柄以及最大等待时间
}
}
}
在回调函数中我们让LED灯进行翻转void CallBacl( TimerHandle_t xTimer)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_10);
}
同样的,在官方的API文档中我们还可以看到许多和软件定时器有关的内容,在这里不一一介绍,有感兴趣的小伙伴可以去官网查看API使用说明。
基于STM32F407的FreeRTOS学习笔记(8)——优先级反转问题以及如何解决(互斥信号量)
前面几期我们介绍过队列、二进制信号量以及计数信号量。但是在使用二进制信号量的时候会有一种优先级反转问题的出现,简而言之就是低优先级任务因为无法及时释放信号量而导致等待信号量发生的高优先级任务迟迟无法进行。 众所周知,FreeRTOS的各任务的运行顺序是由任务的优先级决定的,优先级高的任务比优先级低的任务先执行。 假设我们有三个任务:任务H,任务M,任务L,分别代表高优先级,中优先级以及低优先级。任务H和任务M同时被挂起,正在等待某一个事件的发生,同时任务H和任务L使用同样的全局资源(意味着当任务L正在占用全局资源时任务H的执行需要等待任务L执行完释放信号量)当任务L运行时占用了全局资源。任务H的事件被触发后由于其高优先级任务H获得CPU的使用权当任务H需要使用全局资源时,由于任务L还没有使用完全局资源,因此任务H被挂起等待任务L的信号量释放即使用函数xSemaphoreTake(SemaphoreHandler,portMAX_DELAY);等待信号量此时任务L恢复工作任务M的事件触发,此时任务L在等待任务M的结束,而然后任务H此时也在等待着任务M的任务结束。因此这段时间的任务M优先级高于任务H,这种现象就是优先级反转。等任务M执行完,任务L继续执行直到释放信号量,任务H得以继续运行。 因此使用信号量就会导致优先级 反转 的出现,打破原有的任务运行顺序,这在RTOS系统中应当是尽量避免的。 问题解决 为了解决二进制信号量可能带来的优先级反转现象,FreeRTOS中有一种特殊的二进制信号量——互斥信号量也就是互斥锁。 官方的开发者文档中介绍了互斥锁的存在,互斥锁实际上是一个包含了优先级继承机制的二进制信号量。任务在使用资源时则相当于手持一块令牌,其他没有这块令牌的任务无法就使用资源这就是所谓的互斥。当任务结束使用资源时,也就会返回所对应的令牌。 当两个任务使用相同的信号量时,为了避免前面介绍的优先级反转现象,于是优先级高的任务会把持有令牌的低优先级任务的优先级提升到和自己一样的情况,这样子就可以导致中间出现的中优先级任务抢占CPU资源,使得优先级出现反转。 相当于如图,将之后执行的低优先级任务的有限制强制抬高到与自己同一等级,颇有一种富家少爷为了吃葡萄而包下葡萄园的故事感。 这样子的做法可以有效地防止优先级反转现象的出现,也可以使已经出现的优先级反转得到更快的结束。 注意,互斥锁一定一定一定不能在中断中使用,因为中断无法使用延时函数来阻塞事件。 使用互斥锁 在FreeRTOS中有两种方法创建互斥锁,分别是动态创建和静态创建,我们主要介绍一下动态创建互斥锁。 API文档中关于互斥锁的内容很多,主要是介绍互斥锁以及说明使用互斥锁使用的一些细节。 信号量的释放和获取则和二进制信号量一样,参考二进制信号量文章 我们来测试一下互斥锁。 测试代码 我们需要三个不同优先级的任务,用来测试优先级反转的情况。 我们分别定义高优先级任务,中优先级任务以及低优先级任务。 启动相关宏定义。先测试二进制信号量void High_Task(void * pvParameters);//高优先级任务
void Mid_Task(void * pvParameters);//中优先级任务
void Low_Task(void * pvParameters);//低优先级任务
void Scan(void * pvParameters);
#define START_TASK_PRIO 1
TaskHandle_t High_Handler;
TaskHandle_t Start_LED_Handler;
TaskHandle_t Mid_Handler;
TaskHandle_t Low_Handler;
TaskHandle_t Scan_Handler;
xSemaphoreHandle TaskSemaphoer_Handler;
void Start_LED(void * pvParameters)
{
taskENTER_CRITICAL();
TaskSemaphoer_Handler = xSemaphoreCreateBinary();//创建二进制信号量
if(TaskSemaphoer_Handler!=NULL)
{
printf("Semaphore Create Successfully\r\n");
}
xTaskCreate((TaskFunction_t )High_Task,//任务函数
(char * )"v",//任务名称
(configSTACK_DEPTH_TYPE) 128,//堆栈空间128Byte
(void* ) NULL,//无返回
(UBaseType_t ) 2,//优先级2
(TaskHandle_t * )&High_Handler);//任务函数句柄
xTaskCreate((TaskFunction_t )Mid_Task,//任务函数
(char * )"Mid_Task",//任务名称
(configSTACK_DEPTH_TYPE) 128,//堆栈空间128Byte
(void* ) NULL,//无返回
(UBaseType_t ) 1,//优先级1
(TaskHandle_t * )&Mid_Handler);//任务函数句柄
xTaskCreate((TaskFunction_t )Low_Task,//任务函数
(char * )"Low_Task",//任务名称
(configSTACK_DEPTH_TYPE) 128,//堆栈空间128Byte
(void* ) NULL,//无返回
(UBaseType_t ) 0,//优先级0
(TaskHandle_t * )&Low_Handler);//任务函数句柄
vTaskSuspend(Mid_Handler);//挂起中优先级任务
vTaskSuspend(High_Handler);//挂起高优先级任务
taskEXIT_CRITICAL();
vTaskDelete(NULL);
}
void FreeRTOS_Init()
{
xTaskCreate((TaskFunction_t )Start_LED,
(char * )"starttask",
(configSTACK_DEPTH_TYPE) 128,
(void* ) NULL,
(UBaseType_t )START_TASK_PRIO,
(TaskHandle_t * )&Start_LED_Handler);
vTaskStartScheduler();
}
接着我们按照会出现优先级反转的情况编写测试代码。 首先挂起高优先级和中优先级任务。 低优先级任务持续打印运行信息,当运行到5次时,恢复高优先级任务持续打印信息,高优先级任务打印三次后等待低优先级任务发送信号量。 当低优先级任务再执行5次后高恢复中优先级任务,再次执行5次后发送信号量示意高优先级任务继续运行。 中优先级任务执行3次后挂起自身。void Low_Task(void * pvParameters)//参数为 void * pvParameters
{
int Low_number = 0;
while(1)
{
printf("Low_Task Runing 1111\r\n");
Low_number++;
if(Low_number == 5)
{
vTaskResume(High_Handler);//恢复高优先级
}
if(Low_number == 10)
{
vTaskResume(Mid_Handler);//恢复中优先级任务
}
if(Low_number == 15)
{
xSemaphoreGive(TaskSemaphoer_Handler);//释放信号量
}
}
}
void Mid_Task(void * pvParameters)//参数为 void * pvParameters
{
int Mid_number = 0;
while(1)
{
printf("Mid_Task Runing 2222\r\n");
Mid_number++;
if(Mid_number == 3)
{
vTaskSuspend(NULL);//挂起自身
}
}
}
void High_Task(void * pvParameters)
{
int High_number = 0;
while(1)
{
printf("High_Task Runing 3333\r\n");
High_number++;
if(High_number==3)
{
xSemaphoreTake(TaskSemaphoer_Handler,portMAX_DELAY);//等待低优先级任务释放信号量
}
}
}
可以看见和预想的一样,高优先级的任务被中优先级任务所挤兑。 之后我们把代码中的二进制信号量换成互斥锁。 可以看到,中优先级的任务根本没有办法实现优先级反转跳到高优先级去。 因此善于使用互斥锁,避免优先级反转现象的出现有利于FreeRTOS系统任务调度顺序的正确性,防止出现意外错误。
基于STM32F407的FreeRTOS学习笔记(7)——计数信号量
本期在二进制信号量的基础上介绍计数信号量什么是计数信号量 计数信号量顾名思义是用来计数的信号量,相比于二进制信号量,计数信号量的并不只有两种状态。用官方的开发者文档中的话来说,计数信号量可以看作长度大于1的队列,我们并不关心其中的内容而是关系队列是否为空。如何创建计数信号量 官方的参考文档中提供了两种创建方式(动态和静态)我们使用动态创建方式。调用xSemaphoreCreateCounting函数 其中包含了两个参数,一个是最大计数量还有一个是初始计数量。 创建一个SemaphoreHandler_t类型的句柄变量用以接收返回值。释放和获取信号量 释放和获取信号量和上一期二进制信号量的释放和获取方式一样。均是调用 xSemaphoreGive释放信号量以及调用 xSemaphoreTake获取信号量。 但是计数信号量则多了一个可以调用的函数。 调用这个函数我们就可以获得计数值啦。代码编写测试流程 我们做两个实验,首先是使用一个LED函数,函数每翻转一次就向计数信号量释放一次信号。 第二个函数轮询计数信号量,当计数信号量的数量比一半多时,使另一个LED也开始进行翻转并同样释放信号量。当计数信号量到达最大数时,关闭第二个灯的翻转。大体思路 第一个LED灯翻转,发送信号量。定义一个轮询函数用来时刻检测信号量状况,当信号量到达一定数量时恢复LED2任务的运行,当信号量满时清空信号量列表并挂起LED2 任务的挂起与恢复可以参考这期。代码编写 首先是任务启动函数,在这个函数中我们要创建一个计数信号量并且启动其他的相关任务函数。
void Start_LED(void * pvParameters)
{
taskENTER_CRITICAL();
LED_SemaphoreHandler = xSemaphoreCreateCounting(20,0);//最大计数20,初始0
if(LED_SemaphoreHandler!=NULL)
{
printf("Semaphore Create Successfully\r\n");
}
xTaskCreate((TaskFunction_t )LED_TOG,//任务函数
(char * )"LED_TOG",//任务名称
(configSTACK_DEPTH_TYPE) 128,//堆栈空间128Byte
(void* ) NULL,//无返回
(UBaseType_t ) 1,//优先级1
(TaskHandle_t * )&LED_TOG_Handler);//任务函数句柄
xTaskCreate((TaskFunction_t )LED_TOG2,//任务函数
(char * )"LED_TOG2",//任务名称
(configSTACK_DEPTH_TYPE) 128,//堆栈空间128Byte
(void* ) NULL,//无返回
(UBaseType_t ) 2,//优先级1
(TaskHandle_t * )&LED_TOG2_Handler);//任务函数句柄
xTaskCreate((TaskFunction_t )CountTest,//任务函数
(char * )"GetCount",//任务名称
(configSTACK_DEPTH_TYPE) 128,//堆栈空间128Byte
(void* ) NULL,//无返回
(UBaseType_t ) 0,//优先级1
(TaskHandle_t * )&GetCount_Handler);//任务函数句柄
taskEXIT_CRITICAL();
vTaskSuspend(LED_TOG2_Handler);
vTaskDelete(NULL);
}
其次LED函数的内容非常简单,检测信号量是否创建(指针不为空)如果指针不为空则翻转LED,并且释放信号量。(注意第二个LED的函数不释放信号量防止释放两次信号量)
void LED_TOG(void * pvParameters)//参数为 void * pvParameters
{
while(1)
{
if(LED_SemaphoreHandler!=NULL)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);
xSemaphoreGive(LED_SemaphoreHandler);
}
vTaskDelay(500);//延迟500ms
}
}
void LED_TOG2(void * pvParameters)//参数为 void * pvParameters
{
while(1)
{
if(LED_SemaphoreHandler!=NULL)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_10);
}
vTaskDelay(500);
}
}
在循环检测函数中,我们定义一个count来接收计数信号量的数量。接着当信号量大于10时我们恢复LED2函数的运行(可以多次恢复,只有一次效果)。 当信号量为20时,我们先暂停LED1函数的运行防止我们清空信号量的时候LED1又在释放信号量。之后通过不断的获取信号量来清空信号量,因为信号量本质就是队列,之后恢复他们的运行。
void CountTest(void * pvParameters)
{
while(1)
{
BaseType_t count;
if(LED_SemaphoreHandler!=NULL)
{
count = uxSemaphoreGetCount(LED_SemaphoreHandler);
if(count >= 9 )
{
vTaskResume(LED_TOG2_Handler);//恢复函数2
}
if(count >= 20 )
{
vTaskSuspend(LED_TOG_Handler);//先挂起函数1
while(count != 0)
{
xSemaphoreTake(LED_SemaphoreHandler,10);
count = uxSemaphoreGetCount(LED_SemaphoreHandler);
}
vTaskResume(LED_TOG_Handler);//恢复函数1
vTaskSuspend(LED_TOG2_Handler);//挂起函数2
}
}
vTaskDelay(10);
}
}
##freeRTOS#
干货超硬核,定位略模糊:嘉立创PCB实战指南新书测评
前言 前段时间嘉立创的工作人员向我介绍了他们发布的PCB新书:《从设计到量产:电子工程师PCB智造实战指南》 据嘉立创介绍这是他们十数年来的经验总结,旨在为减少我们在设计过程中由于经验问题而造成的“踩坑”。 博主收到书之后,最近也是仔细的读了一遍并且发表一下自己对这本书的看法。这本书讲了什么?总体上来说这本书的章节分布还是合理的,系统性地讲解PCB从材料选择、核心设计规则到关键生产工艺(钻孔、线路、阻焊、字符、外形、表面处理)的全链条知识。 该书总计13章节,第1章PCB常用软件介绍了PCB设计软件和PCB制造软件,为后续设计打下基础。第2章介绍决定PCB性能和成本的关键因素——基板材料。第3章概述了PCB生产的整个工艺流程和关键参数,让读者对制造环节有宏观认识。第4章~第8章深入剖析了PCB制造中几个最关键的工艺环节的设计要点和制造过程包括钻孔、线路、阻焊、字符标识和外形设计。第9章专门讨论了PCB焊盘表面处理(如喷锡、沉金、OSP、沉银等)的不同选择及其优缺点。第10章单独介绍了FPC的基础知识、生产和注意事项。第11章到第13章则是介绍了嘉立创EDA、嘉立创DFM以及嘉立创CAM的使用。 细节上来说,这本书确实称得上“干货满满”四个字,书中有大量的图、表信息以及非常非常多的专业术语,并且各章节内容几乎都是干货,没有太多的“废话”内容。 专业内容非常详尽,博主在阅读过程中确实收获了很多很多先前接触不到的知识。 在很多需要丰富设计经验支撑的地方,这本书会提供一些错误样例供我们参考,我认为演示错误示范是非常好的积累经验的方式(我们对做什么事情是错误的印象更深)同样的,这本书在绝大部分涉及到设计、制造类内容时,很贴心的附上了视频演示。这是非常大的一个加分项,相比于静态的图片,动态的视频更能让我们了解到PCB制造和设计中的细节知识。缺点和不足毫无疑问这本书的专业知识之硬核,实用性无疑是非常非常高的,但是博主也谈一下自己对这本书的另外一些批判性看法,谨代表个人观点。 其实我最开始听到这本书名字《从设计到量产:电子工程师PCB智造实战指南》的时候,想当然的认为它是类似于项目集,类似手把手教你立创EDA设计与制造PCB的全流程这种。但是收到书本并仔细阅读之后,我对这本书的定位更倾向于“教材”。 部分章节的练习题强化了该书的教材属性,其专业深度也足以胜任教材角色。这种双重特性带来不同阅读体验:作为工具指南,详尽内容反而模糊重点,通读全文略显枯燥;作为专业教材,口语化表达则带有明显的实用指南特征,这带来了一种矛盾感。 而且最重要的是,我认为无论从哪个角度它都缺少了一个压轴菜:实战Demo/课程设计,虽然书名是实战指南,但是更多的实战知识都是分布式的,作为一名读者很期待有一个手把手设计制作的教学。总结 总的来说这本书还是非常不错的,专业知识丰富,我虽然不是专业的PCB工程师,但是仍旧有非常多的收获,而且在阅读过程中也从中看到了自己以前犯的很多错误的影子例如板子的加热温度、PCB过孔规则(单面孔怎么画)等等,同时关于这些知识尤其是PCB制造方面的相关,它也是我遇到的第一本成系统性的去介绍各个工艺的书,涵盖了几乎我能想到和想不到的所有范畴。 我记得大二期间有一门选修课叫:PCB设计,使用的是AD来进行设计了一个NE555的流水灯。实操PCB设计但是缺没有专门的教材来介绍PCB的工艺流程、设计步骤和要点等,可以说这门课相当粗糙,而这本书就很适合作为这类课的教材辅助使用。 因此嘉立创这本书我还是非常推荐对此方面感兴趣和没有系统培训的工程师来阅读一遍以增加工创经验和阅历的,即便目前感觉此书还是有些许瑕疵和需要改进的地方,但是仍旧是能够收获很多实用的知识的。#嘉立创PCB#读起来怪怪的?
基于STM32F407的FreeRTOS学习笔记(6)——二进制信号量(别在傻乎乎的使用flag变量
信号量( Semaphore)也被称为信号灯。有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量(来自百度百科) 简而言之,信号量就是在全局中表示共享资源状态的量。例如一个停车场,其中的车位就是共享资源。每当有车辆进进出出的时候,门口门卫总会统计出入车辆的数量,这就是信号量,我们可以通过信号量来获公共资源的信息(空余车位、已用车位) 而二进制信号量顾名思义只有0和1,例如电话亭的使用情况,当有人的时候其他人就无法使用电话亭。只有当电话亭空余的时候才能使用电话亭,而电话亭的使用状态则是二进制信号,电话亭本身则是共享资源。 在原本的裸机开发中我们通常会使用大量的标记符号并且在main函数中不断轮询该标记,这样子代码的逻辑就会异常复杂,而二进制信号量则可以代替这样子的作用,当任务在继续时二值信号量返回0,任务空闲时二进制信号量返回1,可以完美的替代如下这些标志变量。 在FreeRTOS中我们通常也会使用一个任务来专门轮询信号量,获得信号量的状态,实现信号量的同步。 除此之外我们的程序通常会有一个公共缓存区作为共享资源,每一个资源都可以使用公共缓存区的数据,即可以从中读取数据也可以写入数据。这个公共缓存区就像是停车场,车位有限,而我们则是根据信号量来控制这个停车场是否能够继续停下车辆。 在FreeRTOS的介绍中我们可以看到,而二进制信号量的可以看作只有一个项目的队列,用队列的空和满来代表信息。 导入我们关于信号量的头文件“semphr.h” 关于二进制信号量的API文档中,创建一个二进制信号量首先需要将相对应的宏,即configSUPPORT_DYNAMIC_ALLOCATION打开,接着创建一个SemaphoreHandle_t 的信号量句柄来接收该创建函数的返回值。
LED_SemaphoreHandler = xSemaphoreCreateBinary();
if(LED_SemaphoreHandler!=NULL)
{
printf("Semaphore Create Successfully\r\n");
}
接着我们在启动函数中写上该函数。这样子我们编译并烧录进我们的单片机。
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif
跳转之后发现,其实这个函数就是创建一个项目大小为1 的队列,因此二进制信号量的本质就是队列。 在文档中找到获取信号量的函数,分别是xSemaphoreTake和xSemaphoreTakeFromISR,从名字中我们可以知道这两个函数分别是在普通函数与中断函数中获取信息量的。 可以看到,用法还是非常的简单,返回值是pdTRUE和pdFALSE,是用来判断信号量是否有用,即队列是否有空余。其中的参数xTicksToWait则是用来设置等待时间,在等待时间内阻塞以试图获得信号量。 最后我们看看释放信号量的函数 这个释放信号量,并不是说释放空间那种表示删除的意思,而是如最后表达的那样,发布信号量。简而言之其实也就是向队列中的项目发布数据。 所以正确的流程是:创建信号量,轮询检测信号量是否释放,释放信号量。代码检验 ##freeRTOS# 接下来检验一下我们的信号量。 我们先创建一个任务,轮询信号量并且一直等待信号量是否释放。如果检测到信号量则翻转LEDBaseType_t err;
while(1)
{
if(LED_SemaphoreHandler!=NULL)
{
err = xSemaphoreTake(LED_SemaphoreHandler,portMAX_DELAY);//一直等待信号量
if(err == pdTRUE)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_10);//LED翻转
}
else
{
printf("No Semaphore\r\n");
}
}
vTaskDelay(10);
}
接着编写按钮函数,如果按下按钮则释放一个信号量。 if(key==2)
{
if(LED_SemaphoreHandler!=NULL)
{
err = xSemaphoreGive(LED_SemaphoreHandler);
printf("Give Semaphore Success\r\n");
}
else
{
printf("Give Semaphore Fail\r\n");
}
}
基于STM32F407的FreeRTOS学习笔记(4)——获取各任务运行时间及占用情况
前言 CPU工作的时候,各个任务运行会占用CPU的资源,在Windows系统中我们可以通过任务管理器来看各任务(进程)占用系统资源的情况。 那么,FreeRTOS怎么实现这个功能呢? 我们翻阅FreeRTOS官网,查询API文档,在内核控制函数部分找到了相关的函数。 文档指出实现运行时间功能需要配置外设定时器,即32板载定时器,计时器频率应为滴答计时器(1ms)的至少10倍。 传入参数为pcWriteBUffer,其实是一个char类型的数组用以存储相关信息。代码实例 我们现在工程上调用这个函数。char informationbuff[400];
void Get_info(void * pvParameters)
{
//vTaskGetRunTimeStats(informationbuff);
while(1)
{
if(KEY_Scan(0)==1)//按下按键1
{
memset(informationbuff,0,400);//清空数组内容
vTaskGetRunTimeStats(informationbuff);//获得运行时间
printf("%s\r\n",informationbuff);//打印运行时间
}
vTaskDelay(10);
}
}
上述任务的作用为检测按键,如果按键按下即尝试获得运行状态,并打印运行状态。 出现了如下错误,显示我们未定义该函数,我们利用Ctrl+F全局寻找这个函数定义在哪里。 FreeRTOS\FreeRTOS.axf: Error: L6218E: Undefined symbol vTaskGetRunTimeStats (referred from main.o).
F:\Code\STM32Code\STM32F407_FreeRtos\FreeRTOS\FreeRTOS\Source\tasks.c(4539) : void vTaskGetRunTimeStats( char * pcWriteBuffer )
F:\Code\STM32Code\STM32F407_FreeRtos\FreeRTOS\FreeRTOS\Source\tasks.c(4552) : * vTaskGetRunTimeStats() calls uxTaskGetSystemState(), then formats part
F:\Code\STM32Code\STM32F407_FreeRtos\FreeRTOS\FreeRTOS\Source\tasks.c(4557) : * vTaskGetRunTimeStats() has a dependency on the sprintf() C library
F:\Code\STM32Code\STM32F407_FreeRtos\FreeRTOS\FreeRTOS\Source\tasks.c(4567) : * through a call to vTaskGetRunTimeStats().
第一行内容,即为函数定义的位置,我们跳转过去查看其情况。#if ( ( configGENERATE_RUN_TIME_STATS == 1 ) && ( configUSE_STATS_FORMATTING_FUNCTIONS > 0 ) && ( configUSE_TRACE_FACILITY == 1 ) )
void vTaskGetRunTimeStats( char * pcWriteBuffer )
{
TaskStatus_t * pxTaskStatusArray;
UBaseType_t uxArraySize, x;
configRUN_TIME_COUNTER_TYPE ulTotalTime, ulStatsAsPercentage;
省略后续内容(防止说水字数)
我们看到了函数模型以及相关注释,从头中我们可以看出需要相关的宏定义,分别是configGENERATE_RUN_TIME_STATS、configUSE_STATS_FORMATTING_FUNCTIONS、configUSE_TRACE_FACILITY。 我们在FreeRTOSconfig.h文件(头文件都行,方便管理)中添加使能这三个宏。 再次运行,依旧报错,从报错内容来看,提醒我们如果将 configGE NERATE_RUN_TIME _STATS使能的话,我们也必须定义portCONFIGURE_TIMER_FOR_RUN_TIME_STATS这个启动函数,以及后面的一条报错,我们必须定义portGET_RUN_TIME_COUNTER_VALUE时间的返回值。
#ifndef portCONFIGURE_TIMER_FOR_RUN_TIME_STATS
(import) #error If configGENERATE_RUN_TIME_STATS is defined then portCONFIGURE_TIMER_FOR_RUN_TIME_STATS must also be defined. portCONFIGURE_TIMER_FOR_RUN_TIME_STATS should call a port layer function to setup a peripheral timer/counter that can then be used as the run time counter time base.
#endif /* portCONFIGURE_TIMER_FOR_RUN_TIME_STATS */
#ifndef portGET_RUN_TIME_COUNTER_VALUE
#ifndef portALT_GET_RUN_TIME_COUNTER_VALUE
(import) #error If configGENERATE_RUN_TIME_STATS is defined then either portGET_RUN_TIME_COUNTER_VALUE or portALT_GET_RUN_TIME_COUNTER_VALUE must also be defined. See the examples provided and the FreeRTOS web site for more information.
#endif /* portALT_GET_RUN_TIME_COUNTER_VALUE */
#endif /* portGET_RUN_TIME_COUNTER_VALUE *
启动函数即为外部定时器启动函数,返回值则是一个数用以计算时间。#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() configTIM_START()//定时器1提供时间统计的时基,频率为10K,即周期为100us
#define portGET_RUN_TIME_COUNTER_VALUE() FreeRTOSRunTime//时基
extern volatile unsigned long long FreeRTOSRunTime;
我们定义这两个宏,本来这个括号是没加上去的,后来发现其调用的时候是代括号的,所以定义宏的时候不带括号就会出错 此外我们定义了long long 类型的变量用以存储我们的时间,加上extern表示这个变量的实际定义并不在头文件中,之所以加上volatile是因为我们的变量会在不同的文件以及中断中被修改(这种修改属于意外修改),加上volatile标志给系统提前吱会一声。 之后,我们去CUBEMX启动我们的定时器。 定时器我们选择定时器1,时钟源选择内部时钟,分频系数由于我们的单片机主频是168MHZ,因此我们选择168分频,这样子定时器频率即为1MHZ,溢出值我们选择为50,通过这样的设置我们定时器的频率就是20KHZ,是滴答定时器时钟的20倍。 FreeRTOSRunTime也可以定义在这里。 之后我们将刚才宏定义的启动函数进行定义,内容则是重置计数器并启动定时器。 完成这步之后,我们还需要在主函数中启用定时器1 的中断并且编写相应的中断服务函数,其内容为FreeRTOSRunTime递增。
/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == htim1.Instance)
{
FreeRTOSRunTime++;
}
}
/* USER CODE END 4 */
之后我们运行程序观察串口的输出。 这样子我们就可以打印出各个程序运行时间以及占用系统资源的占比了。 ##freeRTOS#
基于STM32F407的FreeRTOS学习笔记(2)——任务的创建与删除
上一期配置完FreeRTOS的环境后,这一期记录自己关于任务创建的学习过程。 官方的API手册中有这些函数,xTaskCreate和xTaskCreateStatic分别是利用动态方法和静态方法创建任务。(动态和静态的区别之后再研究)vTaskDelete是删除任务,因为freeRTOS的任务内存空间存储在堆区,所以很像C语言的动态内存分配,任务使用和结束我们都应该创建和删除这些任务防止占用过多空间。 xTaskCreate的函数模型如下,参数内容总共有六项:任务函数的函数指针,任务函数的名称,任务函数所需堆栈空间,任务函数的类型,任务函数的优先级,以及任务函数的函数句柄 vTaskDelete的函数模型如下,参数内容为函数句柄,如果为NULL则删除该任务本身。 因此我们创建任务的步骤是:首先定义一个启动任务,该任务是为了启动我们的真正任务,因此在调用完一遍后要用vTaskDelete 中输入NULL删除启动函数本身。任务函数编写/*
LED1翻转
*/
void LED_TOG(void * pvParameters)//参数为 void * pvParameters
{
while(1)
{
printf("LED_TOG running\r\n");//串口打印运行信息
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_10);//LED1翻转
vTaskDelay(500);//延迟500ms
}
}
要注意的是vTaskDelay是FreeRTOS用来延时的函数。 之后我们要创建任务函数的启动函数
TaskHandle_t Start_LED_Handler;
void Start_LED(void * pvParameters)
{
xTaskCreate((TaskFunction_t )LED_TOG,//任务函数
(char * )"LED_TOG",//任务名称
(configSTACK_DEPTH_TYPE) 128,//堆栈空间128Byte
(void* ) NULL,//无返回
(UBaseType_t ) 1,//优先级1
(TaskHandle_t * )LED_TOG_Handler);//任务函数句柄
vTaskDelete(NULL);
}
我们创建启动任务的函数,将任务函数的函数指针,任务函数的名称,任务函数所需堆栈空间,任务函数的类型,任务函数的优先级,以及任务函数的函数句柄,填入vTaskCreate函数中,其中每个参数都使用了强制类型转换防止出现错误。 同样的方法,我们创建启动 启动函数的任务(有点绕口因为启动函数本身是一个任务)
void FreeRTOS_Init()
{
xTaskCreate((TaskFunction_t )Start_LED,
(char * )"Start_LED",
(configSTACK_DEPTH_TYPE) 128,
(void* ) NULL,
(UBaseType_t ) 0,
(TaskHandle_t * )Start_LED_Handler);
vTaskStartScheduler();//启动运行函数
}
这样子我们在主函数中添加刚刚定义的启动启动函数int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
KEY_Init();
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
FreeRTOS_Init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
用上述的方法实现两个灯一起翻转 ##FreeRTOS# ##FreeRTOS# #FreeRTOS# 还是先编辑任务函数如下,并且定义其相关句柄TaskHandle_t LED_TOG2_Handler;
void LED_TOG2(void * pvParameters)//参数为 void * pvParameters
{
while(1)
{
printf("LED_TOG running\r\n");//串口打印运行信息
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);//LED0翻转
vTaskDelay(500);//延迟500ms
}
}
在任务启动函数中加入我们新建的任务。
void Start_LED(void * pvParameters)
{
xTaskCreate((TaskFunction_t )LED_TOG,//任务函数
(char * )"LED_TOG",//任务名称
(configSTACK_DEPTH_TYPE) 128,//堆栈空间128Byte
(void* ) NULL,//无返回
(UBaseType_t ) 1,//优先级1
(TaskHandle_t * )LED_TOG_Handler);//任务函数句柄
xTaskCreate((TaskFunction_t )LED_TOG2,//任务函数
(char * )"LED_TOG2",//任务名称
(configSTACK_DEPTH_TYPE) 128,//堆栈空间128Byte
(void* ) NULL,//无返回
(UBaseType_t ) 2,//优先级2
(TaskHandle_t * )LED_TOG2_Handler);//任务函数句柄
vTaskDelete(NULL);
}
基于STM32F407的FreeRTOS学习笔记(1)——环境搭建
其实从很早之前就想学实时操作系统(RTOS)了,但是一直没有时间去学,以前使用STM32单片机一直停留在逻辑开发以及前后台系统,而真正被广泛使用的则是RTOS。 前后台系统则是我们常用的,使用一个主循环+许多的调用函数这些构成了后系统,利用中断进行异常处理则是前系统,而RTOS则是将任务按照优先级排列,优先进行优先级高的任务例如单片机内如的中断服务函数。 操作更加的灵活方便,也是嵌入式软件工程师必备的技能之一,而FreeRTOS顾名思义,开源且免费,是我们小白入门的不二之选。安装FreeRTOS 某度上搜索FreeRTOS,点入FreeRTOS的官网,进入下载第一个安装包(包含源码) 下载好源码后,我们利用CUBEMX创建我们的工程,我选择的是STM32F407ZGT6这块芯片,之后将我们的下载的RTOS中的Source文件夹拷贝到我们利用CUBEMX创建的工程目录中。 其中的portable文件夹中只需保留以下文件即可,在工程中,我们将这些文件夹的内容全部添加到工程内( MemMang中的文件是实施方法,众多的heap文件我们只需要添加一个即可 ),并从之前FreeRTOS的源码中找到Demo文件夹,找到我们对应的单片机的文件夹。将FreeRTOSConfig.h文件即配置文件拷贝入我们的工程目录,这样子即可完成工程模板的创建。 之后我们编译工程,会发现有关于系统时钟的报错,没有SystemCoreClock的定义。 我们在FreeRTOSConifg.h文件中找到44行,发现这个定义是并不是在MDK这个平台使用的,我们将其进行修改,使之在MDK平台适用 我们去这些文件中挨个注释掉这些重复的函数。(我上述是添加了好几个heap文件,实际上添加一个heap文件即可) 之后我们又遇到了几个报错,这些报错主要是在FreeRTOSConfig.h中我们使能了几个构造函数,但是并没有定义这些构造函数,因此我们需要在FreeRTOSConfig.h中关闭这些构造函数 将对应的构造函数值改为0即可关闭这些构造函数。 这样子我们的编译就没问题啦
基于STM32F407的FreeRTOS学习笔记(3)——任务的挂起与恢复
上一期学习了任务的创建和删除,这一期学习任务的挂起与恢复。 所谓的挂起,也可以认为是 暂停 ,将运行中的任务挂起后,任务将暂停运行,直至系统恢复任务的运行。 在FreeRTOS的API文档中找到任务挂起函数的介绍,函数需要的参数为我们想要挂起的任务句柄,如果传递为NULL则暂停我们的调用任务。 同样的,在文档中也可以找到恢复任务函数介绍。 接下来我们实现一个任务,目标是当LED1闪烁5次后挂起LED0闪烁的任务,当LED1再闪烁5次后恢复LED0闪烁的任务。 我们在API中找到查询任务状态的函数eTaskGetState,该函数传入参数为任务句柄,返回参数为任务状态。 LED0在进行vTaskDelay时是处于阻塞态,因此我们只需要判断LED0是阻塞状态还是挂起状态,再进行挂起和恢复操作。 因此我们的代码如下void LED_TOG2(void * pvParameters)//参数为 void * pvParameters
{
while(1)
{
printf("LED_TOG2 running\r\n");//串口打印运行信息
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_9);//LED0翻转
LED2_Number++;//LED0翻转计数
if(LED2_Number %10==0)
{
if(eTaskGetState(LED_TOG_Handler) == eSuspended)
{
vTaskResume(LED_TOG_Handler);//LED0任务恢复
}
if(eTaskGetState(LED_TOG_Handler) == eBlocked )
{
vTaskSuspend(LED_TOG_Handler);//LED0任务挂起
}
}
vTaskDelay(500);//延迟500ms
}
}
#FreeRTOS#
基于STM32F407的FreeRTOS学习笔记(5)——消息队列(任务间通信与同步)
前言在数据结构中有一种很重要的数据结构叫做队列,其特点是数据先进先出。在FreeRTOS中也有一类队列,我们利用这类队列在FreeRTOS中实现任务与任务间的消息传递,所以也可以称之为消息队列。 队列是任务间通信的主要形式。它们可以用于在任务之间以及中断和任务之间发送消息。在大多数情况下,它们作为线程安全的 FIFO(先进先出)缓冲区使用,新数据被发送到队列的后面, 尽管数据也可以发送到前面。(拷贝自FreeRTOS开发者文档) 队列通过这样子的结构在任务间单方向传递消息。 在FreeRTOS的API引用文档中我们可以看到队列的控制函数。 我们在文档中找到队列创建函数(动态) 首先我们需要在程序中包含入queue.h文件才能使用队列。 其次和之前几期的操作一样,我们需要在FreeRTOSConfig.h文件中需要配置相对应的宏以激活该创建队列的构造函数。 xQueueCreate的参数有两个,首先是uxQueueLength队列可同时容纳的最大项目数简而言之也就是:这个队列有多长。 其次是uxItemSize,顾名思义是每一个项目(每个小块块)能存储多少数据(字节) 最后要强调的是,这个函数的返回值是QueueHandle_t,即以句柄的形式返回,因此我们创建任务的时候也需要以句柄变量接收其返回值。代码测试我们在启动函数中加入我们创建队列的函数,其长度为1,每个项目的大小为一个字节。 我们接着在API引用文档中找到关于队列发送的函数(如上)。 发送的函数平平无奇,但是有几点需要注意; 首先是发布项目按副本排队而不是引用指针,指的是我们传入的数据是先拷贝的临时变量传入,而并非我们传入数据的地址,我想这样子是为了避免在接收端时对数据进行修改导致错误。 其次是该函数不能在中断函数中调用(有专门的函数是在中断中发布项目的) 函数参数中的第三项xTicksWait简单的理解就是可等待的最大时间,我们如果我们的队列已满则尝试等待,超过一定周期认为超时则项目发布失败。 我们在按钮检测任务中编写:按下按钮2时向队列中放入字符p(p初始值为‘a’),每按下这个按钮,p的值递增。 还有一个按钮3,按下按钮3则在队列中读取一则消息,并打印出来。void Get_info(void * pvParameters)
{
unsigned char p = 'a';
unsigned char r;
while(1)
{
unsigned char key = KEY_Scan(0);
BaseType_t err;
if(key==1)
{
memset(informationbuff,0,400);
vTaskGetRunTimeStats(informationbuff);
printf("%s\r\n",informationbuff);
}
if(key==2)
{
printf("Key_2 Press\r\n");
if(KeyNumberHandler!=NULL)//队列句柄有效
{
err = xQueueSend(KeyNumberHandler,&p,10);
p++;
if(err!=pdTRUE)
{
printf("Send Fail \r\n");
}
else
{
printf("Send %c Success\r\n",p);
}
}
}
if(key==3)
{
printf("Key 3 Press\r\n");
if(KeyNumberHandler!=NULL)//队列句柄有效
{
xQueueReceive(KeyNumberHandler,&r,10);
printf("Queue Receive:%c \r\n",r);
r = '\0';//清空
}
}
vTaskDelay(10);
}
}
我们在按钮检测任务中加入按钮2和按钮3的情况,并且定义了一个变量err来检测我们的队列是否添加成功,我们观察串口并看看打印情况。 可以看到,我们按下按钮2,成功将 ‘b’ 消息送入队列(我们是先p++再送入队列的) 我们再继续按下按钮2,由于队列的长度为1,且队列的项目并没有出队列,因此串口会提示送入队列失败。 此时p等于 ' c ',我们按下按钮3,让数据出队列,并且再按下一次按钮3读取是否有数据。 可以看到,读取队列之后队列的内容将被释放,后续的内容将前进。之后我们再按下按钮2 ,此时就可以向队列中送入数据(我多按了一下)。 除此之外,FreeRTOS中还有一个函数为xQueueOverwrite,传入参数除了没有阻塞时间之外和xQueueSend一样,它的作用为将消息送入队列,如果没有空间则覆盖最后一个空间,我们将按钮2中的函数换为该函数再试试。 ##freeRTOS# 我们每次送入队列都成功,因为它会把队列的最后一个项目覆盖掉。 关于队列的介绍就到此啦,具体的API可以上FreeRTOS的官网查看参考文档。
基于STM32的甲醛测量仪以及了解盘中孔工艺
这段时间很久没更新了哈,这几天在完善我的甲醛测量仪。其实对于多功能测量仪来说也只是半成品,因为很多东西我还没做上去包括光照测量(芯片和模块都忘记买了)还有红外测量等等系列。 目前的完成度是:TVOC/CH2O/CO2这些气体的测量,温度(NTC电阻测量),简单的GUI设计,锂电池和TypeC混合供电系统。 当然啦,这个只是其中完成的一部分,实际上可以进行很多拓展内容,拟拓展如下: 外接MAX30100模块可以测量心率以及血氧数据,这个模块也是之前经常使用的模块,可以选择外接接口也是方便选择使用。 MXL90614红外测温模块,使用I2C协议通讯,较为方便准确的获取环境温度和红外温度。 SHT30温湿度传感器,可能主要的目的是用来替代NTC电阻获取温度以及获取湿度数据,当然他也是利用I2C进行通讯的,可能到时候会选择集成到电路上。 除此之外也会选择加上一些实时时钟以及试图使用ESP32来作为主控或者使用ESP32来辅助云端通讯。不过这些都是后话。盘中孔 因为需要对其做许多拓展,之后电路可能会较为麻烦,所以针对甲醛测量仪我设计了一个6层的电路板。没有选择4层是因为在之后升级迭代过程中,可以直接在6层板的基础上更新,减少之后需要重新布线的烦恼。 当然设计6层板的目的不仅仅是为了更方便布线,更可以进一步的缩小PCB体积。相比于传统的4层板,在紧凑型电子电路中,6层板拥有更小的干扰和更优越的性能。 在设计的时候由于大部分都是贴片设计,不可避免的需要使用过孔进行布线,实现不同电气层的跨越,但是之前就有网友指出了我在布线中的不规范行为,在焊盘上直接打孔。 这会导致在焊接过程中,锡会从焊盘上的孔中直接流下导致脱焊、虚焊。这种情况就可能会造成电路出现问题,而且很难察觉,那么如何去解决这个问题呢?有些朋友建议我去了解一下盘中孔工艺。普通的过孔 之后我去查阅了一些盘中孔工艺的相关资料,大概就是在打过孔的时候利用树脂填满过孔,再用铜电镀封孔,这样子锡就不会从孔中流下去导致虚焊,表面也看不出痕迹。 同时在盘中打孔可以大幅度的减少PCB布局面积,在复杂的贴片芯片中可以很大程度上的方便布线的同时避免因为锡的流失而导致虚焊。 了解完工艺之后就准备打板、就近先看嘉立创下单小助手,居然有这项工艺、还免费!二话不说就先下单了。 当我收到六层板仔细查看的时候,可以看到使用盘中孔确实是会将过孔封住填平,并且免费2u沉金工艺的PCB真的很帅,嘉立创很赞! 所以大家在硬件设计过程中也需要去了解一下设计规范以及一些特殊工艺,防止在使用的过程中遇到很多问题而没有解决方向。 关于这个甲醛测量仪,由于没有专业的设备进行校准,因此获得的其实是一个参考值,具体精确的测量可能需要专业的设备来整定,不过这个设计之后还是会趋向集成化以及在此基础上堆上更多的功能。其实这个锂电池混动供电之前的电路不知道为什么老是出毛病,但是这个也是一次成功,有朋友留言说这种供电方式会导致测量电池电压的时候很不准确,关于这个现象我之后去仔细研究一下如何解决。 还有就是选择的电池似乎有点太大了,我开机了一晚上都还是正常工作,下次选择电池容量小一点的,这么大也没有必要。#嘉立创6层板##嘉立创PCB# #高多层PCB#