行情
HOME
行情
正文内容
fpga控制nand flash 基于Flash控制器的FPGA在线加载功能设计
发布时间 : 2024-11-24
作者 : 小编
访问数量 : 23
扫码分享至微信

基于Flash控制器的FPGA在线加载功能设计

近些年来,由于其灵活可配置性及成本的降低,现场可编程门阵列(Field Programmable Gate Array,FPGA)在嵌入式系统中应用越来越广泛,不论产品的初期研发或是后期维护都不可避免地需要频繁更新FPGA程序。传统的JTAG方式更新FPGA程序的方式是通过开发软件将需固化的文件写入Nor Flash器件。当系统很复杂且需要更新的FPGA数量较多时,JTAG方式更新FPGA程序则费时费力且还需拆结构。若在FPGA内部通过逻辑代码搭建一Flash控制器实现对Flash器件的读写操作,即可并行实现系统内每片FPGA对配置文件的在线更新,大大缩短程序固化时间。本文依托于Xilinx公司的FPGA和Spasion公司的Nor Flash,详细介绍了Flash控制器设计和在线加载功能的实现方法。

1 FPGA配置方式

以Xilinx Virtex6 系列FPGA为例,对配置文件的加载方式有串行Flash、并行Flash、JTAG方式等,其中最常用的是并行Flash方式(Byte Peripheral Interface Parallel Flash Mode,BPI),其配置模式如图1所示。

一种实际的FPGA与Nor Flash硬线连接方式如图2所示。

无论是通过JTAG方式还是通过软件操作实现程序更新,都是通过控制这些读写使能信号及地址线实现配置文件的写入,FPGA掉电重启之后再从Flash取出配置数据从而实现加载。本文提出的FPGA在线更新程序的方式则是通过FPGA控制逻辑驱动Flash的数据和地址,从而为多块FPGA并行更新程序的实现提供了前提。

2 Flash控制器设计

Flash器件的基本操作包括读、写、擦除等,这些操作都是通过向相应的命令寄存器写入特定的指令来实现的,这些指令的写入通过操作与FPGA相连的片选信号(ce_n)、写使能信号(we_n)、读使能信号(oe_n)、地址总线(addr[23:0])和数据总线(dq[15:0])实现。

以目前市场上常用的Spasion公司的S29GL-P系列Nor Flash为例,通过FPGA实现的控制器外部接口如图3所示。

其中,start信号用于启动控制器模块对Flash器件实现读写操作,在启动之前,须先告知Flash配置文件大小和配置文件在Flash存储空间的首地址;data_req、data_in和rd_data_count信号用于与一前端FIFO实现数据交互,配置文件先缓存到FIFO中,然后由控制器负责取出并写入Flash器件;config_status信号用于指示程序更新是否完成。

该Flash控制器按时间上的先后顺序分别实现了对Flash器件的读ID、擦除和写缓冲操作,下面将分别进行简要叙述。

每次对Flash进行基本的读写操作之前,会对Flash芯片进行读ID操作以检验器件的好坏,读ID涉及的主要操作为向特定寄存器写入相应的解锁指令,然后再进行多操作读出相应的寄存器值,其时序图及读结果如图4所示。

确认了芯片ID无误,即可进行正确访问,首先对Flash芯片进行擦除操作。Flash芯片擦除可分为两种:整片擦除和扇区擦除。由于实际使用时并未用到整块Flash存储空间,而是根据配置文件的大小选择合适的扇区大小存储配置文件,所以设计中采用的是扇区擦除方式,时序如图5所示。

当成功写入擦除操作指令之后,芯片会进入一个较长的周期等待擦除操作的完成,该期间无法对芯片进行复位之外的其他操作。

完成了擦除操作之后,即可向Flash写入配置数据,其对应的写缓冲时序如图6所示。

擦除和读写的过程中,地址将进行自动累加且起始地址可变。Flash的写操作可为单字写和缓冲写两种,由于缓冲写最大一次可向Flash写入32个字,为了最大化写入速度,选择缓冲写方式;Flash的读操作分为单字读和按页读两种,由于Flash读数据速率较高且系统对读速率没有太高要求,本文选择单字读方式。

3 工程应用及性能测试

以一实际项目工程为例,系统中具有10块FPGA进行程序更新,其在线加载功能系统架构如图7所示。

