FreeRTOS(1):任务

2023-11-09

目录

一、FreeRTOS 介绍

什么是 FreeRTOS ?

为什么选择 FreeRTOS ?

FreeRTOS 资料与源码下载

FreeRTOS 实现多任务的原理

二、移植 FreeRTOS 到STM32

手动移植

使用CubeMX快速移植

快速移植流程

 一些常见问题

 三、任务的创建与删除

1. 什么是任务?

2. 任务创建与删除相关函数

HAL库:创建和删除

 函数原型:

3. 实操

 四、任务调度

什么是任务调度?

FreeRTOS的任务调度规则是怎样的?

抢占式调度运行过程

时间片调度运行过程

五、任务的状态

HAL库 的挂起和继续函数原型:

六、任务综合小实验

实验需求

cubeMX配置 

代码实现 

用到的函数: 

完整代码:


一、FreeRTOS 介绍

什么是 FreeRTOS ?

Free即免费的,RTOS的全称是Real time operating system,中文就是实时操作系统。

注意:RTOS不是指某一个确定的系统,而是指一类操作系统。比如:uc/OS,FreeRTOS,RTX,RT-Thread等这些都是RTOS类操作系统。

FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。

由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少数实时操作系统能在小RAM单片机上运行。相对μC/OS-II、embOS等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行,其最新版本为10.4.4版。

(以上来自百度百科)

为什么选择 FreeRTOS ?

1.FreeRTOS 是免费的;

2.很多半导体厂商产品的SDK(Software Development Kit)软件开发工具包,就使用FreeRTOS作为其操作系统,尤其是WIFI、蓝牙这些带有协议栈的芯片或模块。

3.简单,因为FreeRTOS的文件数量很少。

FreeRTOS 资料与源码下载

最好的资料就是官网提供的资料

FreeRTOS - Market leading RTOS (Real Time Operating System) for embedded systems with Internet of Things extensions

FreeRTOS 实现多任务的原理

严格来说 FreeRTOS 并不是实时操作系统,因为它是分时复用的。

系统将时间分割成很多时间片,然后轮流执行各个任务。

每个任务都是独立运行的,互不影响,由于切换的频率很快,就感觉像是同时运行的一样。

二、移植 FreeRTOS 到STM32

手动移植

过程复杂且繁琐,对新手不友好。如有需要手动移植,可参照以下文章:

(61条消息) FreeRTOS移植到STM32_不秃也很强的博客-CSDN博客

使用CubeMX快速移植

快速移植流程

1. 在 SYS 选项里,将 Debug 设为 Serial Wire ,并且将 Timebase Source 设为 TIM2 (其它定时器也行)。为何要如此配置?下文解说。

2. 将 RCC 里的 HSE 设置为 Crystal/Ceramic Resonator 。

3. 时钟配置

4. 选择 FREERTOS 选项,并将 Interface 改为 CMSIS_V1 。V1 和 V2 有啥区别?下文解释。

 5. 配置项目信息,并导出代码。 

 一些常见问题

1. Timebase Source 为什么不能设置为 SysTick ?

裸机的时钟源默认是 SysTick,但是开启 FreeRTOS 后,FreeRTOS会占用 SysTick (用来生成1ms定时,用于任务调度),所以需要需要为其他总线提供另外的时钟源。

2. FreeRTOS 版本问题

V2 的内核版本更高,功能更多,在大多数情况下 V1 版本的内核完全够用。

3. FreeRTOS 各配置选项卡的解释

Events:事件相关的创建

Task and Queues: 任务与队列的创建

Timers and Semaphores: 定时器和信号量的创建

Mutexes: 互斥量的创建

FreeRTOS Heap Usage: 用于查看堆使用情况

config parameters: 内核参数设置,用户根据自己的实际应用来裁剪定制 FreeRTOS 内核

Include parameters: FreeRTOS 部分函数的使能

User Constants: 相关宏的定义,可以自建一些常量在工程中使用

Advanced settings:高级设置

4. 内核配置、函数使能的一些翻译

内核参数的理解内容非常多,可以参考以下文章:

(61条消息) FreeRTOS系列第6篇---FreeRTOS内核配置说明_vassertcalled_研究是为了理解的博客-CSDN博客

 三、任务的创建与删除

1. 什么是任务?

任务可以理解为进程/线程,创建一个任务,就会在内存开辟一个空间。

比如:

Windows 系统中的 MarkText 、谷歌浏览器、记事本,都是任务。

任务通常都含有 while(1) 死循环。

2. 任务创建与删除相关函数

任务创建与删除相关函数有如下三个:

