FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。
由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少数实时操作系统能在小RAM单片机上运行。相对μC/OS-II、embOS等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行,其最新版本为10.4.4版。
Free: 开源的
R: Real
T: Time
O: Operating
R: System
RTOS: 实时操作系统
所有程序都在一个大循环中进行,在未执行完第一个任务的时候不会执行第二个任务
当有中断进入的时候,先处理中断,然后在回到原来的位置执行未执行文的任务
多个任务并行处理,根据任务优先级决定谁先执行
Arduino是一套便捷、灵活、容易上手的硬件开发平台,它包括多种型号的Arduino控制电路板,和专用编程开发软件。Arduino省略了很多繁琐的底层开发,让人们可以专注在功能实现,快速的开发出智能硬件原型。Arduino的硬件价格也相对便宜,所以,Arduino可以说是适合每个人的硬件开发平台。
有一位意大利依夫雷亚交互设计学院的副教授, 叫马西莫·班兹教授和他的学生赫尔南多·巴拉甘一起开发了一个简单易用的电路板和开发工具,并准备推向市场销售。他们以常去的一家酒吧名字来命名了这个产品。这家酒吧就叫“Bar di re Arduino”,这个名字来源于意大利的末代皇帝杜安,Arduin。
下载地址:https://www.arduino.cc/en/donate/
按默认选择安装即可
安装完毕后需要添加ESP32的开发环境
打开文件 -> 首选项 : 设置 -> 附加开发板管理器地址
填入
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json
两个地址
确定后打开,工具 -> 开发板 -> 开发板管理器,在其中搜索ESP32,如果出现的列表中有2.0以上版本,选择这个,否则就是因为被墙了,需要科学上网才能下载
开发板选择 ESP32S3
调整FlashMode为DIO 80M
调整Partition Table为16MFlash的
其他下载不好使
void setup() {
Serial.begin(115200);
Serial.println("Hello, ESP32-S3!");
}
void loop() {
delay(10);
}
Android分为两个大的函数,分别是setup和loop。
setup函数只运行一次,根据名称可以知道,用于最初的硬件设备驱动设置和安装。
loop是大循环,程序运行到这里后会不断重复执行这个函数,永不退出。
setup函数中,通过 Serial.begin(115200) 函数初始化了串口,这是设置了波特率是115200,但其实还隐含设置了其他三个参数。
串口初始化一般需要 波特率、停止位、校验位、数据位四个参数,在这个初始化中默认停止位1位,没有校验位,8个数据位,也就是我们常说的1N8方式初始化,这也是我们最常用的一种方式,而115200和9600两个波特率也是常用的波特率,在物联网通讯中,为了确保速度,一般都选择115200以上的速率,但也不是随便选的,大家可以参考一下Arduino或者串口调调试助手列出的整数波特率,不要自己写一些奇怪的数字,很有可能丢数据。
串口初始化完毕之后,可以通过 Serial.print() 或者 Serial.println() 向串口输出一些内容,本阶段我们只用于调试程序用,在实际开发中,可以通过Serial指令做一些交互菜单。
共享代码位置:https://wokwi.com/projects/362344392422014977
void setup() {
// 设置引脚为输出模式
pinMode(4, OUTPUT);
}
void loop() {
// 设置引脚输出高电平
digitalWrite(4, HIGH);
// 等待1秒
delay(1000);
// 设置引脚输出低电平
digitalWrite(4, LOW);
// 等待1秒
delay(1000);
// 重复循环
}
在Arduino中,通过pinMode设置引脚的I/O模式,第一个参数是开发板的引脚编号,第二个参数可选择为OUTPUT或者INPUT,目前我们只作为输出使用。
通过 digitalWrite 和 digitalRead 对引脚的电平进行操作,digitalWrite 包含两个参数,第一个依然是引脚编号,第二个只能是 HIGH 或者 LOW 表示高或者低。
而 digitalRead 只需要一个参数,就是引脚编号,但他会返回一个枚举值,可以理解为数字,0表示低电平,1表示高电平。
digital开头的函数都是对引脚进行数字操作,结果结果也只能是0或者1。
使用单任务控制多个LED闪烁逻辑非常复杂,如果使用多任务,事情就变得非常简单了。
需要补全
多任务执行,同时控制三个灯
共享代码位置:https://wokwi.com/projects/362344652502445057
// 第一个任务,控制红灯,每1秒亮灭一次
void task1(void *param_t){
pinMode(4, OUTPUT);
while(1){
// 先读取引脚的高低电平,然后翻转,最后重新设置给这个引脚
digitalWrite(4, !digitalRead(4));
// 等待1秒钟
vTaskDelay(1000/portTICK_PERIOD_MS);
}
}
// 第二个任务,控制绿灯,每2秒亮灭一次
void task2(void *param_t){
pinMode(5, OUTPUT);
while(1){
digitalWrite(5, !digitalRead(5));
vTaskDelay(2000/portTICK_PERIOD_MS);
}
}
// 第三个任务,控制蓝灯,每3秒亮灭一次
void task3(void *param_t){
pinMode(6, OUTPUT);
while(1){
digitalWrite(6, !digitalRead(6));
vTaskDelay(3000/portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(115200);
xTaskCreate(task1, "Blink Red",1024,NULL,1,NULL);
Serial.println("第一个任务被创建,将控制红灯每秒亮灭一次");
xTaskCreate(task2, "Blink Green",1024,NULL,1,NULL);
Serial.println("第二个任务被创建,将控制红灯每2秒亮灭一次");
xTaskCreate(task3, "Blink Blue",1024,NULL,1,NULL);
Serial.println("第三个任务被创建,将控制红灯每3秒亮灭一次");
}
void loop() {
}
该程序中,首先通过函数创建了一个任务的入口,格式如下
void 函数名称(void *pt)
所以该函数需要插入一个指针类型的参数作为任务运行的参数,本节课暂时用不到,下节课的时候我们会讲到传参。
digitalWrite(5, !digitalRead(4));
这段代码的意思是,首先将引脚4的电平读出,然后通过逻辑操作符对这个电平进行翻转,高边低,低变高,最后在写回到这个端口上。
在Arduino的程序中,我们是通过delay进行延时的,在FreeRTOS的Arduino中,其实也可以直接用这个delay函数,框架已经贴心的为我们做了封装,但作为好习惯,在FreeRTOS中,我们尽量还是使用FreeRTOS自身的函数。
FreeRTOS中延时函数使用的是 vTaskDelay 其中传入的是一个Tick值。
Tick可以理解为芯片的心跳间隔时间,也是我们可以最小的延时单位,在不同CPU中心跳都不同,在ESP32-S3中,我们初始设置了Tick为1,表示没间隔1ms产生一次心跳,也就是每秒钟跳1000下。
所以我们通过需要delay的时间(毫秒) ÷ 心跳间隔,就得出了我们应该停留多长时间,当然这里直接写1000也行。
因为这个Tick在后续ESP-IDF开发中我们是需要根据实际情况修改的,所以我们保持好习惯,尽可能通过计算方式获得。
portTICK_PERIOD_MS 是系统的一个常量,可以打印一下试试看。
任务通过 xTaskCreate 创建,在代码中,我们通过
xTaskCreate(task1, "Blink Red",1024,NULL,1,NULL);
这个函数的原型是:
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char *const pcName,
const uint32_t usStackDepth,
void *const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *const pxCreatedTask)
创建了第一个任务,这里一共传入了6个参数,依次为:
pvTaskCode 入口函数,这个函数必须是一个 void(void*)类型的,没有返回值,并有一个指针参数
pcName 表示任务名称的字符串
usStackDepth 任务栈空间的大小,单位是字节,1024表示1kB
pvParameters 任务需要传入的参数,指针类型
uxPriority 优先级,数字越大优先级越高,一般有系统设定的 configMAX_PRIORITIES 参数决定,但一般不建议超过32,不同任务优先级可以相同
pxCreatedTask 返回的任务,用于后续的操作
任务创建后将进入就绪状态,带调度到这个任务的时候开始执行,需要注意的是,任务执行完毕后将退出这个函数,并不会想loop一样重复执行,具体为什么我们下节课会讲。
setup函数中一共创建了三个线程,但第一个线程运行的时候并不会卡主,当他delay的时候会将时间让给其他线程执行,这就是多线程的真谛。