ZYNQ开发中SDK输出串口选择以及打印函数print、printf、xil_printf的差别

文章介绍了在ZYNQ开发中如何选择和切换UART_0和UART_1进行打印,以及print、printf和xil_printf函数的区别。print用于打印字符串,xil_printf不支持浮点数但比printf节省内存,而printf是C标准库函数。文章还探讨了不同打印函数对内存的影响,并建议根据需求选择合适的打印函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ZYNQ开发系列——SDK输出串口选择以及打印函数print、printf、xil_printf的差别


前言

在最初的helloworld工程中,我们实现了通过串口每个1秒钟打印一次Hello World。

在这里插入图片描述

这里我们就来搞清楚以下几个问题,从简单到复杂问题依次为:
1、 我们有两个串口,那怎么区分是从哪个串口打印的
2、 为什么是print函数,一般C标准打印函数是printf呢
3、 如果我两个串口都想打印东西怎么弄
4、 串口的波特率如何修改,最大能支持多少
后面2点放到另一篇文章讲。

两个串口到底是谁在打印?

一开始我们就定义了两个串口,在xparameter.h中的描述如下# 二、使用步骤

在这里插入图片描述

那怎么知道以及设置是要从UART_0打印还是UART_1打印呢?
我们进入print.c文件,找到outbyte函数,里面内容为
XUartPs_SendByte(STDOUT_BASEADDRESS, c);
这个STDOUT_BASEADDRESS的定义为:

在这里插入图片描述

这个就正好和XPAR_PS7_UART_0_BASEADDR吻合了,也即是说,我们当前使用的哪个串口,与这两个宏定义设置有关。
因此我们如果想换成UART_1打印,只要将STDOUT_BASEADDRESS 改成0xE0001000即可,同时STDIN_BASEADDRESS这个也应同步修改。

还有一种方式是,直接在mss文件中修改

在这里插入图片描述

因此如果我们要修改要打印的串口,我觉得最好还是通过第二种方式修改(修改mss文件中的配置),因为第一种方式修改后会和mss中显示的stdin和stdout不一致。

print 和 printf 和 xil_printf

参考 https://www.youtube.com/watch?v=f2pPIRHc0bM

我们阐述下三种打印的差别
差别1:
1、printf 是调用C标准库,使用printf的时候需要加头文件 #include <stdio.h>
2、print 和 xil_printf是使用xilinx自己的库 #include “xil_printf.h”
差别2:
1、 print只能打印字符串
2、 xil_printf和printf,可以带参量打印,但是xil_printf不支持打印浮点数

xilinx的SDK工具支持标准的c库,比如我们最日常使用的printf函数,就是标准c库里的一个重要函数。但是标准c库所谓的标准性,或者所谓的通用性带来的问题就是它必须面对所有的情况,而一些情况在fpga设计中是普通情况下是极少碰到的,比如正常情况下,浮点处理是很少用的。如此之后,这个函数必然会变得体态臃肿。比如这里的printf函数。不知道大家有没有这样的经验,使用printf函数,你的程序最后编译出来变的很大~其实xil_printf和printf的功能是一样的,只是xil_printf除去了浮点的所有功能,如此之后,一下子程序就变得很小了。
如果用专业一点的术语描述,就是使用printf链接过程是静态链接,静态链接的时候他进行链接的是一整个<stdio.h>,而且静态链接的特点在于他是直接把这个对象文件加入到了可执行文件当中,极大的浪费了内存空间。而另外两个函数是动态链接,其链接过程是动态的在可执行程序执行的时候进行链接的。

既然提到使用print 和 printf 和 xil_printf的内存问题,那么,我们就来做个实验看看,到底怎么节省内存法。

实验可查看原链接……

结论:无论是printf 还是xil_printf 多个打印语句不会显著增加内存

总之,需要打印字符串就使用print函数,需要打印浮点数就使用printf,需要打印整点数就使用xil_printf就没错了。可能的话最好所有打印都不要出现printf,只要有一个都会使内存增加不少。

