ZYNQ(PS) 动态加载APP之一

这里主要涉及ZYNQ加载流程及裸机下的动态加载

平台 : zedboard
vivado : vivado 2018.3

1. IAP简介

IAP,全称In Application Programing, 在应用编程。简单理解,即在程序运行过程中进行编程(升级程序,更新固件)

由ZYNQ的启动特性可知,上电后会执行BootROM,然后跳转到FSBL,由FSBL跳转到用户程序。这里实现两个用户程序(基于同一个FSBL,都是默认配置),分别是bootloader和app

ZYNQ动态加载主要参考UG821,常见的方法使用Multiboot。

在bootloader里面实现两个步骤

1 >  往Multiboot寄存器(地址0xF800702C)写便偏移地址,单位32K,比如写0x8, 实际QSPI地址为 32K * 8 = 0x40000 (256K)
2 > 软复位 

2. 本地IAP

本地IAP即提前固化好两段用户程序,bootloader和app,通过在bootloader里面写multiboot来切换到app

本地IAP是后面远程IAP的基础。

以上BSP, HW和FSBL是共用的,系统默认生成(仅仅改名了), FW为bootloader, APP为app

2.1 zynq软件复位

仅仅往SLCR寄存器组里面的PSS_RST_CTRL(地址0xF8000200)写1即可,注意SLCR寄存器写操作前要先unlock,即往SLCR_UNLOCK(地址0xF8000008)写0xDF0D

 

参考代码如下:

#define BASE_SLCR_ADDR		0xF8000000
#define BASE_XDCFG_ADDR		0xF8007000

/*
 * SLCR LOCK
 *  base : BASE_SLCR_ADDR
 */
#define SLCR_LOCK_OFFSET		0x00000004
#define LOCK_KEY_SHIFT			0x0
#define LOCK_KEY_MASK			0xFFFF

/*
 * SLCR UNLOCK
 *  base : BASE_SLCR_ADDR
 */
#define SLCR_UNLOCK_OFFSET		0x00000008
#define UNLOCK_KEY_SHIFT		0x0
#define UNLOCK_KEY_MASK			0xFFFF

/*
 * RESET
 *  base : BASE_SLCR_ADDR
 */
#define PSS_RST_CTRL_OFFSET		0x00000200
#define SOFT_RST_SHIFT			0
#define SOFT_RST_MASK			0x1

static void SLCR_Lock(void)
{
	REG32_WRITE(BASE_SLCR_ADDR + SLCR_LOCK_OFFSET , 0x767B);
}

static void SLCR_Unlock(void)
{
	REG32_WRITE(BASE_SLCR_ADDR + SLCR_UNLOCK_OFFSET , 0xDF0D);
}
c
int SYS_Reset(void)
{
	SLCR_Unlock();
	REG32_WRITE(BASE_SLCR_ADDR + PSS_RST_CTRL_OFFSET , 1<<SOFT_RST_SHIFT);
	SLCR_Lock();

	return 0;
}

2.2 修改multiboot

仅仅往XDCFG寄存器组里面的XDCFG_MULTIBOOT_ADDR_OFFSET(地址0xF800702C)写offset即可,注意XDCFG寄存器写操作前要先unlock,即往XDCFG_UNLOCK_OFFSET(地址0xF8007034)写0x757BDF0D。注意offset是32K最小单位。

由于multiboot只有13位, 因此multiboot最大访问的空间 =  8192 * 32K = 256M

这里设置multiboot = 8, 即8 * 32K = 256K(0x40000)

参考代码如下:

/*
 * XDCFG LOCK
 *  base : BASE_XDCFG_ADDR
 */
#define XDCFG_LOCK_OFFSET 		0x4
#define LOCK_SHIFT				0x0
#define LOCK_MASK				0x1F

/*
 * XDCFG UNLOCK
 *  base : BASE_XDCFG_ADDR
 */
#define XDCFG_UNLOCK_OFFSET 		0x34
#define UNLOCK_SHIFT				0x0
#define UNLOCK_MASK				0xFFFFFFFF
#define UNLOCK_KEY				0x757BDF0D

