利用STM32CubeMX和keil模拟器,3天入门FreeRTOS(2.2) —— 延时函数

发布时间:2024年01月24日

前言

(1)FreeRTOS是我一天过完的,由此回忆并且记录一下。个人认为,如果只是入门,利用STM32CubeMX是一个非常好的选择。学习完本系列课程之后,再去学习网上的一些其他课程也许会简单很多。
(2)本系列课程是使用的keil软件仿真平台,所以对于没有开发板的同学也可也进行学习。
(3)叠甲,再次强调,本系列课程仅仅用于入门。学习完之后建议还要再去寻找其他课程加深理解。
(4)本系列博客对应代码仓库:

前期准备

(1)将1.1章节博客最终的代码复制一份。

在这里插入图片描述

(2)首先,我们调整高其中一个任务的优先级(按Ctrl+F搜索add threads 即可找到如下部分,并进行补充)

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
	keilTaskHandle = xTaskCreateStatic(StartKeilTask,"KeilTask", 128, NULL, osPriorityLow2, g_pucStackKeilTaskBuff,&g_TCBKeilTask);
	if(keilTaskHandle == NULL)
	{
		printf("KeilTask creation failed\r\n");
	}

(3)填充StartCubemxTask()函数。(按Ctrl+F搜索StartCubemxTask即可找到任务函数)

/* USER CODE END Header_StartCubemxTask */
void StartCubemxTask(void *argument)
{
  /* USER CODE BEGIN StartCubemxTask */
	char *CubemxTaskPrintf = (char *)argument;
  /* Infinite loop */
  for(;;)
  {
		printf(CubemxTaskPrintf);
		HAL_Delay(100);
  }
  /* USER CODE END StartCubemxTask */
}

几种延时函数介绍

HAL_Delay()

理论

(1)这是一个阻塞式延时函数,会占用CPU的资源,不会切换到其他任务。
(2)前面章节,我们讲解了FreeRTOS的4种任务状态。在多任务中,使用阻塞延时函数,其实就是一直在Running(运行态)死等延时结束,这样是非常占用CPU资源的,用粗俗一点的话来说,就是占着茅坑不拉*。所以说,对于RTOS而言,是不建议使用HAL_Delay()这种阻塞延时函数的。
(3)其实在裸机中,也不建议使用HAL_Delay()这种阻塞式延时函数。
(4)HAL_Delay()函数声明如下,单位是1ms,传入的值要求是0~65535之间。

/**
 * @brief  阻塞式延时,单位1ms
 *
 * @param  要延时的时间,单位ms,取值范围是0~65535
 *
 * @return 无
 */
__weak void HAL_Delay(uint32_t Delay);

在这里插入图片描述

实操

(1)这里不需要进行任何调整,代码如下。(按Ctrl+F搜索StartKeilTask即可找到任务函数)

void StartKeilTask(void *argument)
{
	while(1)
	{
		//实验1,阻塞式延时
		HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
		HAL_Delay(100);
	}
}

(2)开始调试,会发现,串口没有数据打印。这个是为什么呢?原因很简单,因为波形变化的任务StartKeilTask()优先级高于串口任务StartCubemxTask(),而又因为FreeRTOS作为抢占式操作系统,高优先级任务不停止,低优先级任务无法执行。因此,串口将永远无法出现数据打印信息。

在这里插入图片描述

vTaskDelay()

理论

(1)vTaskDelay()函数的实现,其实就是类似一次任务挂起,等延时时间到,恢复任务。因此vTaskDelay()函数不会造成阻塞情况,当调用vTaskDelay()函数之后,任务进入阻塞态,运行态执行其他任务。这样做能够提高CPU的使用率,同时给低优先级的任务执行空间。
(2)vTaskDelay()函数的声明如下,需要注意的是,vTaskDelay()函数的单位并一定是1msvTaskDelay()函数是以时钟节拍(tick)为单位的时间,我们可以根据portTICK_PERIOD_MS这宏知道时钟节拍是多少。
(3)我们要进行延时101ms,就传入101/portTICK_PERIOD_MS需要各位了解的是,如果时钟节拍(tick)是1ms,那么最终延时的确实是101ms。但是如果时钟节拍(tick)是10ms,那么最终延时的时间是100ms。 因为我们的单位是时钟节拍(tick),最终的延时时间要是时钟节拍(tick)的整数倍。

/**
 * @brief  非阻塞式相对延时
 *
 * @param  要延时的时间,单位tick
 *
 * @return 无
 */
void vTaskDelay( const TickType_t xTicksToDelay );

(4)如果要使用vTaskDelay()函数,需要确认在FreeRTOSConfig.h文件中将INCLUDE_vTaskDelay这个宏定义置1。

在这里插入图片描述

(5)vTaskDelay()函数在使用过程中,需要注意以下两点。
<1>临界区中不能使用。(临界区的知识将会在后面的同步与互斥章节讲解)
<2>不能在中断服务中使用。

// 进入临界区
pthread_mutex_lock(&mutex);

// 访问共享资源,这里不能使用这里不能使用 vTaskDelay()
sharedVariable++;

// 离开临界区
pthread_mutex_unlock(&mutex);

实操

(1)按Ctrl+F搜索StartKeilTask即可找到任务函数,并且进行如下补充。

