学习操作系统,我并没有一开始就学习UCOS,而是选择了FreeRTOS。FreeRTOS可以方便地搭建在各个平台上,因为汇编相关,都已经由官方完成,我们要做的仅是添加自己的代码,可省去很多工作量。
问题1:在使用多任务时,我想利用USART输出信息,但是如果直接放在任务中输出,往往会造成字符收发顺序不一致的情况,这是仿真时遇到的实际问题。为解决这个问题,可以在USART输出信息时挂起其它任务,利用vTaskSuspendAll函数挂起,再利用xTaskResumeAll重启内核调度。这样可以保证USART正确发送信息。但是,我觉得还不如为USART建立一个任务,这样子,USART就不会发生发送字符顺序错乱的问题了!而完成任务间的通信,就可以考虑使用队列!
在此,队列的功能相当于任务与任务之间进行通信的通道。USART的任务可通过查询队列是否为空来决定是否输出。
队列的使用:
定义队列:
xQueueHandle USART_Q;
建立队列:
xQueueCreate( uxQueueLength, uxItemSize );
第一个参数为队列中元素个数;第二个参数为队列中元素大小,单位为字节。
也可以使用
xQueueGenericCreate( uxQueueLength, uxItemSize, queueQUEUE_TYPE_BASE )创建队列,接下来的三个函数都有两种使用方法。
发送消息:
xQueueSend( xQueue, pvItemToQueue, xTicksToWait );
第一个参数为队列句柄,第二个参数为队列元素,第三个参数为阻塞超时时间,如果在发送时队列已满,这个时间即是任务处于阻塞态等待队列空间有效的最长等待时间。
接收消息:
xQueueReceive( xQueue, pvBuffer, xTicksToWait )
一般用法如:while( xQueueReceive( xLCDQueue, &xMessage, portMAX_DELAY ) != pdPASS );
以上为队列用法,因为学习粗浅,不能进行深入分析……
注意,经查看代码,发送元素到队列的过程中,是将元素复制进队列中的,而并不是指针引用。
演示代码:
xQueueHandle USART1_MSGQ; //To receive the usart characters's queue
volatile unsigned long mainDELAY_LOOP_COUNT=0xffff;
void vTask1( void *pvParameters )
{
volatile unsigned long ul;
for( ;; )
{
xQueueSend( USART1_MSGQ, "This is task 1 !n",portMAX_DELAY);
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ );
}
}
void vTask2( void *pvParameters )
{
volatile unsigned long ul;
for( ;; )
{
xQueueSend( USART1_MSGQ, "This is task 2 !n",portMAX_DELAY);
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ );
}
}
void vTask3( void *pvParameters )
{
char str[100];
volatile unsigned long ul;
for( ;; )
{
while( xQueueReceive( USART1_MSGQ, str, portMAX_DELAY ) != pdPASS );
printf(str);
}
}
在main函数中添加:
USART1_MSGQ = xQueueCreate(10,(sizeof(char))*100);
xTaskCreate( vTask1, "Task 1", 1000, NULL, 1, NULL );
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
xTaskCreate( vTask3, "Task 3", 1000, NULL, 2, NULL );
vTaskStartScheduler();
while(1);
注意,printf函数已定位到USART1串口上,使用Keil调试的时候,可以通过串口窗口看到输出内容。
二进制信号量定义与使用:
定义信号量:
xSemaphoreHandle xBinarySemaphore;
vSemaphoreCreateBinary( xBinarySemaphore );
设置信号量:(在中断处理里面设置的时候,一定要使用以FromISR结尾的函数或宏)
xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
xSemaphoreGiveFromISR的第二个参数,。如果调用xSemaphoreGiveFromISR()使得一个任务解除阻塞,并且这个任务的优先级高于当前任务(也就是被中断的任务),那么xSemaphoreGiveFromISR()会在函数内部将*pxHigherPriorityTaskWoken 设为pdTRUE。
如果xSemaphoreGiveFromISR() 将pxHigherPriorityTaskWoken设为pdTRUE,则在中断退出前应当进行一次上下文切换。这样才能保证中断直接返回到就绪态任务中优先级最高的任务中。
上下文切换,使用宏portEND_SWITCHING_ISR()
注意:每种处理器架构都有自己的宏定义,一般在portmacro.h文件里面,声明为portEND_SWITCHING_ISR()
xHigherPriorityTaskWoken参数为portBASE_TYPE类型。取值为pdFALSE或者pdTRUE.
例如:
static void __interrupt __far vExampleInterruptHandler( void )
{
static portBASE_TYPE xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
portEND_SWITCHING_ISR(xHigherPriorityTaskWoken );
}
获取信号量(获取不到,则进入阻塞态):
xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );
注意,xSemaphoreTake函数的第二个参数设置为portMAX_DELAY,且在FreeRTOSConig.h 中设定INCLUDE_vTaskSuspend 为1,那么阻塞等待将没有超时限制。
计数信号量定义与使用:
因为之前没有接触过RTOS,在学习的过程中,我就不明白,为什么要搞个计数信号量,有什么意义?还好资料给我解除了这个疑惑。一个二值信号量最多只可以锁存一个中断事件,如果中断延迟处理任务还没有执行完,而中断又发生了多次,就会导致中断信息丢失。如果采用计数信号量,中断次数就能很好地保存下来。
定义计数信号量:
xSemaphoreHandle为计数信号量的变量类型
创建计数信号量:
xSemaphoreHandle xSemaphoreCreateCounting( unsigned portBASE_TYPE uxMaxCount,unsigned portBASE_TYPE uxInitialCount );
返回值为xSemaphoreHandle变量
uxMaxCount参数为最大计数值,也就是队列深度。
uxInitialCount参数为计数始值。
设置信号量:(在中断处理里面设置的时候,一定要使用以FromISR结尾的函数或宏)
同样使用函数:xSemaphoreGiveFromISR,也要注意上下文切换。
获取信号量:
同样使用函数xSemaphoreTake;
用于临界值的一组宏:
taskENTER_CRITICAL();进入临界区
taskEXIT_CRITICAL();退出临界区
调度器挂起与运行:
void vTaskSuspendAll( void );
portBASE_TYPE xTaskResumeAll( void );
互斥量(及二值信号量)
互斥量是一种特殊的二值信号量,用于控制在两个或多个任务间访问共享资源。可以理解为得到互斥量,即可得到资源。但是用完资源,必须归还互斥量。
互斥量的变量类型为:
xSemaphoreHandle xMutex;
互斥量的创建:
xSemaphoreHandle xSemaphoreCreateMutex( void );
互斥量的取得:
xSemaphoreTake( xMutex, portMAX_DELAY );
互斥量的归还:
xSemaphoreGive( xMutex );
运行时的栈侦测等最后会完善。
FreeRTOS的软时钟与任务延时阻塞:
用硬件定时器有时候并不是很方便,因为得写中断,得分配信号量;而且这类开支会让定时也不能特别精确!如果使用FreeRTOS的软时钟,会比较合适。而且也能省去很多代码。
1、任务延时阻塞vTaskDelay()函数与
vTaskDelay函数的使用
INCLUDE_vTaskDelay must be defined as 1 for this function to be available. See the configuration section for more information.
Delay a task for a given number of ticks. The actual time that the task remains blocked depends on the tick rate. The constant portTICK_RATE_MS can be used to calculate real time from the tick rate .
INCLUDE_vTaskDelay应该定义为1才能使能该函数。可以通过查看配置部分获取更多信息。函数可通过给定数值延时一个任务。任务的实际阻塞时间依赖于时钟频率。常量portTICK_RATE_MS能用来计算时钟频率的实际时间。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 | voidvTaskFunction(void* pvParameters ) { /* Block for 500ms. */ constportTickType xDelay = 500 / portTICK_RATE_MS; for( ;; ) { /* Simply toggle the LED every 500ms, blocking between each toggle. */ vToggleLED(); vTaskDelay( xDelay ); } } |
但是,vTaskDelay并不能给你一个绝对的时延,它是相对于自身的延时!因为任务切换或中断发生等原因会影响vTaskDelay函数的使用。如果要使用绝对的时延,可以使用函数vTaskDelayUntil
vTaskDelayUntil函数的使用
INCLUDE_vTaskDelayUntil must be defined as 1 for this function to be available.
使能函数,INCLUDE_vTaskDelayUntil必须设置为1。
Delay a task until a specified time. This function can be used by cyclical tasks to ensure a constant execution frequency.
延迟一个任务直到指定时间。该函数能用于循环任务保持流畅执行。
This function differs from vTaskDelay() in one important aspect: vTaskDelay() specifies a time at which the task wishes to unblock relative to the time at which vTaskDelay() is called, whereas vTaskDelayUntil() specifies an absolute time at which the task wishes to unblock.
该函数与vTaskDelay函数最大不同的一方面在于:vTaskDelay指定的时间是相对于vTaskDelay调用时开始的,然而vTaskDelayUntil指定的时间是绝对的时间,到时间一定解除阻塞。
vTaskDelayUntil函数的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // Perform an action every 10 ticks. voidvTaskFunction(void* pvParameters ) { portTickType xLastWakeTime; constportTickType xFrequency = 10; // Initialise the xLastWakeTime variable with the current time. xLastWakeTime = xTaskGetTickCount(); for( ;; ) { // Wait for the next cycle. vTaskDelayUntil( &xLastWakeTime, xFrequency ); // Perform action here. } } |
听说FreeRTOS任务可以同优先级,但是发现同优先级创建3个以上任务就无法调度?不知道是什么原因?
原因:一般情况下,刚开始学FreeRTOS都习惯将任务栈空间设置为1000,这意味着将要在系统里面申请4000个字节(栈空间以4字节为单位);创建3个任务,申请的约为12K左右的样子,如果再创建一个任务,也是用的1000; 那么意味着向系统申请16K左右的栈空间。加上空闲任务的栈空间,你FreeRTOSConfig.h文件里面的栈空间默认值为#define configTOTAL_HEAP_SIZE( ( size_t ) ( 17 * 1024 ) );也就是17K。显然运行不了,是因为堆栈空间不足的原因!
解决:两种办法:1、任务栈空间调小点,变成500;2、系统栈空间调大点,变成30K ;一般就足够你跑5到6个任务了。如果再小,跑得更多!
调度器只能调度一次就崩溃了,移植汇编都没问题?
原因:你的任务函数没放在死循环里面!
上一篇:STM32单片机中,FreeRTOS RAM使用情况及优化方法
下一篇:STM32 使用 Keil MDK 中的软件逻辑分析仪参与硬件调试
推荐阅读最新更新时间:2024-11-11 12:00
设计资源 培训 开发板 精华推荐
- NCV302LSN30T1 3V LED条形图电压监视器的典型应用
- 使用 NXP Semiconductors 的 TDA8933BT 的参考设计
- LTC4368CMS-1 100V UV/OV 和具有双向断路器的反向保护控制器的典型应用电路
- arduino_plus
- REF194 精密微功率、低压差堆叠电压基准的典型应用电路
- LB1909MCGEVB,基于 LB1909MC 低饱和电压步进电机驱动器的评估板
- LT6656ACS6-2.048、2.048V 扩展电源范围电压基准的典型应用
- AM2G-0509DH30Z ±9V 2 瓦 DC-DC 转换器的典型应用
- 用于手机的 4-LED 白光 LED 驱动器
- LT3973IMS 12V 降压转换器的典型应用
- TI|痛点解锁机:你的电源设计痛点,我们懂!解锁、评论赢好礼!
- EEWorld邀你来拆解(第15期)拆起来!
- 有奖直播:英飞凌针对电动工具的高功率、高效率以及高可靠性解决方案
- 安森美有奖直播|适用于光储充的SiC及IGBT隔离栅极驱动器方案
- ADI有奖下载活动之19:ADI可编程逻辑控制器(PLC)解决方案(更新版)
- Microchip电源评估板促销,还有红包送!
- 全球首款Cortex-M23内核物联网芯片SAML10和SAM L11系列 闯关获取SAML10/SAML11法宝,拆除电子界安全危机,赢好礼!
- 【EEWORLD第二十五届】2011年04月社区明星人物揭晓!
- 动手学电源第一季:DIY 小风扇,Fan一夏!
- Microchip有奖直播:VectorBlox™ SDK 报名中