Arduino学习篇 FreeRtos的使用

2023-05-16

ESP32-Arduino中的FreeRTOS使用

在platformio中不需要引入FreeRTOS的头文件,直接可用

FreeRTOS使用第一步:任务的创建与删除

下面的代码启用了两个任务,并且在执行10次之后进行删除,如果不删除的话,你们直接使用while(1)在里面循环。

void task1( void * parameter )
{
    for( int i = 0;i<10;i++ ){
        Serial.println("Hello from task 1");
        delay(1000);
    }
    Serial.println("删除task1");
    vTaskDelete( NULL );  //任务的删除
}
 
  
 
void task2( void * parameter)
{
    for( int i = 0;i<10;i++ ){
        Serial.println("Hello from task 2");
        delay(1000);
    }
    Serial.println("删除task2");
    vTaskDelete( NULL );  //任务的删除
}

void setup() {
  Serial.begin(115200);
    xTaskCreate(
                    task1,          //指定任务函数,也就是上面那个task1函数
                    "TaskOne",        //任务名称
                    10000,            //任务堆栈大小
                    NULL,             //作为任务输入传递的参数
                    1,                //优先级
                    NULL);            //任务句柄
 
  xTaskCreate(
                    task2,         
                    "TaskTwo",        
                    10000,            
                    NULL,             
                    1,               
                    NULL); 
}


void loop() {
  delay(1);
}

这里的任务创建使用了xTaskCreate函数,它一般在单核上使用,由于ESP32是双核的,接下来我会在两个核上创建简单任务让他们跑起来。

接下来介绍下xTaskCreatePinnedToCore 这个函数

这个函数就是在创建任务,而传入的参数上面的代码注释里面已经有了,最最注意的是最后一个参数,他有几种选择,esp32 的 FreeRTOS 是设计运行在单核上. 但 ESP32 是双核的,包含 Protocol CPU (称为 CPU 0 或PRO_CPU)和 Application CPU (称为 CPU 1 或 APP_CPU). 这两个核实际上是相同的,并且共享相同的内存. 这允许任务在两个核之间交替运行,而这里最后一个参数是 xCoreID.此参数指定任务运行在那个核上. PRO_CPU 为 0, APP_CPU 为 1,或者 tskNO_AFFINITY 允许任务在两者上运行

 还有几个官方提示应该注意的点

1.为了避免esp32 的FreeRTOS 调度器将在 Ready 状态下具有相同优先级的多个任务之间实施循环调度时跳过任务.我们最好是不要让自己的多个任务有相同的优先级

2.挂起调度器:在 ESP-IDF 中挂起调度器 FreeRTOS 只会影响调用核上的调度器.换句话说,在 PRO_CPU 上调用 vTaskSuspendAll() 不会阻止 APP_CPU 进行调度,反之亦然.使用临界区或信号量代替同时访问保护。他的意思应该就是两个核的任务调度各自独立,具有独立性。

3.滴答中断同步:PRO_CPU 和 APP_CPU 的滴答中断不同步. 不要期望使用 vTaskDelay() 或 vTaskDelayUntil() 作为在两个核之间同步任务执行的准确方法. 使用计数信号量,因为它们的上下文切换不会因抢占而与滴答中断相关联. 这个的意思我觉得应该是告诉我们,不要指望通过延时(这个延时会把CPU让出来让给目前合适的优先级的任务去运行)来让两个CPU长时间精确同步,简而言之,可能是这张情况,比如你分别在两个核创建了一个任务,都是每隔1000ms让LED灯翻转一次,那么开始的时候还能看到两个灯的同步闪烁,但是时间一积累就会发现由于滴答中断不同步而导致两个核运行节奏不一样,开始交替闪烁

4.vTaskDelete() 仍然是任务删除函数

5.浮点运算:ESP32 支持单精度浮点运算 (float) 的硬件加速.然而,硬件加速的使用导致 ESP-IDF FreeRTOS 中的一些行为限制.因此,如果没有这样做,使用 float 的任务将自动固定到核.此外, float 不能用于中断服务程序.

