ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ZYNQ上FreeRTOS实战:Vitis 2023.2从工程创建到调试避坑指南

ZYNQ上FreeRTOS实战:Vitis 2023.2从工程创建到调试避坑指南 1. 为什么要在ZYNQ上跑FreeRTOS而不是裸机大循环很多刚接触ZYNQ的朋友会有一个疑问PS端有双核Cortex-A9主频能跑到667MHz甚至更高直接写个裸机大循环把逻辑塞进while(1)里不也能跑吗我刚开始做ZYNQ项目时也是这么想的直到有一次做一个数据采集加网络转发的活儿裸机方案直接把我坑惨了。裸机大循环的问题在于一旦某个环节阻塞整个系统就卡死了。比如你在等ADC转换完成或者在等网口PHY的链路状态CPU就只能空转。而ZYNQ的PS端资源其实很丰富有DDR控制器、有GIC中断控制器、有SCUSnoop Control Unit这些硬件本身就是为多任务环境设计的。FreeRTOS作为一个轻量级实时操作系统在ZYNQ上跑起来之后你可以把数据采集、协议解析、网络发送拆成独立任务用队列和信号量做同步CPU利用率能提升一大截代码维护性也完全不是一个量级。Vitis 2023.2这个版本对ZYNQ-7000和ZYNQ UltraScale的支持已经相当成熟了。相比早期的SDKVitis在工程管理、调试器集成、FreeRTOS的BSP配置上都做了不少改进。但说实话从创建工程到真正跑起来中间还是有不少细节容易踩坑尤其是调试阶段很多人会遇到“下载程序时识别不到芯片”这类问题。这篇文章我就把整个流程从头到尾捋一遍包括工程创建、BSP配置、任务编写、调试配置以及几个我实际踩过的坑和解决办法。提示本文以ZYNQ-7020为例Vitis版本为2023.2FreeRTOS使用Vitis自带的FreeRTOS 10.x内核。如果你用的是其他型号或版本大部分步骤是通用的个别地方我会标注差异。2. 创建工程前的环境确认与硬件连接2.1 Vitis 2023.2安装时的组件选择Vitis 2023.2的安装包比早期版本大了不少安装时如果组件选不全后面创建工程会直接报错。我建议在安装器里至少勾选以下几项Vitis Unified Software Platform、Zynq-7000 Device Support、Zynq UltraScale Device Support如果你也用MPSoC、以及对应的ARM Cortex-A9和Cortex-R5工具链。另外Vitis 2023.2默认会装一个Xilinx RuntimeXRT这个主要是给Alveo加速卡用的ZYNQ开发用不上但装了也不影响。安装路径尽量别带中文和空格这是老生常谈但真的有人踩。我见过一个同事把Vitis装在“D:\Xilinx 软件\Vitis”下面结果创建平台工程时路径解析直接失败报了一堆莫名其妙的错。后来改成“D:\Xilinx\Vitis”就正常了。2.2 硬件连接与JTAG驱动检查ZYNQ开发板通过JTAG下载器连到PC上下载器一般是FT2232或者CP210x芯片。Windows 10/11下Vitis安装时会自动装好Digilent的JTAG驱动但有时候会被系统自带的USB串口驱动抢占。你可以在设备管理器里看一下如果下载器被识别成“USB Serial Port”而不是“Digilent USB Device”那就需要手动更新驱动指向Vitis安装目录下的\Vitis\2023.2\data\xicom\cable_drivers\nt64\digilent。另外开发板的启动模式拨码开关要拨到JTAG模式。ZYNQ-7020的启动模式由MIO[8:2]决定具体拨法看你的板子原理图。如果拨错了JTAG能连上但下载后不运行这个坑后面调试章节还会细说。2.3 创建Platform Project的正确姿势打开Vitis 2023.2File - New - Platform Project。给平台起个名字比如zynq_freertos_platform。在“Choose Hardware Specification”这一步如果你已经有Vivado导出的XSA文件直接选“Browse”加载如果没有可以选“Create a new hardware specification from a template”但模板里外设配置是固定的实际项目还是建议从Vivado导出XSA。这里有个细节XSA文件里必须包含PS端的配置信息尤其是DDR型号和时钟频率。如果Vivado里PS配置没做对Vitis这边创建平台时会报“No valid processor found”之类的错。我一般会在Vivado里先把PS的DDR、UART、SD、以太网这些外设配好生成bitstream之后再导出XSA这样最稳妥。平台创建完成后在“Platform”视图里右键 - Build等编译完成。然后右键 - “Create Domain”选择standalone或者freertos10_xilinx。这里先选standalone后面创建应用工程时再单独配FreeRTOS这样平台层更干净。3. FreeRTOS应用工程的创建与BSP配置3.1 从模板创建FreeRTOS工程File - New - Application Project。选择刚才建好的平台工程名比如freertos_demo。在“Domain”选择页面注意这里要选“Create new domain”然后在OS Platform里选freertos10_xilinx。Vitis 2023.2自带的FreeRTOS版本是10.4.6内核文件在\Vitis\2023.2\data\embeddedsw\ThirdParty\FreeRTOS\下面。模板选择页面有“FreeRTOS Hello World”和“Empty Application (FreeRTOS)”两个选项。新手建议先选Hello World它能帮你把任务创建、启动调度的框架搭好跑通之后再改成自己的逻辑。选完之后点FinishVitis会自动生成src目录和freertos_hello_world.c。3.2 BSP里的关键配置项工程创建好后在freertos_demo上右键 - “Board Support Package Settings”。这里有几个参数必须改freertos10_xilinx-use_tick_timer默认是true用SCU的私有定时器产生tick。如果你的PS配置里没使能私有定时器这里要改成false用全局定时器代替。freertos10_xilinx-total_heap_size默认是65536字节。如果你任务多、队列多这个值要加大。我一般直接给262144256KBDDR里不差这点空间。freertos10_xilinx-max_priorities默认8够用。但如果你要做复杂的优先级抢占可以加到16。freertos10_xilinx-enable_mutex、enable_queue、enable_semaphore这些默认都是true保持即可。还有一个容易忽略的standalone域里的stdin和stdout要指向正确的UART。默认是ps7_uart_1如果你的板子用的是UART0这里要改。改完之后BSP会自动重新编译生成新的libxil.a和FreeRTOS的库文件。3.3 任务代码的骨架与堆栈分配Vitis生成的Hello World模板里任务函数长这样static void vTaskHelloWorld(void *pvParameters) { while (1) { xil_printf(Hello World from FreeRTOS\r\n); vTaskDelay(pdMS_TO_TICKS(1000)); } }vTaskDelay的参数是tick数pdMS_TO_TICKS宏把毫秒转成tick。默认configTICK_RATE_HZ是100所以1个tick是10ms1000ms就是100个tick。如果你把tick率改成1000那1个tick就是1ms延时精度更高但中断开销也更大。我一般做工业控制会用1000做普通数据采集用100就够了。任务堆栈大小在xTaskCreate里指定单位是word4字节。模板里给的是1024也就是4KB。如果你的任务里用了浮点运算或者大的局部数组这个值要加大。我有个项目里任务函数调用了snprintf格式化字符串栈给到2048才稳定1024的时候偶尔会跑飞。注意FreeRTOS的堆栈溢出检测在BSP里默认是关闭的。调试阶段建议把configCHECK_FOR_STACK_OVERFLOW设为2然后在代码里实现vApplicationStackOverflowHook一旦溢出就能在串口打印出来比直接死机好排查得多。4. 调试配置与“不识别芯片”问题的排查链路4.1 调试配置的创建在freertos_demo上右键 - Debug As - Debug Configurations。双击“Single Application Debug”新建一个配置。在“Target Setup”标签页里确认“Download Application”勾选“Reset Processor”也勾选。在“Application”标签页里确认ELF文件路径正确。Vitis 2023.2的调试器底层用的是GDB OpenOCD和早期SDK的XMD不一样。如果你之前用惯了SDK可能会觉得Vitis的调试连接慢一些但稳定性确实好了不少。4.2 “不识别芯片”的完整排查过程这是ZYNQ开发里最经典的问题之一。现象是点Debug之后Vitis弹窗报“Cannot detect the target”或者“No targets found”或者卡在“Connecting to target”不动。我遇到过好几次排查下来原因五花八门下面按概率从高到低列一下。第一步检查JTAG链路和电源。开发板是否上电JTAG下载器的指示灯是否亮USB线是否插紧这些听起来很基础但确实有人因为USB线只插了一半导致接触不良。用lsusbLinux或设备管理器Windows确认下载器被识别。第二步检查启动模式。ZYNQ的启动模式拨码如果不在JTAG模式JTAG链路上可能看不到ARM核。具体来说如果拨到了QSPI或SD启动PS端会先执行BootROM这时候JTAG虽然能连上PL但PS的调试端口可能被占用。把拨码拨到JTAG模式重新上电。第三步检查Vivado里的JTAG链配置。如果你在Vivado里能正常下载bitstream但Vitis里不行那说明硬件链路没问题是Vitis的调试配置问题。在Vitis的Debug Configuration里点“Target Setup”旁边的“Advanced”看看JTAG频率是不是设得太高。默认是15MHz有些便宜的下载器或长排线跑不了这么高改成5MHz试试。第四步检查XSA和硬件是否匹配。如果你用的XSA是从别的板子导出的PS的DDR配置和实际板子不一致Vitis在初始化DDR时会失败表现就是“识别不到芯片”。这种情况在串口里能看到“DDR init failed”之类的打印。解决办法是用匹配的XSA重新创建平台。第五步检查是否有其他进程占用JTAG。有时候Vivado的Hardware Manager没关或者另一个Vitis实例还在连着会导致JTAG被独占。把Vivado和多余的Vitis窗口全关掉任务管理器里看看有没有残留的hw_server进程有就杀掉。第六步检查下载器固件。Digilent的下载器有时候固件版本太老和Vitis 2023.2不兼容。用Digilent Adept工具更新一下固件或者换一个下载器试试。我实际遇到最多的是第二步和第五步。有一次调了一下午最后发现是Vivado的Hardware Manager在后台连着Vitis死活连不上。关掉Vivado立马就好了。所以遇到这个问题先别急着怀疑硬件把软件环境清干净再说。4.3 调试时的断点与变量观察连上目标之后Vitis的调试界面和Eclipse类似。你可以在任务函数里打断点单步执行看变量值。但FreeRTOS多任务环境下断点会影响任务调度——你停在一个任务里其他任务也被暂停了因为调试器停的是整个CPU。如果你想观察某个任务的运行状态可以用FreeRTOS提供的uxTaskGetSystemState函数把所有任务的状态、优先级、剩余栈空间打印出来。这个在调试任务卡死或者栈溢出时特别有用。代码大概长这样TaskStatus_t taskStatusArray[10]; UBaseType_t arraySize uxTaskGetSystemState(taskStatusArray, 10, NULL); for (UBaseType_t i 0; i arraySize; i) { xil_printf(Task: %s, State: %d, Prio: %d, Stack: %d\r\n, taskStatusArray[i].pcTaskName, taskStatusArray[i].eCurrentState, taskStatusArray[i].uxCurrentPriority, taskStatusArray[i].usStackHighWaterMark); }usStackHighWaterMark是任务运行过程中栈剩余的最小值单位是word。如果这个值接近0说明栈快溢出了赶紧加大。5. 串口调试与printf重定向的实操细节5.1 xil_printf和printf的区别Vitis的BSP里默认提供了xil_printf它是一个轻量级的打印函数不支持浮点数但代码体积小、不依赖堆。printf是标准C库函数支持浮点但会链接一大堆库代码体积大不少。在FreeRTOS任务里我一般用xil_printf够用了。如果你非要用printf打印浮点数需要在BSP设置里把standalone域的stdout改成ps7_uart_1然后在工程属性里把“Linker”的“Libraries”加上-lm。但说实话在嵌入式里打印浮点数意义不大我一般把浮点转成整数再打印比如xil_printf(Voltage: %d mV\r\n, (int)(voltage * 1000))。5.2 串口调试助手的配置PC端用串口调试助手比如SSCOM、SecureCRT、Putty都行波特率一般设115200数据位8停止位1无校验。ZYNQ的PS UART时钟默认是100MHz分频后得到115200误差在可接受范围内。如果你发现串口打印乱码先检查波特率。如果波特率对但还是乱码检查PS的UART时钟配置。在Vivado里PS的UART时钟源是IO PLL还是ARM PLL分频系数是多少这些会影响实际波特率。我遇到过一次Vivado里UART时钟配成了50MHz但BSP里按100MHz算分频结果波特率差了一倍打印出来全是乱码。5.3 多任务下的串口输出竞争FreeRTOS里多个任务同时往串口打印输出会交错在一起很难看。解决办法是加一个互斥信号量每个任务打印前先获取信号量打印完释放。代码大概这样SemaphoreHandle_t xUartMutex; void uart_print(const char *str) { if (xSemaphoreTake(xUartMutex, portMAX_DELAY) pdTRUE) { xil_printf(%s, str); xSemaphoreGive(xUartMutex); } }这个互斥量在main里创建xSemaphoreCreateMutex()。注意创建要在调度器启动之前否则任务里拿不到。6. 几个我实际踩过的坑和解决办法6.1 任务创建后不运行有一次我创建了三个任务结果只有优先级最高的那个在跑另外两个完全没输出。排查了半天发现是优先级设错了。FreeRTOS里优先级数值越大优先级越高我把三个任务都设成了同一个优先级按理说应该轮转调度但configUSE_TIME_SLICING默认是1应该没问题。后来发现是configTICK_RATE_HZ设成了0tick中断根本没起来调度器压根没工作。改回100就好了。所以创建任务后不运行先检查三个地方调度器是否启动vTaskStartScheduler是否调用、tick中断是否正常configTICK_RATE_HZ是否大于0、任务优先级是否合理。6.2 队列发送阻塞导致任务卡死用队列做任务间通信时如果发送端用了portMAX_DELAY而接收端任务优先级更低或者被阻塞了发送端就会一直等整个系统看起来像死机。我一般会在发送时设一个超时比如pdMS_TO_TICKS(100)超时就丢弃数据并打印警告。这样至少不会把系统卡死。if (xQueueSend(xDataQueue, data, pdMS_TO_TICKS(100)) ! pdPASS) { xil_printf(Queue send timeout, data dropped\r\n); }6.3 中断里调用FreeRTOS API导致崩溃FreeRTOS的API分两种任务级和中断级。在中断服务函数里必须用带FromISR后缀的版本比如xQueueSendFromISR、xSemaphoreGiveFromISR。如果你在ISR里调了xQueueSend轻则断言失败重则直接跑飞。这个坑我踩过一次调试了半天才发现是API用错了。另外中断优先级也要注意。ZYNQ的GIC中断优先级数值越小优先级越高而FreeRTOS的configMAX_API_CALL_INTERRUPT_PRIORITY定义了一个阈值高于这个阈值的中断里不能调用FreeRTOS API。在ZYNQ上这个阈值一般是configUNIQUE_INTERRUPT_PRIORITIES - 1具体值在BSP里能看到。6.4 堆空间不足导致创建任务失败xTaskCreate返回pdFAIL大概率是堆空间不够了。FreeRTOS的堆在heap_4.c里管理总大小由configTOTAL_HEAP_SIZE决定。在Vitis的BSP设置里改这个值改完重新编译BSP。我一般给256KB如果还不够可以改用heap_5.c把DDR里多块不连续的内存都管起来。查看当前堆使用情况可以用xPortGetFreeHeapSize()返回剩余字节数。在调试时定期打印这个值能提前发现内存泄漏。7. 从Demo到实际项目的扩展思路跑通Hello World只是第一步。实际项目里你通常需要把FreeRTOS和ZYNQ的PL端逻辑配合起来。比如PL端做数据采集通过AXI DMA把数据搬到DDRPS端的FreeRTOS任务从DDR里读数据、做处理、通过网络发出去。这种架构下DMA中断的优先级要设得比普通任务高但又要低于FreeRTOS的API调用阈值否则中断里没法用队列通知任务。另外Vitis 2023.2支持在FreeRTOS里用xSemaphoreCreateBinary做任务同步也支持xEventGroup做多事件等待。如果你的项目里任务间同步关系复杂建议用事件组比多个信号量组合更清晰。还有一个实用技巧在main函数里调度器启动之前先把所有外设初始化好UART、DMA、中断控制器然后再创建任务、启动调度器。这样任务一跑起来就能直接用外设不用在任务里再做初始化避免竞争。最后说一个调试习惯每次改完代码先编译确认没有warning再下载。Vitis对warning的容忍度比SDK低有些warning其实是潜在的错误。比如“implicit declaration of function”这种说明你漏了头文件运行时可能直接跑飞。我一般把warning级别开到最高把能清的warning全清掉这样调试时少很多麻烦。
返回列表