news 2026/7/18 3:30:44

b站铁头山羊freertos入门篇学习1

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
b站铁头山羊freertos入门篇学习1

FreeRTOS英文名为 real_timeOS,TSOS为分时操作系统(windos系统)

详细见:FreeRTOS官方网站地址: www.freertos.org/

如果我们要在开发版编写以下程序,让LED1每次点亮100ms、熄灭100ms,同时让LED3每次点亮50ms、熄灭150ms。

我们注意到这两个LED的亮灭周期都为200ms,程序相对来说比较好设计

文件:main.c int main(void) { ....... while (1) { // 1. 同时点亮LED1和LED3,并延迟50ms HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_SET); HAL_Delay(50); // 2. 熄灭LED3,并再次延迟50ms HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_RESET); HAL_Delay(50); // 3. 熄灭LED1,并延迟100ms HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); HAL_Delay(100); } }

那如果变复杂一点,比如说周期不一样,那么就需要用到状态机的思想。

LED1仍然点亮100ms、熄灭100ms,但LED3的情况有所变化,这次它亮灯30ms、灭灯150ms。

首先需要画出状态转换图:接着写代码的思路应该为:需要获取当前时间,设置好状态变量。代码见下

文件:main.c uint8_t led1State = 0; // LED1的状态:0 - 熄灭,1 - 点亮 uint8_t led3State = 0; // LED3的状态:0 - 熄灭,1 - 点亮 uint32_t led1LastToggleTime = 0; // LED1上次翻转的时间 uint32_t led3LastToggleTime = 0; // LED3上次翻转的时间 // @作用:LED1的状态机 void LED1_Proc(void) { uint32_t now = HAL_GetTick(); // 获取当前时间 switch(led1State) { case 0: // 当前处于熄灭状态 if(HAL_GetTick() - led1LastToggleTime > 100) // 超过100ms后点亮 { HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); led1State = 1; // 状态机切换到点亮状态 led1LastToggleTime = now; } break; case 1: // 当前处于点亮状态 if(HAL_GetTick() - led1LastToggleTime > 100) // 超过100ms后熄灭 { HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); led1State = 0; // 状态机切换到熄灭状态 led1LastToggleTime = now; } break; default:break; } } // @作用:LED3的状态机 void LED3_Proc(void) { uint32_t now = HAL_GetTick(); // 获取当前时间 switch(led3State) { case 0: // 当前处于熄灭状态 if(HAL_GetTick() - led3LastToggleTime > 150) // 超过150ms后点亮 { HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_SET); led3State = 1; // 状态机切换到点亮状态 led3LastToggleTime = now; } break; case 1: // 当前处于点亮状态 if(HAL_GetTick() - led3LastToggleTime > 30) // 超过30ms后熄灭 { HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_RESET); led3State = 0; // 状态机切换到熄灭状态 led3LastToggleTime = now; } break; default:break; } } int main(void) { ......... while (1) { LED1_Proc(); // 循环运行LED1的状态机 LED3_Proc(); // 循环运行LED3的状态机 } }

我们发现这么简单的任务,需要的代码比较复杂,需要编写状态机,所以为了简化流程,我们需要学习FreeRTOS。

怎么用freertos创建任务呢,下面是简单任务的创建代码,

// 任务函数的一般写法 // 任务函数使用void类型的返回值,但是有一个void*类型的参数 void vTask1(void *pvParameters) { // 编程方式类似于main()方法,一般写一个无限循环 for(;;) { // 使用vTaskDelay()而不使用HAL_Delay()进行延迟 } // 坚决不能写return! vTaskDelete(NULL); //一旦退出需删除自身 }

主函数的写法:

int main(void) { //一些底层的初始化代码放这里 ......... //先创建任务 xTaskCreate(vTask1, "Task1", .....); //任务1 xTaskCreate(vTask2, "Task2", .....); //任务2 //再启动调度器 vTaskStartScheduler(); //无限循环 while (1) //代码不会执行到这里,除非因创建空闲任务或者定时器任务失败而退出 { } }

接下来我们来了解freertos的总图

由于框图我们可以看出,核心是调度器,不一样的调度算法决定了任务之间如何切换以及什么时间点切换,接着看到任务这一栏,空闲任务和定时器任务是两个特殊的任务(这两个任务必须被创建创建失败则启动rtos失败),定时器任务我们后面在看,先来了解空闲任务,看到以下这段代码,

文件:main.c // @作用LED1的任务函数 void vLED1Task(void *pvParameters) { while(1) { HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET); // 点亮 vTaskDelay(pdMS_TO_TICKS(100)); // 延迟100ms HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET); // 熄灭 vTaskDelay(pdMS_TO_TICKS(100)); // 延迟100ms } } int main(void) { xTaskCreate(vLED1Task, "LED1", 128, NULL, 1, NULL); // 创建LED1任务 while (1) { } }

我们用的是vTaskDelay()这个函数,任务调用该函数后,系统内核将该任务从“就绪列表”移动到“阻塞列表”,并立即触发任务调度器,这个时候cpu执行空闲任务。而我们熟知的HAL_Delay(),它让 CPU 依然处于运行态。它虽然在“空转”,但并没有停止工作。

接下来看三个中断:在调度器的上方也有3个中断:Systick中断、PendSV中断和SVC中断。这3个中断大家可能没见过,它们比较特殊,都是跟内核相关的,所以大家见得不多。这里先跟大家说一下它们的地位:这3种中断是FreeRTOS调度器赖以生存的底层硬件,驱动调度器运行的原动力。其中Systick中断用于产生系统节拍(计时用的),并驱动调度器的“时间片机制”(刚刚讲过,跟调度算法有关);SVC中断用来启动第一个任务;PendSV中断用来执行后续的任务切换

2.2内核的文件结构

文件的分类

在kernal这一栏,task和list必须添加,其他的根据需要,我们需要知道每个文件的中文名,portable为移植层,移植层为FreeRTOS内核和某款单片机之间的媒介

config文件里是各种宏定义,介绍几个常见的配置项

配置项1:configCPU_CLOCK_HZ - 内核的运行频率(告诉FreeRTOS你的单片机的内核频率)

配置项2:configTICK_TYPE_WIDTH_IN_BITS - 设置TickType_t类型的位宽

配置项3:configKERNEL_INTERRUPT_PRIORITY - PendSV和SYSTICK的中断优先级(它们分别是SVC中断、PendSV中断和SYSTICK中断。SVC中断默认设置最高优先级,剩下的两个中断的优先级由该配置项设置。)

下面需要进行中断服务程序的配置

3.3回顾c语言基础

熟悉类型的变量,以及变量的作用域和生命周期,还有加上static后对全局变量和局部变量的影响

SRAM被划分为不同的区域:(SRAM的起始地址是0x2000 0000,长度为48k字节)

堆:

使用malloc()函数时,数据会被存储到堆中,而需要的空间为:数据的大小+信息头(8个字节).当我们调用xTaskCreate()创建一个任务的时候,在xTaskCreate()内部会自动调用pvPortMalloc()为新创建的任务在堆里分配一段内存,用来保存这个新任务的关键数据。其中,pvPortMalloc()是FreeRTOS里用来管理堆内存的编程接口,它的功能和标准C语言里的malloc()相似。

栈:每次调用时需要保存上下文,以便返回时恢复。这里所说的“上下文”包括参数、局部变量和返回地址

在freertos中,每个任务都有自己栈,因为在freertos中,任务是"并行的",需要在各个任务之间来回切换。

编写代码时的规范:

特别注意:xTaskGetTickCount(void);返回值为系统节拍数,如果需要的是时间,需要在使用pdTICKS_TO_MS()宏将获取到的系统节拍转化为毫秒级的时间。

变量的命名规则:类型前缀”和“实际名称”两部分组成,举个例子

函数的命名规则:当函数前边有static关键字时(静态方法),前缀应该替换为prv(prv = private = 私有的,表示这个方法是私有的,作用域在文件内部)。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/18 3:30:38

DBT查询注释:结构化调试基础设施实践

1. 项目概述:为什么在 DBT 模型 SQL 中加查询注释不是“多此一举”,而是调试效率的分水岭在 DBT(Data Build Tool)项目里,我见过太多团队把-- 这是用户表这类注释当成“写完就删”的临时标记,也见过有人把整…

作者头像 李华
网站建设 2026/7/18 3:28:56

Python 字符串模块方法全解析(附带注释与案例)

1. 字符串基础操作Python 的字符串(str)是不可变序列,提供了丰富的内置方法用于文本处理。以下汇总了字符串模块中的所有方法,每个方法都附带注释说明和实用案例。2. 大小写转换方法2.1 str.capitalize()功能:将字符串…

作者头像 李华
网站建设 2026/7/18 3:28:36

学生作业高效完成指南:类型分析与应对策略

1. 第一次作业的常见类型与应对策略作为从业多年的教育工作者,我见过太多学生面对"第一次作业"时手足无措的样子。第一次作业通常分为以下几种典型类型:课程导入型:教授通过简单任务让学生熟悉教学平台和作业提交流程能力摸底型&am…

作者头像 李华
网站建设 2026/7/18 3:28:24

测试开发工程师必备网络命令实战指南

1. 测试开发工程师必备的网络命令手册作为在测试开发领域摸爬滚打多年的老鸟,我深刻体会到网络知识对测试工作的重要性。无论是接口测试、性能测试还是自动化测试,网络问题总是如影随形。记得刚入行时,因为不熟悉基本的网络排查命令&#xff…

作者头像 李华
网站建设 2026/7/18 3:28:08

桌面文件误删怎么办?这6款恢复工具,简单操作高效找回

在数字时代,电脑桌面是我们最常存放文件的地方,却也成为误操作的高发区。一不小心,重要文档、珍贵照片就可能被删除。调查显示,超过60%的用户曾因误删、系统故障或病毒攻击丢失过桌面文件,不仅影响工作,更可…

作者头像 李华
网站建设 2026/7/18 3:27:16

EasySpider:可视化无代码网页爬虫工具完全指南

一、项目简介 EasySpider(易采集)是一款完全免费、开源的可视化无代码网页爬虫工具,由开发者 NaiboWang 开发并维护。该项目在 GitHub 上已获得超过 44,000 颗星标,是目前最受欢迎的可视化爬虫软件之一。 EasySpider 采用图形化界…

作者头像 李华