系统主控CPU运行在Linux系统下,CPU通过以太网与远程计算机进行通信调试,同时通过PCIE或SRIO高速总线向每块FPGA下发配置数据,最后再由Flash控制器完成数据的写入。如前文所述,一次写缓冲操作的数据量为32个字,而配置文件的大小有可能不是32个字的整数倍,所以还需要应用软件对配置文件进行补齐操作。单个FPGA在线加载流程简要叙述如图8所示。

更新单块FPGA程序时,以一实际测试的3.47 MB大小的配置文件为例,经FPGA在线更新程序固化的时间和使用ISE14.7软件JTAG方式固化的时间如表1所示。

Flash芯片手册中给出的典型擦除和写缓冲时间分别0.5 s和480 μs,则可计算出理论上3.47 MB大小的文件固化所需的典型时间约为41 s。由于器件等因素影响,实测时用时往往大于该时间,但总体来说,使用在线更新方式的耗时要比用JTAG方式耗时更少。

而当更新系统中的10块FPGA时,由于每块FPGA均可同时进行在线更新操作,程序固化所消耗的时间与单块FPGA程序更新时间开销大致相当,可见并行在线更新方式相较于传统JTAG方式无疑可以大大缩短时间。

4 配置文件不断电加载和备份方法

每次更新完配置之后,通常做法是对FPGA进行断电重启实现重新加载,但工程应用时断电可能会影响系统其他模块的正常工作。为使FPGA在不断电的情况下自动加载更新完的配置程序,可通过配置FPGA芯片的PROGRAM_B信号来实现,BPI模式下的时序如图9所示。

考虑到若在配置过程中FPGA发生断电等异常,则Flash中的配置文件损坏,FPGA将无法实现加载,需要重新使用JTAG方式烧写配置文件。为防止该种情况发生,可分配Flash的一块区域用于备份配置文件,发生FPGA无法正常加载的异常时,可读取该备份配置文件实现FPGA再次正常加载,但前提是该部分Flash存储空间预先固化了带有FPGA在线更新功能的程序。

5 结论

本文利用FPGA逻辑设计了一款Flash控制器,实现了FPGA在线更新功能,由于FPGA直接操作Flash,因此相比传统FPGA程序更新方法具有更新速度快、硬件电路精简、易于系统集成等优点,同时能够实现复杂系统多块FPGA并行更新程序,大大节省了程序固化时间,便于项目前期开发及后期排故调试。

参考文献

[1] Xilinx UG360,Virtex-6 FPGA configuration user guide[EB/OL].[2015-11-18].http://www.xilinx.com.

[2] Spansion.S29GL-P MirrorBit Flash family datasheet[EB/OL].[2009-11-20].http://www.spansion.com.

[3] 于乐,王嘉良.易于移植的FPGA在线更新控制器设计[J].航空电子技术,2015(4):47-50.

[4] 杨鹏.基于Linux系统的FPGA芯片在线加载的设计和实现[J].电子设计工程,2015(6):161-164.

作者信息:

林天静,阮 翔,刘 春

(中国电子科技集团第52研究所,浙江 杭州310000)

「正点原子FPGA连载」第十一章QSPI Flash读写测试

1)摘自【正点原子】领航者 ZYNQ 之嵌入式开发指南

2)实验平台:正点原子领航者ZYNQ开发板3)平台购买地址:https://item.taobao.com/item.htm?&id=6061601087614)全套实验源码+手册+视频下载:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html5)对正点原子FPGA感兴趣的同学可以加群讨论:8767449006)关注正点原子公众号,获取最新资料

第十一章QSPI Flash读写测试实验

Quad-SPI Flash控制器是PS内部输入/输出外设(IOP)的一部分,用来访问多位串行Flash存储器件,适合于使用较少的引脚数目达到较高数据带宽的应用场景。本章我们将通过QSPI Flash控制器,来完成对QSPI Flash的读写操作。本章包括以下几个部分:1111.1简介11.2实验任务11.3硬件设计11.4软件设计11.5下载验证11.1简介ZYNQ中的QSPI Flash控制器可以工作在三种模式下:I/O模式、线性地址模式,以及传统SPI模式。在I/O模式中,软件负责实现Flash器件的通信协议。软件需要将Flash命令和数据写到控制器中的TXD寄存器中,然后将接收到的数据从RXD寄存器中读出。而线性地址模式则使用一组器件操作来减小软件从Flash中读取数据的开销。线性模式使用硬件来实现来自AXI接口的命令到Flash指令的转换。对用户来说,在线性模式下读QSPI Flash就像读取ROM一样简单。但是该模式只支持读操作,不支持写操作。传统模式下的QSPI Flash控制器就像一个普通的SPI控制器,这个模式用的相对较少。QSPI Flash控制器的系统框图如下所示:

图 11.1.1 QSPI系统框图

从图 11.1.1中可以看到,QSPI Flash控制器通过MIO与外部Flash器件连接,支持三种模式:单个从器件模式、双从器件并行模式和双从器件堆模式。通过使用双从器件模式可以扩展QSPI Flash的存储容量。在使用单个器件的时候,直接存储访问Flash器件的地址映射FC00_0000到FCFF_FFFF(16MB)。在使用双器件模式时,地址空间可以扩展为FC00_0000至FDFF_FFFF(32MB)。另外,在上图中可以看到控制器左侧有两种类型的接口:AXI接口和APB接口。其中AXI接口用于线性地址模式,而APB接口用于I/O模式。QSPI Flash控制器的模块示意图如图 11.1.2所示。从中我们可以清晰的看出QSPI Flash控制器两种模式的差异。由于线性地址模式不支持写操作,因此本次实验重点介绍I/O模式。在I/O模式下,软件需要把命令和数据转化成QSPI Flash协议下的指令,转换之后的指令将被写入Tx FIFO。然后发送逻辑将Tx FIFO中的内容按照QSPI接口规范进行并串转换,最后通过MIO将转换后的数据送到Flash存储器中。在发送逻辑将Tx FIFO中的数据发送出去的同时,接收逻辑会采样所发送的串行数据,进行串并转换后存储到Rx FIFO里面。如果执行的是读操作,在发送读指令和读地址之后,MIO会在发送逻辑的控制下由输出模式切换成输入模式,从Flash中读出的数据将被存储丰Rx FIFO中。由于Rx FIFO中会同时接收由软件发出的指令,因此我们需要对Rx FIFO中的原始数据进行过滤,从而得到从Flash中读出的有效数据。

图 11.1.2 QSPI Flash控制器功能框图

11.2实验任务本章的实验任务是使用QSPI Flash控制器,先后对领航者核心板上的QSPI Flash进行写、读操作。通过对比读出的数据是否等于写入的数据,从而验证读写操作是否正确。11.3硬件设计根据实验任务我们可以画出本次实验的系统框图,如下图所示:

图 11.3.1 系统框图

从图 11.3.1中可以看出,本次实验是在“Hello Wold”实验的基础上增加了一个QSPI Flash控制器。我们将通过该控制器对QSPI Flash进行读写操作,并通过串口打印读写数据对比之后的结果。首先创建Vivado工程,工程名为“qspi_rw_test”,然后创建Block Design设计(system.bd)并添加ZYNQ7 Processing System模块。接下来按照《“Hello World”实验》中的步骤对ZYNQ PS模块进行配置,配置完成 后我们要添加本次实验所使用的QSPI Flash控制器模块。如下图所示:

图 11.3.2 QSPI配置界面

如图 11.3.2所示,在左侧导航栏中选择“Peripheral I/O Pins”,然后在右侧勾选“Quad SPI Flash”,并选择“Single SS 4bit IO”模式。“Single SS 4bit IO”指的是单个从器件模式,其中“SS”为“Slave Select”的缩写。看以看出,该模式下控制器使用了MIO1至MIO6共6个引脚。最后点击右下角的“OK”,本次实验ZYNQ处理系统就配置完成了。接下来在Diagram窗口中选择自动连接PS模块端口,连接完成后模块如下图所示:

图 11.3.3 ZYNQ7模块

到这里我们的Block Design就设计完成了,在Diagram窗口空白处右击,然后选择“Validate Design”验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl + S”保存设计。接下来在Source窗口中右键点击Block Design设计文件“system.bd”,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。然后在菜单栏中选择 File > Export > Export hardware导出硬件,最后在菜单栏选择File > Launch SDK,启动SDK软件。11.4软件设计在SDK软件中新建一个BSP工程和一个空的应用工程,应用工程名为“qspi_Flash_test”。然后为应用工程新建一个源文件“main.c”,我们在新建的main.c文件中输入本次实验的代码。代码的主体部分如下所示:

1 #include "xparameters.h" /* SDK generated parameters */2 #include "xqspips.h" /* QSPI device driver */3 #include "xil_printf.h"4 5 #define QSPI_DEVICE_ID XPAR_XQSPIPS_0_DEVICE_ID6 7 //发送到Flash器件的指令8 #define WRITE_STATUS_CMD 0x019 #define WRITE_CMD 0x0210 #define READ_CMD 0x0311 #define WRITE_DISABLE_CMD 0x0412 #define READ_STATUS_CMD 0x0513 #define WRITE_ENABLE_CMD 0x0614 #define FAST_READ_CMD 0x0B15 #define DUAL_READ_CMD 0x3B16 #define QUAD_READ_CMD 0x6B17 #define BULK_ERASE_CMD 0xC718 #define SEC_ERASE_CMD 0xD819 #define READ_ID 0x9F20 21 //Flash BUFFER中各数据的偏移量22 #define COMMAND_OFFSET 0 // Flash instruction23 #define ADDRESS_1_OFFSET 1 // MSB byte of address to read or write24 #define ADDRESS_2_OFFSET 2 // Middle byte of address to read or write25 #define ADDRESS_3_OFFSET 3 // LSB byte of address to read or write26 #define DATA_OFFSET 4 // Start of Data for Read/Write27 #define DUMMY_OFFSET 4 // Dummy byte offset for reads28 29 #define DUMMY_SIZE 1 // Number of dummy bytes for reads30 #define RD_ID_SIZE 4 // Read ID command + 3 bytes ID response31 #define BULK_ERASE_SIZE 1 // Bulk Erase command size32 #define SEC_ERASE_SIZE 4 // Sector Erase command + Sector address33 34 #define OVERHEAD_SIZE 4 // control information: command and address35 36 #define SECTOR_SIZE 0x1000037 #define NUM_SECTORS 0x10038 #define NUM_PAGES 0x1000039 #define PAGE_SIZE 25640 41 /* Number of Flash pages to be written.*/42 #define PAGE_COUNT 1643 44 /* Flash address to which data is to be written.*/45 #define TEST_ADDRESS 0x0005500046 #define UNIQUE_VALUE 0x0547 48 #define MAX_DATA (PAGE_COUNT * PAGE_SIZE)49 50 void FlashErase(XQspiPs *QspiPtr, u32 Address, u32 ByteCount);51 void FlashWrite(XQspiPs *QspiPtr, u32 Address, u32 ByteCount, u8 Command);52 void FlashRead(XQspiPs *QspiPtr, u32 Address, u32 ByteCount, u8 Command);53 int FlashReadID(void);54 void FlashQuadEnable(XQspiPs *QspiPtr);55 int QspiFlashPolledExample(XQspiPs *QspiInstancePtr, u16 QspiDeviceId);56 57 static XQspiPs QspiInstance;58 59 int Test = 5;60 61 u8 ReadBuffer[MAX_DATA + DATA_OFFSET + DUMMY_SIZE];62 u8 WriteBuffer[PAGE_SIZE + DATA_OFFSET];63 64 int main(void)65 {66 int Status;67 68 xil_printf("QSPI Flash Polled Example Test \r\n");69 70 /* Run the Qspi Interrupt example.*/71 Status = QspiFlashPolledExample(&QspiInstance, QSPI_DEVICE_ID);72 if (Status != XST_SUCCESS) {73 xil_printf("QSPI Flash Polled Example Test Failed\r\n");74 return XST_FAILURE;75 }76 77 xil_printf("Successfully ran QSPI Flash Polled Example Test\r\n");78 return XST_SUCCESS;79 }

首先,本次实验的C程序是在官方提供的示例程序“xqspips_Flash_polled_example.c”的基础上修改得到的,该示例程序演示了如何使用轮询模式对QSPI Flash进行读写操作。在程序的开头,我们定义了一系列的参数,包括Flash器件的指令、Flash BUFFER中各数据段的偏移量、Flash器件PAGE、SECTOR的数目和大小等信息。这些信息针对不同型号的Flash器件有所不同,需要通过查看器件的数据手册得到。接下来在程序第50至55行声明了六个函数,这些函数是前面我们提到的示例程序中所提供的。我们对其中最后一个函数QspiFlashPolledExample(XQspiPs *QspiInstancePtr, u16 QspiDeviceId)进行修改,从而简化读写测试过程。而其他的函数如擦除FlashErase( )、写操作FlashWrite( )、读操作FlashRead( )等,我们可以将其当作库函数来使用。程序的主函数特别简单,就是通过调用修改之后的示例函数QspiFlashPolledExample( )来对Flash进行读写测试,并打印最终的测试结果。下面是该示例函数的代码:

81 int QspiFlashPolledExample(XQspiPs *QspiInstancePtr, u16 QspiDeviceId)82 {83 int Status;84 u8 *BufferPtr;85 u8 UniqueValue;86 int Count;87 int Page;88 XQspiPs_Config *QspiConfig;89 90 //初始化QSPI驱动91 QspiConfig = XQspiPs_LookupConfig(QspiDeviceId);92 XQspiPs_CfgInitialize(QspiInstancePtr, QspiConfig, QspiConfig->BaseAddress);93 //初始化读写BUFFER94 for (UniqueValue = UNIQUE_VALUE, Count = 0; Count < PAGE_SIZE;95 Count++, UniqueValue++) {96 WriteBuffer[DATA_OFFSET + Count] = (u8)(UniqueValue + Test);97 }98 memset(ReadBuffer, 0x00, sizeof(ReadBuffer));99 100 //设置手动启动和手动片选模式101 XQspiPs_SetOptions(QspiInstancePtr, XQSPIPS_MANUAL_START_OPTION |102 XQSPIPS_FORCE_SSELECT_OPTION |103 XQSPIPS_HOLD_B_DRIVE_OPTION);104 //设置QSPI时钟的分频系数105 XQspiPs_SetClkPrescaler(QspiInstancePtr, XQSPIPS_CLK_PRESCALE_8);106 //片选信号置为有效107 XQspiPs_SetSlaveSelect(QspiInstancePtr);108 //读Flash ID109 FlashReadID();110 //使能Flash Quad模式111 FlashQuadEnable(QspiInstancePtr);112 //擦除Flash113 FlashErase(QspiInstancePtr, TEST_ADDRESS, MAX_DATA);114 //向Flash中写入数据115 for (Page = 0; Page < PAGE_COUNT; Page++) {116 FlashWrite(QspiInstancePtr, (Page * PAGE_SIZE) + TEST_ADDRESS,117 PAGE_SIZE, WRITE_CMD);118 }119 //使用QUAD模式从Flash中读出数据120 FlashRead(QspiInstancePtr, TEST_ADDRESS, MAX_DATA, QUAD_READ_CMD);121 122 //对比写入Flash与从Flash中读出的数据123 BufferPtr = &ReadBuffer[DATA_OFFSET + DUMMY_SIZE];124 for (UniqueValue = UNIQUE_VALUE, Count = 0; Count < MAX_DATA;125 Count++, UniqueValue++) {126 if (BufferPtr[Count] != (u8)(UniqueValue + Test)) {127 return XST_FAILURE;128 }129 }130 131 return XST_SUCCESS;132 }在示例函数中,首先对QSPI Flash控制器驱动进行初始化。然后对读写BUFFER进行初始化,初始化完成后WriteBuffer中为需要写入Flash的测试数据;而ReadBuffer则全部清零,准备用于接收从Flash中读回的数据,进而与WriteBuffer中的数据进行对比。接下来,通过调用xqspips.h头文件中的库函数来对QSPI Flash控制器进行配置。将其配置成手动启动和手动片选模式,并将片选信号置为有效状态。QSPI Flash控制器在I/O模式下,有两种流控(Flow Control)方法:手动模式和自动模式。手动模式下,将由用户来控制数据传输的开始。而在手动模式下,又分为“手动片选”和“自动片选”,它们指的是片选信号的控制权。自动片选所传输的数据量受限于Tx FIFO的深度,而手动片选更适合批量数据的传输。片选信号置为有效状态意味着Flash传输序列的开始。在程序的108至120行,我们调用示例程序“xqspips_Flash_polled_example.c”所提供的函数,来执行一系列Flash操作,包括读Flash ID、使能Flash Quad模式、擦除Flash等。其中最核心的是通过FlashWrite( )函数向Flash指定地址写入测试数据,然后通过FlashRead( )函数将数据从该地址读出,放至读BUFFER中。最后,在程序的122至129行,通过对比写BUFFER与读BUFFER中的数据是否一致,从而判断Flash读写测试实验是否成功。程序的剩余部分是前面所声明的一系列操作Flash的函数的实现,因为我们将其当作库函数直接调用,因此代码就不再贴出来了。大家有兴趣的话也可以研究一下,这些函数是如何将读写指令和数据转换成QSPI Flash所要求的命令格式的。实际上,这些函数的功能也都是通过调用xqspips.h头文件中的库函数XQspiPs_PolledTransfer( )来实现的。11.5下载验证首先我们将下载器与领航者底板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用Mini USB连接线将开发板左侧的USB_UART接口与电脑连接,用于串口通信。最后连接开发板的电源,并打开电源开关。在SDK软件下方的SDK Terminal窗口中点击右上角的加号设置并连接串口。然后下载本次实验软件程序,下载完成后,在下方的SDK Terminal中可以看到应用程序打印的信息如下图所示:

图 11.5.1 串口打印结果

从图 11.5.1中可以看出,本次实验所实现的QSPI Flash读写测试功能,在领航者ZYNQ开发板上面下载验证成功。

相关问答

flash fpga 有什么区别?

flash是存储器,fpga是逻辑编程器flash是存储器,fpga是逻辑编程器

fpga 采用什么工艺,需要外挂配置什么存储芯片?

1.理解为配置储存器,一般指我们fpga的配置程序储存的地方。多用串行flash也就是EPCS,有时例如我们也可以用norflash。2.理解为配置储存器,对储存器进行配...

加载到 FPGA 上的程序如何使用?

你好,加载到FPGA上的程序需要通过JTAG或者配置存储器(如Flash)进行加载。一般来说,开发工具会提供相应的加载工具,用户只需要按照工具的指导操作即可。具体...

fpga 和cpld什么区别,哪个更强大一些,谢谢了?

1、FPGA(Field-ProgrammableGateArray),即现场可编程门阵列,它是在PAL、GAL、CPLD等可编程器件的基础上进一步发展的产物。它是作为专用集成电路(ASIC)....

FPGA 与CPLD的概念及其区别?

一、FPGA与CPLD的基本概念1.CPLDCPLD主要是由可编程逻辑宏单元(LMC,LogicMacroCell)围绕中心的可编程互连矩阵单元组成。2.FPGAFPGA通常包含三类可....

fpga 基于什么可编程结构?

尽管FPGA和CPLD都是可编程ASIC器件,有很多共同特点,但由于CPLD和FPGA结构上的差异,具有各自的特点:①CPLD更适合完成各种算法和组合逻辑,FPGA更适合于完成时...

简述 FPGA 与CPLD在硬件结构上的区别?

FPGA与CPLD的区别系统的比较,与大家共享:尽管FPGA和CPLD都是可编程ASIC器件,有很多共同特点,但由于CPLD和FPGA结构上的差异,具有各自的特点:①CPLD更适合完...

fpga 中分布式ram描述正确的是?

FPGA内部的RAM分为两部分,一部分就是你的分布式的RAM,用于LUT,还有一种是块ram(BLOCKRAM)是一块用于存储数据的专用RAM,也就是你的系统设计中需要用到容量...

中国的紫光和中芯国际,两家公司的区别是什么?都是研制芯片的么?

总结最后,值得一提的是,紫光集团也入股了中芯国际,是中芯国际的大股东,双方共同组成集设计、封装测试、制造生产于一体的完整集成电路产业链,为中国“芯”...虽...

【求NIos我在使用NiosII编程,进行flashprogrammer时总是出现...

[回答]应该是nioside/sdk使用的sopcinfo或者ptf文件和你对fpga配置的文件不是同一个工程建立的.

 醇粹猫粮  东楼kappa女 
王经理: 180-0000-0000(微信同号)
10086@qq.com
北京海淀区西三旗街道国际大厦08A座
©2024  上海羊羽卓进出口贸易有限公司  版权所有.All Rights Reserved.  |  程序由Z-BlogPHP强力驱动
网站首页
电话咨询
微信号

QQ

在线咨询真诚为您提供专业解答服务

热线

188-0000-0000
专属服务热线

微信

二维码扫一扫微信交流
顶部