函数名称 函数作用
xTaskCreate() 动态方式创建任务
xTaskCreateStatic() 静态方式创建任务
vTaskDelete() 删除任务

任务动态创建与静态创建的区别:

动态创建任务的堆栈由系统分配,而静态创建任务的堆栈由用户自己传递。

通常情况下使用动态方式创建任务。

xTaskCreate 函数原型

1. pvTaskCode:指向任务函数的指针,任务必须实现为永不返回(即连续循环);

2. pcName:任务的名字,主要是用来调试,默认情况下最大长度是16;

3. pvParameters:指定的任务栈的大小;

4. uxPriority:任务优先级,数值越大,优先级越大

5. pxCreatedTask:用于返回已创建任务的句柄可以被引用。

返回值 描述
pdPASS 任务创建成功
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 任务创建失败

HAL库:创建和删除

    //创建
  /* Create the thread(s) */
  /* definition and creation of LED1 */
  osThreadDef(LED1, Start_LED1, osPriorityNormal, 0, 128);
  LED1Handle = osThreadCreate(osThread(LED1), NULL);

  /* definition and creation of LED2 */
  osThreadDef(LED2, Start_LED2, osPriorityNormal, 0, 128);
  LED2Handle = osThreadCreate(osThread(LED2), NULL);

    //删除
  osThreadTerminate(LED1Handle);
  osThreadTerminate(LED2Handle);


 函数原型:

/*********************** Thread Management *****************************/
/**
* @brief  Create a thread and add it to Active Threads and set it to state READY.
* @param  thread_def    thread definition referenced with \ref osThread.
* @param  argument      pointer that is passed to the thread function as start argument.
* @retval thread ID for reference by other functions or NULL in case of error.
* @note   MUST REMAIN UNCHANGED: \b osThreadCreate shall be consistent in every CMSIS-RTOS.
*/
osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument)
{
  TaskHandle_t handle;
  
#if( configSUPPORT_STATIC_ALLOCATION == 1 ) &&  ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
  if((thread_def->buffer != NULL) && (thread_def->controlblock != NULL)) {
    handle = xTaskCreateStatic((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name,
              thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority),
              thread_def->buffer, thread_def->controlblock);
  }
  else {
    if (xTaskCreate((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name,
              thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority),
              &handle) != pdPASS)  {
      return NULL;
    } 
  }
#elif( configSUPPORT_STATIC_ALLOCATION == 1 )

    handle = xTaskCreateStatic((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name,
              thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority),
              thread_def->buffer, thread_def->controlblock);
#else
  if (xTaskCreate((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name,
                   thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority),
                   &handle) != pdPASS)  {
    return NULL;
  }     
#endif
  
  return handle;
}
/**
* @brief  Terminate execution of a thread and remove it from Active Threads.
* @param   thread_id   thread ID obtained by \ref osThreadCreate or \ref osThreadGetId.
* @retval  status code that indicates the execution status of the function.
* @note   MUST REMAIN UNCHANGED: \b osThreadTerminate shall be consistent in every CMSIS-RTOS.
*/
osStatus osThreadTerminate (osThreadId thread_id)
{
#if (INCLUDE_vTaskDelete == 1)
  vTaskDelete(thread_id);
  return osOK;
#else
  return osErrorOS;
#endif
}

官方案例:

/* Task to be created. */
void vTaskCode( void * pvParameters )
{
	/* The parameter value is expected to be 1 as 1 is passed in the
	pvParameters value in the call to xTaskCreate() below.*/
	configASSERT( ( ( uint32_t ) pvParameters ) == 1 );
	for( ;; )
	{
		/* Task code goes here. */
	}
}
/* Function that creates a task. */
void vOtherFunction( void )
{
	BaseType_t xReturned;
	TaskHandle_t xHandle = NULL;
	
	/* Create the task, storing the handle. */
	xReturned = xTaskCreate(
			vTaskCode, /* Function that implements the task. */
			"NAME", /* Text name for the task. */
			STACK_SIZE, /* Stack size in words, not bytes. */
			( void * ) 1, /* Parameter passed into the task. */
			tskIDLE_PRIORITY,/* Priority at which the task is created. */
			&xHandle ); /* Used to pass out the created task's handle. */
	if( xReturned == pdPASS )
	{
		/* The task was created. Use the task's handle to delete the task. */
		vTaskDelete( xHandle );
	}
}

vTaskDelete 函数原型

void vTaskDelete(TaskHandle_t xTaskToDelete);

只需将待删除的任务句柄传入该函数,即可将该任务删除。

当传入的参数为NULL,则代表删除任务自身(当前正在运行的任务)。

3. 实操

两LED分别闪烁。