<think>好的,用户现在的问题是关于如何在Zynq 7020裸机环境下调试lwIP协议栈中的tcp_write函数。我需要先理清用户的需求和背景。 首先,用户提到了Zynq 7020裸机运行lwIP,这说明他们可能在嵌入式系统中使用FPGA和ARM Cortex-A9的架构,没有操作系统支持,直接跑在硬件上。这种情况下,调试手段可能比较有限,通常需要依赖JTAG、串口输出,或者一些硬件调试工具。 接下来,用户想进入tcp_write函数进行调试,可能遇到了问题,比如连接失败、发送数据出错,或者之前提到的ERR_MEM错误。他们需要具体的调试方法,可能包括设置断点、打印日志、分析内存状态等。 我需要回顾之前的历史对话,之前用户询问过tcp_write返回ERR_MEM的错误,我给出了系统性的分析和解决方案。现在的问题更偏向于调试方法,特别是在裸机环境下的具体步骤。所以需要结合之前的解决方案,补充调试的具体技术手段。 考虑到裸机调试的常见方法,首先想到的是使用JTAG调试器,比如Xilinx的SDK或Vivado自带的调试工具,设置断点,单步执行。但用户可能需要更详细的步骤,比如如何配置调试环境,需要注意哪些关键点。 另外,裸机环境下可能没有操作系统支持,因此printf输出可能需要通过串口实现,用户需要确保串口初始化正确,能够在代码中添加调试信息输出串口终端。 还可能涉及到lwIP的配置选项,比如启用调试宏定义,调整内存池的大小,或者在代码中添加日志输出,跟踪tcp_write的调用流程和参数状态。 此外,用户可能需要检查内存分配情况,比如pbuf的申请和释放,是否存在内存泄漏或碎片,导致tcp_write无法分配足够的内存而返回错误。 还需要考虑网络连接的状态,比如TCP连接是否建立成功,窗口大小是否合理,是否有ACK未能及时收到,导致发送缓冲区满。 综合这些点,我应该分步骤给出调试方法,从硬件调试工具的使用,到软件日志的添加,再到协议栈的配置和内存分析,最后结合网络状态的监控,全面覆盖可能的调试途径。 同时,需要提醒用户注意裸机环境下的资源限制,比如堆栈大小、内存池配置,这些都可能影响lwIP的运行。如果在之前的讨论中已经调整过这些参数,可能需要再次确认当前的配置是否合适。 可能还需要检查中断处理是否正确,因为lwIP在裸机环境下通常依赖定时器中断来驱动协议栈的任务处理,比如ARP、超时重传等。如果中断处理有问题,可能导致协议栈无法正常运作,进而影响tcp_write的执行。 最后,应该给出一个系统化的调试流程,帮助用户逐步排查问题,从设置断点开始,到观察变量状态,再到分析网络数据包,确保用户能够有条理地进行调试。</think>在Zynq 7020裸机环境下调试lwIP的`tcp_write`函数,需要结合硬件调试工具和协议栈特性进行系统性分析。以下是分步调试方案: --- ### **一、调试环境搭建** #### 1. 硬件调试基础配置 ```c // 确保串口调试通道已启用(用于打印日志) void uart_init() { XUartPs_Config *config = XUartPs_LookupConfig(XPAR_XUARTPS_0_DEVICE_ID); XUartPs_CfgInitialize(&uart_inst, config, config->BaseAddress); XUartPs_SetBaudRate(&uart_inst, 115200); // 波特率匹配调试终端 } ``` #### 2. lwIP调试宏启用 ```c // 在lwipopts.h中开启关键调试选项 #define LWIP_DEBUG 1 // 全局调试开关 #define TCP_DEBUG LWIP_DBG_ON // TCP层调试 #define PBUF_DEBUG LWIP_DBG_ON // 内存分配跟踪 #define TCP_INPUT_DEBUG LWIP_DBG_ON // 输入数据处理日志 ``` --- ### **二、断点调试法** #### 1. Vivado硬件调试器配置 ```bash # 在Xilinx SDK中操作: 1. 创建Debug Configuration -> 选择Zynq硬件 2. 在"Application"选项卡加载ELF文件 3. 设置断点:右键点击tcp_write函数入口 -> Toggle Breakpoint ``` #### 2. 关键参数观察窗口 当断点触发时,在调试器中监控以下关键寄存器/变量: | 观察对象 | 作用 | 典型值示例 | |----------------------|-----------------------------------|------------------| | pcb->snd_buf | 发送缓冲区剩余空间 | 0表示缓冲区满 | | pcb->snd_queuelen | 当前发送队列长度 | 超过配置值会阻塞 | | pcb->state | TCP连接状态 | ESTABLISHED=4 | | pcb->snd_wnd | 接收方通告窗口大小 | 0时触发等待 | | p->ref | pbuf引用计数 | 非0表示内存被占用| --- ### **三、动态日志追踪** #### 1. 定制调试输出 ```c // 在tcp_out.c中添加专用调试代码 err_t tcp_write(struct tcp_pcb *pcb, const void *arg, u16_t len, u8_t apiflags) { LWIP_DEBUGF(TCP_DEBUG, ("[TCP_Write] Entry: pcb=%p len=%d flags=0x%x\n", pcb, len, apiflags)); /* 发送缓冲区状态实时打印 */ printf("snd_buf: %d, snd_queuelen: %d\n", tcp_sndbuf(pcb), pcb->snd_queuelen); if (pcb->snd_queuelen >= pcb->snd_queuelen_max) { LWIP_DEBUGF(TCP_DEBUG, ("[TCP_Write] Queue overflow!\n")); } // ...原函数逻辑... } ``` #### 2. 关键错误码拦截 ```c // 在调用tcp_write后添加错误处理 err_t err = tcp_write(pcb, data, len, 0); if (err != ERR_OK) { printf("TCP Write Error: %d - ", err); switch(err) { case ERR_MEM: printf("Memory exhausted\n"); break; case ERR_CONN: printf("Connection closed\n"); break; case ERR_VAL: printf("Invalid parameters\n"); break; default: printf("Unknown error\n"); } // 打印协议栈内部状态 tcp_debug_print_pcb(pcb); } ``` --- ### **四、内存状态分析** #### 1. PBUF池监控 ```c // 在应用层添加内存统计输出 void print_mem_stats() { printf("PBUF Pool: free/%d, used/%d\n", memp_pools[MEMP_PBUF_POOL]->stats->avail, memp_pools[MEMP_PBUF_POOL]->stats->used); } // 在tcp_write调用前后调用该函数 ``` #### 2. 内存泄漏检测 ```c // 在lwipopts.h中启用内存调试 #define MEMP_OVERFLOW_CHECK (1) // 内存越界检测 #define MEMP_STATS (1) // 内存统计 #define LWIP_STATS (1) // 全局统计 #define LWIP_STATS_DISPLAY (1) // 统计信息显示 ``` --- ### **五、网络流量抓取** #### 1. 硬件级抓包 ```bash # 使用Wireshark+以太网调试器(如J-Link Trace): 1. 连接开发板ETH接口到PC的USB转以太网适配器 2. 在Wireshark中过滤目标IP:ip.dst == 192.168.1.100 3. 观察TCP序列号(seq)和窗口大小(win)变化 ``` #### 2. 协议栈内部数据流可视化 ```c // 添加数据包标记代码 void tcp_write_debug(struct tcp_pcb *pcb, const void *data, u16_t len) { static uint32_t packet_counter = 0; printf(">> Sending Packet #%d, Len=%d\n", ++packet_counter, len); hexdump(data, len); // 自定义十六进制打印函数 tcp_write(pcb, data, len, 0); } ``` --- ### **六、典型调试场景** #### 场景1:阻塞在tcp_write入口 - **现象**:程序卡在`tcp_write`函数入口 - **诊断步骤**: 1. 检查`pcb->state`是否为`ESTABLISHED` 2. 确认`tcp_sndbuf(pcb)`返回值是否大于0 3. 验证数据指针`arg`是否指向有效内存区域 #### 场景2:间歇性返回ERR_MEM - **调试流程**: 1. 在`tcp_output`函数后添加延迟: ```c tcp_output(pcb); usleep(1000); // 人为制造发送间隔 ``` 2. 监控`pcb->snd_queuelen`变化趋势 3. 检查`MEMP_NUM_TCP_SEG`配置是否过小 --- ### **七、调试优化技巧** 1. **时序敏感问题捕捉**: ```c // 在中断服务例程(ISR)中添加时间戳标记 uint32_t get_timestamp() { return *((volatile uint32_t*)0xE0001004); // 读取Zynq全局定时器 } ``` 2. **发送速率可视化**: ```c // 统计每秒发送量 static uint32_t last_time = 0, bytes_sent = 0; uint32_t now = get_timestamp(); if (now - last_time >= 1000000) { // 1秒间隔 printf("TX Rate: %d KB/s\n", bytes_sent / 1024); bytes_sent = 0; last_time = now; } bytes_sent += len; ``` 3. **关键断点条件设置**: ```bash # 在GDB中设置条件断点 (gdb) break tcp_out.c:1234 if pcb->snd_buf < 512 (gdb) commands > print pcb->snd_wnd > print pcb->state > continue > end ``` --- ### **调试流程图解** ``` 开始调试 │ ˅ [检查物理连接]→ETH灯是否亮?─否→检查PHY配置 │ 是 ˅ [验证TCP状态]→pcb->state == ESTABLISHED?─否→检查三次握手 │ 是 ˅ [监控发送窗口]→tcp_sndbuf() > 0?─否→等待窗口更新 │ 是 ˅ [检查内存池]→memp_pools[MEMP_PBUF_POOL].avail >0?─否→增大PBUF_POOL_SIZE │ 是 ˅ [单步执行]→观察数据拷贝路径 │ ˅ [触发tcp_output]→验证数据包实际发送 │ ˅ [Wireshark验证]→确认数据到达对端 ``` 通过上述方法,可以系统性地定位`tcp_write`函数相关问题。对于Zynq 7020平台,要特别注意**DMA缓冲区对齐**要求和**Cache一致性**问题(建议在数据操作前后使用`Xil_DCacheFlush()`)。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值