提供一种刷新X410内部EMMC存储器的方法

USRP X410内部采用了16G的EMMC存储器,内有内核和文件系统。官方站注1提供了多个版本的EMMC映像文件,并提供了多种刷新方法注2

1,如果内核还能运行只是文件系统破坏,可以从外接USB盘,之后使用mount挂载U盘,使dd指令恢复映像到EMMC。

2,如果EMMC损坏无法进入LINUX系统,按照官方的说明,将USB-OTG和调试口的TYPEC接口都接入电脑,可以使用JTAG模式下载并运行U-BOOT,输入指令使X410的内部EMMC存储器映射成一个移动硬盘,之后将映像烧写到这个影射的盘里。这个烧写方式再linux下可以使用dd指令,在win下可以使用专门烧写软件。

官方网页还提供了其他更加上层一些的烧写方式,我没有仔细分析。

无论第一种方法还是第二种方法,我们都是直接EMMC的扇区进行写,不同是第一种是X410进行写,而第二种是外接的用户电脑进行写。我就在想我们完全可以把X410当做一个开发板,之后自己写程序读扇区进行写,而要写的内容可以通过网口收过来。

我分析了X410的原理图,觉得应该可以像使用开发板一样玩起来实现上述功能。我首先配置了DDR4内存以及串口,对DDR4进行了检测,看到没有问题。之后配置了EMMC和GMAC,使用SDK的的LWIP例子运行起来了tcp echo server.这里注意X410使用的PHY收发器是KSZ9031,我从网上搜索了一下才找到了修改寄存器以支持SDK的LWIP的配置。

这里上几张X410的PS配置的截图。

下图是DDR4的配置。

下图是EMMC的配置,注意SD1实际可以不配置,因为在X410里面SD卡部分电路实际没有焊接~

串口和TTC设置:

网口设置

其中网口用到的PHY芯片是KSZ9031,来我们所做的X410克隆版本的原理图。

SDK中默认代码不支持,经过网络搜索我找到了支持的代码如下:

cpp 复制代码
u32_t phymapemac0[32];
u32_t phymapemac1[32];
static u32_t get_KSZ_phy_speed(XEmacPs *xemacpsp, u32_t phy_addr)
{
	xil_printf("Start PHY autonegotiation get_KSZ_phy_speed  \r\n");

	u16_t temp;
		u16_t control;
		u16_t status;
		u16_t status_speed;
		u32_t timeout_counter = 0;
		u32_t temp_speed;
		u32_t phyregtemp;
		u16_t phy_clk_delay_reg;
		u16_t phy_rx_delay_reg;

		xil_printf("Start PHY autonegotiation \r\n");

		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_CONTROL_REG, 0x0002);//set up register address for MMD-Device Address 2h
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0x0008);//select register 08h for MMD-Device address 2h
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_CONTROL_REG, 0x4002);//select register data for MMD-Device Address 2h,Register 08h
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0x01ef);//default
		XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, &phy_clk_delay_reg);

		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_CONTROL_REG, 0x0002);//set up register address for MMD-Device Address 2h
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0x0005);//select register 05h for MMD-Device address 2h
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_CONTROL_REG, 0x4002);
		//XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0x0000);//-0.42ns
		//XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0xcccc);//+0.3ns
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0x7777);//default
		XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, &phy_rx_delay_reg);


		xil_printf("The clk delay register is:%x\r\n",phy_clk_delay_reg);
		xil_printf("The rx delay register is:%x\r\n",phy_rx_delay_reg);


		//Auto-negotiation Advertisement reg
		XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_AUTONEGO_ADVERTISE_REG, &control);//reg 0x04
		control |= IEEE_ASYMMETRIC_PAUSE_MASK;//0x0800 流控
		control |= IEEE_PAUSE_MASK;//0x0400
		control |= ADVERTISE_100;
		control |= ADVERTISE_10;
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_AUTONEGO_ADVERTISE_REG, control);
		//1000Basic-T Control reg
		XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_1000_ADVERTISE_REG_OFFSET,
						&control);
		control |= ADVERTISE_1000;
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_1000_ADVERTISE_REG_OFFSET,
						control);


		//basic control
		XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, &control);//reg 00
		control |= IEEE_CTRL_AUTONEGOTIATE_ENABLE;  //bit12
		control |= IEEE_STAT_AUTONEGOTIATE_RESTART; //bit9
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, control);

		//basic control
		XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, &control);
		control |= IEEE_CTRL_RESET_MASK;//software PHY reset,
		XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, control);

		while (1) {
			XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, &control);
			if (control & IEEE_CTRL_RESET_MASK)//this bit is self-cleared after a "1" is written to it
				continue;
			else
				break;
		}

		XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);

		xil_printf("Waiting for PHY to complete autonegotiation.\r\n");

		while ( !(status & IEEE_STAT_AUTONEGOTIATE_COMPLETE) ) {
			sleep(1);
			XEmacPs_PhyRead(xemacpsp, phy_addr,
							IEEE_COPPER_SPECIFIC_STATUS_REG_2,  &temp);
			xil_printf("Link Status is:%x \r\n",temp);
			timeout_counter++;

			if (timeout_counter == 30) {
				xil_printf("Auto negotiation error \r\n");
				return ;
			}
			XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
		}
		xil_printf("autonegotiation complete \r\n");

		XEmacPs_PhyRead(xemacpsp, phy_addr,0x1f,
						&status_speed);

		if ( (status_speed & 0x40) == 0x40)/* 1000Mbps */
			return 1000;
		else if ( (status_speed & 0x20) == 0x20)/* 100Mbps */
			return 100;
		else if ( (status_speed & 0x10) == 0x10)/* 10Mbps */
			return 10;
		else
			return 0;
		return XST_SUCCESS;
 }