/*
 * multiboot
 *  base : BASE_XDCFG_ADDR
 */
#define XDCFG_MULTIBOOT_ADDR_OFFSET 	0x0000002C
#define MULTIBOOT_SHIFT					0x0
#define MULTIBOOT_MASK					0x1FFF

int SYS_Upgrade(void)
{
	// unlock XDCFG
	REG32_WRITE(BASE_XDCFG_ADDR + XDCFG_UNLOCK_OFFSET, UNLOCK_KEY);

	// update the multiboot
	REG32_WRITE(BASE_XDCFG_ADDR + XDCFG_MULTIBOOT_ADDR_OFFSET, 0x8);

	SYS_Reset();
	return 0;
}

2.3 bootloader

bootloader和FSBL生成bootloader.bin烧到偏移offset = 0的位置

2.4 App

app和FSBL生成App.bin烧到偏移offset = 0x40000的位置

2.5 操作

上电后,系统默认进入bootloader

输入up,实际上执行SYS_Upgrade即可以跳到app

注意这里跳到app的过程

1. bootloader先往multiboot 寄存器写0x8 ,表示下个image在0x8*32K=0x40000
2. bootloader紧接着软件复位,使得BootROM被再次执行
3. 在BootROM里面会去读取multiboot寄存器的offset,然后在offset读取image header并判断是否合法
4. 以上判断合法后跳到offset指向的应用程序,即app

以上两段程序分别烧写到flash不同的偏移,其具体的操作可以从program flash的日志里面看到,比如第二段app, offset=0x40000

cmd /C program_flash -f D:\02_Code\zedboard\SW\APP\bootimage\App.bin -offset 0x40000 \
-flash_type qspi-x4-single -fsbl D:\02_Code\zedboard\SW\FSBL\Debug\FSBL.elf -cable type \
xilinx_tcf url TCP:127.0.0.1:3121 

U-Boot 2018.01-00073-g63efa8c-dirty (Oct 04 2018 - 08:22:22 -0600)
Model: Zynq CSE QSPI Board
Board: Xilinx Zynq
Silicon: v3.1
DRAM:  256 KiB
WARNING: Caches not enabled

Using default environment

In:    dcc
Out:   dcc
Err:   dcc

Zynq> sf probe 0 10000000 0
SF: Detected s25fl256s_64k with page size 256 Bytes, erase size 64 KiB, total 32 MiB

Zynq> Sector size = 65536.
f probe 0 10000000 0

Performing Erase Operation...
sf erase 40000 40000              // 从偏移0x40000擦除256K(0x40000)

SF: 262144 bytes @ 0x40000 Erased: OK
Zynq> Erase Operation successful.
INFO: [Xicom 50-44] Elapsed time = 0 sec.
Performing Program Operation...
0%...sf write FFFC0000 40000 20000  // 写操作, OCM被重定位到0xFFFC0000, 写128K

device 0 offset 0x40000, size 0x20000
SF: 131072 bytes @ 0x40000 Written: OK

Zynq> 100%
sf write FFFC0000 60000 15750       // 写剩余的size

device 0 offset 0x60000, size 0x15750

SF: 87888 bytes @ 0x60000 Written: OK

Zynq> Program Operation successful.
INFO: [Xicom 50-44] Elapsed time = 2 sec.

Flash Operation Successful

3 QSPI的操作

可以参考mss文件提供的实例代码

4 Dump/Restore data file

vivado-sdk里面有个工具Dump/Restore可以实现加载/读取内存(DDR)数据

 其使用必须在Debug As或者Run As之后

最后配置如下

其中Start Address是DDR地址,注意不要覆盖当前运行区(一般为0x1000000), DDR范围0x00100000~0x3FFFFFFF,还有板载DDR实际容量,比如我的是512M,那么有效范围是0x00100000~0x20000000, 这里选0x10000000, 应该是合理的。

Size(in bytes)是App.bin实际大小,不能随便填

与App.bin对比可知正常

5 从DDR更新到QSPI

从第4节的Restore,可以把PC机的bin文件加载到DDR,这里讨论从DDR数据固化到QSPI。