/* USER CODE END Header_Start_LED1 */
void Start_LED1(void const * argument)
{
  /* USER CODE BEGIN Start_LED1 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(500);
		
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
  }
  /* USER CODE END Start_LED1 */
}

/* USER CODE BEGIN Header_Start_LED2 */
/**
* @brief Function implementing the LED2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_LED2 */
void Start_LED2(void const * argument)
{
  /* USER CODE BEGIN Start_LED2 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1000);
		
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
  }
  /* USER CODE END Start_LED2 */
}

 四、任务调度

什么是任务调度?

调度器就是使用相关的调度算法来决定当前需要执行的哪个任务。

FreeRTOS中开启任务调度的函数是 vTaskStartScheduler() ,但在 CubeMX 中被封装为osKernelStart() 。

FreeRTOS的任务调度规则是怎样的?

FreeRTOS 是一个实时操作系统,它所奉行的调度规则:

        1. 高优先级抢占低优先级任务,系统永远执行最高优先级的任务(即抢占式调度

        2. 同等优先级的任务轮转调度(即时间片调度

还有一种调度规则是协程式调度,但官方已明确表示不更新,主要是用在小容量的芯片上,用得

也不多。

抢占式调度运行过程


总结:

        1. 高优先级任务,优先执行;

        2. 高优先级任务不停止,低优先级任务无法执行;

        3. 被抢占的任务将会进入就绪态

时间片调度运行过程

 

 总结:

        1. 同等优先级任务,轮流执行,时间片流转;

        2. 一个时间片大小,取决为滴答定时器中断周期;

        3. 注意没有用完的时间片不会再使用,下次任务 Task3 得到执行,

            还是按照一个时间片的时钟节拍运行。

五、任务的状态

FreeRTOS中任务共存在4种状态:

        Running 运行态

当任务处于实际运行状态称之为运行态,即CPU的使用权被这个任务占用(同一时间仅一个任务

处于运行态)。

        Ready 就绪态

处于就绪态的任务是指那些能够运行(没有被阻塞和挂起),但是当前没有运行的任务,因为同

优先级或更高优先级的任务正在运行。

        Blocked 阻塞态

如果一个任务因延时,或等待信号量、消息队列、事件标志组等而处于的状态被称之为阻塞态。

        Suspended 挂起态

类似暂停,通过调用函数 vTaskSuspend() 对指定任务进行挂起,挂起后这个任务将不被执行,

只有调用函数 xTaskResume() 才可以将这个任务从挂起态恢复。

总结:

        1. 仅就绪态可转变成运行态

        2. 其他状态的任务想运行,必须先转变成就绪态

HAL库 的挂起和继续函数原型:

#endif /* INCLUDE_eTaskGetState */
/**
* @brief  Suspend execution of a thread.
* @param   thread_id   thread ID obtained by \ref osThreadCreate or \ref osThreadGetId.
* @retval  status code that indicates the execution status of the function.
*/
osStatus osThreadSuspend (osThreadId thread_id)
{
#if (INCLUDE_vTaskSuspend == 1)
    vTaskSuspend(thread_id);
  
  return osOK;
#else
  return osErrorResource;
#endif
}

/**
* @brief  Resume execution of a suspended thread.
* @param   thread_id   thread ID obtained by \ref osThreadCreate or \ref osThreadGetId.
* @retval  status code that indicates the execution status of the function.
*/
osStatus osThreadResume (osThreadId thread_id)
{
#if (INCLUDE_vTaskSuspend == 1)  
  if(inHandlerMode())
  {
    if (xTaskResumeFromISR(thread_id) == pdTRUE)
    {
      portYIELD_FROM_ISR(pdTRUE);
    }
  }
  else
  {
    vTaskResume(thread_id);
  }
  return osOK;
#else
  return osErrorResource;
#endif
}

六、任务综合小实验

实验需求

创建 4 个任务:taskLED1,taskLED2,taskKEY1,taskKEY2,任务要求如下:

taskLED1:间隔 500ms 闪烁 LED1;

taskLED2:间隔 1000ms 闪烁 LED2;

taskKEY1:如果 taskLED1 存在,则按下 KEY1 后删除 taskLED1 ,否则创建 taskLED1 ;

taskKEY2:如果 taskLED2 正常运行,则按下 KEY2 后挂起 taskLED2 ,否则恢复 taskLED2

cubeMX配置 

代码实现 

用到的函数: 

osThreadDef(LED1, Start_LED1, osPriorityNormal, 0, 128);
 LED1Handle = osThreadCreate(osThread(LED1), NULL);

osThreadTerminate(LED1Handle);

osThreadSuspend(LED2Handle);

osThreadResume(LED2Handle);

完整代码:

usart.c        串口1输出文字

/* USER CODE BEGIN 0 */
#include "stdio.h"
//覆写printf,usart1输出
int fputc(int ch, FILE *f)
{      
    unsigned char temp[1]={ch};
    HAL_UART_Transmit(&huart1,temp,1,0xffff);  
    return ch;
}
/* USER CODE END 0 */

  freertos.c

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */


/* USER CODE BEGIN Header_Start_LED1 */
/**
  * @brief  Function implementing the LED1 thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_Start_LED1 */
void Start_LED1(void const * argument)
{
  /* USER CODE BEGIN Start_LED1 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(500);
		
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
  }
  /* USER CODE END Start_LED1 */
}

/* USER CODE BEGIN Header_Start_LED2 */
/**
* @brief Function implementing the LED2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_LED2 */
void Start_LED2(void const * argument)
{
  /* USER CODE BEGIN Start_LED2 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1000);
		
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
  }
  /* USER CODE END Start_LED2 */
}

/* USER CODE BEGIN Header_Start_KEY1 */
/**
* @brief Function implementing the KEY1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_KEY1 */
void Start_KEY1(void const * argument)
{
  /* USER CODE BEGIN Start_KEY1 */
  /* Infinite loop */
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET)
		{
			osDelay(10);//防抖
			if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET)
			{
				printf("按键1按下\r\n");
				if(LED1Handle == NULL)
				{
					printf("任务1未创建,开始创建任务1\r\n");
					
					osThreadDef(LED1, Start_LED1, osPriorityNormal, 0, 128);
					LED1Handle = osThreadCreate(osThread(LED1), NULL);
					
					if(LED1Handle != NULL)
						printf("任务1创建成功");
				}
				else
				{
					printf("任务1已存在,准备删除\r\n");
					osThreadTerminate(LED1Handle);
					LED1Handle = NULL;//手动置null
				}
			}
			while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
  /* USER CODE END Start_KEY1 */
}

/* USER CODE BEGIN Header_Start_KEY2 */
/**
* @brief Function implementing the KEY2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_KEY2 */
void Start_KEY2(void const * argument)
{
  /* USER CODE BEGIN Start_KEY2 */
	static uint8_t flag = 0;
  /* Infinite loop */
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET)
		{
			osDelay(10);//防抖
			if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET)
			{
				printf("按键2按下\r\n");
				if (flag == 0)
				{
					osThreadSuspend(LED2Handle);
					printf("任务2已暂停\r\n");
					flag = 1;
				}
				else
				{
					osThreadResume(LED2Handle);
					printf("任务2已恢复\r\n");
					flag = 0;
				}
			}
			while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
  /* USER CODE END Start_KEY2 */
}

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

