ZC706千兆网测试(ZYNQ,FreeRTOS,Echo,消息队列,QSPI启动,FSBL固化,lwIP,TCP,RGMII,Xilinx)

初学 FreeRTOS:
(1)使用 ZC706 开发板测试 PS 端网口(Echo,lwIP协议栈);
(2)配合操作 PL 端 LED(直接驱动和使用消息队列两种方式);
(3)PS 端串口 UART 打印调试信息;
(4)QSPI 固化(Dual Quad SPI Parallel 8 bit模式)。

ZC706中,MAC 控制器与 PHY 通过 RGMII(Reduced Gigabit Media Independent Interface)接口进行连接,实现千兆网。

一、工程概述

1.开发板配置

  使用 Xilinx ZYNQ 开发板 ZC706,默认配置 ARM 后即可使用PS 端网口、串口和 QSPI,放置 AXI GPIO 的 IP 核驱动 PL 端的 4 个 LED。
在这里插入图片描述

  ARM端配置如下图所示,以5处的ARM-A9为核心,使用1处的UART1打印调试信息,使用2处的网口0进行以太网通信,使用3处的AXI GP(General Port)Master通用主设备接口连接PL端的AXI GPIO,最后使用4处的QSPI固化程序,烧录Boot文件。
在这里插入图片描述

2.SDK程序

  上述工程综合、布局布线并生成bit流后,导出硬件。
  新建应用工程Application Project,选择 OS Platform 平台为 freertos10_xilinx(Vivado及SDK版本2018.2,低版本的可能是freertos9_xilinx),选择Next,选中“FreeRTOS lwIP Echo Server”。
在这里插入图片描述

在这里插入图片描述

  新建完成后,即可进行最基础的网络通信了。
  这里注意,默认设置的是DCHP动态主机配置协议,需要开发板和电脑都连接到一个路由器上。如果直接使用网线连接开发板和电脑,则启用IPv4协议,默认配置的IP地址为 192.168.1.10,子网掩码 255.255.255.0,网关 196.128.1.1,如果想要更改默认配置,可以在 main.c 文件的 main_thread() 主线程中修改,如下所示:

xil_printf("ERROR: DHCP request timed out\r\n");
xil_printf("Configuring default IP of 192.168.1.10\r\n");
IP4_ADDR(&(server_netif.ip_addr),  192, 168, 1, 10);
IP4_ADDR(&(server_netif.netmask), 255, 255, 255,  0);
IP4_ADDR(&(server_netif.gw),  192, 168, 1, 1);

  LWIP 是一个小型开源的 TCP/IP 协议栈,支持IPv4、IPv6、TCP、UDP、DHCP等。
• IGMP 协议,用于网络组管理,可以实现多播数据的接收
• Internet 协议(IP),包括 IPv4 和 IPv6,支持 IP 分片与重装,包括通过多个网络接口的数据包转发
• 用于网络维护和调试的 Internet 控制消息协议(ICMP)
• 用户数据报协议(UDP)
• 传输控制协议(TCP)拥塞控制,往返时间(RTT)估计,快速恢复和重传
• DNS,域名解析
• SNMP,简单网络管理协议
• 动态主机配置协议(DHCP)
• 以太网地址解析协议(ARP)
• AUTOIP,IP 地址自动配置
• PPP,点对点协议,支持

3.网络设置

  使用网线直接连接ZC706开发板和计算机网口,配置计算机IP地址为192.168.1.11,子网掩码255.255.255.0,网关192.168.1.1,其中IP地址的最后一处可以更改为其他值,但是不能和开发板的相同。
在这里插入图片描述

在这里插入图片描述

4.开启监听测试

  使用SecureCRT软件监听,除此之外,使用其他网口助手也可以。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、工程测试

1.测试Echo官方例程

  先打开串口,波特率115200,下载官方例程到ZC706开发板,连接SecureCRT_CN,初始化工程中串口打印信息如下:配置DCHP动态主机协议超时,自动转为IPv4,将板子的IP地址配置为192.168.1.10,子网掩码255.255.255.0,网关192.168.1.1,使用端口7。
在这里插入图片描述

  在SecureCRT_CN界面输入字符或字符串,回车,通过网口向开发板发送数据,开发板会返回同样的数据,测试正确。

在这里插入图片描述

2.分析源码

2.1 main函数

  打开main.c文件,找到main()函数。在main函数中创建了一个线程,传入的参数依次为线程名(调试用)、函数指针、函数需要的参数、需要的堆栈大小、优先级。
  按照如下配置,调用了main_thread函数,不需要传参(用0或NULL),堆栈大小由#define定义为1024,优先级为2。

