XiLinx SDK FreeRTOS学习记录(一)——HelloWorld示例工程

创建工程

首先启动SDK,然后选择新建项目,在OS Platform一栏中,之前选的是裸机,现在选freertos,然后选择Hello World工程,SDK就会帮助我们创建一个包含了FreeRTOS下运行的demo.

1

2

代码分析

首先来看一下项目的宏定义以及一些全局变量。

#define TIMER_ID	1
#define DELAY_10_SECONDS	10000UL
#define DELAY_1_SECOND		1000UL
#define TIMER_CHECK_THRESHOLD	9

static TaskHandle_t xTxTask;
static TaskHandle_t xRxTask;
static QueueHandle_t xQueue = NULL;
static TimerHandle_t xTimer = NULL;
char HWstring[15] = "Hello World";

TIMER_ID定义了一个定时器ID,之后创建定时器时会用到。
DELAY_10_SECONDS以及DELAY_1_SECOND定义的数值是以毫秒为单位的,分别定义了10秒以及1秒。
TIMER_CHECK_THRESHOLD定义了在定时器回调函数中,判断接收的次数。
xTxTaskxRxTask定义了发送认为和接收任务的句柄,该句柄在任务创建时赋值,后续删除任务时也会用到。
xQueue定义了队列句柄,用于创建一个队列。
xTimer定义了定时器句柄。
HWstring定义了发送任务中要发送的字符串。

然后来看main函数,我们将一个一个介绍其中每个函数的作用。

int main( void )
{
	const TickType_t x10seconds = pdMS_TO_TICKS( DELAY_10_SECONDS );

	xil_printf( "Hello from Freertos example main\r\n" );


	xTaskCreate( 	prvTxTask, 					
					( const char * ) "Tx", 		
					configMINIMAL_STACK_SIZE, 	
					NULL, 						
					tskIDLE_PRIORITY,			
					&xTxTask );

	xTaskCreate( prvRxTask,
				 ( const char * ) "GB",
				 configMINIMAL_STACK_SIZE,
				 NULL,
				 tskIDLE_PRIORITY + 1,
				 &xRxTask );


	xQueue = xQueueCreate( 	1,						
							sizeof( HWstring ) );	


	configASSERT( xQueue );


	xTimer = xTimerCreate( (const char *) "Timer",
							x10seconds,
							pdFALSE,
							(void *) TIMER_ID,
							vTimerCallback);

	configASSERT( xTimer );


	xTimerStart( xTimer, 0 );


	vTaskStartScheduler();

	for( ;; );
}

首先是pdMS_TO_TICKS()函数,准确来说它并不是一个函数,而是一个宏定义。它的作用是根据系统的时钟频率和节拍周期,将毫秒数转换为相应的时钟节拍数。FreeRTOS 使用时钟节拍来进行任务调度和时间管理。时钟节拍是操作系统内部的基本时间单位,其长度由 configTICK_RATE_HZ 宏定义决定,表示每秒钟的节拍数。main函数的第一行就是将10秒长的一段时间转换成了持续10秒的时钟节拍数。

其次是xTaskCreate()函数,这个函数用于动态创建一个新的任务,需要的条件是FreeRTOSConfig.h文件中的configSUPPORT_DYNAMIC_ALLOCATION定义为1。它的原型是

BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
							const char * const pcName,	
							const configSTACK_DEPTH_TYPE usStackDepth,
							void * const pvParameters,
							UBaseType_t uxPriority,
							TaskHandle_t * const pxCreatedTask )

其中pxTaskCode是一个指向任务函数的指针。任务函数是一个无返回值的函数,它是任务的实际执行体。
pcName是一个字符串,用于标识任务的名称。这个参数主要用于调试和诊断目的。
usStackDepth是一个整数,表示任务堆栈的大小。堆栈大小决定了任务可以使用的内存空间大小。
pvParameters是一个指向任务函数参数的指针。通过这个参数,可以将数据传递给任务函数。
uxPriority是一个无符号整数,表示任务的优先级。优先级越高的任务会在调度时被优先执行。
pxCreatedTask: 是一个指向任务句柄的指针。通过这个参数,可以获取到创建的任务的句柄,以便后续对任务进行操作。
在main函数中,先创建了一个发送数据的函数,并将这个函数的优先级设置为0,又创建了一个接收函数,并将接收函数优先级设置为了1,也就是说接收函数的优先级是高于发送函数的。

