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中默认代码不支持,经过网络搜索我找到了支持的代码如下:
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 2hXEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0x0008);//select register 08h for MMD-Device address 2hXEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_CONTROL_REG, 0x4002);//select register data for MMD-Device Address 2h,Register 08hXEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0x01ef);//defaultXEmacPs_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 2hXEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0x0005);//select register 05h for MMD-Device address 2hXEmacPs_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.3nsXEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_MMD_ACCESS_ADDRESS_DATA_REG, 0x7777);//defaultXEmacPs_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 regXEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_AUTONEGO_ADVERTISE_REG, &control);//reg 0x04control |= IEEE_ASYMMETRIC_PAUSE_MASK;//0x0800 流控control |= IEEE_PAUSE_MASK;//0x0400control |= ADVERTISE_100;control |= ADVERTISE_10;XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_AUTONEGO_ADVERTISE_REG, control);//1000Basic-T Control regXEmacPs_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 controlXEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, &control);//reg 00control |= IEEE_CTRL_AUTONEGOTIATE_ENABLE; //bit12control |= IEEE_STAT_AUTONEGOTIATE_RESTART; //bit9XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, control);//basic controlXEmacPs_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 itcontinue;elsebreak;}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;elsereturn 0;return XST_SUCCESS;}
这里多了几个专门寄存器的设置,之后在获取速度的函数调用一下:
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;
}
对扇区的读写用到下面的代码。
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xsdps.h"static XSdPs SdIn;#define EMMC1_SD0 0int 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端代码:
#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 ;}elsereturn 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];// 创建 socketif ((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 counterrcv_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 服务器修改过来的,只需要修改一下收到数据的回调函数。
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博客