int main()
{
    
    
	sys_thread_new("main_thrd", (void(*)(void*))main_thread, 0,
	                THREAD_STACKSIZE,
	                DEFAULT_THREAD_PRIO);
	vTaskStartScheduler();
	while(1);
	return 0;
}

2.2 main_thread函数

  此函数中实现的功能如下:
(1)初始化lwip协议栈;lwip_init();
(2)调用network_thread()创建线程;
(3)调用echo_application_thread()创建线程;
  每500ms检测一次DHCP是否成功,若成功则创建echo应用线程,如果10秒还没有成功,则启用IPv4,配置IP地址、子网掩码和网关后,创建echo应用程序;创建成功后推出while,配置完成;

while (1) {
    
    
	vTaskDelay(DHCP_FINE_TIMER_MSECS / portTICK_RATE_MS);
		if (server_netif.ip_addr.addr) {
    
    
			xil_printf("DHCP request success\r\n");
			print_ip_settings(&(server_netif.ip_addr), &(server_netif.netmask), &(server_netif.gw));
			print_echo_app_header();
			xil_printf("\r\n");
			sys_thread_new("echod", echo_application_thread, 0,
					THREAD_STACKSIZE,
					DEFAULT_THREAD_PRIO);
			break;
		}
		mscnt += DHCP_FINE_TIMER_MSECS;
		if (mscnt >= 10000) {
    
    
			xil_printf("ERROR: DHCP request timed out\r\n");
			xil_printf("Configuring default IP of 192.168.1.10\r\n");
			IP4_ADDR(&(server_netif.ip_addr),  192, 168, 1, 10);
			IP4_ADDR(&(server_netif.netmask), 255, 255, 255,  0);
			IP4_ADDR(&(server_netif.gw),  192, 168, 1, 1);
			print_ip_settings(&(server_netif.ip_addr), &(server_netif.netmask), &(server_netif.gw));
			/* print all application headers */
			xil_printf("\r\n");
			xil_printf("%20s %6s %s\r\n", "Server", "Port", "Connect With..");
			xil_printf("%20s %6s %s\r\n", "--------------------", "------", "--------------------");

			print_echo_app_header();
			xil_printf("\r\n");
			sys_thread_new("echod", echo_application_thread, 0,
					THREAD_STACKSIZE,
					DEFAULT_THREAD_PRIO);
			break;
		}
	}

2.3 echo_application_thread函数

位置:echo.c文件。
(1)创建socket,绑定端口,监听;
(2)调用process_echo_request函数创建线程;
此函数需要传入参数。

while (1) {
    
    
	if ((new_sd = lwip_accept(sock, (struct sockaddr *)&remote, (socklen_t *)&size)) > 0) {
    
    
		sys_thread_new("echos", process_echo_request,
				(void*)new_sd,
				THREAD_STACKSIZE,
				DEFAULT_THREAD_PRIO);
	}
}

2.4 process_echo_request函数

位置:echo.c文件,用户需要注意的最重要的函数,发送和接收的移植全部在这个函数。
(1)接收数据,最大数据长度2048,char类型,存储在recv_buff数组中,若接收出错,打印错误信息并退出while;
(2)若接收到的数据的前4个字符为quit,则退出while;
(3)将接收到的数据发送出去;

void process_echo_request(void *p)
{
    
    
	int sd = (int)p;
	int RECV_BUF_SIZE = 2048;
	char recv_buf[RECV_BUF_SIZE];
	int n, nwrote;

	while (1) {
    
    
		/* read a max of RECV_BUF_SIZE bytes from socket */
		if ((n = read(sd, recv_buf, RECV_BUF_SIZE)) < 0) {
    
    
			xil_printf("%s: error reading from socket %d, closing socket\r\n", __FUNCTION__, sd);
			break;
		}

		/* break if the recved message = "quit" */
		if (!strncmp(recv_buf, "quit", 4))
			break;

		/* break if client closed connection */
		if (n <= 0)
			break;

		/* handle request */
		if ((nwrote = write(sd, recv_buf, n)) < 0) {
    
    
			xil_printf("%s: ERROR responding to client echo request. received = %d, written = %d\r\n",
					__FUNCTION__, n, nwrote);
			xil_printf("Closing socket %d\r\n", sd);
			break;
		}
	}

	/* close connection */
	close(sd);
	vTaskDelete(NULL);
}

3.测试网口发送数据

  由2.4可知,在process_echo_request函数中更改发送即可。新增一个字符数组:

char tx_buf[16]={
    
    'H','e','l','l','o',',','W','o','r','l','d','\r','\n'};

  在发送完接收到的数据后,新增一个发送函数,即可发送tx_buf数组,长度为16:

write(sd, tx_buf, 16);

在这里插入图片描述

4.测试网口接收数据并控制LED

  在向开发板发送数据时,规定一组特殊数据,如 “led0”、“led5”、“led8” 等,前3个字符 “led” 用于指示这部分数据是用于控制LED的,第4个字符表示点亮组合,四个LED使用二进制编码数据为0~15,注意,这里发送的是ASCII字符,在控制LED时需处理成数字(减 ’0’)。
  接收到数据后,仿照函数中对quit字符串的处理方式,新增一个处理,将接收到的字符串与字符串“led”比较,如果收到的字符串的前3个字符是“led”,则使用第4个字符控制LED的亮灭。
  strncmp函数,字符串比较函数,字符串大小的比较以ASCII 码表上的顺序来决定。函数声明为int strncmp ( const char * str1, const char * str2, size_t n ),把 str1 和 str2 进行比较,最多比较前 n 个字节,若str1与str2的前n个字符相同,则返回0;若s1大于s2,则返回大于0的值;若s1 小于s2,则返回小于0的值。

if (!strncmp(recv_buf, "led", 3)) {
    
    
	XGpio_DiscreteWrite(&Gpio_Led, 1, recv_buf[3]-'0');
	xil_printf("Led Value =  %d\r\n", recv_buf[3]-'0');
}

在这里插入图片描述
在这里插入图片描述

5.测试LED任务及消息队列

5.1 包含头文件,声明队列

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "timers.h"
QueueHandle_t xQueue = NULL;

5.2 在main函数中创建消息队列

  传入两个参数,分别为队列长度和队列中每个元素的长度,xQueueCreate(1,1)表示队列长度为1,队列中的每个元素时一个char类型数据,xQueueCreate(2,15)表示队列长度为2,每个元素都是一个char[15]类型的字符数组。

xQueue = xQueueCreate(1,1);
/* Check the queue was created. 检查队列是否创建成功*/
configASSERT( xQueue );

5.3 在main函数中创建LED任务,接收队列消息

xTaskCreate( prvPlLedTask,
				( const char * ) "PL Led",
				configMINIMAL_STACK_SIZE,
				NULL,
				tskIDLE_PRIORITY + 1,
				NULL);

  其中,调用的prvPlLedTask定义如下,每次从队列中读取一个char类型的数据,若队列为空则等待,若队列不为空则读出后控制LED,注意这里的rece_led_value一定要加取地址符号&,表示传入指针,否则出错。

static void prvPlLedTask( void *pvParameters )
{
    
    
	const TickType_t x1second = pdMS_TO_TICKS( DELAY_1_SECOND );
	char rece_led_value;

	for( ;; )
	{
    
    
		xil_printf( "PL LED task\r\n" );
		xQueueReceive( 	xQueue,			/* The queue being read. */
				&rece_led_value,	/* Data is read into this address. */
						portMAX_DELAY );	/* 延时 */

		xil_printf( "PL LED task\r\n" );
		xil_printf( "rece_led_value = %d\r\n", rece_led_value-'0' );
		XGpio_DiscreteWrite(&Gpio_Led, 1, rece_led_value-'0');

		/* Delay for 1 second. */
		vTaskDelay( x1second );
	}
}

5.4 在process_echo_request中添加发送队列消息

  若满足条件,则将对LED的控制信息写入队列,注意要加取地址符号&。

if (!strncmp(recv_buf, "led", 3)) {
    
    
	xQueueSend( xQueue,
				&recv_buf[3],
				0UL );
}

在这里插入图片描述
在这里插入图片描述

三、程序固化

1 新建FSBL工程

在这里插入图片描述

2 生成Boot镜像文件

  生成工程后,右键“Create Boot Image”,依次添加FSBL工程的elf(默认已添加)、工程的bit文件(默认已添加)、需固化的程序elf(Add找到路径添加),“Create Image”。

在这里插入图片描述

3 烧录QSPI Flash

  选择 Image 和 FSBL 的路径,对 Flash,一定选择 “qspi_dual_parallel”,若选择 “qspi_single” 也能下载成功,但是无法加载,ZC706 板载指示灯亮红灯。
在这里插入图片描述


4 配置启动模式

在这里插入图片描述
在这里插入图片描述




欢迎关注,获取FPGA资料在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/DengFengLai123/article/details/113790744
今日推荐