6.临界区和禁用中断:在 ESP-IDF FreeRTOS 中,临界区是使用互斥锁实现的.进入临界区涉及获取互斥锁,然后禁用调度器和调用核的中断.然而,另一个核不受影响.如果另一个核尝试使用相同的互斥锁,它将自旋直到调用核通过退出临界区释放互斥锁。这个是锁以及一些信号量之类的内容后面还会更用法,其目的就是资源分配,大家应该有死锁的基本概念,就是两个任务各自占有对方想要的资源但是两个任务一不愿意放手目前手中的资源,二想抢夺对方手里的资源好让自己运行下去而导致两个任务都停滞的情况,这些东西存在的目地基本就是避免这些情况的,合理高效分配内部资源用的

 接下来讲解下xTaskCreatePinnedToCore这个函数的参数讲解:

xTaskCreatePinnedToCore(task,"task_name",stack_depth,some_param,priority,handler,core_id);
字段含义
task任务指针,创建任务用的那个函数名
task_name任务名称
stack_depth栈空间,根据任务类型设置大小,空间不够会有串口debug报错
some_param可选参数,一般不填
priority优先级
handler任务句柄,用于后续挂起或恢复运行
core_id核ID,esp32共两个核,Protocol CPU(0) 和 Application CPU(1)

这里顺便讲解下FreeRTOS任务的一些简单函数:

挂起任务

vTaskSuspend(handler);
  • handler是那个任务的句柄

恢复任务

vTaskResume(handler);

延时

vTaskDelay(1000);  //ms
 

讲到了任务就不得不讲一下队列:

队列是什么?
队列是可以从一个任务向其他任务以并发安全的方式发送消息的机制,也就是说他的目标是实现任务间的通讯,比如A任务向名为Queue的队列中发布了数据,那么B任务就可以从Queue这个队列中又把数据给取出来,并且,这个数据是复制式的,也就是说把数据复制一份送入队列,B任务取出后对原来A的数据完全不会造成影响(实际上是形象的描述,本质上是通过一系列指针实现的),好了知道一些概念就行了,重要的是知道怎么用。

话不多说上代码:

#include <Arduino.h>
#include <soc/soc.h> 
#include <soc/rtc_cntl_reg.h>
#include <stdio.h>
#include <stdlib.h>

//这是队列数据结构体,数据是可以以结构体存在的
typedef struct{
  int sender;
  char *msg;
}Data;

//以下句柄名字以x开头,用作双核版本的队列测试
xQueueHandle xqueue0;  //创建的测试队列句柄,我们定义数据为int型
xQueueHandle xqueue1;  //创建的测试队列句柄,我们定义数据为字符串型
xQueueHandle xqueue2;  //创建的测试队列句柄,我们定义数据为结构体型,结构体里面包括一个int型以及一个字符串型
TaskHandle_t xTask0;   //任务0的句柄
TaskHandle_t xTask1;   //任务1的句柄

void Task1(void *pvParameters) {
  //在这里可以添加一些代码,这样的话这个任务执行时会先执行一次这里的内容(当然后面进入while循环之后不会再执行这部分了)
  Data data_send;  //创建一个数据结构体用于发送数据
  data_send.sender = 1314;  //这个数据结构体的整数直接赋值为1314
  BaseType_t xStatus;  //用于状态返回在下面会用到
  BaseType_t xStatus1;
  BaseType_t xStatus2;
  int send_int = 615;
  char send_str[10] = "cx??";
  const TickType_t xTicksToWait = pdMS_TO_TICKS(100);   // 阻止任务的时间,直到队列有空闲空间 ,应该是如果发送需要阻滞等待(比如队列满了)或者别的情况需要用到的
  while(1)
  {
    vTaskDelay(1200);
    Serial.print("PRO_CPU正在运行:");
    Serial.println(xPortGetCoreID());
    Serial.print("发送任务固定在: ");
    Serial.println(xTaskGetAffinity(xTask0));    //获取任务被固定到哪里,xTask1 就是 Task0任务本身的句柄
    data_send.msg = (char *)malloc(20);  //分配所需的内存空间,并返回一个指向它的指针,里面传入的参数是SIZE。
    memset(data_send.msg, 0, 20);  //清空这个data_send,也就是上面分配的这个空间
    // 从存储区 str2 复制 n 个字节到存储区 str1。 str2就是"hello world" ,str1就是data_send.msg , strlen("hello world")就是n
    memcpy(data_send.msg, "hello world", strlen("hello world"));  
    xStatus = xQueueSendToFront( xqueue2, &data_send, xTicksToWait );  //发送data_send这个数据结构体到 xqueue2 队列
    
    xStatus1 = xQueueSendToFront( xqueue0, &send_int, xTicksToWait );  //发送send_int这个数据结构体到 xqueue0 队列
    xStatus2 = xQueueSendToFront( xqueue1, &send_str, xTicksToWait );  //发送send_str这个数据结构体到 xqueue1 队列
    if( xStatus == pdPASS && xStatus1 == pdPASS && xStatus2 == pdPASS) {
      Serial.println("send data OK");  // 发送正常 
    }
    Serial.println("******************************************************************");
  }
}
 
