基于小梅哥AC620开发板的NIOS II LWIP百兆以太网例程移植到自己做的板子上

原程序是运行在小梅哥AC620开发板上的:基于小梅哥AC620开发板的NIOS II LWIP百兆以太网例程_ZLK1214的专栏-CSDN博客_小梅哥ac620【开发板】开发板型号:小梅哥AC620FPGA型号:EP4CE10F17C8N晶振频率:50MHzPHY芯片型号:RTL8201CP(MII接口,百兆以太网PHY芯片)【程序功能展示】1. ping通开发板的NetBIOS设备名,IPv4地址和IPv6地址2. 访问开发板上的http服务器(设备名方式、IPv6方式):3. 在路由器管理页面看到开发板的信息:【主要程序代码】main.v:module main( input clock,https://blog.csdn.net/ZLK1214/article/details/115337628鉴于小梅哥AC620 RTL8201CP百兆网口版本的板子已经买不到了(现在只有RTL8211千兆版本了),笔者根据小梅哥板子的电路,自己新做了一块板子,如下图所示。

【新电路板与小梅哥AC620电路的不同之处】
(1)FPGA芯片由EP4CE10F17C8(BGA封装)换成了EP4CE10E22C8(LQFP封装)。

SD卡槽有两个卡检测引脚(CD)。第2脚CD/DAT3是SD卡上的引脚,低电平是没有插卡,高电平是插了卡。第9脚CD是SD卡槽上的引脚,不是SD卡上的引脚,高电平是没插卡,低电平是插了卡。

(2)SDRAM由W9812G6KH(16MB)换成了W9825G6KH(32MB),多了A12这根地址线。

(3)网口的灯改成了绿灯表示连接状态,黄灯表示数据包收发状态,灯的限流电阻大小改成了330Ω。
eth_tx_clk和eth_rx_clk接的是时钟专用引脚CLK4和CLK5,所以main模块中不再需要altclkctrl_0和altclkctrl_1时钟缓冲器。

(4)数码管添加了OE使能引脚,用一个上拉电阻拉高,默认是关闭状态。这样做的目的是防止刚通电时数码管乱显示。
只有当RCLK出现上升沿后(正确的数据存入了芯片),才将OE拉低,开显示。

(5)5V降压至3.3V、2.5V、1.2V全部由AMS1117完成。

【程序改动】
修改Verilog程序:
(1)main模块的sdram_addr位宽改为[12:0]。
(2)修改Pin Planner里面的引脚定义,电平全部改为3.3V。
(3)altpll_0改成输出altpll_c0(100MHz 0deg)和altpll_c1(100MHz -63deg)两路时钟。
     main模块中assign sdram_clk = altpll_c0改为altpll_c1。
(4)QSYS里面再添加一个clock source,频率设为100MHz。
     SDRAM的row width改为13,refresh command间隔改为7.8125us(即64ms/8192)。
     SDRAM IP核的时钟和复位引脚修改为连接100MHz的那个新clock source。
     main模块的nios设置clk_0_clk为clock,clk_1_clk为altpll_c0,reset_0_reset_n和reset_1_reset_n为altpll_locked。
(5)eth_tx_clk和eth_rx_clk接的是时钟专用引脚,所以main模块中去除altclkctrl_0和altclkctrl_1时钟缓冲。
     eth_tse_0_pcs_mac_rx_clock_clk括号里改成eth_rx_clk,eth_tse_0_pcs_mac_tx_clock_clk括号里改成eth_tx_clk。

修改C程序:
(1)main函数里面的netbiosns_set_name设备名
(2)ethernetif_init函数里面的netif->hostname设备名
(3)添加了串口非阻塞接收功能,串口收到t会显示sys_now()的值

【遇到的问题记录】
(1)SDRAM某些地址要读5次才能读正确。
     解决办法:SDRAM芯片的时钟sdram_clk由50MHz -63deg改成100MHz -63deg,SDRAM IP核的时钟改为100MHz 0deg,其余IP核时钟不变。
(2)插上网线后,两个网口灯不亮。但RTL8201CP复位瞬间,不管插不插网线,两个灯都要亮一下,然后熄灭。
     原因:原理图画错。CRS和RX_ER接的应该是下拉电阻,原理图画错了画成了上拉电阻。
           RX_ER接上拉电阻,选择的是光纤模式,而非网线模式,所以网口灯不亮。
     解决办法:板子上飞线,把RX_ER电阻的另一端由3V3改成GND。CRS不用管。
     提示:虽然RTL8201芯片内RX_ER自带有弱下拉,但RX_ER和FPGA的I/O口相连,FPGA在未配置状态下所有I/O口都是带上拉电阻的,这又会导致RX_ER被FPGA上拉,因此RX_ER必须外接下拉电阻。
(3)运行C程序,串口打印Hello fr后,nios就死机。
     原因:UART和SGDMA都不能在100MHz频率下正常工作。
     解决办法:QSYS里面,把UART和SGDMA的时钟和复位改成50MHz的那个clock source。

【调通后的程序截图】

程序和PCB文件下载链接:https://pan.baidu.com/s/18lYFoZGcAetSqgeIS1_KNg(提取码:pimw)

lwIP - 一个轻量级的TCP/IP栈
您当前看到的页面是由一个运行在轻量级TCP/IP栈lwIP栈顶的简单的Web服务器提供的。
lwIP是一个TCP/IP协议的开源实现,最初是由瑞士计算机科学院的Adam Dunkels编写的,但现在由一个分布在全球各地的开发团队开发。自从发布后,lwIP就激发了人们浓厚的兴趣,已经移植到了很多平台和操作系统上运行。lwIP可以裸机运行,也可以在有系统的环境下运行。
lwIP在实现的时候主要侧重于在保证TCP拥有完整功能的情况下降低内存的使用量。这使得lwIP适合在只有几十KB内存和大约40KB代码空间的嵌入式系统中使用。
要了解lwIP的更多信息,请访问lwIP主页和lwIP wiki页面。

【Qsys框图】

SDRAM IP核时钟为100MHz,其余IP核时钟为50MHz。

SDRAM参数配置:

除了刷新间隔为7.8125us(=64ms/8912),其他的时序参数都是默认值。

【程序代码】

main.v:

module main(
    input clock,
    input uart_rx,
    output uart_tx,
    output sdram_clk,
    output [12:0] sdram_addr,
    output [1:0] sdram_ba,
    output sdram_cas_n,
    output sdram_cke,
    output sdram_cs_n,
    inout [15:0] sdram_dq,
    output [1:0] sdram_dqm,
    output sdram_ras_n,
    output sdram_we_n,
    output eth_rst_n,
    output eth_mdc,
    inout eth_mdio,
    input eth_col,
    input eth_crs,
    input eth_tx_clk,
    output eth_tx_en,
    output [3:0] eth_txd,
    input eth_rx_clk,
    input eth_rx_dv,
    input [3:0] eth_rxd,
    input eth_rx_er,
    output epcs_dclk,
    output epcs_sce,
    output epcs_sdo,
    input epcs_data0
    );
    
    /* 产生复位信号 */
    wire nrst;
    Reset reset(clock, nrst);
    
    /* PLL倍频 */
    wire altpll_c0;
    wire altpll_c1;
    wire altpll_locked;
    altpll_0 altpll_0(
        .areset(~nrst),
        .inclk0(clock), // 50MHz
        .c0(altpll_c0), // 100MHz, 0deg
        .c1(altpll_c1), // 100MHz, -63deg
        .locked(altpll_locked)
    );
    
    /* NIOS II */
    wire eth_tse_0_mac_mdio_mdio_oen;
    wire eth_tse_0_mac_mdio_mdio_out;
    
    wire eth_tse_0_mac_mii_mii_tx_err;
    
    wire eth_tse_0_mac_misc_ff_rx_a_empty;
    wire eth_tse_0_mac_misc_ff_rx_a_full;
    wire eth_tse_0_mac_misc_ff_rx_dsav;
    wire eth_tse_0_mac_misc_ff_tx_a_empty;
    wire eth_tse_0_mac_misc_ff_tx_a_full;
    wire eth_tse_0_mac_misc_ff_tx_septy;
    wire eth_tse_0_mac_misc_magic_wakeup;
    wire [17:0] eth_tse_0_mac_misc_rx_err_stat;
    wire [3:0] eth_tse_0_mac_misc_rx_frm_type;
    wire eth_tse_0_mac_misc_tx_ff_uflow;
    
    wire eth_tse_0_mac_status_ena_10;
    wire eth_tse_0_mac_status_eth_mode;
    
    assign sdram_clk = altpll_c1;
    assign eth_mdio = (!eth_tse_0_mac_mdio_mdio_oen) ? eth_tse_0_mac_mdio_mdio_out : 1'bz;
    assign eth_rst_n = altpll_locked;
    
    nios nios(
        .clk_0_clk(clock),
        .reset_0_reset_n(altpll_locked),
        .clk_1_clk(altpll_c0),
        .reset_1_reset_n(altpll_locked),
        .eth_tse_0_mac_mdio_mdc(eth_mdc),
        .eth_tse_0_mac_mdio_mdio_in(eth_mdio),
        .eth_tse_0_mac_mdio_mdio_oen(eth_tse_0_mac_mdio_mdio_oen),
        .eth_tse_0_mac_mdio_mdio_out(eth_tse_0_mac_mdio_mdio_out),
        
        .eth_tse_0_mac_mii_mii_rx_d(eth_rxd),
        .eth_tse_0_mac_mii_mii_rx_dv(eth_rx_dv),
        .eth_tse_0_mac_mii_mii_rx_err(eth_rx_er),
        .eth_tse_0_mac_mii_mii_tx_d(eth_txd),
        .eth_tse_0_mac_mii_mii_tx_en(eth_tx_en),
        .eth_tse_0_mac_mii_mii_tx_err(eth_tse_0_mac_mii_mii_tx_err),
        
        .eth_tse_0_mac_misc_ff_rx_a_empty(eth_tse_0_mac_misc_ff_rx_a_empty),
        .eth_tse_0_mac_misc_ff_rx_a_full(eth_tse_0_mac_misc_ff_rx_a_full),
        .eth_tse_0_mac_misc_ff_rx_dsav(eth_tse_0_mac_misc_ff_rx_dsav),
        .eth_tse_0_mac_misc_ff_tx_a_empty(eth_tse_0_mac_misc_ff_tx_a_empty),
        .eth_tse_0_mac_misc_ff_tx_a_full(eth_tse_0_mac_misc_ff_tx_a_full),
        .eth_tse_0_mac_misc_ff_tx_crc_fwd(1'b0),
        .eth_tse_0_mac_misc_ff_tx_septy(eth_tse_0_mac_misc_ff_tx_septy),
        .eth_tse_0_mac_misc_magic_sleep_n(1'b1),
        .eth_tse_0_mac_misc_magic_wakeup(eth_tse_0_mac_misc_magic_wakeup),
        .eth_tse_0_mac_misc_rx_err_stat(eth_tse_0_mac_misc_rx_err_stat),
        .eth_tse_0_mac_misc_rx_frm_type(eth_tse_0_mac_misc_rx_frm_type),
        .eth_tse_0_mac_misc_tx_ff_uflow(eth_tse_0_mac_misc_tx_ff_uflow),
        .eth_tse_0_mac_misc_xoff_gen(1'b0),
        .eth_tse_0_mac_misc_xon_gen(1'b0),
        
        .eth_tse_0_mac_status_ena_10(eth_tse_0_mac_status_ena_10),
        .eth_tse_0_mac_status_eth_mode(eth_tse_0_mac_status_eth_mode),
        .eth_tse_0_mac_status_set_10(1'b0),
        .eth_tse_0_mac_status_set_1000(1'b0),
        
        .eth_tse_0_pcs_mac_rx_clock_clk(eth_rx_clk),
        .eth_tse_0_pcs_mac_tx_clock_clk(eth_tx_clk),
        
        .sdram_0_addr(sdram_addr),
        .sdram_0_ba(sdram_ba),
        .sdram_0_cas_n(sdram_cas_n),
        .sdram_0_cke(sdram_cke),
        .sdram_0_cs_n(sdram_cs_n),
        .sdram_0_dq(sdram_dq),
        .sdram_0_dqm(sdram_dqm),
        .sdram_0_ras_n(sdram_ras_n),
        .sdram_0_we_n(sdram_we_n),
        
        .uart_0_rxd(uart_rx),
        .uart_0_txd(uart_tx),
        
        .epcs_flash_0_dclk(epcs_dclk),
        .epcs_flash_0_sce(epcs_sce),
        .epcs_flash_0_sdo(epcs_sdo),
        .epcs_flash_0_data0(epcs_data0)
    );
    
endmodule

hello_world.c:

/*
 * "Hello World" example.
 *
 * This example prints 'Hello from Nios II' to the STDOUT stream. It runs on
 * the Nios II 'standard', 'full_featured', 'fast', and 'low_cost' example
 * designs. It runs with or without the MicroC/OS-II RTOS and requires a STDOUT
 * device in your system's hardware.
 * The memory footprint of this hosted application is ~69 kbytes by default
 * using the standard reference design.
 *
 * For a reduced footprint version of this template, and an explanation of how
 * to reduce the memory footprint for a given application, see the
 * "small_hello_world" template.
 *
 */

#include <fcntl.h>
#include <lwip/apps/httpd.h>
#include <lwip/apps/netbiosns.h>
#include <lwip/dhcp.h>
#include <lwip/dns.h>
#include <lwip/init.h>
#include <lwip/netif.h>
#include <lwip/timeouts.h>
#include <netif/ethernetif.h>
#include <stdio.h>
#include <sys/alt_alarm.h>
#include <system.h>
#include <unistd.h>

static struct netif netif_rtl8201cp;

alt_u32 sys_now(void)
{
	// BSP Editor里面Settings -> Common -> hal -> sys_clk_timer必须选择一个定时器
	// 而且该定时器的计时间隔必须为1ms
	LWIP_ASSERT("incorrect tick rate", alt_ticks_per_second() == 1000);
	return alt_nticks();
}

static void display_ip(void)
{
	const ip_addr_t *addr;
	static uint8_t ip_displayed = 0;
	static uint8_t ip6_displayed = 0;
	int i, ip_present;
	int dns = 0;

	if (netif_dhcp_data(&netif_rtl8201cp) == NULL)
		ip_present = 1; // 使用静态IP地址
	else if (dhcp_supplied_address(&netif_rtl8201cp))
		ip_present = 2; // 使用DHCP获得IP地址, 且已成功获取到IP地址
	else
		ip_present = 0; // 使用DHCP获得IP地址, 且还没有获取到IP地址

	// 显示IPv4地址
	if (ip_present)
	{
		if (ip_displayed == 0)
		{
			ip_displayed = 1;

			if (ip_present == 2)
				printf("DHCP supplied address!\r\n");
			printf("IP address: %s\r\n", ipaddr_ntoa(&netif_rtl8201cp.ip_addr));
			printf("Subnet mask: %s\r\n", ipaddr_ntoa(&netif_rtl8201cp.netmask));
			printf("Default gateway: %s\r\n", ipaddr_ntoa(&netif_rtl8201cp.gw));
			dns = 1;
		}
	}
	else
		ip_displayed = 0;

	// 显示IPv6地址
	for (i = 1; i < LWIP_IPV6_NUM_ADDRESSES; i++) // 0号地址是本地链路地址, 不需要显示
	{
		if (ip6_addr_isvalid(netif_ip6_addr_state(&netif_rtl8201cp, i)))
		{
			if ((ip6_displayed & _BV(i)) == 0)
			{
				ip6_displayed |= _BV(i);
				printf("IPv6 address %d: %s\r\n", i, ipaddr_ntoa(netif_ip_addr6(&netif_rtl8201cp, i)));
				dns = 1;
			}
		}
		else
			ip6_displayed &= ~_BV(i);
	}

	// 显示DNS服务器地址
	// 在lwip中, IPv4 DHCP和IPv6 SLAAC获取到的DNS地址会互相覆盖
	if (dns)
	{
		addr = dns_getserver(0);
		if (ip_addr_isany(addr))
			return;
		printf("DNS Server: %s", ipaddr_ntoa(addr));

		addr = dns_getserver(1);
		if (!ip_addr_isany(addr))
			printf(" %s", ipaddr_ntoa(addr));

		printf("\r\n");
	}
}

static void net_config(int use_dhcp)
{
	ip4_addr_t ipaddr, netmask, gw;

	if (use_dhcp)
		netif_add_noaddr(&netif_rtl8201cp, NULL, ethernetif_init, netif_input);
	else
	{
		IP4_ADDR(&ipaddr, 192, 168, 0, 19);
		IP4_ADDR(&netmask, 255, 255, 255, 0);
		IP4_ADDR(&gw, 192, 168, 0, 1);
		netif_add(&netif_rtl8201cp, &ipaddr, &netmask, &gw, NULL, ethernetif_init, netif_input);
	}
	netif_set_default(&netif_rtl8201cp);
	netif_set_up(&netif_rtl8201cp);

	if (use_dhcp)
		dhcp_start(&netif_rtl8201cp);

	netif_create_ip6_linklocal_address(&netif_rtl8201cp, 1);
	printf("IPv6 link-local address: %s\r\n", ipaddr_ntoa(netif_ip_addr6(&netif_rtl8201cp, 0)));
	netif_set_ip6_autoconfig_enabled(&netif_rtl8201cp, 1);
}

int main(void)
{
	char ch;
	int ret;

	printf("Hello from Nios II!\r\n");

	lwip_init();
	net_config(1);

	httpd_init();
	netbiosns_init();
	netbiosns_set_name("EP4CE10E22C8");

	fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); // 串口设为非阻塞接收模式
	while (1)
	{
		ethernetif_check_link(&netif_rtl8201cp);
		display_ip();

		ethernetif_input(&netif_rtl8201cp);
		sys_check_timeouts();

		ret = read(STDIN_FILENO, &ch, 1);
		if (ret != -1)
		{
			// 收到串口字符
			switch (ch)
			{
			case 't':
				printf("sys_now()=%lu\r\n", sys_now());
				break;
			}
		}
	}
}

【2022年1月29日问题记录】

又做了一块新的绿色板子,新板子把CRS和RX_ER的上拉电阻纠正为了下拉电阻。SD卡的D3引脚改成了470kΩ下拉电阻,第9脚改成了10kΩ上拉电阻,使这两个引脚都具备卡检测功能。去掉了S4按键。
板子上S1是复位按键,S2和S3是普通按键。焊好后发现按下S2或者S3后板子居然也要复位。
再仔细观察,发现板子四周用螺丝柱支起来后,用手指按压板子空白部分,板子也会复位。
最后检查到是FPGA芯片底部的方形焊盘(145脚)虚焊了,接触不良,这个方形焊盘如果不接地,FPGA芯片就会一直处于复位状态。
重新焊接底部焊盘,问题解决。无论怎么按S2、S3键,板子都不再重启。

然后就是SDRAM问题了,原来程序在蓝色板子上运行没有问题,在新的绿色板子上,SDRAM读写失败。
新建一个工程,让所有的SDRAM引脚都输出50kHz方波,用示波器一个脚一个脚地看有没有信号,看看哪个引脚虚焊了。
示波器发现SDRAM除了23脚A0一直为低电平外,其他脚都能看到方波。
FPGA的第49脚是A0,示波器检测到也没有方波输出,一直是低电平。FPGA的第48脚刚好是GND,用肉眼仔细看发现48脚和49脚之间有连锡,49脚被短路到GND了。
重新焊了一下,SDRAM的23脚终于能看到方波了。SDRAM程序烧进去,读写也正常了。

【绿色板SDRAM时钟频率测试】
测试条件:只改变SDRAM IP核(0deg)和SDRAM芯片时钟引脚(-63deg)的频率,其余IP核(包括NIOS)都是50MHz 0deg时钟。C程序运行在32768字节的on-chip memory中,全片读写32MB的SDRAM内存空间。
修改点1:altpll_0的c0和c1的频率。
修改点2:qsys里面clk_1的频率。
SDRAM手册上标明的最高频率:166MHz/CL3。

               频率                   结果
100MHz/CL3 -63deg        正常
110MHz/CL3 -63deg        正常
120MHz/CL3 -63deg        正常
130MHz/CL3 -63deg        正常
140MHz/CL3 -63deg        正常
150MHz/CL3 -63deg        正常
160MHz/CL3 -63deg        正常
166MHz/CL3 -63deg        正常
170MHz/CL3 -63deg      程序卡死
改回166MHz/CL3 -63deg  读写错误

结论:绿色板子可以运行到最高的166MHz的频率(CAS Latency=3)。
如果是170MHz,哪怕只高了4MHz,strcpy((char *)0x2000000, "hello world")都无法执行成功,程序直接卡死。


实际跑lwip网口程序,C程序运行在SDRAM内存中,110MHz/CL3, 130MHz/CL3都没有问题。
160MHz/CL3频率下,lwip程序能成功下载进SDRAM,Verify OK,运行程序,前面1秒串口输出正常。
但是1秒过后,如下图所示,[Send] len=86之后,串口就停止输出了,串口发送t没有反应,证明程序已死机。
估计原因:
(1)SDRAM运行频率太高,某一时刻SDRAM发生了读错误,nios执行了错误的程序指令而死机。
不过笔者觉得不太可能,因为程序下载进去之后,eclipse验证了都没问题。如果nios真的执行了错误的程序指令而死机,那么此后usb blaster调试器应该再也检测不到nios才对。但事实上程序卡死后现象是eclipse还能再次下载程序,读到sys id和timestamp,verify也能通过。
(2)SDRAM没问题,只是qsys里面的IP核跑不了那么高的频率。
笔者觉得这个最有可能,因为之前也遇到过100MHz下UART IP核发送字符会出错,还有100MHz下SGDMA直接死机的问题。
(3)SDRAM的时序参数除了刷新时间外,其余都采用了默认值,可能这些默认值在160MHz频率下会有问题。

SDR SDRAM内存(本文所用的内存)电压:LVTTL 3.3V
标准频率:PC66(66MHz)、PC100(100MHz)和PC133(133MHz)

DDR SDRAM内存电压:SSTL_2(2.5V)
年份:1998年
标准频率:DDR-200(100MHz)、DDR-266(133MHz)、DDR-333(166MHz)和DDR-400(200MHz)

DDR2 SDRAM内存电压:SSTL_18(1.8V)
年份:2003年
标准频率:DDR2-400(200MHz)、DDR2-533(266MHz)、DDR2-667(333MHz)、DDR2-800(400MHz)和DDR2-1066(533MHz)

DDR3 SDRAM内存电压:SSTL_15(1.5V)
年份:2007年
标准频率:DDR3-800(400MHz)、DDR3-1066(533MHz)、DDR3-1333(667MHz)、DDR3-1600(800MHz)、DDR3-1866(933MHz)和DDR3-2133(1066MHz)

DDR4 SDRAM内存电压:1.2V
年份:2014年
标准频率:DDR4-1600(800MHz)、DDR4-1866(933MHz)、DDR4-2133(1066MHz)、DDR4-2400(1200MHz)、DDR4-2666(1333MHz)、DDR4-2933(1466MHz)和DDR4-3200(1600MHz)

DDR5 SDRAM内存电压:1.1V
年份:2020年
标准频率:DDR5-4800(2400MHz)和DDR5-7200(3600MHz)

(这些信息都可以在英文wikipedia上查到,然而遗憾的是,国内封禁了所有语言的wikipedia,完全无法访问!)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值