之后是xQueueCreate(uxQueueLength, uxItemSize)函数,这个函数用于创建一个队列,并定义了该队列的长度,也就是元素个数以及每个元素的大小。在main函数中,创建了一个能容纳1个元素的队列,并且该元素的大小是HWstring. 再之后便是安全性检查,检查队列是否创建成功。

再之后是xTimerCreate()函数,用于创建一个软件定时器(software timer)。软件定时器是一种基于时间的机制,可以在指定的时间间隔内触发回调函数。函数原型如下:

TimerHandle_t xTimerCreate(const char *pcTimerName, 
                            TickType_t xTimerPeriod, 
                            UBaseType_t uxAutoReload, 
                            void *pvTimerID, 
                            TimerCallbackFunction_t pxCallbackFunction
                            );

其中pcTimerName是定时器的名称,用于识别和调试。
xTimerPeriod是定时器的周期,即触发回调函数的时间间隔,以时钟节拍数(tick)为单位。
uxAutoReload表示是否自动重载定时器。如果设置为pdFALSE,则定时器只触发一次;如果设置为pdTRUE,则定时器会按照周期循环触发。
pvTimerID是用户定义的定时器ID,可以用于在回调函数中识别该定时器。
pxCallbackFunction是定时器触发时调用的回调函数。
在main函数中,我们创建了一个名称为Timer,周期为10秒,不会自动重载,ID为1,回调函数为vTimerCallback()的定时器,进行安全性检查,检查定时器时候创建成功。

最后,xTimerStart()函数开启软件定时器,参数0表示立即开启。vTaskStartScheduler()函数启动任务调动器。调用vTaskStartScheduler函数之后,任务调度器将按照任务的优先级和调度策略来决定任务的执行顺序。任务调度器会根据任务的状态(就绪、阻塞、挂起等)来进行任务的切换,并在每个任务的时间片用完之后进行任务的切换。

说完了main函数,我们再来看看具体的任务函数以及定时器回调函数。

发送任务函数的源码如下:

static void prvTxTask( void *pvParameters )
{
const TickType_t x1second = pdMS_TO_TICKS( DELAY_1_SECOND );

	for( ;; )
	{
		vTaskDelay( x1second );

		xQueueSend( xQueue,		
					HWstring, 
					0UL );	
	}
}

这个函数首先创建了一个1秒的时间节拍,然后在任务的主循环中先延迟1秒,然后向队列xQueue中发送HWstring中的内容。超时时间设置为0.

接收任务函数的源码如下:

static void prvRxTask( void *pvParameters )
{
char Recdstring[15] = "";

	for( ;; )
	{
		/* Block to wait for data arriving on the queue. */
		xQueueReceive( 	xQueue,				/* The queue being read. */
						Recdstring,	/* Data is read into this address. */
						portMAX_DELAY );	/* Wait without a timeout for data. */

		/* Print the received data. */
		xil_printf( "Rx task received string from Tx task: %s\r\n", Recdstring );
		RxtaskCntr++;
	}
}

在接收任务中,首先创建了一个接收缓冲区用来存放接收到的内容,由于接收任务的优先级比发送任务的优先级高,因此应该会先执行接收任务,但是由于队列为空,于是接收任务阻塞等待队列里面有数据,因此会先执行发送任务然后立即执行接收任务。并且每接收一次就将接收任务计数变量加1.

最后当节拍到第10秒钟时,本应该执行发送任务的,但是由于软件定时器周期运行完了,因此进入了定时器回调函数,源码如下:

static void vTimerCallback( TimerHandle_t pxTimer )
{
	long lTimerId;
	configASSERT( pxTimer );

	lTimerId = ( long ) pvTimerGetTimerID( pxTimer );

	if (lTimerId != TIMER_ID) {
		xil_printf("FreeRTOS Hello World Example FAILED");
	}

	/* If the RxtaskCntr is updated every time the Rx task is called. The
	 Rx task is called every time the Tx task sends a message. The Tx task
	 sends a message every 1 second.
	 The timer expires after 10 seconds. We expect the RxtaskCntr to at least
	 have a value of 9 (TIMER_CHECK_THRESHOLD) when the timer expires. */
	if (RxtaskCntr >= TIMER_CHECK_THRESHOLD) {
		xil_printf("FreeRTOS Hello World Example PASSED");
	} else {
		xil_printf("FreeRTOS Hello World Example FAILED");
	}

	vTaskDelete( xRxTask );
	vTaskDelete( xTxTask );
}

该函数首先判断了调用这个函数的定时器是不是合法的定时器,然后再执行该执行的任务,首先判断接收函数接收了多少次,若是≥9则说明成功了,若没有则说明程序失败。最后删掉接收任务和发送任务。