1)实验平台:正点原子MPSoC开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html
第十九章FreeRtos Hello World实验
我们在使用Vitis新建工程时,在软件配置界面中有对操作系统的选择,这个选择有两个选项,一个选项是单机操作(standalone,即无操作系统),一个选项是实时操作系统(freertos10_xilinx)。Vitis开发指南的工程在开发时选择的都是单机操作,本章实验的目的是使用实时操作系统进行简单的基础实验开发,例如使用实时操作系统运行“Hello World”实验。
本章包括以下几个部分:
1919.1简介
19.2实验任务
19.3硬件设计
19.4软件设计
19.5下载验证
19.1简介
什么是FreeRtos?
Free即免费的,RTOS全称是Real Time Operating System,中文就是实时操作系统。注意,RTOS不是指某一个确定的系统,而是指一类系统,比如uC/OS,FreeRTOS,RTX,RT-Thread等这些都是RTOS类操作系统。
操作系统允许多个任务同时运行,这个叫做多任务。实际上,一个处理器核心在某一时刻只能运行一个任务。操作系统中任务调度器的责任就是决定在某一时刻究竟运行哪个任务。任务调度在各个任务之间的切换非常快,就给人们造成了同一时刻有多个任务同时运行的错觉。
某些操作系统给每个任务分配同样的运行时间,时间到了就轮到下一个任务,比如Unix操作系统。FreeRTOS操作系统则是由用户给每个任务分配一个任务优先级,任务调度器就可以根据此优先级来决定下一刻应该运行哪个任务。
FreeRTOS是RTOS系统的一种,FreeRTOS十分的小巧,可以在资源有限的微控制器中运行,当然,FreeRTOS不仅局限于在微控制器中使用。但从文件数量上来看FreeRTOS要比uC/OSII和uC/OSIII小的多。
在嵌入式领域中,嵌入式实时操作系统正得到越来越广泛的应用。采用嵌入式实时操作系统(RTOS)可以更合理、更有效地利用CPU的资源,简化应用软件的设计,缩短系统开发时间,更好地保证系统的实时性和可靠性。
FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。
由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少数实时操作系统能在小RAM单片机上运行。相对μC/OS-II、embOS等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行。
FreeRtos的特点
FreeRTOS是一个可裁剪的小型RTOS系统,其特点包括:
● FreeRTOS的内核支持抢占式,合作式和时间片调度。
● 提供了一个用于低功耗的Tickless模式。
● 系统的组件在创建时可以选择动态或者静态的RAM,比如任务、消息队列、信号量、软件定时器等等。
● 已经在超过30种架构的芯片上进行了移植。
● FreeRTOS系统简单、小巧、易用,通常情况下内核占用4k-9k字节的空间。
● 高可移植性,代码主要C语言编写。
● 支持实时任务和协程(co-routines也有称为合作式、协同程序)。
● 任务与任务、任务与中断之间可以使用任务通知、消息队列、二值信号量、数值型、信号量、递归互斥信号量和互斥信号量进行通信和同步。
● 创新的事件组(或者事件标志)。
● 具有优先级继承特性的互斥信号量。
● 高效的软件定时器。
● 强大的跟踪执行功能。
● 堆栈溢出检测功能。
● 任务数量不限。
● 任务优先级不限。
19.2实验任务
本章的实验任务是在MPSOC开发板上创建实时操作系统,在使用串口打印“Hello World”信息的同时分别使ps端与pl端的LED灯以不同的频率闪烁。
19.3硬件设计
根据实验任务我们可以画出本次实验的系统框图,如下图所示:
图 19.3.1系统框图
本实验的硬件搭建是在AXI_GPIO的实验基础上进行设计的,首先是将AXI_GPIO实验的工程另存为FreeRtos工程,如下图所示:
图 19.3.2 创建“FreeRtos”工程
“FreeRtos”工程建立完成后如下图所示,然后点击Flow Navigator—IP INTEGRATOR—Open Block Design:
图 19.3.3 打开硬件设计
图 19.3.4硬件设计如上图
从前面的实验任务介绍可知,本实验的vitis – FreeRtos工程设计是要使用官方提供的Hello World模板,该模板中进行的是一个定时器控制的打印信息的例程,所以在建立vitis – FreeRtos工程前需要在硬件设计中添加定时器选项。
在硬件设计中勾选定时器,如下图所示:
图 19.3.5双击“ZYNQ UltraSCALE+”
图 19.3.6打开 “Re-customize IP”界面
在I/O Configuration—Low Speed—Processing Unit—TTC路径下勾选定时器设置:
图 19.3.7 勾选定时器
上面勾选定时器后点击“OK”。
根据实验任务本实验还要在硬件设计中添加一个PL端的LED灯,因为本实验中不需要使用PL_KEY,所以将该GPIO接口修改成一个PL_LED输出接口,双击“axi_gpio_0”模块打开后设置该模块参数,进行如下所示设置:
图 19.3.8 PL_LED GPIO配置
点击上图“OK”后,接下来是对下图中红框指示的名称进行修改,修改如下图所示:
图 19.3.9 修改引脚名称
将PL_KEY的GPIO引脚AXI_GPIO_KEY在上图左侧的红框中修改为AXI_GPIO_LED,名称修改完成后硬件搭建完成,进行设计检查。
右击“Diagram”空白处:
图 19.3.10检查设计
检查完成后没有错误,点击OK,按Ctrl+S保存,如下图所示:
图 19.3.11检查完成
保存设计,然后右键点击design_1_wrapper选择Generate Output Products。
因为设计中使用了PL的资源,则需要添加引脚约束并对该设计进行综合、实现并生成Bitstream文件。
如下图所示的添加pl端引脚约束文件:
图 19.3.12 pl引脚约束
#LED
set_property PACKAGE_PIN AE10 [get_ports {AXI_GPIO_LED_tri_o[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {AXI_GPIO_LED_tri_o[0]}]
接下来生成bit文件,如下图所示:
图 19.3.13生成bit文件
bit文件成功生成后进行导出硬件操作:
导出Hardware,在弹出的对话框中,因为生成了bitstream文件,所以需要勾选“Include bitstream”,直接点击“OK”按钮。
图 19.3.14 勾选“Include bitstream”
上图中,XSA file name一栏是产生的硬件信息文件的文件名,这里我们保持默认。Export to后面的路径是生成的包含硬件信息文件的路径,生成的文件如下所示:
图 19.3.15生成的xsa文件
将导出的design_1_wrapper.xsa放到vitis文件夹,启动Vitis。
19.4软件设计
在硬件设计的最后,我们启动了软件开发环境(Vitis),如下图所示:
图 19.4.1 Vitis开发环境界面
在菜单栏选择File > New > Application Project, 新建一个vitis应用工程,如下图所示:
图 19.4.2 新建应用工程
在弹出的对话框中,输入工程名“FreeRtos_hello_world”,其它选项保持默认即可,点击“Next”,如下图所示:
图 19.4.3 配置工程
打开Create a new platform from hardware(XSA)标签页,点击“+”添加xsa文件,如下图所示:
图19.4.4 添加xsa文件
在弹出的窗口中选择design_1_wrapper.xsa文件,如下图所示:
图 19.4.5 选择design_1_wrapper.xsa文件
添加xsa文件后的页面如下图所示,点击next:
图19.4.6 添加完成xsa文件
在弹出的页面中有一个Generate boot components选项,如果勾选,软件会自动生成fsbl工程,这里我们选择默认勾选,然后点击next,如下图所示:
图19.4.7 默认生成fsbl工程
在弹出的工程模板选择页面里,我们选择已有的FreeRTOS Hello World模板,然后点击Finish,如下图所示:
图 19.4.8 选择Hello World模板
工程建立完成后的页面如下图所示,我看可以看到生成了两个工程,一个是硬件平台工程,即platform工程,一个是应用工程。
图19.4.9 工程建立完成
双击打开FreeRtos_hello_world/src工程目录下freertos_hello_world.c文件,可以看到官方提供的源代码,本实验代码是在官方源代码上添加了ps与pl端led灯闪烁的任务,本实验代码如下:
1 /* FreeRTOS includes. */
2 #include "FreeRTOS.h"
3 #include "task.h"
4 #include "queue.h"
5 #include "timers.h"
6 /* Xilinx includes. */
7 #include "xil_printf.h"
8 #include "xparameters.h"
9 #include "xgpiops.h"
10 #include "xgpio.h"
11
12 #define TIMER_ID 1
13 #define DELAY_60_SECONDS 60000UL //定义1分钟连续中断时间
14 #define DELAY_1_SECOND 1000UL //定义1s的发送时间
15 #define TIMER_CHECK_THRESHOLD 59 //连续中断的次数
16
17 #define DELAY_100_MSECOND 100UL //PL端闪烁间隔为100ms
18 #define DELAY_500_MSECOND 500UL //PS端闪烁间隔为500ms
19
20 //PS端 GPIO 器件(LED) ID
21 #define MIO_0_ID XPAR_PSU_GPIO_0_DEVICE_ID
22 //PL端 AXI GPIO 器件(LED) ID
23 #define LED_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID
24
25 #define GPIO_OUTPUT 1
26 XGpioPs GPIO_PTR ;
27
28 #define LED_CHANNEL 1
29 XGpio Gpio_led ;
30
31 unsigned int PsLedVal = 0 ; //ps_led显示状态0:灭 1:亮
32 unsigned int PlLedVal = 0x0 ; //pl_led显示状态
33 /*----------------------------------------------------------*/
34
35 /* Tx 和 Rx 任务。 */
36 static void prvTxTask( void *pvParameters );
37 static void prvRxTask( void *pvParameters );
38 static void vTimerCallback( TimerHandle_t pxTimer );
39
40 /* PS/PL端LED灯闪烁任务 */
41 static void prvPsLedTask( void *pvParameters );
42 static void prvPlLedTask( void *pvParameters );
43 /*-----------------------------------------------------------*/
44
45 /*-----------------------------------------------------------*/
46 void PsGpioSetup() ;
47 void PlGpioSetup() ;
48
49 /* Tx 和 Rx 任务使用的队列 */
50 static TaskHandle_t xTxTask;
51 static TaskHandle_t xRxTask;
52 static QueueHandle_t xQueue = NULL;
53 static TimerHandle_t xTimer = NULL;
54 char HWstring[15] = "Hello World";
55 long RxtaskCntr = 0;
在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式,但是如果在使用操作系统的应用中用全局变量来传递消息就会涉及到“资源管理”的问题。FreeRTOS对此提供了一个叫做“队列”的机制来完成任务与任务、任务与中断之间的消息传递。50行到55行代码是定义Tx和Rx任务使用的队列。
代码的81行到83行使用xQueueCreate函数创建了队列,该对列中只有一个空间,队列中的每个空间都足以容纳一个uint32_t。xQueueCreate本质上是一个宏,用来动态创建队列,此宏最终调用的是函数xQueueGenericCreate(),函数原型如下:#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif
由该函数的功能可知,该函数是通过队列空间的个数与队列的空间大小来创造队列。
57 int main( void )
58 {
59 const TickType_t x60seconds
60 = pdMS_TO_TICKS( DELAY_60_SECONDS );
61 xil_printf( "Hello from Freertos example main\r\n" );
62 PsGpioSetup() ;
63 PlGpioSetup() ;
64
65 xTaskCreate( prvTxTask, /* 实现任务的函数。*/
66 /* 任务的文本名称,仅用于协助调试。 */
67 ( const char * ) "Tx",
68 configMINIMAL_STACK_SIZE, /* 分配给任务的堆栈。*/
69 /* 未使用任务参数,因此设置为 NULL。 */
70 NULL,
71 tskIDLE_PRIORITY, /* 任务以空闲优先级运行。*/
72 &xTxTask );
73
74 xTaskCreate( prvRxTask,
75 ( const char * ) "GB",
76 configMINIMAL_STACK_SIZE,
77 NULL,
78 tskIDLE_PRIORITY + 1,
79 &xRxTask );
80
81 xQueue = xQueueCreate(1, /* 队列中只有一个空间。*/
82 /* 队列中的每个空间都足以容纳一个 uint32_t。*/
83 sizeof( HWstring ) );
84
85 /* 检查队列是否已创建。 */
86 configASSERT( xQueue );
87
88 xTimer = xTimerCreate( (const char *) "Timer",
89 x60seconds,
90 pdFALSE,
91 (void *) TIMER_ID,
92 vTimerCallback);
93
94 xTaskCreate( prvPsLedTask,
95 ( const char * ) "Ps Led",
96 configMINIMAL_STACK_SIZE,
97 NULL,
98 tskIDLE_PRIORITY + 1,
99 NULL);
100
101 xTaskCreate( prvPlLedTask,
102 ( const char * ) "PL Led",
103 configMINIMAL_STACK_SIZE,
104 NULL,
105 tskIDLE_PRIORITY + 1,
106 NULL);
107
108 /* 检查计时器是否已创建。 */
109 configASSERT( xTimer );
110
111 /* 以 0 滴答的块时间启动计时器。这意味着开发板一旦开始上电运行,
112 计时器将开始运行并在60秒后到期 */
113 xTimerStart( xTimer, 0 );
114
115 /* 启动任务并运行计时器。 */
116 vTaskStartScheduler();
117
118 for( ;; );
119 }
59行到60行代码,延时60s,但是函数xTimerCreate ()的89行代码参数(x60seconds)需要设置的是延时的节拍数,不能直接设置延时时间,因此使用函数pdMS_TO_TICKS将时间转换为节拍数。88行到92行代码是创建了创建一个计时器到期时间为60秒的计时器,计时器将在60秒后到期,并且将调用计时器回调。在计时器回调中进行检查以确保任务在此之前一直正常运行,任务在计时器回调中被删除,并打印一条消息以传达示例已成功运行。计时器到期时间设置为60秒,计时器设置为不自动重新加载。
在使用FreeRTOS的过程中,我们要使用函数xTaskCreate()来创建任务。FreeRTOS官方给出的任务函数模板如下:
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 )
(1)、这个函数的第一个参数pxTaskCode,就是这个任务的任务函数。什么是任务函数?任务函数就是完成本任务工作的函数。我这个任务要干嘛?要做什么?要完成什么样的功能都是在这个任务函数中实现的。任务函数本质也是函数,所以肯定有任务名什么的,不过这里我们要注意:任务函数的返回类型一定要为void类型,也就是无返回值,而且任务的参数也是void指针类型的!任务函数名可以根据实际情况定义。
(2)、这个函数的第二个参数是pcName,任务的文本名称,仅用于协助调试。
(3)、这个函数的第三个参数是configSTACK_DEPTH_TYPE usStackDepth,分配给任务的堆栈。
(4)、这个函数的第四个参数是pvParameters,我们本实验里未使用任务参数,因此设置为NULL。
(5)、这个函数的第五个参数是uxPriority,该参数是定义任务运行优先级。
(6)、这个函数的第六个参数是pxCreatedTask,是任务使用的队列。
65行到72行代码使用函数xTaskCreate()来创建一个发送任务(prvTxTask())的函数,74行到79行代码使用函数xTaskCreate()来创建一个接收任务(prvRxTask())的函数,94行到99行代码使用函数xTaskCreate()来创建一个使ps端的LED闪烁任务的函数,闪烁间隔为500ms,101行到106行代码使用函数xTaskCreate()来创建一个使pl端的LED闪烁任务的函数,闪烁间隔为100ms。
代码的86行代码中的函数是断言(configASSERT),86行代码断言函数是检测队列是否已创建,109行代码的断言函数是检查计时器是否已创建。断言(configASSERT)类似C标准库中的assert()函数,调试代码的时候可以检查传入的参数是否合理,FreeRTOS内核中的关键点都会调用configASSERT(x),当x为0的时候说明有错误发生,使用断言的话会导致开销加大,一般在调试阶段使用。configASSERT()需要在FreeRTOSConfig.h文件中定义,如下实例:
#define configASSERT( x ) if( ( x ) == 0 ) vApplicationAssert( FILE, LINE )
当参数x错误的时候就通过串口打印出发生错误的文件名和错误所在的行号,调试代码的可以使用断言,当调试完成以后尽量去掉断言,防止增加开销!
113行代码使用的函数xTimerStart开启软件定时器。如果软件定时器停止运行的话可以使用FreeRTOS提供的两个开启函数来重新启动软件定时器,xTimerStart()开启软件定时器,用于任务中,xTimerStartFromISR()开启软件定时器,用于中断中。
116行代码使用开启任务调度函数(vTaskStartScheduler),这个函数的功能就是开启任务调度器的,这个函数在文件tasks.c中有定义。调用函数xPortStartScheduler()来初始化跟调度器启动有关的硬件,比如滴答定时器、FPU单元和PendSV中断等等。
121 /*-----------------------------------------------------------*/
122 static void prvTxTask( void *pvParameters )
123 {
124 const TickType_t x1second = pdMS_TO_TICKS( DELAY_1_SECOND );
125
126 for( ;; )
127 {
128 /* 延迟 1 秒。*/
129 vTaskDelay( x1second );
130
131 /*发送队列中的下一个值。
132 此时队列应始终为空,因此使用的阻塞时间为0。*/
133 xQueueSend( xQueue, /* 正在写入的队列。 */
134 HWstring, /* 正在发送的数据的地址。 */
135 0UL ); /* 阻塞时间。 */
136 }
137 }
122行到137行是创建的发送任务函数,本章的发送任务每隔一秒发送一条信息,所以在第14行代码定义了该延迟时间,延时1s,但是函数vTaskDelay()的129行代码参数(x1second)需要设置的是延时的节拍数,不能直接设置延时时间,因此124行代码使用函数pdMS_TO_TICKS将时间转换为节拍数。
129行代码调用的函数vTaskDelay是FreeRTOS的延时函数,此处不一定要用延时函数,其他只要能让FreeRTOS发生任务切换的API函数都可以,比如请求信号量、队列等,甚至直接调用任务调度器。只不过最常用的就是FreeRTOS的延时函数。在FreeRTOS中延时函数有相对模式和绝对模式,在FreeRTOS中不同的模式用的函数不同,其中函数vTaskDelay()是相对模式(相对延时函数),函数vTaskDelayUntil()是绝对模式(绝对延时函数)。函数vTaskDelay()在文件tasks.c中有定义,要使用此函数的话宏INCLUDE_vTaskDelay必须为1。需要注意的是延时时间函数的延时时间是时间的节拍数,延时时间肯定要大于0。
133行到135行代码使用的函数xQueueSend用于向队列中发送消息的,即将“Hello World”字符串写入队列。这个函数的本质是宏,函数xQueueSend()是后向入队,即将新的消息插入到队列的后面。这个函数最后调用的是函数:xQueueGenericSend()。这三个函数只能用于任务函数中,不能用于中断服务函数,中断服务函数有专用的函数,它们以“FromISR”结尾,这个函数的原型如下:
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
参数:
xQueue:队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvItemToQueue:指向要发送的消息,发送时候会将这个消息拷贝到队列中。xTicksToWait:阻塞时间,此参数指示当队列满的时候任务进入阻塞态等待队列空闲的最大时间。如果为0的话当队列满的时候就立即返回;当为portMAX_DELAY的话就会一直等待,直到队列有空闲的队列项,也就是死等,但是宏INCLUDE_vTaskSuspend必须为1。
返回值:
pdPASS:向队列发送消息成功!
errQUEUE_FULL:队列已经满了,消息发送失败。
126行到135行代码是说明任务的具体执行过程是一个大循环,for(; ; )就代表一个循环,作用和while(1)一样。循环里面就是真正的任务代码了,主要说明此任务具体要干的活就在这里实现!任务函数一般不允许跳出循环,如果一定要跳出循环的话在跳出循环以后一定要调用函数vTaskDelete(NULL)删除此任务!
139 /*---------------------------------------------------------------*/
140 static void prvRxTask( void *pvParameters )
141 {
142 char Recdstring[15] = "";
143 for( ;; )
144 {
145 /* 阻塞等待数据到达队列。 */
146 xQueueReceive( xQueue, /*正在读取的队列。 */
147 Recdstring, /*数据被读入该地址。*/
148 portMAX_DELAY );/*等待数据没有超时。*/
149
150 /* 打印接收到的数据。 */
151 xil_printf( "Rx task received string
152 from Tx task: %s\r\n", Recdstring );
153 xil_printf( "The number of RX task
154 executions is %d\r\n", RxtaskCntr );
155 RxtaskCntr++;
156 }
157 }
140行到157行是创建的接收任务函数,本章的接收任务是从队列中读取一条(请求)消息。从142行代码定义的输入接收任务函数具体执行过程个也是一个大循环,for(; ; )就代表一个循环。
146行到156行代码使用的是队列接收函数(xQueueReceive)读取队列中的“Hello World”字符串,此函数用于在任务中从队列中读取一条(请求)消息,读取成功以后就会将队列中的这条数据删除,此函数的本质是一个宏,真正执行的函数是xQueueGenericReceive()。此函数在读取消息的时候是采用拷贝方式的,所以用户需要提供一个数组或缓冲区来保存读取到的数据,所读取的数据长度是创建队列的时候所设定的每个队列项目的长度,函数原型如下:
BaseType_t xQueueReceive(QueueHandle_t xQueue, void * pvBuffer, TickType_t xTicksToWait);
参数:
xQueue:队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的队列句柄。
pvBuffer:保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区中。
xTicksToWait:阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最大时间。如果为0的话当队列空的时候就立即返回;当为portMAX_DELAY的话就会一直等待,直到队列有数据,也就是死等,但是宏INCLUDE_vTaskSuspend必须为1。
返回值:
pdTRUE:从队列中读取数据成功。
pdFALSE:从队列中读取数据失败。
151行到155行代码是打印从队列里读取的“Hello World”字符串与每次打印的编号(RxtaskCntr)。
159 /*-----------------------------------------------------------*/
160 static void vTimerCallback( TimerHandle_t pxTimer )
161 {
162 long lTimerId;
163 configASSERT( pxTimer );
164
165 lTimerId = ( long ) pvTimerGetTimerID( pxTimer );
166
167 if (lTimerId != TIMER_ID) {
168 xil_printf("FreeRTOS Hello World Example FAILED");
169 }
170
171 if (RxtaskCntr >= TIMER_CHECK_THRESHOLD) {
172 xil_printf("FreeRTOS Hello World Example PASSED");
173 } else {
174 xil_printf("FreeRTOS Hello World Example FAILED");
175 }
176 vTaskDelete( xRxTask );
177 vTaskDelete( xTxTask );
178 }
vTimerCallback任务是一个计时60s的定时器,33行代码是定义的定时器的ID(TIMER_ID),163行代码检测定时器是否已经被创建,165行代码通过pvTimerGetTimerID()函数获取创建定时器的ID(lTimerId),167行到169行代码将获取的创建的定时器的ID(lTimerId)与33定义的定时器的ID(TIMER_ID)进行对比,如果两者不一致则会打印"FreeRTOS Hello World Example FAILED"语句,结束接收与发送任务,本次实验失败,结束本此次实验。如果两个ID一致则不打印"FreeRTOS Hello World Example FAILED"语句,定时器将正常工作,开始计时,函数会进行171行到175行代码描述的判断语句,171行代码是将发送任务发送次数的计数值(RxtaskCntr)与定时器到期时的值(TIMER_CHECK_THRESHOLD)进行比较,发送任务发送次数的计数值(RxtaskCntr)超过定时器到期时的值(TIMER_CHECK_THRESHOLD)后,会打印"FreeRTOS Hello World Example PASSED"语句,并结束接收与发送任务,本次实验成功,结束本此次实验,否则打印"FreeRTOS Hello World Example FAILED",本次实验失败。
vTaskDelete()函数可以删除一个用函数xTaskCreate()或者xTaskCreateStatic()创建的任务,176行代码与177行代码就是删除了xTaskCreate()创建的接收任务(xRxTask)与发送任务(xTxTask),被删除了的任务不再存在,也就是说再也不会进入运行态。任务被删除以后就不能再使用此任务的句柄!如果此任务是使用动态方法创建的,也就是使用函数xTaskCreate()创建的,那么在此任务被删除以后此任务之前申请的堆栈和控制块内存会在空闲任务中被释放掉,因此当调用函数vTaskDelete()删除任务以后必须给空闲任务一定的运行时间。只有那些由内核分配给任务的内存才会在任务被删除以后自动的释放掉,用户分配给任务的内存需要用户自行释放掉,比如某个任务中用户调用函数pvPortMalloc()分配了500字节的内存,那么在此任务被删除以后用户也必须调用函数vPortFree()将这500字节的内存释放掉,否则会导致内存泄露。此函数原型如下:vTaskDelete( TaskHandle_t xTaskToDelete )。如果要使用函数vTaskDelete()的话需要将宏INCLUDE_vTaskDelete定义为1。
180 void PsGpioSetup()
181 {
182 int Status ;
183 XGpioPs_Config *GpioCfg ;
184 GpioCfg = XGpioPs_LookupConfig(MIO_0_ID) ;
185 Status = XGpioPs_CfgInitialize(&GPIO_PTR,
186 GpioCfg, GpioCfg->BaseAddr) ;
187 if (Status != XST_SUCCESS)
188 {
189 xil_printf("PS GPIO Configuration failed!\r\n") ;
190 }
191 /* 设置 MIO 38 作为输出 */
192 XGpioPs_SetDirectionPin(&GPIO_PTR, 38, GPIO_OUTPUT) ;
193 /* 启用 MIO 38 输出 */
194 XGpioPs_SetOutputEnablePin(&GPIO_PTR, 38, GPIO_OUTPUT) ;
195 }
PsGpioSetup()函数的作用是设置初始化ps端的GPIO端口,184行代码配置PS端GPIO端口器件(ps端LED)ID,185行代码对PS端GPIO端口进行初始化,187行到190行代码是判断PS端GPIO端口初始化是否成功,如果没有成功,会打印"PS GPIO Configuration failed!\r\n"语句。192行代码使用XGpioPs_SetDirectionPin()函数设置PS端LED的GPIO端口号为38,并进行初始赋值。194行代码是使用XGpioPs_SetOutputEnablePin()函数使能PS端LED的GPIO端口输出。
197 /*-----------------------------------------------------------*/
198 static void prvPsLedTask( void *pvParameters )
199 {
200 const TickType_t x1second
201 = pdMS_TO_TICKS( DELAY_500_MSECOND );
202 for( ;; )
203 {
204 XGpioPs_WritePin(&GPIO_PTR, 38, PsLedVal) ;
205 PsLedVal = ~PsLedVal ;
206 /* 延迟 1秒。 */
207 vTaskDelay( x1second );
208 }
209 }
prvPsLedTask()任务函数的功能是使ps端LED灯以500ms的间隔闪烁。200行到201行代码是使用函数pdMS_TO_TICKS将时间延时500ms转换为节拍数。202到208行的for( ;; )里就是实现任务函数的功能代码,204行代码通过XGpioPs_WritePin()函数将PS端LED灯的值写入配置好的GPIO端口。205行代码的功能是PS端LED灯的值每隔500ms进行一次翻转。
211 void PlGpioSetup()
212 {
213 int Status ;
214 /* 初始 GPIO 引导 */
215 Status = XGpio_Initialize(&Gpio_led, LED_DEVICE_ID) ;
216 if (Status != XST_SUCCESS)
217 xil_printf("PL GPIO Configuration failed!\r\n") ;
218 /* 将 led 设置为输出 */
219 XGpio_SetDataDirection(&Gpio_led, LED_CHANNEL, 0x0);
220
221 }
PlGpioSetup()函数的作用是设置初始化pl端的GPIO端口。215行代码是使用XGpio_Initialize()函数对pl端的GPIO_LED端口进行初始化配置,216到217行代码的功能是对初始化设置结果的判断,初始化结果失败会打印"PL GPIO Configuration failed! "语句。219行代码是使用XGpio_SetDataDirection()函数配置pl端LED灯使用的GPIO通道与初始值。
223 static void prvPlLedTask( void *pvParameters )
224 {
225 const TickType_t x1second
226 = pdMS_TO_TICKS( DELAY_100_MSECOND );
227 for( ;; )
228 {
229 XGpio_DiscreteWrite(&Gpio_led, LED_CHANNEL, PlLedVal);
230 PlLedVal = ~PlLedVal ;
231 /* 延迟 1 秒。*/
232 vTaskDelay( x1second );
233 }
234 }
prvPlLedTask()任务函数的功能是使pl端LED灯以100ms的间隔闪烁。225行到226行代码是使用函数pdMS_TO_TICKS将时间延时100ms转换为节拍数。227到233行的for( ;; )里就是实现任务函数的功能代码,229行代码通过XGpio_DiscreteWrite ()函数将PL端LED灯的值(PlLedVal)写入配置好的GPIO端口。230行代码的功能是PL端LED灯的值每隔500ms进行一次翻转。至此,创建的任务函数基本运行完成,代码编写完成,接下是要对代码进行编译。本章实验只是简单介绍了工程中使用到的一些函数,如果需要灵活使用FreeRtos实时操作系统的可以系统学习原子官方编写的FreeRtos开发手册,在开源电子网资料下载可以获得。
选中应用工程,右键Build Project对工程进行编译。
图19.4.10 编译工程
编译进度可以在工具下方的控制台面板(Console)中进行查看,编译完成后显示“Finished building:FreeRtos_hello_world.elf”,如下图所示:
图19.4.11 编译完成
到这里我们已经完成了本次实验的软件设计部分。
19.5下载验证
首先我们将下载器与MPSOC开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将开发板USB_UART (PS_PORT)接口与电脑连接,用于串口通信。接下来将开发板上四个启动模式开关均置为ON,即设置为JTAG模式。最后连接开发板电源给开发板上电。如下图所示:
图 19.5.1 MPSOC开发板实物图
使用UART串口打印信息对于串口的设置可以直接参考“Hello Word”中对串口的设置。
下载程序,右键点击FreeRtos_hello_world工程,选择“Run As”,然后选择最后一项“Run Confagurations…”,如下图所示:
图 19.5.2打开下载页面
在打开的下载页面中,没有出现下载选项,这时需要双击左侧列表中Single Application Debug一项,双击后,该项下面出现新的项Debugger_FreeRtos_hello_world-Default,同时在右侧出现的页面中选择Target Setup标签页,勾选复位,然后点击run下载程序,如下图所示:
图 19.5.3下载程序
下载完成后,MPSOC PS端的串口会打印应用工程中打印函数里面的字符串。在Terminal窗口可以看到上位机接收到的字符串,如下图所示:
图 19.5.4程序运行结果
从串口打印信息可知,定时60s,成功打印59次“Hello World”。最后程序成功打印出了“FreeRtos Hello World Example PASSED”字符串,说明本次实验在MPSOC开发板上面下载验证成功。