void Task2(void *pvParameters) {
  //  这些变量作用与上面相同,只是这里的data_get就是我们从队列里面获取的东西了
  BaseType_t xStatus;
  BaseType_t xStatus1;
  BaseType_t xStatus2;
  const TickType_t xTicksToWait = pdMS_TO_TICKS(50);  //这里就是用于取数据阻塞了,我觉得本来这种收发就不可能同步,但是应该接收来满足发送,接收速度大于发送速度才行
  Data data_get;
  int get_int;
  char get_str[10];
  while(1)
  {
    vTaskDelay(800);
    Serial.print("APP_CPU正在运行:");
    Serial.println(xPortGetCoreID());
    Serial.print("获取任务固定在:");
    Serial.println(xTaskGetAffinity(xTask1));
    xStatus = xQueueReceive( xqueue2, &data_get, xTicksToWait );  //从队列2中取一条数据
    xStatus = xQueueReceive( xqueue0, &get_int, xTicksToWait );
    xStatus = xQueueReceive( xqueue1, &get_str, xTicksToWait );
    if(xStatus == pdPASS){
      free(data_get.msg);  //释放数据结构体的字符串部分的空间
      Serial.print("获取结构体数据整数部分:");
      Serial.println(data_get.sender);
      Serial.print("获取结构体数据字符串部分:");
      Serial.println(data_get.msg);
      Serial.print("队列零的整数获取:");
      Serial.println(get_int);
      Serial.print("队列1的字符串获取:");
      Serial.println(get_str);
    }
    Serial.println("******************************************************************");
  }
}

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);//关闭低电压检测,避免无限重启
  Serial.begin(112500);
  delay(1000);
  Serial.print("获取Setup的优先级: ");
  Serial.println(uxTaskPriorityGet(NULL));  //优先级获取函数
  
  xqueue0 = xQueueCreate( 10, sizeof( int ) );
  xqueue1 = xQueueCreate( 10, sizeof( char[10] ) );
  xqueue2 = xQueueCreate(10, sizeof(Data));
  
  xTaskCreatePinnedToCore(Task1, "Task1", 10000, NULL, 11, &xTask0,  0);  //最后一个参数至关重要,决定这个任务创建在哪个核上.PRO_CPU 为 0, APP_CPU 为 1,或者 tskNO_AFFINITY 允许任务在两者上运行.
  xTaskCreatePinnedToCore(Task2, "Task2", 10000, NULL, 12, &xTask1,  1);  //xTaskGetAffinity(xTask1)  是可以查询到任务被固定到哪里的
  Serial.print("获取xTask0的优先级: ");
  Serial.println(uxTaskPriorityGet(xTask0));  //优先级获取函数
  Serial.print("获取xTask1的优先级: ");
  Serial.println(uxTaskPriorityGet(xTask1));  //优先级获取函数
  if(xqueue0 != NULL && xqueue1 != NULL && xqueue2 != NULL) Serial.println("开始队列测试!!!");
  Serial.println("******************************************************************");
}
 
void loop() {
}

先说说上面这段代码实现了什么
我分别在两个核上创建了一个任务,一个用于向队列发送数据,一个用于向队列接收数据,而队列,我创建了三个,一个是int型的,一个是char[10],也就是定长字符串型的,还有一个是结构体型,没错,队列不仅可以实现单类型,还可以实现结构体型,结构体里面你可以自己选择用那些形式的数据.

队列的创建与初始化

xQueueHandle xqueue2;  //创建队列的句柄,详细看上面的代码
xqueue2 = xQueueCreate(10, sizeof(Data));  //这个是初始化队列,Data就是队列的数据类型,允许结构体型
//这个是从队列中读取,xqueue2是队列的句柄,data_get是我们传入的数据,xTicksToWait 是等待时间
xStatus = xQueueReceive( xqueue2, &data_get, xTicksToWait ); 
//下面这个是发送的,传入参数与上面类似
xStatus = xQueueSendToFront( xqueue2, &data_send, xTicksToWait );

