【第20期】延时的艺术:HAL_Delay vs vTaskDelay

裸机与RTOS核心差异:裸机的延时是**“死等”(烧电、霸占 CPU);RTOS 的延时是“挂起”**(让权、省电)。

1. 裸机延时:焦虑的失眠者 (HAL_Delay)

我们在学习 STM32 的第一天就用过 HAL_Delay()。来看看它的底层逻辑(简化版):

void HAL_Delay(uint32_t Delay) {
    uint32_t tickstart = HAL_GetTick(); // 记录开始时间
    
    // 死循环检查
    while ((HAL_GetTick() - tickstart) < Delay) {
        // 空转!CPU 在这里疯狂跑圈,什么有意义的事都没做。
        // 就像一个人盯着手表看,每一秒都数着过。
    }
}

后果

  1. 霸道:当 CPU 执行 HAL_Delay(1000) 时,这 1 秒钟内,主循环里排在后面的所有任务(按键扫描、屏幕刷新)全部被迫暂停。整个系统处于“假死”状态。

  2. 烧电:虽然 CPU 没干正事,但它处于全速运行状态(Run Mode),电流消耗是最大的(例如 20mA)。

2. RTOS 延时:定闹钟睡觉 (vTaskDelay)

RTOS 的 vTaskDelay (FreeRTOS) 或 OS_Delay (uCOS) 完全不同。

当你在任务 A 中调用 vTaskDelay(1000) 时,操作系统内核会做一系列复杂的动作:

  1. 移出:调度器把任务 A 从**【就绪列表 (Ready List)】**中拿走。这意味着调度器下次挑选“谁来运行”时,根本不会看任务 A 一眼。

  2. 记录:调度器把任务 A 放入**【延时列表 (Delayed List)】**,并给它贴个条子:“在系统时间到达 X+1000 时叫醒我”。

  3. 让权 (Yield):任务 A 此时交出 CPU 使用权。调度器立刻去【就绪列表】找优先级最高的任务 B。

  4. 切换:CPU 保存 A 的现场,恢复 B 的现场,开始运行 B。

直观感受: 任务 A 说:“我要睡 1 秒。” 然后它就真的“消失”了。CPU 转头去干别的事。直到 1 秒后,系统滴答中断(SysTick)发现时间到了,才会把任务 A 从“小黑屋”里放出来,重新回到【就绪列表】排队。


3. 神奇的空闲任务 (Idle Task)

你可能会问:“如果系统里只有任务 A,它延时了,CPU 把权交出来,交给谁呢?”

这时候,RTOS 会自动创建一个最低优先级的保底任务——空闲任务 (Idle Task)

当所有业务任务都处于“阻塞”或“延时”状态时,CPU 就会运行空闲任务。 更厉害的是,我们可以在空闲任务里植入低功耗代码(Hook 函数):

void vApplicationIdleHook(void) {
    // 汇编指令 WFI (Wait For Interrupt)
    // 它的作用是:CPU 暂停运行,停止取指,时钟停振,进入休眠。
    // 直到下一个中断(比如 SysTick 或 串口中断)来临,CPU 才会瞬间醒来。
    __WFI(); 
}

巨大的功耗差异

  • 裸机 Delay:CPU 100% 时间全速跑,电流 ~20mA。

  • RTOS Delay:如果任务大部分时间在 Delay,CPU 大部分时间在 Idle Task 里执行 WFI 睡觉。电流可能降到 ~5mA 甚至更低。


4. 这里的“坑”:相对延时 vs 绝对延时

RTOS 通常提供两种延时 API,新手容易混淆。

  • vTaskDelay (相对延时)

    • 含义:从调用这一行代码的时刻开始,延时 N 个节拍。

    • 场景:简单的 Sleep

    • 缺点:如果有高优先级中断打断,或者任务执行本身耗时,会导致周期累计误差。比如你想每 1000ms 闪一次灯,结果变成了 1005ms, 1010ms...

  • vTaskDelayUntil (绝对延时)

    • 含义:从上一次唤醒的时刻算起,让整个任务周期严格等于 N 个节拍。

    • 场景:需要高精度周期的采样任务(如每 2ms 读取一次 ADC)。它会自动扣除任务执行本身消耗的时间,多退少补。


总结陈词

  1. HAL_Delay忙等待 (Busy Wait),既阻碍别人运行,又浪费电能,是 RTOS 编程的大忌。

  2. vTaskDelay阻塞 (Blocking),它是 RTOS 实现多任务并发调度的基础——只有你让出了 CPU,别人才能运行。

  3. Idle Task + WFI 是 RTOS 天然低功耗的秘诀。

