ZYNQ FLASH+EMMC手动移植LINUX启动
前言
虽可使用Petalinux
进行移植,简单方便,但为了更清楚明白的了解整个流程,还是尝试了一波手动移植。
参考资料
ZYNQ Linux 移植:包含petalinux移植和手动移植debian9
ZYNQ #5 - 从vivado工程开始,从emmc启动Linux_里先森-CSDN博客
流程
对于手动移植,所需的文件为:
BOOT.bin(FSBL+fpga_bit文件+u_boot.elf)、uImage、devicetree.dtb、uEnv.txt、文件系统
文件放置位置说明:
FLASH:BOOT.bin(FSBL+fpga_bit文件+u_boot.elf)
EMMC:
第一个分区放置:uImage、devicetree.dtb、uEnv.txt
第二个分区放置:文件系统
启动流程为:板子设置为QSPI启动模式,FSBL执行调u-boot执行,u-boot根据uEnv.txt调EMMC分区1中的内核执行,内核最后跳到分区二中的文件系统。
FSBL、bit文件、uImage、文件系统都可复用SD卡移植模式下的内容:参考参考资料第一个链接。
本文章主要讲述的重点在于:u-boot、设备树、uEnv.txt的更改部分
板子主要信息说明:板子芯片:ZYNQ7035,板子的调试打印串口为PS0,板子上有SD卡(SD0)、EMMC(SD1),板子上有FLASH。其他外设不赘述。
u-boot移植说明:
1.下载u-boot源码:git clone [https://github.com/Xilinx/u-boot-xlnx.git](https://github.com/Xilinx/u-boot-xlnx.git)
NOTE:u-boot xilinx-v2018.3版本的zynq-common.h
跟xilinx-v2018.1版本的不一样,这里检出v2018.1版本使用。
主要是CONFIG_EXTRA_ENV_SETTINGS
环境变量不一致。
默认的如下所示:
/* Default environment */
#ifndef CONFIG_EXTRA_ENV_SETTINGS
#define CONFIG_EXTRA_ENV_SETTINGS \
"ethaddr=00:0a:35:00:01:22\0" \
"kernel_image=uImage\0" \
"kernel_load_address=0x2080000\0" \
"ramdisk_image=uramdisk.image.gz\0" \
"ramdisk_load_address=0x4000000\0" \
"devicetree_image=devicetree.dtb\0" \
"devicetree_load_address=0x2000000\0" \
"bitstream_image=system.bit.bin\0" \
"boot_image=BOOT.bin\0" \
"loadbit_addr=0x100000\0" \
"loadbootenv_addr=0x2000000\0" \
"kernel_size=0x500000\0" \
"devicetree_size=0x20000\0" \
"ramdisk_size=0x5E0000\0" \
"boot_size=0xF00000\0" \
"fdt_high=0x20000000\0" \
"initrd_high=0x20000000\0" \
"bootenv=uEnv.txt\0" \
"loadbootenv=load mmc 0 ${loadbootenv_addr} ${bootenv}\0" \
"importbootenv=echo Importing environment from SD ...; " \
"env import -t ${loadbootenv_addr} $filesize\0" \
"sd_uEnvtxt_existence_test=test -e mmc 0 /uEnv.txt\0" \
"preboot=if test $modeboot = sdboot && env run sd_uEnvtxt_existence_test; " \
"then if env run loadbootenv; " \
"then env run importbootenv; " \
"fi; " \
"fi; \0" \
"mmc_loadbit=echo Loading bitstream from SD/MMC/eMMC to RAM.. && " \
"mmcinfo && " \
"load mmc 0 ${loadbit_addr} ${bitstream_image} && " \
"fpga load 0 ${loadbit_addr} ${filesize}\0" \
"norboot=echo Copying Linux from NOR flash to RAM... && " \
"cp.b 0xE2100000 ${kernel_load_address} ${kernel_size} && " \
"cp.b 0xE2600000 ${devicetree_load_address} ${devicetree_size} && " \
"echo Copying ramdisk... && " \
"cp.b 0xE2620000 ${ramdisk_load_address} ${ramdisk_size} && " \
"bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}\0" \
"qspiboot=echo Copying Linux from QSPI flash to RAM... && " \
"sf probe 0 0 0 && " \
"sf read ${kernel_load_address} 0x100000 ${kernel_size} && " \
"sf read ${devicetree_load_address} 0x600000 ${devicetree_size} && " \
"echo Copying ramdisk... && " \
"sf read ${ramdisk_load_address} 0x620000 ${ramdisk_size} && " \
"bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}\0" \
"uenvboot=" \
"if run loadbootenv; then " \
"echo Loaded environment from ${bootenv}; " \
"run importbootenv; " \
"fi; " \
"if test -n $uenvcmd; then " \
"echo Running uenvcmd ...; " \
"run uenvcmd; " \
"fi\0" \
"sdboot=if mmcinfo; then " \
"run uenvboot; " \
"echo Copying Linux from SD to RAM... && " \
"load mmc 0 ${kernel_load_address} ${kernel_image} && " \
"load mmc 0 ${devicetree_load_address} ${devicetree_image} && " \
"load mmc 0 ${ramdisk_load_address} ${ramdisk_image} && " \
"bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}; " \
"fi\0" \
"usbboot=if usb start; then " \
"run uenvboot; " \
"echo Copying Linux from USB to RAM... && " \
"load usb 0 ${kernel_load_address} ${kernel_image} && " \
"load usb 0 ${devicetree_load_address} ${devicetree_image} && " \
"load usb 0 ${ramdisk_load_address} ${ramdisk_image} && " \
"bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}; " \
"fi\0" \
"nandboot=echo Copying Linux from NAND flash to RAM... && " \
"nand read ${kernel_load_address} 0x100000 ${kernel_size} && " \
"nand read ${devicetree_load_address} 0x600000 ${devicetree_size} && " \
"echo Copying ramdisk... && " \
"nand read ${ramdisk_load_address} 0x620000 ${ramdisk_size} && " \
"bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}\0" \
"jtagboot=echo TFTPing Linux to RAM... && " \
"tftpboot ${kernel_load_address} ${kernel_image} && " \
"tftpboot ${devicetree_load_address} ${devicetree_image} && " \
"tftpboot ${ramdisk_load_address} ${ramdisk_image} && " \
"bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}\0" \
"rsa_norboot=echo Copying Image from NOR flash to RAM... && " \
"cp.b 0xE2100000 0x100000 ${boot_size} && " \
"zynqrsa 0x100000 && " \
"bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}\0" \
"rsa_nandboot=echo Copying Image from NAND flash to RAM... && " \
"nand read 0x100000 0x0 ${boot_size} && " \
"zynqrsa 0x100000 && " \
"bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}\0" \
"rsa_qspiboot=echo Copying Image from QSPI flash to RAM... && " \
"sf probe 0 0 0 && " \
"sf read 0x100000 0x0 ${boot_size} && " \
"zynqrsa 0x100000 && " \
"bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}\0" \
"rsa_sdboot=echo Copying Image from SD to RAM... && " \
"load mmc 0 0x100000 ${boot_image} && " \
"zynqrsa 0x100000 && " \
"bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}\0" \
"rsa_jtagboot=echo TFTPing Image to RAM... && " \
"tftpboot 0x100000 ${boot_image} && " \
"zynqrsa 0x100000 && " \
"bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}\0" \
DFU_ALT_INFO \
BOOTENV
#endif
可以看到其上定义了一堆的东西及不同的启动指令。
不同的启动指令由#define CONFIG_BOOTCOMMAND指定。
这里想FLASH+EMMC启动,则可以选择sdboot,同时因为EMMC是SD1,所以上述的文件需要进行更改:更改uEnv.txt的存储位置为mmc 1;去掉sdboot中的ramdisk,及存储kernel、设备树位置为mmc 1;修改bootm参数。
"loadbootenv=load mmc 1 ${loadbootenv_addr} ${bootenv}\0" \
"importbootenv=echo Importing environment from SD ...; " \
"env import -t ${loadbootenv_addr} $filesize\0" \
"sd_uEnvtxt_existence_test=test -e mmc 1 /uEnv.txt\0" \
"sdboot=if mmcinfo; then " \
"run uenvboot; " \
"echo Copying Linux from hahahah SD to RAM... && " \
"load mmc 1 ${kernel_load_address} ${kernel_image} && " \
"load mmc 1 ${devicetree_load_address} ${devicetree_image} && " \
"bootm ${kernel_load_address} - ${devicetree_load_address}; " \
"fi\0" \
上述命令的执行逻辑为:u-boot启动后,执行sdboot指令:
(1)装载uEnv.txt文件并运行;
(2)装载内核、设备树;
(3)运行;
2.由于是自己做的板子,则可以基于类似的板子defconfig进行修改适配u-boot:这里使用zynq_zed_defconfig文件:
(1)增加传统镜像文件支持:CONFIG_IMAGE_FORMAT_LEGACY=y
(2)因为已经知道是从EMMC启动相关文件,则可直接指定:CONFIG_BOOTCOMMAND="mmc dev 1;run sdboot"
mmc dev 1是为了避免u-boot死锁bug(移植petalinux中u-boot存在,为官方bug,这里加上,不知道有没有用);sdboot会直接调用执行zynq-common.h中的对应选项。
(3)因为板子上串口使用的是PS0,修改:CONFIG_DEBUG_UART_BASE=0xe0000000(地址可在设备树中查看)
文件参考:
CONFIG_ARM=y
CONFIG_ARCH_ZYNQ=y
CONFIG_SYS_TEXT_BASE=0x4000000
CONFIG_SPL_STACK_R_ADDR=0x200000
CONFIG_DEFAULT_DEVICE_TREE="zynq-zed"
CONFIG_DEBUG_UART=y
CONFIG_DISTRO_DEFAULTS=y
CONFIG_IMAGE_FORMAT_LEGACY=y
CONFIG_FIT=y
CONFIG_FIT_SIGNATURE=y
CONFIG_FIT_VERBOSE=y
CONFIG_BOOTCOMMAND="mmc dev 1;run sdboot"
# CONFIG_DISPLAY_CPUINFO is not set
CONFIG_SPL=y
CONFIG_SPL_STACK_R=y
CONFIG_SPL_OS_BOOT=y
CONFIG_SYS_PROMPT="Zynq> "
CONFIG_CMD_THOR_DOWNLOAD=y
CONFIG_CMD_DFU=y
# CONFIG_CMD_FLASH is not set
CONFIG_CMD_FPGA_LOADBP=y
CONFIG_CMD_FPGA_LOADFS=y
CONFIG_CMD_FPGA_LOADMK=y
CONFIG_CMD_FPGA_LOADP=y
CONFIG_CMD_GPIO=y
CONFIG_CMD_MMC=y
CONFIG_CMD_SF=y
CONFIG_CMD_USB=y
# CONFIG_CMD_SETEXPR is not set
CONFIG_CMD_TFTPPUT=y
CONFIG_CMD_CACHE=y
CONFIG_CMD_EXT4_WRITE=y
CONFIG_OF_EMBED=y
CONFIG_ENV_IS_IN_SPI_FLASH=y
CONFIG_NET_RANDOM_ETHADDR=y
CONFIG_SPL_DM_SEQ_ALIAS=y
CONFIG_DFU_MMC=y
CONFIG_DFU_RAM=y
CONFIG_FPGA_XILINX=y
CONFIG_DM_GPIO=y
CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_ZYNQ=y
CONFIG_SPI_FLASH=y
CONFIG_SPI_FLASH_BAR=y
CONFIG_SF_DUAL_FLASH=y
CONFIG_SPI_FLASH_SPANSION=y
CONFIG_SPI_FLASH_STMICRO=y
CONFIG_SPI_FLASH_WINBOND=y
CONFIG_PHY_MARVELL=y
CONFIG_PHY_REALTEK=y
CONFIG_PHY_XILINX=y
CONFIG_ZYNQ_GEM=y
CONFIG_DEBUG_UART_ZYNQ=y
CONFIG_DEBUG_UART_BASE=0xe0000000
CONFIG_DEBUG_UART_CLOCK=50000000
CONFIG_ZYNQ_SERIAL=y
CONFIG_ZYNQ_QSPI=y
CONFIG_USB=y
CONFIG_USB_EHCI_HCD=y
CONFIG_USB_ULPI_VIEWPORT=y
CONFIG_USB_ULPI=y
CONFIG_USB_STORAGE=y
CONFIG_USB_GADGET=y
CONFIG_USB_GADGET_MANUFACTURER="Xilinx"
CONFIG_USB_GADGET_VENDOR_NUM=0x03fd
CONFIG_USB_GADGET_PRODUCT_NUM=0x0300
CONFIG_CI_UDC=y
CONFIG_USB_GADGET_DOWNLOAD=y
3.修改u-boot的设备树:实际上u-boot也带了设备树,这里需要进行修改:
(1)修改uart为uart0;
(2)修改SD为mmc1;
(3)去除QSPI中的分区表;
文件参考:
/*
* Xilinx ZED board DTS
*
* Copyright (C) 2011 - 2015 Xilinx
* Copyright (C) 2012 National Instruments Corp.
*
* SPDX-License-Identifier: GPL-2.0+
*/
/dts-v1/;
#include "zynq-7000.dtsi"
/ {
model = "Zynq Zed Development Board";
compatible = "xlnx,zynq-zed", "xlnx,zynq-7000";
aliases {
ethernet0 = &gem0;
serial0 = &uart0;
spi0 = &qspi;
mmc1 = &sdhci1;
};
memory@0 {
device_type = "memory";
reg = <0x0 0x20000000>;
};
chosen {
bootargs = "";
stdout-path = "serial0:115200n8";
};
usb_phy0: phy0@e0002000 {
compatible = "ulpi-phy";
#phy-cells = <0>;
reg = <0xe0002000 0x1000>;
view-port = <0x0170>;
drv-vbus;
};
};
&clkc {
ps-clk-frequency = <33333333>;
};
&gem0 {
status = "okay";
phy-mode = "rgmii-id";
phy-handle = <ðernet_phy>;
ethernet_phy: ethernet-phy@0 {
reg = <0>;
device_type = "ethernet-phy";
};
};
&qspi {
u-boot,dm-pre-reloc;
status = "okay";
is-dual = <0>;
num-cs = <1>;
flash@0 {
compatible = "n25q128a11";
reg = <0x0>;
spi-tx-bus-width = <1>;
spi-rx-bus-width = <4>;
spi-max-frequency = <50000000>;
#address-cells = <1>;
#size-cells = <1>;
};
};
&sdhci1 {
u-boot,dm-pre-reloc;
status = "okay";
};
&uart0 {
u-boot,dm-pre-reloc;
status = "okay";
};
&usb0 {
status = "okay";
dr_mode = "host";
usb-phy = <&usb_phy0>;
};
- 编译u-boot:
清除中间编译:
make distclean
使用配置文件:
make CROSS_COMPILE=arm-linux-gnueabihf- zynq_zed_defconfig
通过下述指令可在界面中uboot进行进一步修改配置:改defconfig文件也可以,暂时默认即可
make CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
工具编译:
make CROSS_COMPILE=arm-linux-gnueabihf- tools
编译u-boot:
make CROSS_COMPILE=arm-linux-gnueabihf-
修改总设备树的boottags:console=ttyPS0,115200n8 root=/dev/mmcblk1p2 rw noinitrd rootfstype=ext4 rootwait
修改uEnv.txt为:mmc 0修改为mmc 1;root=/dev/mmcblk1p2
bootargs=console=ttyPS0,115200 root=/dev/mmcblk1p2 rw earlyprintk rootfstype=ext4 rootwait
load_image=fatload mmc 1 ${kernel_load_address} ${kernel_image} && fatload mmc 1 ${devicetree_load_address} ${devicetree_image}
uenvcmd=echo Copying Linux from SD to RAM... && mmcinfo && run load_image && bootm ${kernel_load_address} - ${devicetree_load_address}
- 组装BOOT.bin,不赘述。
- 烧录BOOT.bin到FLASH,对EMMC进行分区,EMMC分区一中放置uImage、devicetree.dtb、uEnv.txt,EMMC分区二中放置文件系统即可。
- 启动。
U-Boot 2018.01-dirty (Jul 26 2021 - 20:05:00 +0800)
Model: Zynq Zed Development Board
Board: Xilinx Zynq
Silicon: v3.1
DRAM: ECC disabled 512 MiB
MMC: sdhci_transfer_data: Error detected in status(0x208000)!
sdhci@e0101000: 1 (eMMC)
SF: Detected s25fl128s_64k with page size 256 Bytes, erase size 64 KiB, total 16 MiB
*** Warning - bad CRC, using default environment
In: serial@e0000000
Out: serial@e0000000
Err: serial@e0000000
Net: ZYNQ GEM: e000b000, phyaddr 0, interface rgmii-id
eth0: ethernet@e000b000
Hit any key to stop autoboot: 0
sdhci_transfer_data: Error detected in status(0x208000)!
switch to partitions #0, OK
mmc1(part 0) is current device
Device: sdhci@e0101000
Manufacturer ID: 13
OEM: 14e
Name: Q2J55
Tran Speed: 25000000
Rd Block Len: 512
MMC version 5.0
High Capacity: Yes
Capacity: 7.1 GiB
Bus Width: 4-bit
Erase Group Size: 512 KiB
HC WP Group Size: 8 MiB
User Capacity: 7.1 GiB WRREL
Boot Capacity: 16 MiB ENH
RPMB Capacity: 4 MiB ENH
reading uEnv.txt
356 bytes read in 11 ms (31.3 KiB/s)
Loaded environment from uEnv.txt
Importing environment from SD ...
Running uenvcmd ...
Copying Linux from SD to RAM...
Device: sdhci@e0101000
Manufacturer ID: 13
OEM: 14e
Name: Q2J55
Tran Speed: 25000000
Rd Block Len: 512
MMC version 5.0
High Capacity: Yes
Capacity: 7.1 GiB
Bus Width: 4-bit
Erase Group Size: 512 KiB
HC WP Group Size: 8 MiB
User Capacity: 7.1 GiB WRREL
Boot Capacity: 16 MiB ENH
RPMB Capacity: 4 MiB ENH
reading uImage
4001616 bytes read in 377 ms (10.1 MiB/s)
reading devicetree.dtb
17052 bytes read in 18 ms (924.8 KiB/s)
## Booting kernel from Legacy Image at 02080000 ...
Image Name: Linux-4.14.0-xilinx
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 4001552 Bytes = 3.8 MiB
Load Address: 00008000
Entry Point: 00008000
Verifying Checksum ... OK
## Flattened Device Tree blob at 02000000
Booting using the fdt blob at 0x2000000
Loading Kernel Image ... OK
Loading Device Tree to 1eb12000, end 1eb1929b ... OK
以上。
ZYNQ FLASH+EMMC手动移植LINUX启动的更多相关文章
- Zynq ZC706 传统方式移植Linux -- 编译kernel 文件系统 devicetree
1.kernel 实际操作时候,下面两条命令就够了. make ARCH=arm xilinx_zynq_defconfig make ARCH=arm CROSS_COMPILE=arm-xilin ...
- Zynq ZC706 传统方式移植Linux -- 编译u-boot
我用的是zc706不是zed 基本思路是: 1.安装交叉编译工具(见 https://www.cnblogs.com/idyllcheung/p/10532654.html ) 2.下载xilinx ...
- ZYNQ Linux 移植:包含petalinux移植和手动移植debian9
参考: https://electronut.in/workflow-for-using-linux-on-xilinx-zynq/ https://blog.csdn.net/m0_37545528 ...
- S04_CH01_搭建工程移植LINUX/测试EMMC/VGA
S04_CH01_搭建工程移植LINUX/测试EMMC/VGA 1.1概述: 本章内容是在已经提供安装了VIVADO2015.4 的ubuntu系统下,进行.大家可以下周我们已经提供的虚拟机镜像,我们 ...
- u-boot 移植 --->6、引导Linux启动测试
在引导Linux开机之前需要先清楚Linux启动的必要或者说是先决条件,这里就是提到了u-boot的作用了引用百度云---主要用于嵌入式系统的引导加载,其实在我调试下来总结一下就是初始化硬件这里的硬件 ...
- ZYNQ跑系统 系列(二) petalinux方式移植linux
三.搭建petalinux工程 0.定位目录 先在shell中找一个准备存放工程的地方,(我的是home/hlf/PRO),命令行cd home/hlf/PRO 1.定位编译链 根据安装p ...
- Xilinx zynq-7000系列FPGA移植Linux操作系统详细教程
Xilinx zynq-7000系列FPGA移植Linux操作系统详细教程 一:前言 最近手上压了一块米联客的Miz7035,一块xilinx zynq-7000系列的开发板,想着正好学习一下linu ...
- 在基于or1200处理器的SoC上移植linux
经历了前端的艰苦奋斗.SoC前端设计已经调试完毕,如今直接进入uboot移植 首先cd入u-boot-master 找到子文件夹include下得de2_115.h文件进行改动: (下一步计划:加 ...
- 嵌入式Linux启动优化手记2 U…
参考一下 原文地址:U-boot优化">嵌入式Linux启动优化手记2 U-boot优化作者:ZhaoJunling 既然不能使用新的U-boot,那就优化一点是一点,慢慢干吧. 1. ...
随机推荐
- 微调BERT:序列级和令牌级应用程序
微调BERT:序列级和令牌级应用程序 Fine-Tuning BERT for Sequence-Level and Token-Level Applications 为自然语言处理应用程序设计了不同 ...
- 1-3. SpringBoot基础,Java配置(全注解配置)取代xml配置
最近突发奇想,整合一下以前一些学习笔记,分享自己这几年爬过的坑,逐步更新文章,谢谢大家的关注和支持. 这节讲一下SpringBoot的学习必须的一些基础,Java配置.其实在Spring2.0时代就已 ...
- (3)虚拟Web主机
虚拟Web主机 作用:让一台Web服务器,提供多个页面 搭建方式: 1.基于域名的虚拟Web 2.基于端口的虚拟Web 3.基于IP地址的虚拟Web ######################### ...
- 大家看看大佬对Maven仓库的讲解,有何高明之处?
Maven在某个统一的位置存储所有项目的共享的构件,这个统一的位置,我们就称之为仓库.(仓库就是存放依赖和插件的地方). 分类 maven的仓库只有两大类:1.本地仓库 2.远程仓库,在远程仓库中又分 ...
- JDK并发包二
JDK并发包二 线程复用--线程池 在线程池中,总有那么几个活跃的线程,当程序需要线程时可以从池子中随便拿一个控线程,当程序执行完毕,线程不关闭,而是将这个线程退会到池子,等待使用. JDK提供了一套 ...
- Python语言规范之Pylint的使用
1.Pylint是什么 pylint是一个Python源代码中查找bug的工具,能找出错误,和代码规范的运行.也就是你的代码有Error错误的时候能找出来错误,没有错误的时候,能根据Python代码规 ...
- OpenResty高并发
在电商项目中所有的访问都是通过首页访问进去的,那么首页门户的访问频率会是非常高的,用我们专业术语来说就是并发量高,这时问题就来了,并发量高我们在做程序时就要保证首页的抗压能力强,而且还要保证抗压的同时 ...
- redis淘汰+过期双向保证高可用 | redis 为什么那么快?
前言 redis和数据相比除了他们的结构型颠覆以外!还有他们存储位置也是不相同.传统数据库将数据存储在硬盘上每次数据操作都需要IO而Redis是将数据存储在内存上的.这里稍微解释下IO是啥意思.IO就 ...
- sql循环说明
while循环:主要是判断,不能使用表中的ID,临时表是ID自增的,通过自增ID可以查出表ID(语法简单,需要配合其他代码操作表ID)游标循环:可以使用表中的ID ,进行修改等操作(语法难一点,核心代 ...
- MetingJS 是如何配合 Aplayer 加载歌单的?
Meting.js 介绍 Meting.js 依赖 APlayer.js,扩展了 APlayer.js 的功能,能够使 APlayer.js 加载网易云音乐.QQ 音乐.虾米音乐中的歌单. 安装 &l ...