上面写的是双核版的队列测试,接下来写一下单核版本的,实现起来也比双核的要简单,也便于我们写STM32上的FreeRTOS,话不多说,上代码:

#include <Arduino.h>
QueueHandle_t queue;    //单核多进程测试队列,这是队列的句柄
TaskHandle_t Task1;   //任务0的句柄
TaskHandle_t Task2;

void task1( void * parameter )
{
  int data_get = 520;
  BaseType_t Status;
  const TickType_t xTicksToWait = pdMS_TO_TICKS(100);
    while(1)
    {
      vTaskDelay(800);
      Status = xQueueReceive(queue, &data_get, xTicksToWait);  //单核调用队列接收的函数
      if(Status == pdPASS)
      {
        Serial.print("成功收到数据:");
        Serial.println(data_get);
      }
      Serial.print("接收数据任务的优先级:");
      Serial.println(uxTaskPriorityGet(Task1));  // 就是获取任务的优先级,而传入的参数就是任务的句柄
      Serial.println("***********************************************************");
    }
}
 
  
 
void task2( void * parameter)
{
  int data_send = 625;
  BaseType_t Status;
  const TickType_t xTicksToWait = pdMS_TO_TICKS(200);
    while(1)
    {
      vTaskDelay(1200);  //每1200ms向队列中发送一次数据
      Status = xQueueSend(queue, &data_send, xTicksToWait);  //向队列中发送数据
      if(Status == pdPASS)
      {
        Serial.print("成功发送数据:");
      }
      Serial.print("发送数据任务的优先级:");
      Serial.println(uxTaskPriorityGet(Task2));
      Serial.println("***********************************************************");
    }
}


void setup() {
  Serial.begin(115200);
  queue = xQueueCreate( 10, sizeof( int ) );  //创建一个队列,用的是整数型
  //下面是创建两个任务
  xTaskCreate(
                    task1,          //指定任务函数,也就是上面那个task1函数
                    "TaskOne",        //任务名称
                    10000,            //任务堆栈大小
                    NULL,             //作为任务输入传递的参数
                    1,                //优先级
                    &Task1);            //任务句柄,可以不用创建,直接用NULL
 
  xTaskCreate(
                    task2,         
                    "TaskTwo",        
                    10000,            
                    NULL,             
                    3,               
                    &Task2); 
  if(queue == NULL){
    Serial.println("创建队列失败");
  }
}
  
void loop() {
  Serial.println("主程序仍然在运行");
  delay(1000);
}

以上就是一些FreeRTOS的基础运用,接下来补上一些FreeRTOS的编码标准和命名风格。

1. FreeRTOS 的编码标准

FreeRTOS 核心源码文件的编写遵循 MISRA 代码规则,同时支持各种编译器。但考虑到有些编译器的性能还比较弱,不支持 C 语言的新标准 C99 和 C11 的一些特性和语法,所以 FreeRTOS 的源码中就没有引入 C99 和 C11 的新特性,但是有一个例外,源码中有用到头文件 stdint.h(这个文件是C99标准才引入的)。

2. FreeRTOS 的命名规则

变量

  1. uint32_t 定义的变量都加上前缀 ul。 u 代表 unsigned 无符号,l 代表 long 长整型。
  2. uint16_t 定义的变量都加上前缀 us。 u 代表 unsigned 无符号,s 代表 short 短整型。
  3. uint8_t 定义的变量都加上前缀 uc。 u 代表 unsigned 无符号,c 代表 char 字符型。
  4. stdint.h 文件中未定义的变量类型,在定义变量时需要加上前缀 x,比如 BaseType_t 和
  5. TickType_t 定义的变量。
  6. stdint.h 文件中未定义的无符号变量类型,在定义变量时要加上前缀 u,比如 UBaseType_t 定义
  7. 的变量要加上前缀 ux。
  8. size_t 定义的变量也要加上前缀 ux。
  9. 枚举变量会加上前缀 e。
  10. 指针变量会加上前缀 p,比如 uint16_t 定义的指针变量会加上前缀 pus。
  11. 根据 MISRA 代码规则,char 定义的变量只能用于 ASCII 字符,前缀使用 c。
  12. 根据 MISRA 代码规则,char *定义的指针变量只能用于 ASCII 字符串,前缀使用 pc。