从第二章本地IAP的固化log可以简化如下

Downloading FSBL...
sf probe 0 10000000 0
sf erase 40000 40000              // 从偏移0x40000擦除256K(0x40000)
sf write FFFC0000 40000 20000  // 写操作, OCM被重定位到0xFFFC0000, 写128K
sf write FFFC0000 60000 15750       // 写剩余的size

这里bootloader和app的FSBL采用同一个,先不管FSBL,看下FSBL.elf和App.bin的文件属性

FSBL    : 613K 627864   0x99498
App.bin : 213K  218960  0x35750

根据log, flash擦除0x40000 ,size=0x40000(256K),但是却写到0xFFFC0000(很费解???),大小刚好等于App.bin文件的大小。

我们这里直接操作0x40000这个地址

5.1 擦除

bootloader # se 0x40000 0x40000
void FlashEraseSectors(u32 addr, u32 len)
{
	u8 writeEnableCmd = { WRITE_ENABLE_CMD };
	u8 readStatusCmd[] = { READ_STATUS_CMD, 0 };  /* must send 2 bytes */
	u8 flashStatus[2];
	int sector;

	for (sector = 0; sector < ((len / SECTOR_SIZE) + 1); sector++) {
		/*
		 * Send the write enable command to the SEEPOM so that it can be
		 * written to, this needs to be sent as a seperate transfer
		 * before the write
		 */
		XQspiPs_PolledTransfer(QspiInstancePtr, &writeEnableCmd, NULL,
					sizeof(writeEnableCmd));

		/*
		 * Setup the write command with the specified address and data
		 * for the FLASH
		 */
		writeBuffer[COMMAND_OFFSET]   = SEC_ERASE_CMD;
		writeBuffer[ADDRESS_1_OFFSET] = (u8)(addr >> 16);
		writeBuffer[ADDRESS_2_OFFSET] = (u8)(addr >> 8);
		writeBuffer[ADDRESS_3_OFFSET] = (u8)(addr & 0xFF);

		/*
		 * Send the sector erase command and address; no receive buffer
		 * is specified since there is nothing to receive
		 */
		XQspiPs_PolledTransfer(QspiInstancePtr, writeBuffer, NULL,
					SEC_ERASE_SIZE);


		while (1) {
			XQspiPs_PolledTransfer(QspiInstancePtr, readStatusCmd,
						flashStatus,
						sizeof(readStatusCmd));

			if ((flashStatus[1] & 0x01) == 0) {
				break;
			}
		}

		addr += SECTOR_SIZE;
	}	
}   

REGISTER_CMD(
        se,
        3,
        ARG_TYPE_U32,
		CLI_FlashErase,
        "<addr> <len> flash erase"
);

// se 0x40000 0x40000  三个参数分别是命令字se, addr=0x40000, len=0x40000 (256K) 足够

5.2 Restore App.bin

根据第4节加载App.bin,首先Debug as当前FW项目

然后

其中0x35750是App.bin文件的实际大小

在终端里面输入

可以读到0x10000000地址的值,和App.bin一致

5.3 写回QSPI

以上通过Vivado的Restore把App.bin写到DDR,现在看下怎么烧写到QSPI地址0x40000

命令

sload 0x40000 0x10000000 0x35750

其实现

REGISTER_CMD(
        sload,
        4,
        ARG_TYPE_U32,
		CLI_Upgrade,
        "<dest> <src> <len> load data to wb from ddr"
);
// 三个参数,第一个命令字,第二个src,这里是QSPI的偏移地址0x40000, src这里是DDR里面App.bin加载地址,0x10000000 , 最后一个len是App.bin实际长度

CLI_Upgrade

#define COMMAND_OFFSET		0 /* FLASH instruction */
#define ADDRESS_1_OFFSET	1 /* MSB byte of address to read or write */
#define ADDRESS_2_OFFSET	2 /* Middle byte of address to read or write */
#define ADDRESS_3_OFFSET	3 /* LSB byte of address to read or write */
#define DATA_OFFSET			4 /* Start of Data for Read/Write */
#define DUMMY_OFFSET		4 /* Dummy byte offset for fast, dual and quad */
#define PAGE_SIZE		256
u8 writeBuffer[PAGE_SIZE + DATA_OFFSET];

