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上手介绍