函数

  1. 加上了 static 声明的函数,定义时要加上前缀 prv,这个是单词 private 的缩写。
  2. 带有返回值的函数,根据返回值的数据类型,加上相应的前缀,如果没有返回值,即 void 类型,函数的前缀加上字母 v。
  3. 根据文件名,文件中相应的函数定义时也将文件名加到函数命名中,比如 tasks.c 文件中函数vTaskDelete,函数中的 task 就是文件名中的 task。

宏定义

  1. 根据宏定义所在的文件,文件中的宏定义声明时也将文件名加到宏定义中,比如宏定义configUSE_PREEMPTION 是定义在文件 FreeRTOSConfig.h 里面。 宏定义中的 config 就是文件名中的 config。 另外注意,前缀要小写。
  2. 除了前缀,其余部分全部大写,同时用下划线分开。

 3. FreeRTOS 中数据类型

  • FreeRTOS 使用的数据类型主要分为 stdint.h 文件中定义的和自己定义的两种。 其中 char 和 char *定义的变量要特别注意。
  • FreeRTOS 主要自定义了以下四种数据类型:
  • TickType_t

        如果用户使能了宏定义 configUSE_16_BIT_TICKS,那么 TickType_t 定义的就是 16         位无符号数,如果没有使能,那么 TickType_t 定义的 就是 32 位无符号数。 对于 32 位 架构的处理器,一定要禁止此宏定义,即设置此宏定义数值为 0 即可。

  • BaseType_t

        这个数据类型根据系统架构的位数而定,对于 32 位架构,BaseType_t 定义的是 32 位有符号数,对

于 16 位架构,BaseType_t 定义的是 16 位有符号数。 如果 BaseType_t 被定义成了 char 型,要特别

注意将其设置为有符号数,因为部分函数的返回值是用负数来表示错误类型。

  • UBaseType_t

        这个数据类型是 BaseType_t 类型的有符号版本。StackType_t栈变量数据类型定义,这个数量类型由系统架构决定,对于 16 位系统架构,StackType_t 定义的是16 位变量,对于 32 位系统架构,StackType_t 定义的是 32 位变量。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Arduino学习篇 FreeRtos的使用 的相关文章