void FlashWrite(u32 addr, u32 len, u8 cmd)
{
	u8 writeEnableCmd = { WRITE_ENABLE_CMD };
	u8 readStatusCmd[] = { READ_STATUS_CMD, 0 };  /* must send 2 bytes */
	u8 flashStatus[2] ;

	/*
	 * Send the write enable command to the FLASH so that it can be
	 * written to, this needs to be sent as a seperate transfer before
	 * the write
	 */
	XQspiPs_PolledTransfer(QspiInstancePtr, &writeEnableCmd, NULL,
				sizeof(writeEnableCmd));

	/*
	 * Setup the write command with the specified address and data for the
	 * FLASH
	 */
	writeBuffer[COMMAND_OFFSET]   = cmd;
	writeBuffer[ADDRESS_1_OFFSET] = (u8)((addr & 0xFF0000) >> 16);
	writeBuffer[ADDRESS_2_OFFSET] = (u8)((addr & 0xFF00) >> 8);
	writeBuffer[ADDRESS_3_OFFSET] = (u8)(addr & 0xFF);

	/*
	 * Send the write command, address, and data to the FLASH to be
	 * written, no receive buffer is specified since there is nothing to
	 * receive
	 */
	XQspiPs_PolledTransfer(QspiInstancePtr, writeBuffer, NULL,
				len + OVERHEAD_SIZE);

	// TODO : add timeout
	while (1) {

		XQspiPs_PolledTransfer(QspiInstancePtr, readStatusCmd, flashStatus,
					sizeof(readStatusCmd));

		if ((flashStatus[1] & 0x01) == 0) {
			break;
		}
	}
	
}


int CLI_Upgrade(u32 destAddr, u32 srcAddr, u32 len)
{
	int i = 0;
	u32 ddrValue = 0;
	u32 cnt = 0;
	int page_cnt = len / PAGE_SIZE;
	int last_page = len - page_cnt * PAGE_SIZE;

	if(len & 0x3)
	{
		debug(PR_LEVEL_ERROR, "not aligned\r\n");
		return -1;
	}

	do{	
		for(i = DATA_OFFSET; i < PAGE_SIZE + DATA_OFFSET; i += 4, srcAddr+=4)
		{
			ddrValue = REG32_READ(srcAddr);
			writeBuffer[i] = ddrValue & 0xFF;
			writeBuffer[i + 1] = (ddrValue >> 8) & 0xFF;
			writeBuffer[i + 2] = (ddrValue >> 16) & 0xFF;
			writeBuffer[i + 3] = (ddrValue >> 24) & 0xFF;
		}

		FlashWrite(destAddr, PAGE_SIZE, WRITE_CMD);

		destAddr += PAGE_SIZE;
		
		cnt ++;
	}while(cnt < page_cnt);

	/* last page */
	for(i = DATA_OFFSET; i < last_page + DATA_OFFSET; i += 4, srcAddr+=4)
	{
		ddrValue = REG32_READ(srcAddr);
			writeBuffer[i] = ddrValue & 0xFF;
			writeBuffer[i + 1] = (ddrValue >> 8) & 0xFF;
			writeBuffer[i + 2] = (ddrValue >> 16) & 0xFF;
			writeBuffer[i + 3] = (ddrValue >> 24) & 0xFF;
	}
	
	FlashWrite(destAddr, last_page, WRITE_CMD);
	
	return 0;
}

最后

 

其中up命令

#define BASE_XDCFG_ADDR		0xF8007000
/*
 * XDCFG UNLOCK
 *  base : BASE_XDCFG_ADDR
 */
#define XDCFG_UNLOCK_OFFSET 		0x34
#define UNLOCK_SHIFT				0x0
#define UNLOCK_MASK				0xFFFFFFFF
#define UNLOCK_KEY				0x757BDF0D

/*
 * multiboot
 *  base : BASE_XDCFG_ADDR
 */