这里多了几个专门寄存器的设置,之后在获取速度的函数调用一下:

cpp 复制代码
static u32_t get_IEEE_phy_speed(XEmacPs *xemacpsp, u32_t phy_addr)
{
	u16_t phy_identity;
	u32_t RetStatus;

	XEmacPs_PhyRead(xemacpsp, phy_addr, PHY_IDENTIFIER_1_REG,
					&phy_identity);
	if (phy_identity == PHY_TI_IDENTIFIER) {
		RetStatus = get_TI_phy_speed(xemacpsp, phy_addr);
	} else if (phy_identity == PHY_REALTEK_IDENTIFIER) {
		RetStatus = get_Realtek_phy_speed(xemacpsp, phy_addr);
	} else {
		//RetStatus = get_Marvell_phy_speed(xemacpsp, phy_addr);
		RetStatus = get_KSZ_phy_speed(xemacpsp, phy_addr); //liwei
	}

	return RetStatus;
}

对扇区的读写用到下面的代码。

cpp 复制代码
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xsdps.h"

static XSdPs SdIn;

#define EMMC1_SD0 0

int sdio_init(int id )
{
	static int init = 0 ;
	if (init != 0) return 0  ; init = 1;
	XSdPs_Config *SdConfig;
	int Status;

	if(id==0) SdConfig = XSdPs_LookupConfig(XPAR_XSDPS_0_DEVICE_ID);
	else SdConfig = XSdPs_LookupConfig(XPAR_XSDPS_1_DEVICE_ID);
	if (NULL == SdConfig) {printf("error XSdPs_LookupConfig\r\n");return XST_FAILURE;}

	Status = XSdPs_CfgInitialize(&SdIn, SdConfig,SdConfig->BaseAddress);
	if (Status != XST_SUCCESS) {printf("error XSdPs_CfgInitialize\r\n");return XST_FAILURE;}

	Status = XSdPs_CardInitialize(&SdIn);
	if (Status != XST_SUCCESS) {printf("error XSdPs_CardInitialize\r\n");return XST_FAILURE;}
}

void read_sector(uint32_t sector, uint8_t*buffer)
{
	sdio_init(0);
	int Status = XSdPs_ReadPolled(&SdIn, sector, 1,buffer);
	if(Status != XST_SUCCESS){printf("Error readingsector %d\r\n",sector);}
}

void write_sector( uint32_t sector, const uint8_t *data)
{
	sdio_init(0);
	int Status = XSdPs_WritePolled(&SdIn, sector, 1, data);
	if(Status != XST_SUCCESS){printf("Error writingsector  %d\r\n",sector);}
}

关于EMMC或者SD卡扇区的读写,我同事小周试验成功并写过一篇blog注4

以上硬件和板级支持软件都做好后就开始写正式的应用了。

我们再次梳理一下思路:

1,为了简单起见,我们使用TCP协议,这样就可以不必关心流的控制。

2,PC段负责读映像文件,不断地发送给X410。

3,X410每收到512就顺序写入扇区。

4,设置一个计数器,每写一个扇区就加一。这个计数器也需要清零。我们规定在最初连接上发发送一个32字节指示X410进行计数器清零,并要求得到X410的恢复确认。

思路很简单,我们看PC端代码:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define SERVER_IP "192.168.5.136"  // 服务器 IP 地址
#define SERVER_PORT 7         // 服务器端口
#define BUFFER_SIZE 1024        // 缓冲区大小




#include <stdio.h>
#include <stdlib.h>

#define SECTOR_SIZE 512
#define N 1
//printf("sector=%d\r",sector++);

unsigned char * get_one_block( int len  )
{
    static  unsigned  int  buff[1024 ] ;
    static  unsigned  int  sbuff[1024 ] = {0} ;
    static FILE *fp=NULL;/// = fopen(file_name, "rb");


    static  unsigned int sector = 0 ;


    if (fp==NULL) {
        fp = fopen("usrp_x4xx_fs.sdimg", "rb");
        if (fp == NULL) {
            perror("Error opening file");
            return NULL;
        }
    }
    size_t bytesRead;
    if  ((bytesRead = fread(buff, 1, len, fp)) > 0) {
        return  buff ;
    }
    else
        return NULL ;
}



