当前位置: 首页 > news >正文

【正点原子FPGA连载】第十九章FreeRtos Hello World实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

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开发板上面下载验证成功。

相关文章:

【正点原子FPGA连载】第十九章FreeRtos Hello World实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id692450874670 3)全套实验源码手册视频下载地址: http://www.openedv.com/thread-340252-1-1.html 第十九章FreeRto…...

php mysql高校田径运动会成绩管理系统

第一章 引言 1 1.1 选题背景 1 1.2 编写目的 2 1.3 目标 2 1.4 功能需求 3 第二章 开发工具介绍 4 2.1 PHP 4 2.2 APACHE 5 2.3 MYSQL数据库 5 2.4 运行环境 WINDOWS XP 6 2.5 XAMPP 6 2.6 DREAMWEAVE8 6 2.7 EDITPLUS 7 第三章 需求…...

scrum敏捷项目管理软件三款

Leangoo领歌Leangoo是国产的一个项目管理软件,www.leangoo.com , 专门的Scrum敏捷开发工具,看板的管理方式,高度可视化。它支持敏捷开发全流程。从产品路线图-需求-迭代-缺陷-测试-上线。燃尽图,工作量,迭代…...

【项目设计】高并发内存池(二)[高并发内存池整体框架设计|threadcache]

🎇C学习历程:入门 博客主页:一起去看日落吗持续分享博主的C学习历程博主的能力有限,出现错误希望大家不吝赐教分享给大家一句我很喜欢的话: 也许你现在做的事情,暂时看不到成果,但不要忘记&…...

西电编译原理期末核心考点汇总(期末真题+相关知识点)

文章目录前言一、正规式1.1 相关知识点1.1.1 正规式定义1.1.2 辅助定义1.2 历年真题二、二义文法2.1 相关知识点2.1.1 二义性概念2.2 历年考题三、全部短语、直接短语和句柄3.1 相关知识点3.1.1 短语,直接短语和句柄定义3.1.2 短语,直接短语和句柄例题3.…...

追梦之旅【数据结构篇】——详解C语言实现二叉树

详解C语言实现二叉树~😎前言🙌什么是二叉树?二叉树的性质总结:整体实现内容分析💞1.头文件的编写:🙌2.功能文件的编写:🙌1)前序遍历的数值来创建树——递归函…...

独家 | Gen-1——可以改变视频风格的AI模型

翻译:吴振东校对:张睿毅本文约1000字,建议阅读3分钟 本文简单介绍了Runway公司的发展史,以及他们新推出的生成式AI模型Gen-1,可用于通过应用文本提示或者参考图像所指定的任意风格,将现有视频转换为新视频。…...

戴尔dell inspiron-5598电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网,转载需注明出处。硬件型号驱动情况主板X99 K9 v2 Machinist处理器i5-10210U / *i7-10510U已驱动内存20GB已驱动硬盘1000GB SAMSUNG 860 QVO SATA已驱动显卡Intel UHD 620已驱动声卡Realtek ALC3204/236已驱动网卡RTL8168H Gigabit Ethernet已…...

3.2 网站图的爬取路径

深度优先与广度优先方法都是遍历树的一种方法,但是网站的各个网页 之间的关系未必是树的结构,它们可能组成一个复杂的图形结构,即有回路。如果在前面的网站中每个网页都加一条Home的语句,让每个网页都能回到主界面,那么…...

《SQL基础》12. SQL优化

SQL优化SQL优化数据插入insert优化大批量插入数据主键优化order by优化group by优化limit优化count优化count用法update优化SQL优化 数据插入 insert优化 如果我们需要一次性往数据库表中插入多条记录,可以从以下三个方面进行优化。 批量插入手动控制事务主键顺…...

fork之后是子进程先执行还是父进程先执行

CFS(完全公平调度器)是Linux内核2.6.23版本开始采用的进程调度器,它的基本原理是这样的:设定一个调度周期(sched_latency_ns),目标是让每个进程在这个周期内至少有机会运行一次,换一种说法就是每个进程等待CPU的时间最长不超过这个…...

2023年java初级面试题(5道)

一、两个对象值相同(x.equals(y) true),但却可有不同的hash code,这句话对不对?答:不对,如果两个对象x和y满足x.equals(y) true,它们的哈希码(hash code)应当相同。Java对于eqauls…...

【内网安全】——Linux权限维持