随机推荐

  • 正点原子stm32F407学习笔记3——蜂鸣器实验

    一 硬件设计 蜂鸣器为有源蜂鸣器 xff0c 当 PF 8 输出高电平的时候 xff0c 蜂鸣器将发声 xff0c 当 PF 8 输出低电平的时候 xff0c 蜂鸣器停止发声 xff0c 硬件原理图如下 二 软件设计 1 新建beep c文
  • Ubuntu 安装Cmake

    1 安装Cmake 1 Cmake与makefile xff1a Cmake好处是 1 可以自动化生成makefile xff0c 不需要手动编写 2 跨平台 可生成 native 编译配置文件 在 Linux Unix 平台 生成 mak
  • 什么是字节序?

    字节序 字节序 xff0c 顾名思义 xff0c 就是字节组织的顺序 我们可以将其根据其存储时从低位开始还是从高位开始分为两种 xff0c 具体如下 xff1a 类型简写本质大端BE big endian 将高序字节存储在起始地址小端LE
  • PX4开发基础

    文章目录 一 与无人机的缘分二 开发前三 背景知识硬件PIXHAWKpixhawkV1 xff1a pixhawkV2 xff1a pixhawkV3x xff1a PX4 xff08 推荐 xff09 地面站 xff08 GCS xff0
  • 利用蜂鸣器播放音乐

    我们知道 xff0c 蜂鸣器可分为有源蜂鸣器和无源蜂鸣器 xff0c 有源蜂鸣器内置了频率发生电路 xff0c 因此其通电就能够发出声音 xff0c 但是其频率是固定的 xff0c 而无源蜂鸣器内部没有频率发生电路 xff0c 需要外界给予
  • 51单片机之智能小车(避障、跟随、循迹)

    目录 基本概述 硬件组成 功能 关键字 模块介绍 电机模块L9110S 循迹模块 xff08 TCRT5000传感器 xff09 红外避障模块 测速模块 小车 移动小车 xff08 控制电机转动 xff0c 使小车前进 后退 左转 右转 x
  • PX4开发环境搭建(Ubuntu1804+QGC+Qt Creator )

    文章目录 前言一 安装环境二 PX4环境搭建1 在虚拟机中安装Ubuntu2 安装PX4环境 1 下载PX4源码 xff1a 2 安装工具链 3 jMAVSim仿真 4 编译验证 三 安装QGC四 安装Qt Creator五 镜像分享 前言
  • ESP8266————AT指令+网络透传

    一 AT指令 ESP8266可以通过串口的AT指令和其他设备进行通讯或控制 刚开始我以为AT指令是ESP8266本就封装好的 xff0c 直接就可以用 xff0c 结果不是 xff0c 需要自己安装AT固件 xff0c 安装好固件之后直接用
  • 修改pip安装路径的方法

    当我们需要安装python的第三方库时 xff0c 通常都是打开cmd输入pip install xxx去安装 但是默认安装路径在C盘 xff0c 极大占用空间 xff0c 看看我的C盘空间 xff0c 已经不足了 xff01 xff01
  • 详细介绍如何在华为云调用SDK的Python代码(以文字识别OCR技术实现身份证识别为例)

    文章目录 1 保存信息2 安装SDK3 生成代码 1 保存信息 首先我们在首页的产品一栏输入OCR xff0c 找到文字识别OCR 然后找到证件类 xff0c 并点击 xff0c 然后点击立即使用 进入到文字识别 控制台 xff0c 找到身
  • 还是搞不懂Anaconda是什么?读这一篇文章就够了

    文章目录 1 Anaconda介绍2 conda介绍3 安装Anaconda4 Anaconda的使用配置Anaconda源 5 创建虚拟环境并使用5 1 创建虚拟环境5 2 查看所有环境5 3 激活环境5 4 安装包5 4 1 conda
  • 安装tensorflow的GPU版本(详细图文教程)--CUDA11.6的安装

    文章目录 TensorFlow简介TensorFlow是什么tensorflow版本变迁tensorflow 2 0 架构 安装过程常用IDE安装python3 9的安装Anaconda的安装 CUDA安装cuda软件安装cuDNN神经网络
  • 解决visual studio C++ build tools时安装包缺失或损坏的问题(亲测绝对有用!!!)

    安装visual studio C 43 43 build tools时遇到安装包缺失或损坏的问题 这个问题真的困扰我好久 xff0c 尝试过很多种办法都失败了 xff0c 我真的很无语 我尝试过卸载掉visual studio重新下载 用
  • ROS1学习笔记:tf坐标系广播与监听的编程实现(ubuntu20.04)

    参考B站古月居ROS入门21讲 xff1a tf坐标系广播与监听的编程实现 基于VMware Ubuntu 20 04 Noetic版本的环境 文章目录 一 创建功能包二 创建代码2 1 以C 43 43 为例2 1 1 配置代码编译规则2
  • Ubuntu上使用Qt Creator编译、下载PX4代码

    文章目录 一 创建工程文件二 Qt 加载工程文件三 工程的配置四 编译工程五 下载程序六 连接QGC验证 一 创建工程文件 在命令行执行以下几条命令 xff1a span class token function cd span src F
  • ROS1学习笔记:launch启动文件的使用方法

    参考B站古月居ROS入门21讲 xff1a launch启动文件的使用方法 基于VMware Ubuntu 20 04 Noetic版本的环境 文章目录 一 launch的文件结构二 launch的文件语法2 1 根元素2 2 参数设置2
  • ROS1学习笔记:常用可视化工具的使用(ubuntu20.04)

    参考B站古月居ROS入门21讲 xff1a 常用可视化工具的实现 基于VMware Ubuntu 20 04 Noetic版本的环境 文章目录 一 日志输出工具 xff1a rqt console二 绘制数据曲线 xff1a rqt plo
  • 笔记(STM32篇)day1——工程创建、操作寄存器点灯

    目录 一 STM32F103VET6 二 创建工程 1 主要文件 2 生成文件 三 操作寄存器点灯 前言 这一年 xff0c 从调剂到各种找工作面试 去实习 xff0c 感受总结下来就是 出走半生 xff0c 归来仍是萌新 xff0c 作为
  • 手把手教CMake编译Qt5工程

    一 新建CMakeLists txt 可以在工程目录下新建一个名为CMakeLists txt文件 xff0c 亦或是 xff0c 在Qtcreator中 xff0c 右键项目 xff0c 添加新文件 xff0c 选择General中的Em
  • Arduino学习篇 FreeRtos的使用

    ESP32 Arduino中的FreeRTOS使用 在platformio中不需要引入FreeRTOS的头文件 xff0c 直接可用 FreeRTOS使用第一步 xff1a 任务的创建与删除 下面的代码启用了两个任务 xff0c 并且在执行