int  rcv_blocking ( int this_sock )
{
    unsigned char b [2048] ;
    int bytes_received = recv(this_sock, b, 2047, 0);
    if (bytes_received < 0) {
        perror("Receive failed");
        return -1;
    }
    return 0 ;
}


int main()
{
    int sock;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE];

    // 创建 socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址结构
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);

    // 将 IPv4 地址从文本转换为二进制
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
        perror("Invalid address/ Address not supported");
        exit(EXIT_FAILURE);
    }

    // 连接到服务器
    if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Connection failed");
        exit(EXIT_FAILURE);
    }

    // 发送消息



    long long int i,d=0,m=-1,sm=0;
    int  bytes_received ;
    unsigned char *p;
    static unsigned char b[1024] ;
    send(sock,  b,32, 0);  //  reset sector counter
    rcv_blocking(sock) ;


    for(;;) {


        for (i=0; i<N; ++i) {
            p=get_one_block(SECTOR_SIZE);
            if (p==NULL)  exit(1);
            send(sock,  p,SECTOR_SIZE, 0);
        }

        d+=N*512 ;
        m= d/(1024*1024*10) ;
        if (sm!=m)  {
            sm=m;
            printf(  "%dM \n",m*10);
        }
        //   rcv_blocking(sock) ;




    }

    close(sock);
    return 0;
}

这里设置了N本打算发送多块之后再集中写入EMMC后应答给PC,后来觉得TCP自动实现应答确认,上位机一味发就可以,没必要考虑流控。

X410上我们使用SDK的LWIP TCP ECHO 服务器修改过来的,只需要修改一下收到数据的回调函数。

cpp 复制代码
err_t recv_callback(void *arg, struct tcp_pcb *tpcb,
                               struct pbuf *p, err_t err)
{
	/* do not read the packet if we are not in ESTABLISHED state */
	if (!p) {
		tcp_close(tpcb);
		tcp_recv(tpcb, NULL);
		return ERR_OK;
	}
	/* indicate that the packet has been received */
	tcp_recved(tpcb, p->len);
	if ((p->len!=1024 ) )	printf("len=%d not  1024! \n",p->len );
	if (p->len==32 ){  sector = 0 ; printf("len=%d\r" , p->len); tcp_write(tpcb, p->payload, 4, 1);pbuf_free(p); return ERR_OK;}
	if (p->len>=512 )write_sector( sector ++ , p->payload   ) ;
	if ( p->len==1024)	write_sector( sector ++ , p->payload+512   ) ;
///	printf("sector=%d  \r" ,sector);
	pbuf_free(p);
	return ERR_OK;
}

连续发送512的数据包,我在这个回调函数里面收到第一个发送过来的512字节的包,周都是1024的包,是两个包组合在一起发送了。(这期间我尝试修改修改成别的发送长度连续发送,最终每包也是1024字节,这个没有继续实验,在这里提一下。)

注意调试时候保留了上述代码的printf,打印出正在写的扇区号。发现这个占用了时间拉低了烧写速度。我用的是100M的交换机,保留printf烧写速度是2Mb/s,注释掉烧写速度是6Mb/s .

这个实现烧写速度比较慢,16G的映像文件烧写实测用了5个小时的样子(100M局域网,如果用更1G网应该快一点,但是估计也有限,考虑主要消耗在TCP的对话导致通讯效率比较低)。烧写完毕后确实启动成了。如果要求更高速度可以使用UDP,但是那要自己设计对话实现流控制。

设置X410运行在JTAG模式的命令:

1,SCU的命令里先输入reboot关掉ps.此时power灯是黄色。

2,输入zynqmp bootmode jtag.

3,按一下开关键,此时power灯是绿色,表示PS部分已经启动并进入jtag模式。

4,如果要退出jtag模式,我使用的方法是SCU命令执行reboot.之后从先上电。

这里所说的SCU就是X410实现BIOS功能的STM32,官方叫SCU。具体可以看看我另外一篇BLOG注3

by :李伟

复制代码
通过网盘分享的文件:BRUN_EMMC_RJ45.rar
链接: https://pan.baidu.com/s/15ci9CLgbKOnkPM8MkOYdgg 提取码: 66u1 

注:

1,files.ettus.com:/binaries/cache/x4xx/ 内有EMMC映像的官方链接。

2,USRP X410/X440 Getting Started Guide - Ettus Knowledge Base X410上手介绍

3,X410启动过程串口的显示-CSDN博客

4,TQRFSOC开发板47DR :EMMC和SD卡扇区读写_rfsoc47dr 开发语言-CSDN博客

相关推荐
用户97183563346615 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪16 小时前
linux 拷贝文件或目录到指定的位置
linux
大树881 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质1 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush41 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5201 天前
Linux 11 动态监控指令top
linux
小宇宙Zz1 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工1 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智1 天前
ARP代理--工作原理
运维·网络·arp·arp代理