作者名:白昼安全主页面链接: 主页传送门创作初心: 以后赚大钱座右铭: 不要让时代的悲哀成为你的悲哀专研方向: web安全,后渗透技术每日鸡汤: 钱至少对于现在的我来说,的确是万能的在…...

Linux 真实使用内存计算

获取Linux内存信息,可通过cat /proc/meminfo查看,比如,Ubuntu 20.04.5 LTS上会显示以下信息: leoyaDESKTOP-LMR:~$ cat /proc/meminfo MemTotal: 16017572 kB MemFree: 15637472 kB MemAvailable: 15533140 kB Bu…...

Unity Jobsystem ECS

简介随着ECS的加入,Unity基本上改变了软件开发方面的大部分方法。ECS的加入预示着OOP方法的结束。随着实体组件系统ECS的到来,我们在Unity开发中曾使用的大量实践方法都必须进行改变以适应ECS,也许不少人需要些时间适应ECS的使用,…...

Java中创建线程有哪几种方式

1.继承Thread类 总结:通过继承 Thread 类,重写 run() 方法,而不是 start() 方法 Thread 类底层实现 Runnable 接口类只能单继承 接口可以多继承2.实现Runnable接口 总结:通过实现 Runnable 接口,实现 run() 方法,依然…...

C++【string类用法详细介绍string类模拟实现解析】

文章目录string 类用法介绍及模拟实现一、string介绍二、string类常用接口1. string类对象的常见构造接口2.string类对象的常见容量接口3.string类对象的常见修改接口4. string类对象的常见访问及遍历接口5.string其他接口1.不常用查找接口2.字符替换3.字符串拼接4.字符串排序5…...

常见的开发模型和测试模型

软件的生命周期软件开发阶段的生命周期需求分析->计划->设计->编码->测试->运维软件测试阶段的生命周期需求分期->测试计划->测试设计与开发->执行测试->测试评估开发模型瀑布模型可以看到,这个模型和我们上面的软件开发生命周期很相似采用的是线性…...

印度和印度尼西亚有什么关系吗?

印度和印度尼西亚,这两个国家很多人都比较熟悉。因为两国都是人口大国,而且经济总量也比较高,在全球还是有很大影响的。不过很多人刚看到这两个国家的时候,都会觉得这两个国家肯定有什么关系,要不然国名也不会这么像。…...

单调栈(C/C++)

目录 1. 单调栈的定义 2. 单调栈的常见用途 3. 案例分析 3.1 暴力解法 3.2 单调栈 4. 单调栈总结 1. 单调栈的定义 单调栈顾名思义,就是栈内的元素是单调的。根据栈内元素的单调性的不同,可以分为: 单调递增栈:栈内元素是单…...

算法设计与智能计算 || 专题一: 算法基础

专题一: 算法基础 文章目录专题一: 算法基础1. 算法的定义及特点1.1 算法的基本特征1.2 算法的基本要素1.3 算法的评定2 算法常见执行方法2.1 判断语句2.2 循环语句2.3 综合运用3. 计算复杂度4. 代码的重用5. 类函数的定义与使用5.1 定义类5.2 调用类函数1. 算法的定义及特点 …...

用javascript分类刷leetcode13.单调栈(图文视频讲解)