#define XDCFG_MULTIBOOT_ADDR_OFFSET 	0x0000002C
#define MULTIBOOT_SHIFT					0x0
#define MULTIBOOT_MASK					0x1FFF

int SYS_Upgrade(void)
{
	// unlock XDCFG
	REG32_WRITE(BASE_XDCFG_ADDR + XDCFG_UNLOCK_OFFSET, UNLOCK_KEY);

	// update the multiboot
	REG32_WRITE(BASE_XDCFG_ADDR + XDCFG_MULTIBOOT_ADDR_OFFSET, 0x8);

	SYS_Reset();
	return 0;
}


REGISTER_CMD(
        up,
        1,
        ARG_TYPE_NONE,
		SYS_Upgrade,
        "<addr> load the kernel"
);

很简单,把新的image地址/32K写到multiboot寄存器即可。

6 从其他方式升级

常用方法有基于uart的ymodem协议传输升级,基于udp的tftp协议传输升级,基于usb设备的传输升级等,原理一样,不再赘述。

  • 8
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: Zynq是一款Xilinx公司的片上系统(SoC),它将传统的处理系统(PS,即Processing System)和可编程逻辑(PL,即Programmable Logic)集成在一起。PS和PL之间的通信是通过DMA(Direct Memory Access)实现的。 DMA是一种高效的数据传输方式,它可以在不经过处理器的情况下直接将数据从一个设备传输到另一个设备的内存中。在Zynq中,DMA控制器可以在PS和PL之间进行数据传输,以实现高速的数据交换。 在使用ZynqPS和PL之间进行通信时,首先需要在PL中实例化一个DMA控制器,并将其配置为与PS内存进行交互。然后,在PS中通过相应的软件驱动程序或API接口配置和控制DMA控制器。通过设置合适的寄存器和缓冲区,可以实现从PS到PL的数据传输或从PL到PS的数据传输,以及在传输过程中的中断处理。 PS与PL之间的DMA通信可以实现快速的数据交换,因为数据可以直接在PL中进行处理,无需经过PS的干预。这对于需要高速数据处理的应用非常有用,比如图像处理、信号处理等。 需要注意的是,使用DMA进行PS和PL之间的通信需要合理地配置和管理DMA控制器的缓冲区和寄存器,以及在PS和PL之间的数据传输过程中进行正确的同步和互斥操作,以避免数据冲突和错误。 总之,Zynq PS与PL之间的通信使用DMA可以实现高速的数据传输和处理,为嵌入式应用带来了更大的灵活性和高效性。 ### 回答2: Zynq系统中的PS(Processing System)与PL(Programmable Logic)之间的通信可以通过DMA(Direct Memory Access)实现。 DMA是一种特殊的数据传输机制,它可以在不需要CPU的干预下,在内存和外设之间直接传输数据。在Zynq系统中,PS和PL之间的DMA通信可以通过AXI(Advanced eXtensible Interface)总线实现。 首先,在Zynq系统中,PS可以使用AXI DMA控制器来设置数据传输的源地址、目的地址、传输长度等参数。这些参数可以通过PS的程序来配置。 然后,在PL中,我们可以使用AXI接口来设计自定义的IP(Intellectual Property)核,该IP核可以与AXI DMA进行通信。这个IP核可以通过PL的开发工具进行开发和配置。 接下来,通过适当的设计和配置,我们可以在PL中连接AXI DMA核和其他IP核,以实现PS和PL之间的数据传输。例如,我们可以将一个读取数据的IP核连接到AXI DMA的输入端口,将一个写入数据的IP核连接到AXI DMA的输出端口。这样,当AXI DMA启动数据传输时,读取数据的IP核将从PL的某个存储器中读取数据,然后通过DMA传输到PS的某个存储器中,而写入数据的IP核将从PS的某个存储器中读取数据,然后通过DMA传输到PL的某个存储器中。 总之,通过使用AXI DMA和适当的设计和配置,Zynq系统中的PS和PL之间可以进行高效的数据传输和通信。这种方式可以提高系统的性能和可扩展性,并且降低了CPU的负载,提供了更好的系统整合能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值