int key1_state(){ static int key_Num=0; static int Time_Out=0; static int times=0; if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4)==GPIO_PIN_RESET){ if(HAL_GetTick()-Time_Out>=20){ Time_Out=HAL_GetTick(); times=1; if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4)==GPIO_PIN_RESET&&times==1){ key_Num+=1; } } } if(key_Num>2) key_Num=0; return key_Num; } void servo_proc() { static int key_Num=0; key_Num=key1_state(); if (key_Num==0) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, SERVO_0); } else if (key_Num==1) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, SERVO_45); } else if(key_Num==2){ __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, SERVO_90); } } void servo_reset(){ __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, SERVO_0); } void DeviceTask(void *argument){ while(1){ servo_proc(); vTaskDelay(pdMS_TO_TICKS(50)); } } // 舵机控制信号定义 #define SERVO_0 100 // 停止信号 (0.5ms) #define SERVO_45 200 // 45°信号 (1ms) #define SERVO_90 300 // 90°信号 (1.5ms) void MX_TIM2_Init(void) { /* USER CODE BEGIN TIM2_Init 0 */ /* USER CODE END TIM2_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfigOC = {0}; /* USER CODE BEGIN TIM2_Init 1 */ /* USER CODE END TIM2_Init 1 */ htim2.Instance = TIM2; htim2.Init.Prescaler = 360-1; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 2000-1; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } if (HAL_TIM_PWM_Init(&htim2) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 0; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) { Error_Handler(); } if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) { Error_Handler(); } if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_3) != HAL_OK) { Error_Handler(); } if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_4) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM2_Init 2 */ /* USER CODE END TIM2_Init 2 */ HAL_TIM_MspPostInit(&htim2); } void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle) { if(tim_baseHandle->Instance==TIM1) { /* USER CODE BEGIN TIM1_MspInit 0 */ /* USER CODE END TIM1_MspInit 0 */ /* TIM1 clock enable */ __HAL_RCC_TIM1_CLK_ENABLE(); /* USER CODE BEGIN TIM1_MspInit 1 */ /* USER CODE END TIM1_MspInit 1 */ } else if(tim_baseHandle->Instance==TIM2) { /* USER CODE BEGIN TIM2_MspInit 0 */ /* USER CODE END TIM2_MspInit 0 */ /* TIM2 clock enable */ __HAL_RCC_TIM2_CLK_ENABLE(); /* USER CODE BEGIN TIM2_MspInit 1 */ /* USER CODE END TIM2_MspInit 1 */ } 我想执行用按键来控制舵机从0°到90°进行换挡的任务,但舵机却完全没有响应
06-13
/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "tim.h" #include "usart.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "FreeRTOS.h" #include "task.h" #include "stdio.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 10); return ch; } //启动任务 void START(void * pvParam); #define START_STACK_SIZE 128 #define START_PRIORITY 3 TaskHandle_t START_handle; //task1 void task1(void * pvParam); #define TASK1_STACK_SIZE 128 #define TASK1_PRIORITY 3 TaskHandle_t task1_handle;//任务的身份证 //task2 void task2(void * pvParam); #define TASK2_STACK_SIZE 128 #define TASK2_PRIORITY 2 TaskHandle_t task2_handle; //task3 删除任务 void task3(void * pvParam); #define TASK3_STACK_SIZE 256 #define TASK3_PRIORITY 2 TaskHandle_t task3_handle; void FreeRTOS_Start(void) { xTaskCreate(START,"START",START_STACK_SIZE,NULL,START_PRIORITY,&START_handle); //创建启动函数 //portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() //启动定时器的定义,不需要在主函数开启 vTaskStartScheduler();//开启调度 } void START(void * pvParam) { xTaskCreate(task1,"task1",TASK1_STACK_SIZE,NULL,TASK1_PRIORITY,&task1_handle); //第一个参数是任务,第二个是任务名,第三个是堆栈的字数,第四个PvParam一般为空,第五个是优先级,数字越大优先级越高,第六个是身份证 //xTaskCreate(task2,"task2",TASK2_STACK_SIZE,NULL,TASK2_PRIORITY,&task2_handle); xTaskCreate(task3,"task3",TASK3_STACK_SIZE,NULL,TASK3_PRIORITY,&task3_handle); vTaskDelete(NULL);//删除自身 } uint16_t count=0; void task1(void * pvParam)//void*就是任意类型的指针 { while(1) { HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET); vTaskDelay(500); //HAL_Delay(500); HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET); vTaskDelay(500); } } void task2(void * pvParam) { while(1) { HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET); vTaskDelay(1000); //HAL_Delay(500); HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET); vTaskDelay(1000); //HAL_Delay(500); } } uint8_t sum=0; uint8_t buffer[256]; void task3(void * pvParam) { while(1) { if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==RESET) { vTaskDelay(20); while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==RESET); vTaskDelay(20); vTaskGetRunTimeStats((char *) buffer ); HAL_UART_Transmit(&huart1, (uint8_t *)&buffer, sizeof(buffer), 10000); } else if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_11)==RESET) { vTaskDelay(20); while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_11)==RESET); vTaskDelay(20); } } } /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ 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 */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ void FreeRTOS_Start(void); /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM1_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ //HAL_TIM_Base_Start_IT(&htim1);//开启中断 FreeRTOS_Start();//启用调度器,后续的代码没有意义 /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ 为什么无法打印
11-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值