239. 滑动窗口最大值 (hard) 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1: 输入:nums [1,…...

英语基础语法学习(B站英语电力公司)

1. 句子结构 五大基本句型: 主谓主谓宾主谓宾宾主谓宾宾补主系表 谓语: 一般来说,谓语是指主语发出的动作。(动词)但是很多句子是没有动作的,但是还是必须要有谓语。(此时需要be动词&#x…...

【计算机网络】网络层IP协议

文章目录一、认识IP协议二、IP协议头部格式三、IP地址划分1. IP地址分类2. 子网划分四、IP地址数量危机1. IP地址的数量限制2. NAT技术五、私网IP和公网IP六、路由1. 认识路由2. 路由表生成算法一、认识IP协议 IP协议是Internet Protocol(互联网协议)的…...

Eclipse快捷键大全

编辑类快捷键Ctrl1: 快速修复(最经典的快捷键, 可以解决很多问题, 比如import类、try catch包围等)CtrlShiftF: 格式化当前代码CtrlShiftM: 添加类的import导入CtrlShiftO: 组织类的导入(既有CtrlShiftM的作用,又可以去除没用的导入, 一般用这个导入包)CtrlY: 重做(与CtrlZ相反…...

JavaScript 高级2 :构造函数和原型 d331702016e84f54b3594ae05e0eeac

JavaScript 高级2 :构造函数和原型 Date: January 16, 2023 Text: 构造函数和原型、继承、ES5中的新增方法 目标 能够使用构造函数创建对象 能够说出原型的作用 能够说出访问对象成员的规则 能够使用 ES5新增的一些方法 构造函数和原型 概述 在典型的 OOP 的…...

maven-war-plugin插件 overlays maven-war-plugin翻译

说明 翻译maven-war-plugin插件的部分内容 官方地址为:https://maven.apache.org/plugins/maven-war-plugin/index.html Overview 概述 Introduction 介绍 Apache Maven WAR Plugin apache maven war 插件 The WAR Plugin is responsible for collecting all artifa…...

【数据结构】初识二叉树(二叉树的入门知识)

初识二叉树一、树概念及结构1、树的概念2、树的相关概念3、树的表示4、树在实际中的运用(表示文件系统的目录树结构)二、二叉树概念及结构1、概念2、特殊的二叉树3、二叉树的性质4、二叉树的存储结构三、结语一、树概念及结构 1、树的概念 树是一种非线…...

RV1126笔记三十二:基于 FastDeploy 在 RV1126 上的部署示例(RV1126 上部署 YOLOv5 检测模型测试)

若该文为原创文章,转载请注明原文出处。 FastDeploy是一款全场景、易用灵活、极致高效的AI推理部署工具, 支持云边端部署。提供超过 🔥160+ Text,Vision, Speech和跨模态模型📦开箱即用的部署体验,并实现🔚端到端的推理性能优化。包括 物体检测、字符识别(OCR)、…...

JVM垃圾回收——G1垃圾收集器

目录 一、什么是G1垃圾收集器 二、G1垃圾收集器的内存划分 三、G1垃圾收集器的收集过程 四、G1收集器的优缺点 五、G1收集器的JVM参数配置 一、什么是G1垃圾收集器 Garbage First(简称G1)收集器是垃圾收集器技术发展史上里程碑式的成果,它摒弃了传统垃圾收集器的…...

长宁做网站公司/一键制作网站

今天是不错的一天今天我和我哥还有我上海的朋友聊了很多.大都聊的是生活,未来,工作,理财的问题或许这些都是我这个年龄段的人关注的吧,今天的聊天让我觉得我进入了这个阶段,让我觉得,恩,是这样的,我进入人生的这个阶段了.邹和我说了许多关于理财的东西,哥和我说了很多和工作,前…...

自学网站查分数/长春最新发布信息

课程描述 这门课是对自然语言处理(NLP)详细介绍,自然语言处理是对能够用人类语言处理、理解或交流的计算系统的研究。该课程涵盖了基本方法,主要是机器学习和深度学习,用于整个自然语言处理领域,以及一套历史和当代的自然语言处理…...

苏南建设集团网站/nba最新交易汇总

深海才会有鲸鱼 首先,在此,感怀毛星云先生,虽并无交集,但若曾我有幸认识你的话,我想我一定是你的忠实的粉丝,你一定是我的追逐的榜样。 听到这样的消息真的好惋惜,如此勤奋、聪颖、执着且有梦想…...

建小网站多少钱/做seo推广一年大概的费用

国内it软件外包公司排行榜是怎么样的由于互联网技术的快速发展,特别是手机移动端的的普及,使得企业越来越需要开发自己自己的软件,但是软件开发人才缺口很大,企业没有这个技术实力去开发自己的软件,对于中小企业也不好…...

古尔邦节网站建设/怎么发帖子做推广

题意&#xff1a;给一个无向无环图(n<1000)&#xff0c;在尽量少的节点上放灯&#xff0c;使得所有边都被照亮&#xff0c;灯可以照亮相邻的边&#xff0c;在灯数最小的前提下&#xff0c;使得被两盏灯照亮的边最多&#xff0c;输出灯数以及被两盏灯照亮的边数&#xff0c;及…...

公司的网站建设费用属于什么费/seo优化排名百度教程

企业Java开发人员想要一个快速的Web框架解决方案&#xff0c;而又不增加日常开发工作的想法&#xff0c;这并不是什么新鲜事物。 现在提供了多个选项&#xff0c;允许开发人员即时创建UI。 快速开发平台的一个例子就是OpenXava&#xff0c;它使开发人员可以完全通过编写Java或G…...