FreeRTOS(1):任务 的相关文章

  • C# HttpWebRequest 获取 HTTPS 网页内容

    在 C 中 可以使用 HttpWebRequest 类来获取 HTTPS 网页内容 需要注意的是 HTTPS 网页采用了 SSL TLS 加密传输机制 必须在发送请求之前获取服务器端的证书并进行验证才能成功获取网页内容 下面是一份示例代码

随机推荐

  • js--纯js实现省市地区三级联动

    QQ自己的JS省市区三级联动 已验证 使用引用外部JS来实现三级联动的 JS如下 http ip qq com js geo js
  • CPU是如何工作的

    晶体管到门电路 二极管的工作 相信大家都知道二极管的工作特性 那如何使用二极管去构建一个门电路呢 二极管的与门 上拉电阻 只有AB同时为高电平 输出为高电平 二极管的或门 下拉电阻 只要AB任一一个为高电平 输出为高电平 MOS管的工作 在
  • 初学React,制作案例页面1

    通过node搭建开发环境 先下载node并安装 然后在cmd中用npm命令下载react脚手架 我选择了react bootstrap作为UI框架 所以也在cmd中下载react bootstrap资源包 npm install g cre
  • C++ - 成员函数(member function)模板(template) 详解 及 代码

    成员函数 member function 模板 template 详解 及 代码 本文地址 http blog csdn net caroline wendy article details 16918085 成员模板 member tem
  • Flink Table API 与 Flink SQL 实现Kafka To Kafka 版本1.12

    Table API版本 0 前提 1 创建流和表执行环境 2 连接Source并创建Table 3 筛选Table对象中的数据 4 连接Sink并创建临时表 5 将Table对象写入临时表 测试 杠精打住 SQL 版本 最近有铁汁问我 一闪
  • leetcode 第 1025 题:除数博弈(C++)

    1025 除数博弈 力扣 LeetCode 第一反应是动态规划 f i 是否为true取决于在 0 i 之间能否找到一个因数j使得f i j 为false 也就是能否找到一个因数使得鲍勃必败 class Solution public bo
  • poj 3074 Sudoku

    Time Limit 1000MS Memory Limit 65536K Total Submissions 7613 Accepted 2696 Description In the game of Sudoku you are giv
  • ST官方库函数之GPIO复位函数void GPIO_DeInit(GPIO_TypeDef* GPIOx) 的理解

    通常我们需要对ST单片机的整个IO进行复位操作 这是我们可以用到复位函数void GPIO DeInit GPIO TypeDef GPIOx 先来看一下这个函数的定义 void GPIO DeInit GPIO TypeDef GPIOx
  • JUST技术:时空轨迹挖掘助力物流小哥减负增效

    电商的发展需要依赖高效而可靠的物流服务 如何通过大数据和人工智能技术提升物流小哥的配送效率是我们一直在深入研究的问题 在刚刚结束的数据挖掘顶会KDD2020中 这篇 Doing in One Go Delivery Time Inferen
  • 31岁拿了阿里P6的offer,还有必要去吗?

    前几天看到一个非常现实且扎心的提问 31岁拿了阿里P6的offer 要去吗 首先给大家简单普及一下 估计很多小伙伴对阿里P6的概念比较模糊 甚至不少人觉得阿里P几啊什么的 就都很牛b 其实不然 P级只是阿里现行的职级划分体系 校招生进入阿里
  • sbt入门

    sbt入门 sbt 介绍 sbt 下载 sbt 安装 sbt 入门 helloworld sbt 目录结构 sbt 命令 总结 sbt 介绍 sbt是什么 sbt到底是什么 在网上找找 似乎找不到一个非常准确 而且大家都认可的定义 我自己对
  • FASTJSON和JACKSON基本使用

    Json是一种轻量级的数据交换格式 采用一种 键 值 对的文本格式来存储和表示数据 在系统交换数据过程中常常被使用 是一种理想的数据交换语言 在使用Java做Web开发时 不可避免的会遇到Json的使用 JSON形式与语法 JSON对象 我
  • linux使用xe命令管理远程xenserver机器

    1 linux下安装xe工具 xenserver6 2的dom0是32位centos5系统 xenserver6 5是64位centos系统 xe工具都是以rpm包形式提供 在ubuntu下安装时 先要用alien转换rpm包为deb包 注
  • group by的使用场景

    group by的使用场景 自我理解 一般配合sum使用 用来分组统计总 量 面试的时候最好说我们当时用的group 加sum查询每个sku的销量 group by 分组 查询 就是把记录集中的记录按一定规则进行 分组统计 假设一个学生名单
  • Android 关于inflate

    通俗的说 inflate就相当于将一个xml中定义的布局找出来 因为在一个Activity里如果直接用findViewById 的话 对应的是setConentView 的那个layout里的组件 因此如果你的Activity里如果用到别的
  • AWK入门到精通系列——awk快速入门

    简介 AWK是一个优良的文本处理工具 Linux及Unix环境中现有的功能最强大的数据处理引擎之一 这种编程及数据操作语言 其名称得自于它的创始人阿尔佛雷德 艾侯 彼得 温伯格和布莱恩 柯林汉姓氏的首个字母 的最大功能取决于一个人所拥有的知
  • nrm下载报错(C:\Users\16500\AppData\Roaming\npm\node_modules\nrm\cli.js:9const open = require(‘open‘)问题解决

    问题报错 建议大家下载nrm的时候尽量在node js 16版本之上 不然会造成很多报错问题无法解决 node 16版本下载地址 node js 16 问题原因是应该使用 open 的 CommonJs规范的包 现在 open v9 0 0
  • 华为云计算01——虚拟化技术

    云计算概述 云计算不是一个新的技术 它是通过虚拟化技术 将物理硬件资源虚拟化成为多个能独立运行且相同的 能为多个虚拟机提供服务 最开始云计算由亚马逊提出 并完成了云计算的初步架构 但是 亚马逊云是私有的 未公布其代码与实现原理 而后来开源社
  • Flutter drawer 侧滑实现二(通过点击现实显示侧滑栏)

    1 首先在需要使用的页面加入下方代码 GlobalKey
  • FreeRTOS(1):任务

    目录 一 FreeRTOS 介绍 什么是 FreeRTOS 为什么选择 FreeRTOS FreeRTOS 资料与源码下载 FreeRTOS 实现多任务的原理 二 移植 FreeRTOS 到STM32 手动移植 使用CubeMX快速移植 快