void StartKeilTask(void *argument)
{
	while(1)
	{
		//实验2,非阻塞式相对延时
		HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
		vTaskDelay(100/portTICK_PERIOD_MS); 
	}
}

(2)开始仿真此时我们可以看到,虚拟逻辑分析仪上有电平变化,并且虚拟串口有数据打印了。对照这两张图的结果,就很好的能够理解和区分vTaskDelay()HAL_Delay()这两个函数的阻塞与非阻塞的意思。

在这里插入图片描述

vTaskDelayUntil()

理论

(1)vTaskDelay()延时函数很好理解,即相对于 vTaskDelay() 被调用的时刻开始进行相对的时间延时。
(2)但是,假如我想像裸机定时器那样,进行周期性的绝对延时,应当如何处理呢?为了解决这个问题,FreeRTOS提供了vTaskDelayUntil()
(3)vTaskDelayUntil()函数每次延时是根据调用xTaskGetTickCount()的时刻开始,然后间隔你设置的延时时间进行延时。xTaskGetTickCount()被调用的时刻,你可以理解为裸机定时器的定时器计数开始时刻,传入的xTimeIncrement就是裸机定时的周期定时时间,单位是tick
(4)需要注意的是,这里的vTaskDelayUntil()提供了裸机定时器的效果,但是却有些许不同,裸机的定时器只要配置好之后,就会以绝对时间周期性调用,之后就不再需要理会。这里不同,你每次被唤醒之后,还想以绝对时间周期性延时,那么就需要再调用一次vTaskDelayUntil()重新装载延时时间。否则将会停止延时。

/**
 * @brief  非阻塞式相对延时
 *
 * @param  无
 *
 * @return 系统时钟节拍数
 */
TickType_t xTaskGetTickCount( void );
/**
 * @brief  非阻塞式相对延时
 *
 * @param  -pxPreviousWakeTime 存储任务上一次唤醒的时间,如果第一次使用,调用xTaskGetTickCount()进行初始化
 *         -xTimeIncrement     要延时的时间,单位tick
 *
 * @return 无
 */
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement );

实操1—绝对延时和相对延时区别

(1)按Ctrl+F搜索StartKeilTask即可找到任务函数,并且进行如下补充。

void StartKeilTask(void *argument)
{
	//实验3,周期性非阻塞式延时
	TickType_t preTime = xTaskGetTickCount(); 
	int i = 1;
	while(1)
	{
		//实验3,非阻塞式绝对延时,了解绝对延时和相对延时区别
		HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
		if(i < 4)
		{
			i++;
			HAL_Delay(i*20);
		}
		else
		{
			i = 1;
			HAL_Delay(i*20);
		}
		HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
		//vTaskDelay(100/portTICK_PERIOD_MS);
		vTaskDelayUntil(&preTime, 100/portTICK_PERIOD_MS);
	}
}

(2)我们对比vTaskDelay(100/portTICK_PERIOD_MS)vTaskDelayUntil(&preTime, 100/portTICK_PERIOD_MS)看一下结果。
<1>在vTaskDelay(100/portTICK_PERIOD_MS)的测试结果中,我们发现,低电平的持续时间永远是0.1s。因为他的延时是基于vTaskDelay(100/portTICK_PERIOD_MS)被调用时刻开启的相对延时。

在这里插入图片描述

<2>在vTaskDelayUntil(&preTime, 100/portTICK_PERIOD_MS)的测试结果中,我们发现,两次上升沿的间隔时间永远是0.1s。因为这个的延时是基于pxPreviousWakeTime这个参数为基准的绝对延时,而这个参数的值一般都是xTaskGetTickCount()函数提供。

在这里插入图片描述

实操2—vTaskDelayUntil()的延时特性

(1)因为,上面我说了vTaskDelayUntil()提供了裸机定时器的效果,但是却有些许不同。这里我们将会展示,如果只调用vTaskDelayUntil()函数10次,10次之后,是否还会存在低电平。(注意,如果vTaskDelayUntil()不再具备延时效果之后,将不会看到低电平。因为引脚置低之后,将会马上置高。

void StartKeilTask(void *argument)
{
	//实验3,周期性非阻塞式延时
	TickType_t preTime = xTaskGetTickCount(); 
	int i = 1,j = 0;
	while(1)
	{
		//实验4,了解vTaskDelayUntil()的延时特性
		HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
		if(i < 4)
		{
			i++;
			HAL_Delay(i*20);
		}
		else
		{
			i = 1;
			HAL_Delay(i*20);
		}
		HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
		if(j<10)
		{
			j++;
			vTaskDelayUntil(&preTime, 100/portTICK_PERIOD_MS);
		}
	}
}

(2)测试后发现,这个延时函数,每次绝对延时,都需要被调用不能够像裸机定时器那样自动延时。

在这里插入图片描述

osDelay()和osDelayUntil()

(1)这两个函数分别是vTaskDelay()vTaskDelayUntil()RTOS抽象层。使用方法和上述一样,不再赘述。

参考

(1)C站:freertos的vTaskDelay使用禁忌
(2)C站:FreeRTOS一天一个小知识之任务延时函数vTaskDelay
(3)FreeRTOS官方文档:vTaskDelay函数介绍
(4)FreeRTOS官方文档:vTaskDelayUntil函数介绍

文章来源:https://blog.csdn.net/qq_63922192/article/details/134704977
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。