XiLinx SDK FreeRTOS学习记录(一)——HelloWorld示例工程
创建工程
首先启动SDK,然后选择新建项目,在OS Platform一栏中,之前选的是裸机,现在选freertos,然后选择Hello World工程,SDK就会帮助我们创建一个包含了FreeRTOS下运行的demo.
代码分析
首先来看一下项目的宏定义以及一些全局变量。
#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
定义了在定时器回调函数中,判断接收的次数。
xTxTask
xRxTask
定义了发送认为和接收任务的句柄,该句柄在任务创建时赋值,后续删除任务时也会用到。
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则说明成功了,若没有则说明程序失败。最后删掉接收任务和发送任务。