tag 标签: freertos

相关帖子
相关博文
  • 热度 5
    2020-8-23 17:10
    3670 次阅读|
    3 个评论
    定时器 硬件定时器: CPU内部自带的定时器模块,通过初始化、配置可以实现定时,定时时间到以后就会执行相应的定时器中断处理函数。硬件定时器一般都带有其他功能,比如PWM输出、输入捕获等功能。但缺点是硬件定时器数量少!!! 软件定时器: 软件定时器允许设置一段时间,当设置的时间到达以后就执行指定的功能函数,被定时器调用的这个功能函数就叫做定时器的回调函数。回调函数的两次执行间隔,就叫做定时器的定时周期。简而言之,当定时器的定时周期到了就会执行回调函数。 回调函数的注意事项: 软件定时器的 回调函数是在定时器服务任务中执行 的,所以一定 不能在回调函数中调用任何会阻塞任务的 API 函数 !比如,定时器回调函数中 千万不能调用 vTaskDelay()、vTaskDelayUnti() ,还有一些 访问队列或者信号量的非零阻塞时间的 API 函数也不能调用。 定时器服务 FreeRTOS软件定时器的API函数,大多都 使用队列发送命令给定时器服务任务 。这个队列叫做定时器命令队列。定时器命令队列是提供给FreeRTOS的软件定时器使用的,用户不能直接访问!通信过程如下所示: 通信过程详解: 定时器服务任务,是在任务调度器中创建的: 在创建定时器服务任务中,有创建定时器任务的函数: 定时器服务任务函数分析: 定时器消息队列的命令,是在第3个函数中处理的。 其中,创建队列的函数如下所示: 首先创建了一个定时器消息队列结构体: 接下来创建定时器命令消息队列: 定时器相关配置: 之前提到,软件定时器有一个定时器服务任务和定时器命令队列,这两个东西肯定是要配置的,配置方法和之前其他条件编译一样,而且相关的配置也是放到文件 FreeRTOSConfig.h 中的,涉及到的配置如下: 单次定时器和周期定时器: 定时器相关的操作与API函数: 1 复位软件定时器 2 创建软件定时器: 这个宏 portTICK_PERIOD_MS 非常有用,如下所示: # define configTICK_RATE_HZ ((TickType_t)1000) # define portTICK_PERIOD_MS ((TickType_t)1000 / configTICK_RATE_HZ) 其中,configTICK_RATE_HZ 为每秒的 Tick 频率,portTICK_PERIOD_MS 则为每 Tick 一次需要多少 ms(周期ms)。 这个宏定义,可以用于 将时间转换成 Tick 数,即 Tick 数 = 总时间 / (每次 Tick 的时间) 如我们需要 延时800ms ,则 Tick_count = 800 / (portTICK_PERIOD_MS) 3 开启定时器: 4 停止定时器: 测试实验 软件定时器测试实验: 创建两个任务, start_task 用于创建 timer_ctrl_task 和两个软件定时器; 周期定时器(周期1000节拍),单次定时器(周期2000节拍) timer_ctrl_task 任务通过检测2个按键,来开始或者停止两个软件定时器; 程序很简单,如下所示: TimerHandle_t auto_reload_timer_handle; // 周期定时器句柄 TimerHandle_t one_shot_timer_handle; // 单次定时器句柄 void start_task ( void *pvParameters) { taskENTER_CRITICAL(); // 创建周期定时器 auto_reload_timer_handle = xTimerCreate(( char * ) "auto_reload_timer" , (TickType_t )( 1000 / portTICK_PERIOD_MS), // 定时1秒 (UBaseType_t )pdTRUE, // 周期 ( void * ) 1 , (TimerCallbackFunction_t)auto_reload_timer_callback); // 创建单次定时器 one_shot_timer_handle = xTimerCreate(( char * ) "one_shot_timer" , (TickType_t )( 2000 / portTICK_PERIOD_MS), // 定时2秒 (UBaseType_t )pdFALSE, // 单次 ( void * ) 2 , (TimerCallbackFunction_t)one_shot_timer_callback); // 创建定时器控制任务 xTaskCreate((TaskFunction_t )timer_ctrl_task, ( char * ) "timer_ctrl_task" , ( uint16_t )TIMER_TASK_SIZE, ( void * ) NULL , (UBaseType_t )TIMER_TASK_PRIO, (TaskHandle_t * )&timer_task_Handle); vTaskDelete(Start_Task_Handle); taskEXIT_CRITICAL(); } void timer_ctrl_task ( void *pvParameters) { printf ( "\r\ntimer_ctrl_task start!\r\n" ); printf ( "press KEY1 to start auto_reload_timer\r\n" ); printf ( "press KEY2 to start one_shot_timer\r\n" ); for (;;) { if (key_scan(KEY1_GPIO_Port, KEY1_Pin) == KEY_ON) { // 发送队列阻塞时间设置为0 if (xTimerStart(auto_reload_timer_handle, 0 ) == pdFAIL) { printf ( "auto_reload_timer start failed\r\n" ); } } if (key_scan(KEY2_GPIO_Port, KEY2_Pin) == KEY_ON) { // 发送队列阻塞时间设置为0 if (xTimerStart(one_shot_timer_handle, 0 ) == pdFAIL) { printf ( "one_shot_timer start failed\r\n" ); } } vTaskDelay( 50 ); } } // 周期定时器回调函数 void auto_reload_timer_callback (TimerHandle_t pxTimer) { static uint16_t times = 0 ; printf ( "auto_reload_timer running = %d times\r\n" , ++times); } // 单次定时器回调函数 void one_shot_timer_callback (TimerHandle_t pxTimer) { static uint16_t times = 0 ; printf ( "one_shot_timer running = %d times\r\n" , ++times); } 程序运行结果如下: 多定时器共用回调函数: 前面提到,创建定时器时有一个pvTimerID,可以用于区分不同的定时器,如下所示: 函数原型如下所示: 其实就是根据定时器句柄,来获取 TimerID 通过在回调函数中调用这个函数获取ID,就能直到当前是哪个定时器调用回调函数,如下所示: // 定时器共用回调函数 void timer_common_callback (TimerHandle_t pxTimer) { static uint16_t one_shot_times = 0 ; static uint16_t auto_reload_times = 0 ; if (pvTimerGetTimerID(pxTimer) == ( void *) 1 ) { printf ( "周期定时器运行%d次\r\n" , ++auto_reload_times); } if (pvTimerGetTimerID(pxTimer) == ( void *) 2 ) { printf ( "单次定时器运行%d次\r\n" , ++one_shot_times); } } 测试结果和之前分开写回调函数一致,如下所示: 既然创建定时器时,已经给每个定时器创建了句柄,那么定时器回调函数中,应该也可以直接通过句柄来进行当前是那个定时器在调用回调函数,如下所示: // 定时器共用回调函数 void timer_common_callback (TimerHandle_t pxTimer) { static uint16_t one_shot_times = 0 ; static uint16_t auto_reload_times = 0 ; # if 0 if (pvTimerGetTimerID(pxTimer) == ( void *) 1 ) { printf ( "周期定时器运行%d次\r\n" , ++auto_reload_times); } if (pvTimerGetTimerID(pxTimer) == ( void *) 2 ) { printf ( "单次定时器运行%d次\r\n" , ++one_shot_times); } # else if (pxTimer == auto_reload_timer_handle) { printf ( "周期定时器运行%d次\r\n" , ++auto_reload_times); } if (pxTimer == one_shot_timer_handle) { printf ( "单次定时器运行%d次\r\n" , ++one_shot_times); } # endif } 两种方式的运行结果相同,但推荐使用获取ID的方式,因为这是FreeRTOS推荐的。 转载于: https://blog.csdn.net/dingyc_ee/article/details/104118508
  • 热度 4
    2020-8-23 17:05
    2209 次阅读|
    0 个评论
    计数型信号量简介: 计数型信号量的创建: 计数型信号量动态创建函数: 释放和获取信号量(与二值信号量相同) 释放信号量: 获取信号量: 测试实验 用按键来模拟事件,按键按下后表示有事件发生,则释放计数型信号量。 创建两个任务,任务1用于按键检测和释放信号量,按键2用于获取信号量。 测试程序如下所示: void start_task ( void *pvParameters) { taskENTER_CRITICAL(); count_semphr = xSemaphoreCreateCounting( 5 , 0 ); if (count_semphr != NULL ) { printf ( "计数型信号量创建成功\n" ); } else { printf ( "计数型信号量创建失败\n" ); } // 创建任务1 xTaskCreate((TaskFunction_t ) task1_task, ( char * ) "task1_task" , ( uint16_t ) TASK1_TASK_SIZE, ( void * ) NULL , (UBaseType_t ) TASK1_TASK_PRIO, (TaskHandle_t * ) &Task1_Handle); // 创建任务2 xTaskCreate((TaskFunction_t )task2_task, ( char * ) "task2_task" , ( uint16_t )TASK2_TASK_SIZE, ( void * ) NULL , (UBaseType_t )TASK2_TASK_PRIO, (TaskHandle_t * )&Task2_Handle); taskEXIT_CRITICAL(); // 删除开始任务 vTaskDelete(Start_Task_Handle); } void task1_task ( void *pvParameters) { BaseType_t error_state; for (;;) { // 按键KEY1用于释放信号量 if (key_scan(KEY1_GPIO_Port, KEY1_Pin) == KEY_ON) { if (count_semphr != NULL ) { error_state = xSemaphoreGive(count_semphr); if (error_state == pdTRUE) { printf ( "信号量释放成功\n" ); } else { printf ( "信号量释放失败\n" ); } } } vTaskDelay( 20 ); } } void task2_task ( void *pvParameters) { BaseType_t error_state; UBaseType_t current_semphr_value; for (;;) { // 按键KEY2用于获取信号量 if (key_scan(KEY2_GPIO_Port, KEY2_Pin) == KEY_ON) { if (count_semphr != NULL ) { // 等待获取信号量 error_state = xSemaphoreTake(count_semphr, portMAX_DELAY); if (error_state == pdTRUE) { printf ( "获取信号量成功\n" ); current_semphr_value = uxSemaphoreGetCount(count_semphr); printf ( "当前信号量计数值为%d\n" , ( int )current_semphr_value); } else { printf ( "获取信号量失败\n" ); } } } vTaskDelay( 50 ); } } 测试结果: 转载于: https://blog.csdn.net/dingyc_ee/article/details/104114161
  • 热度 4
    2020-8-23 16:54
    2215 次阅读|
    0 个评论
    信号量 1 信号量用于共享资源的访问: 2 信号量用于任务同步: 为什么一直说在中断服务函数中,不能够做太多的事情? 在进入中断服务函数时,低优先级的中断就不能响应,同类型的中断也无法响应,所以就要求ISR一定要短,快进快出。 最好的解决方案时,在中断服务函数中发送一个信号量,在任务中等待信号量,实现任务同步。 二值信号量 二值信号量简介: 二值信号量其实就是一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空的,这不正好就是二值的吗? 任务和中断使用这个特殊队列不用在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的。可以利用这个机制来完成任务与中断之间的同步。 二值信号量执行流程: 创建信号量: 动态创建二值信号量: 实际上,信号量就是通过队列来实现的,源码如下: # define semSEMAPHORE_QUEUE_ITEM_LENGTH ((uint8_t)0U) # if (configSUPPORT_DYNAMIC_ALLOCATION == 1) # define xSemaphoreCreateBinary() xQueueGenericCreate((UBaseType_t)1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE) # endif 实际上,就是调用创建队列的函数。 信号量的创建方式如下所示: 释放信号量: 获取信号量: 获取函数参数说明: 中断中获取信号量: 二值信号量测试: 串口中断服务函数中接收数据,接收完成后(IDLE)释放二值信号量,任务中获取信号量,根据串口数据来响应。 程序如下所示: uint8_t RX_BUFFER ; // 串口接收缓冲区 SemaphoreHandle_t binary_semaphore = NULL ; // 二值信号量句柄 void start_task ( void *pvParameters) { taskENTER_CRITICAL(); binary_semaphore = xSemaphoreCreateBinary(); // 创建信号量 if (binary_semaphore != NULL ) { printf ( "信号量创建成功!\n" ); } else { printf ( "信号量创建失败!\n" ); } // 创建任务1 xTaskCreate((TaskFunction_t )task1_task, ( char * ) "task1_task" , ( uint16_t )TASK1_TASK_SIZE, ( void * ) NULL , (UBaseType_t )TASK1_TASK_PRIO, (TaskHandle_t * )&task1_task_Handle); // 创建任务2 xTaskCreate((TaskFunction_t )task2_task, ( char * ) "task2_task" , ( uint16_t )TASK2_TASK_SIZE, ( void * ) NULL , (UBaseType_t )TASK2_TASK_PRIO, (TaskHandle_t * )&task2_task_Handle); taskEXIT_CRITICAL(); // 删除开始任务 vTaskDelete(start_Task_Handle); } void task1_task ( void *pvParameters) { for (;;) { printf ( "task1 running\r\n" ); vTaskDelay( 1000 ); } } void task2_task ( void *pvParameters) { uint16_t count = 0 ; BaseType_t err_state; for (;;) { if (binary_semaphore != NULL ) { // 一直等待,直到获取到二值信号量 err_state = xSemaphoreTake(binary_semaphore, portMAX_DELAY); if (err_state == pdTRUE) { if ( strcmp (( char *)RX_BUFFER, "LED_ON" ) == 0 ) { LED_RED; } if ( strcmp (( char *)RX_BUFFER, "LED_OFF" ) == 0 ) { LED_ALL_OFF; } if ( strcmp (( char *)RX_BUFFER, "BEEP_ON" ) == 0 ) { BEEP_ON; } if ( strcmp (( char *)RX_BUFFER, "BEEP_OFF" ) == 0 ) { BEEP_OFF; } memset (RX_BUFFER, 0 , USART_BUFFER_LEN); } } printf ( "we get %d times binary\n" , ++count); vTaskDelay( 20 ); } } void USART1_IRQHandler ( void ) { static uint16_t i = 0 ; uint32_t temp; BaseType_t pxHigherPriorityTaskWoken; BaseType_t error_state; // 保存接收数据到RX_BUFFER缓冲区 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) { DR; __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE); } // 串口数据接收完成,串口空闲时,释放信号量 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { i = 0 ; if (binary_semaphore != NULL ) { error_state = xSemaphoreGiveFromISR(binary_semaphore, &pxHigherPriorityTaskWoken); if (error_state != pdTRUE) { printf ( "二值信号量释放失败!\n" ); } // 判断是否需要进行任务切换 if (pxHigherPriorityTaskWoken == pdTRUE) { taskYIELD(); } } // 清除空闲中断(依次读取SR DR) SR; DR; } } 测试结果: 转载于: https://blog.csdn.net/dingyc_ee/article/details/104108665
  • 热度 24
    2020-8-23 16:49
    2341 次阅读|
    0 个评论
    队列简介: 注意,队列长度并不是每个队列的字节数,而是能存多少条数据。如我们创建一个队列,每条消息最大10字节,队列可以保存4条消息,那么队列的长度为4。 队列的功能: 1 数据拷贝 为什么一定要使用数据拷贝? 考虑以下情况,我们在任务中创建了一个局部变量,而局部变量可能随时会被删掉,此时如果使用引用传递(指针),数据可能就会出现问题;另外,如果采用指针,则在这个变量被队列引用之前,变量值是不能改变的,这就意味着此时任务将不能继续随意使用此变量;如果采用数据拷贝,则不会出现上述问题。 2 多任务访问 队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中提取消息。 3 出队阻塞 4 入队阻塞 队列操作过程 1 创建队列 2 写入队列 3 读取队列 队列结构体原型: typedef struct QueueDefinition { int8_t *pcHead; /* 指向队列存储区开始地址 */ int8_t *pcTail; /* 指向队列存储区最后一个字节 */ int8_t *pcWriteTo; /* 指向存储区中下一个空闲区域 */ union /* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */ { int8_t *pcReadFrom; /* 当用作队列的时候指向最后一个出队的队列项首地址 */ UBaseType_t uxRecursiveCallCount; /* 当用作递归互斥量的时候用来记录递归互斥量被调用的次数 */ } u; List_t xTasksWaitingToSend; /* 等待发送任务列表,那些因为队列满导致入队失败而进入阻塞态的任务就会挂到此列表上 */ List_t xTasksWaitingToReceive; /* 等待接收任务列表,那些因为队列空导致出队失败而进入阻塞态的任务就会挂到此列表上 */ volatile UBaseType_t uxMessagesWaiting; /* 队列中当前队列项数量,也就是消息数 */ UBaseType_t uxLength; /* 创建队列时指定的队列长度,也就是队列中最大允许的队列项(消息)数量 */ UBaseType_t uxItemSize; /* 创建队列时指定的每个队列项(消息)最大长度,单位字节 */ /* 当队列上锁以后用来统计从队列中接收到的队列项数量,也就是出队的队列项数量,当队列没有上锁的话此字段为 queueUNLOCKED */ volatile int8_t cRxLock; /* 当队列上锁以后用来统计发送到队列中的队列项数量,也就是入队的队列项数量,当队列没有上锁的话此字段为 queueUNLOCKED */ volatile int8_t cTxLock; # if ((configSUPPORT_STATIC_ALLOCATION == 1) && (configSUPPORT_DYNAMIC_ALLOCATION == 1)) uint8_t ucStaticallyAllocated; /* 如果使用静态存储的话此字段设置为 pdTURE */ # endif # if (configUSE_QUEUE_SETS == 1) struct QueueDefinition * pxQueueSetContainer ; # endif # if (configUSE_TRACE_FACILITY == 1) UBaseType_t uxQueueNumber; uint8_t ucQueueType; # endif } xQUEUE; typedef xQUEUE Queue_t; 创建队列 1 动态创建队列 函数原型: 实现的功能,就是动态内存申请需要大小的内存 = 消息数 × 消息长度,然后将队列存储区指针偏移到缓存区。 QueueHandle_t xQueueGenericCreate ( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType) { Queue_t *pxNewQueue; size_t xQueueSizeInBytes; uint8_t *pucQueueStorage; (UBaseType_t) 0 ); if (uxItemSize == (UBaseType_t) 0 ) { /* There is not going to be a queue storage area. */ xQueueSizeInBytes = ( size_t ) 0 ; } else { /* Allocate enough space to hold the maximum number of items that can be in the queue at any time. */ xQueueSizeInBytes = ( size_t )(uxQueueLength * uxItemSize); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ } pxNewQueue = (Queue_t *)pvPortMalloc( sizeof (Queue_t) + xQueueSizeInBytes); if (pxNewQueue != NULL ) { /* Jump past the queue structure to find the location of the queue storage area. */ pucQueueStorage = (( uint8_t *)pxNewQueue) + sizeof (Queue_t); # if (configSUPPORT_STATIC_ALLOCATION == 1) { /* Queues can be created either statically or dynamically, so note this task was created dynamically in case it is later deleted. */ ucStaticallyAllocated = pdFALSE; } # endif /* configSUPPORT_STATIC_ALLOCATION */ prvInitialiseNewQueue(uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue); } return pxNewQueue; } 函数说明: 静态创建队列: 入队函数: 入队函数说明: 函数参数说明: 通用发送函数简单分析: 数据拷贝过程: 中断级队列发送: 下面是正点原子串口中断中发送队列的程序: void USART1_IRQHandler ( void ) //串口1中断服务程序 { u8 Res; BaseType_t xHigherPriorityTaskWoken; if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾) { Res = USART_ReceiveData(USART1); //读取接收到的数据 if ((USART_RX_STA & 0x8000 ) == 0 ) //接收未完成 { if (USART_RX_STA & 0x4000 ) //接收到了0x0d { if (Res != 0x0a ) USART_RX_STA = 0 ; //接收错误,重新开始 else USART_RX_STA |= 0x8000 ; //接收完成了 } else //还没收到0X0D { if (Res == 0x0d ) USART_RX_STA |= 0x4000 ; else { USART_RX_BUF = Res; USART_RX_STA++; if (USART_REC_LEN - 1 )) USART_RX_STA = 0 ; //接收数据错误,重新开始接收 } } } } //就向队列发送接收到的数据 if ((USART_RX_STA & 0x8000 ) && (Message_Queue != NULL )) { xQueueSendFromISR(Message_Queue, USART_RX_BUF, &xHigherPriorityTaskWoken); //向队列中发送数据 USART_RX_STA = 0 ; memset (USART_RX_BUF, 0 , USART_REC_LEN); //清除数据接收缓冲区USART_RX_BUF,用于下一次数据接收 portYIELD_FROM_ISR(xHigherPriorityTaskWoken); //如果需要的话进行一次任务切换 } } 其实这个程序,个人觉得局限性比较大,因为它规定了数据格式。总体思路是, 在串口接收中断中,将接收到的数据一个一个的保存到缓冲区里,等待串口数据接收完毕(可以通过读取寄存器标志位,串口空闲标志位来判断接收完毕)时,将缓冲区里的数据发送到队列中,然后清空缓冲区,并根据相关变量确定是否需要进行任务切换。 出队函数 删除读取队列函数参数说明: 不删除读取队列函数参数说明: 同样的,中断读取队列函数: 中断中进行任务切换的判别函数: portYIELD_FROM_ISR(xHigherPriorityTaskWoken); 队列实验 设计2个任务,其中按键任务用于获取键值,并发送至队列;按键处理任务获取队列中的消息,并处理键值。 按键队列任务如下所示: key_queue = xQueueCreate( 1 , sizeof ( uint8_t )); // 创建按键队列 if (key_queue == NULL ) { printf ( "按键队列创建失败!\n" ); } void key_task ( void *pvParameters) { uint8_t key_value; BaseType_t error_value; for (;;) { if (key_scan(KEY1_GPIO_Port, KEY1_Pin) == KEY_ON) { key_value = 1 ; if (key_queue != NULL ) { // error_value = xQueueSendToBack(key_queue, &key_value, 10); // 发送键值至队列 error_value = xQueueOverwrite(key_queue, &key_value); // 覆盖发送键值至队列 if (error_value == errQUEUE_FULL) { printf ( "按键队列已满,消息发送失败!\n" ); } } } if (key_scan(KEY2_GPIO_Port, KEY2_Pin) == KEY_ON) { key_value = 2 ; if (key_queue != NULL ) { error_value = xQueueSendToBack(key_queue, &key_value, 10 ); // 发送键值至队列 if (error_value == errQUEUE_FULL) { printf ( "按键队列已满,消息发送失败!\n" ); } } } vTaskDelay( 50 ); } } void print_task ( void *pvParameters) { uint8_t rx_key_value; BaseType_t rx_queue_state; for (;;) { if (key_queue != NULL ) { // 等待从队列中读取数据 rx_queue_state = xQueueReceive(key_queue, &rx_key_value, portMAX_DELAY); if (rx_queue_state == pdTRUE) { printf ( "key_value = %d\n" , rx_key_value); switch (rx_key_value) { case 1 : LED_RED; break ; case 2 : LED_BLUE; break ; default : LED_ALL_OFF; break ; } } } vTaskDelay( 50 ); } } 测试结果如下所示: 中断服务函数使用队列 这个比较复杂,总体思路是,在串口接受回调函数中,将接收到的数据保存至RX_BUFFER;在空闲中断中,将RX_BUFFER的数据全部发送至队列;在队列处理任务中,将队列中的数据读到TX_BUFFER中,然后打印输出。 1 使能接收中断和空闲中断 // 使能串口接收中断和空闲中断,并清除标志位 __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_IDLE); __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE); __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); 2 接收中断处理中,将接收到的字符保存至RX_BUFFER缓冲区 // 串口数据接收完成,串口空闲时,将RX_BUFFER发送至队列 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { i = 0 ; // 发送RX_BUFFER至usart_queue队列 xQueueSendToBackFromISR(usart_queue, RX_BUFFER, &pxHigherPriorityTaskWoken); // 判断是否需要进行任务切换 if (pxHigherPriorityTaskWoken == pdTRUE) { taskYIELD(); } // 清空RX_BUFFER缓冲区 memset (RX_BUFFER, 0 , USART_QUEUE_LEN); // 清除空闲中断(依次读取SR DR) SR; DR; } 整个串口中断服务函数如下所示: void USART1_IRQHandler ( void ) { static uint16_t i = 0 ; uint32_t temp; // 保存接收数据到RX_BUFFER缓冲区 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) { DR; __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE); } // 串口数据接收完成,串口空闲时,将RX_BUFFER发送至队列 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { i = 0 ; // 发送RX_BUFFER至usart_queue队列 xQueueSendToBackFromISR(usart_queue, RX_BUFFER, &pxHigherPriorityTaskWoken); // 判断是否需要进行任务切换 if (pxHigherPriorityTaskWoken == pdTRUE) { taskYIELD(); } // 清空RX_BUFFER缓冲区 memset (RX_BUFFER, 0 , USART_QUEUE_LEN); // 清除空闲中断(依次读取SR DR) SR; DR; } } 读取串口消息队列数据的任务如下所示: void usart_task ( void *pvParameters) { BaseType_t rx_queue_state; for (; ;) { if (usart_queue != NULL ) { // 等待从串口消息队列中读取数据 rx_queue_state = xQueueReceive(usart_queue, TX_BUFFER, portMAX_DELAY); if (rx_queue_state == pdTRUE) { printf ( "%s\n" , TX_BUFFER); memset (TX_BUFFER, 0 , USART_QUEUE_LEN); } } vTaskDelay( 50 ); } } 注意,这个实验要求,串口的中断优先级可以被屏蔽,即 ≥ 5(5为预设的屏蔽优先级,只有这样,才能在中断服务函数中使用fromISR函数,发送队列),如下所示: 测试结果如下所示: 完整的工程代码: main.c # include "main.h" # include "i2c.h" # include "usart.h" # include "gpio.h" # include "bsp_led.h" # include "bsp_key.h" # include "bsp_usart.h" # include "FreeRTOS.h" # include "task.h" # include "queue.h" # include # define USART_QUEUE_DEPTH 1 # define USART_QUEUE_LEN 100 void start_task ( void *pvParameters) ; void key_task ( void *pvParameters) ; void print_task ( void *pvParameters) ; void usart_task ( void *pvParameters) ; /* 任务相关参数 */ # define START_TASK_SIZE 128 # define START_TASK_PRIO 1 TaskHandle_t start_Task_Handle; # define KEY_TASK_SIZE 128 # define KEY_TASK_PRIO 2 TaskHandle_t key_task_Handle; # define PRINT_TASK_SIZE 128 # define PRINT_TASK_PRIO 3 TaskHandle_t print_task_Handle; # define USART_TASK_SIZE 128 # define USART_TASK_PRIO 3 TaskHandle_t usart_task_Handle; /* 队列相关参数 */ QueueHandle_t key_queue; QueueHandle_t usart_queue; uint8_t RX_BUFFER ; // 串口接收缓冲区 uint8_t TX_BUFFER ; // 串口发送缓冲区 int main ( void ) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_I2C1_Init(); MX_I2C2_Init(); // 使能串口接收中断和空闲中断,并清除标志位 __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_IDLE); __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE); __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); // 创建开始任务 xTaskCreate((TaskFunction_t )start_task, ( char * ) "start_task" , ( uint16_t )START_TASK_SIZE, ( void * ) NULL , (UBaseType_t )START_TASK_PRIO, (TaskHandle_t * )&start_Task_Handle); vTaskStartScheduler(); // 开启调度器 } void start_task ( void *pvParameters) { taskENTER_CRITICAL(); /* 创建按键队列 */ key_queue = xQueueCreate( 1 , sizeof ( uint8_t )); if (key_queue == NULL ) { printf ( "按键队列创建失败!\n" ); } /* 创建串口消息队列 */ usart_queue = xQueueCreate(USART_QUEUE_DEPTH, USART_QUEUE_LEN); if (usart_queue == NULL ) { printf ( "串口消息队列创建失败!\n" ); } // 创建按键任务 xTaskCreate((TaskFunction_t )key_task, ( char * ) "key_task" , ( uint16_t )KEY_TASK_SIZE, ( void * ) NULL , (UBaseType_t )KEY_TASK_PRIO, (TaskHandle_t * )&key_task_Handle); // 创建消息处理任务 xTaskCreate((TaskFunction_t )print_task, ( char * ) "print_task" , ( uint16_t )PRINT_TASK_SIZE, ( void * ) NULL , (UBaseType_t)PRINT_TASK_PRIO, (TaskHandle_t * )&print_task_Handle); // 创建串口队列任务 xTaskCreate((TaskFunction_t )usart_task, ( char * ) "usart_task" , ( uint16_t )USART_TASK_SIZE, ( void * ) NULL , (UBaseType_t )USART_TASK_PRIO, (TaskHandle_t * )&usart_task_Handle); taskEXIT_CRITICAL(); // 删除开始任务 vTaskDelete(start_Task_Handle); } void key_task ( void *pvParameters) { uint8_t key_value; BaseType_t error_value; for (;;) { if (key_scan(KEY1_GPIO_Port, KEY1_Pin) == KEY_ON) { key_value = 1 ; if (key_queue != NULL ) { // error_value = xQueueSendToBack(key_queue, &key_value, 10); // 发送键值至队列 error_value = xQueueOverwrite(key_queue, &key_value); // 覆盖发送键值至队列 if (error_value == errQUEUE_FULL) { printf ( "按键队列已满,消息发送失败!\n" ); } } } if (key_scan(KEY2_GPIO_Port, KEY2_Pin) == KEY_ON) { key_value = 2 ; if (key_queue != NULL ) { error_value = xQueueSendToBack(key_queue, &key_value, 10 ); // 发送键值至队列 if (error_value == errQUEUE_FULL) { printf ( "按键队列已满,消息发送失败!\n" ); } } } vTaskDelay( 50 ); } } void print_task ( void *pvParameters) { uint8_t rx_key_value; BaseType_t rx_queue_state; for (;;) { if (key_queue != NULL ) { // 等待从队列中读取数据 rx_queue_state = xQueueReceive(key_queue, &rx_key_value, portMAX_DELAY); if (rx_queue_state == pdTRUE) { printf ( "key_value = %d\n" , rx_key_value); switch (rx_key_value) { case 1 : LED_RED; break ; case 2 : LED_BLUE; break ; default : LED_ALL_OFF; break ; } } } vTaskDelay( 50 ); } } void usart_task ( void *pvParameters) { BaseType_t rx_queue_state; for (; ;) { if (usart_queue != NULL ) { // 等待从串口消息队列中读取数据 rx_queue_state = xQueueReceive(usart_queue, TX_BUFFER, portMAX_DELAY); if (rx_queue_state == pdTRUE) { printf ( "%s\n" , TX_BUFFER); memset (TX_BUFFER, 0 , USART_QUEUE_LEN); } } vTaskDelay( 50 ); } } 中断服务函数: void USART1_IRQHandler ( void ) { static uint16_t i = 0 ; uint32_t temp; // 保存接收数据到RX_BUFFER缓冲区 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) { DR; __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE); } // 串口数据接收完成,串口空闲时,将RX_BUFFER发送至队列 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { i = 0 ; // 发送RX_BUFFER至usart_queue队列 xQueueSendToBackFromISR(usart_queue, RX_BUFFER, &pxHigherPriorityTaskWoken); // 判断是否需要进行任务切换 if (pxHigherPriorityTaskWoken == pdTRUE) { taskYIELD(); } // 清空RX_BUFFER缓冲区 memset (RX_BUFFER, 0 , USART_QUEUE_LEN); // 清除空闲中断(依次读取SR DR) SR; DR; } } 转载于: https://blog.csdn.net/dingyc_ee/article/details/104096636
  • 热度 2
    2020-8-23 16:45
    1928 次阅读|
    0 个评论
    FreeRTOS时间管理 FreeRTOS的两个延时函数: vTaskDelay() 相对延时 vTaskDelayUntil() 绝对延时 1 相对延时 主要过程:挂起任务调度器,将任务以参数延时值添加到演示列表,然后恢复任务调度器。 void vTaskDelay ( const TickType_t xTicksToDelay) { BaseType_t xAlreadyYielded = pdFALSE; /* A delay time of zero just forces a reschedule. */ if (TickType_t) 0U ) { configASSERT(uxSchedulerSuspended == 0 ); vTaskSuspendAll(); { traceTASK_DELAY(); /* A task that is removed from the event list while the scheduler is suspended will not get placed in the ready list or removed from the blocked list until the scheduler is resumed. This task cannot be in an event list as it is the currently executing task. */ prvAddCurrentTaskToDelayedList(xTicksToDelay, pdFALSE); } xAlreadyYielded = xTaskResumeAll(); } else { mtCOVERAGE_TEST_MARKER(); } /* Force a reschedule if xTaskResumeAll has not already done so, we may have put ourselves to sleep. */ if (xAlreadyYielded == pdFALSE) { portYIELD_WITHIN_API(); } else { mtCOVERAGE_TEST_MARKER(); } } 函数实现过程分析: 添加任务到演示列表的过程: 先将任务从当前的状态列表中移除,然后计算当前tick值+延时值,以计算的值添加到延时列表,然后重新计算下一次任务切换的阻塞延时变量值 。 static void prvAddCurrentTaskToDelayedList (TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely) { TickType_t xTimeToWake; const TickType_t xConstTickCount = xTickCount; # if (INCLUDE_xTaskAbortDelay == 1) { /* About to enter a delayed list, so ensure the ucDelayAborted flag is reset to pdFALSE so it can be detected as having been set to pdTRUE when the task leaves the Blocked state. */ ucDelayAborted = pdFALSE; } # endif /* Remove the task from the ready list before adding it to the blocked list as the same list item is used for both lists. */ if xStateListItem)) == (UBaseType_t) 0 ) { /* The current task must be in a ready list, so there is no need to check, and the port reset macro can be called directly. */ uxPriority, uxTopReadyPriority); } else { mtCOVERAGE_TEST_MARKER(); } # if (INCLUDE_vTaskSuspend == 1) { if ((xTicksToWait == portMAX_DELAY) && (xCanBlockIndefinitely != pdFALSE)) { /* Add the task to the suspended task list instead of a delayed task list to ensure it is not woken by a timing event. It will block indefinitely. */ xStateListItem)); } else { /* Calculate the time at which the task should be woken if the event does not occur. This may overflow but this doesn't matter, the kernel will manage it correctly. */ xTimeToWake = xConstTickCount + xTicksToWait; /* The list item will be inserted in wake time order. */ xStateListItem), xTimeToWake); if (xTimeToWake < xConstTickCount) { /* Wake time has overflowed. Place this item in the overflow list. */ xStateListItem)); } else { /* The wake time has not overflowed, so the current block list is used. */ xStateListItem)); /* If the task entering the blocked state was placed at the head of the list of blocked tasks then xNextTaskUnblockTime needs to be updated too. */ if (xTimeToWake < xNextTaskUnblockTime) { xNextTaskUnblockTime = xTimeToWake; } else { mtCOVERAGE_TEST_MARKER(); } } } } # else /* INCLUDE_vTaskSuspend */ { /* Calculate the time at which the task should be woken if the event does not occur. This may overflow but this doesn't matter, the kernel will manage it correctly. */ xTimeToWake = xConstTickCount + xTicksToWait; /* The list item will be inserted in wake time order. */ xStateListItem), xTimeToWake); if (xTimeToWake < xConstTickCount) { /* Wake time has overflowed. Place this item in the overflow list. */ xStateListItem)); } else { /* The wake time has not overflowed, so the current block list is used. */ xStateListItem)); /* If the task entering the blocked state was placed at the head of the list of blocked tasks then xNextTaskUnblockTime needs to be updated too. */ if (xTimeToWake < xNextTaskUnblockTime) { xNextTaskUnblockTime = xTimeToWake; } else { mtCOVERAGE_TEST_MARKER(); } } /* Avoid compiler warning when INCLUDE_vTaskSuspend is not 1. */ ( void )xCanBlockIndefinitely; } # endif /* INCLUDE_vTaskSuspend */ } 具体分析: 什么是相对延时? void test_task ( void *pvParameters) { // 整个任务的执行时间 = 主体函数执行时间(不定)+ 延时值(800) for (; ;) { // 主体函数,如读取寄存器值,则这函数执行时间是不确定的 task_main_function(); vTaskDelay( 800 ); } } 在上面的任务函数中,如主体函数是在某条件下读取寄存器值(状态寄存器可能需要等待),那么这个主体函数的执行时间是不确定的,整个循环的执行时间等于主体函数执行时间 + 延时值(800)。 这里的相对,只是 vTaskDelay() 相对于主体函数延时800,而整个 for (; ;)的执行时间是浮动的 。 2 绝对延时 void vTaskDelayUntil (TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement) 参数说明: 绝对延时的原理: void test_task ( void *pvParameters) { uint32_t origin_tick = xTickCount; // 获取初始 Tick 值 for (; ;) { task_main_function(); // 这个主体函数的执行时间浮动不定 vTaskDelayUntil(origin_tick, 800 ); // 绝对延时函数 } } 如下所示,绝对延时函数会获取进入循环时的系统 Tick 值,然后每隔固定的延时节拍数(800)都会唤醒任务,不管任务的主体函数执行时间如何浮动,内核都会在 初始Tick值 + 延时Tick值(×1 ×2 ×3...)唤醒任务,因此是绝对延时函数。 函数源码: 实现要点:获取下次唤醒时间值=上次唤醒时间值+绝对延时值,自动更新上次唤醒时间值(设定为当前唤醒值,以实现绝对时间间隔),根据下次唤醒时间值,以及当前的Tick值,来计算延时值并插入到延时列表中。 void vTaskDelayUntil (TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement) { TickType_t xTimeToWake; BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE; configASSERT(pxPreviousWakeTime); 0U )); configASSERT(uxSchedulerSuspended == 0 ); vTaskSuspendAll(); { /* Minor optimisation. The tick count cannot change in this block. */ const TickType_t xConstTickCount = xTickCount; /* Generate the tick time at which the task wants to wake. */ // 获取下次唤醒时间值 = 上次唤醒时间值 + 绝对延时值 xTimeToWake = *pxPreviousWakeTime + xTimeIncrement; if (xConstTickCount < *pxPreviousWakeTime) { /* The tick count has overflowed since this function was lasted called. In this case the only time we should ever actually delay is if the wake time has also overflowed, and the wake time is greater than the tick time. When this is the case it is as if neither time had overflowed. */ if ((xTimeToWake xConstTickCount)) { xShouldDelay = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } else { /* The tick time has not overflowed. In this case we will delay if either the wake time has overflowed, and/or the tick time is less than the wake time. */ if ((xTimeToWake xConstTickCount)) { xShouldDelay = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } /* Update the wake time ready for the next call. */ // 自动更新上次唤醒时间值(设定为当前唤醒值,以实现绝对时间间隔) *pxPreviousWakeTime = xTimeToWake; if (xShouldDelay != pdFALSE) { traceTASK_DELAY_UNTIL(xTimeToWake); /* prvAddCurrentTaskToDelayedList() needs the block time, not the time to wake, so subtract the current tick count. */ // 根据下次唤醒时间值,以及当前的 Tick 值,来计算延时值并插入到延时列表中 prvAddCurrentTaskToDelayedList(xTimeToWake - xConstTickCount, pdFALSE); } else { mtCOVERAGE_TEST_MARKER(); } } xAlreadyYielded = xTaskResumeAll(); /* Force a reschedule if xTaskResumeAll has not already done so, we may have put ourselves to sleep. */ if (xAlreadyYielded == pdFALSE) { portYIELD_WITHIN_API(); } else { mtCOVERAGE_TEST_MARKER(); } } 函数实现过程分析: 绝对函数执行图示: 绝对延时函数的调用方式: 其实 使用函数 vTaskDelayUntil()延时的任务也不一定就能周期性的运行 ,使用函数vTaskDelayUntil()只能保证你按照一定的周期取消阻塞,进入就绪态。 如果有更高优先级或者中断的话你还是得等待其他的高优先级任务或者中断服务函数运行完成才能轮到你。这个绝对延时只是相对于 vTaskDelay()这个简单的延时函数而言的。 滴答定时器 SysTick直属于Cortex-M内核, 他不是stm32专属的,只要是Cortex-M内核的MCU就都有SysTick 。SysTick计数器是个24位的向下计数器,这个计数器的使命就是为系统提供服务的。操作系统都需要一个系统时钟,每个系统时钟周期都会触发OS内核执行一些系统调用,比如进行任务管理任务切换等。SysTick就可以完成此功能,使能SysTick中断,设定好定时周期,SysTick就会周期性的触发中断,跟系统有关的操作就可以在SysTick的中断服务函数中完成。如果不使用系统的话,SysTick也可以当成普通的定时器来使用。 Cortex-M编程手册中关于SysTick和寄存器的描述: 在库函数中关于 SysTick 的描述: # define SCS_BASE (0xE000E000UL) /*!< System Control Space Base Address */ # define SysTick_BASE (SCS_BASE + 0x0010UL) /*!< SysTick Base Address */ # define SysTick ((SysTick_Type *)SysTick_BASE) /*!< SysTick configuration struct */ /** \brief Structure type to access the System Timer (SysTick). */ typedef struct { __IOM uint32_t CTRL; /*!< Offset: 0x000 (R/W) SysTick Control and Status Register */ __IOM uint32_t LOAD; /*!< Offset: 0x004 (R/W) SysTick Reload Value Register */ __IOM uint32_t VAL; /*!< Offset: 0x008 (R/W) SysTick Current Value Register */ __IM uint32_t CALIB; /*!< Offset: 0x00C (R/ ) SysTick Calibration Register */ } SysTick_Type; FreeRTOS中,有关SysTick的中断服务函数: 其实就是在中断服务函数中,先禁用系统中断,然后调用xTaskIncrementTick() 来递增系统计数值,根据函数返回值来判断是否要进行任务切换,通过将PENDSV寄存器中断位置1以启用中断,并在PENDSV中断服务函数中进行任务切换。 /** * @brief This function handles System tick timer. */ void SysTick_Handler ( void ) { # if (INCLUDE_xTaskGetSchedulerState == 1) if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) { xPortSysTickHandler(); } # endif } void xPortSysTickHandler ( void ) { vPortRaiseBASEPRI(); { /* Increment the RTOS tick. */ if (xTaskIncrementTick() != pdFALSE) { /* A context switch is required. Context switching is performed in the PendSV interrupt. Pend the PendSV interrupt. */ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; } } vPortClearBASEPRIFromISR(); } 不管是什么系统,运行都需要有个系统时钟节拍,xTickCount 就是FreeRTOS 的系统时钟节拍计数器。每个滴答定时器中断中 xTickCount 就会加一,xTickCount 的具体操作过程是在函数 xTaskIncrementTick()中进行的,这个函数相当复杂,不用纠结。 转载于: https://blog.csdn.net/dingyc_ee/article/details/104092532
相关资源