uboot-1.3.0-rc3引导启动学习笔记

uboot下的mw写内存和md显示内存命令以及nand命令 注意,网络地址环境变量的设置要根据自己的实际情况,确保主机和开发板的 IP地址在同一个网段内,比如我现在的开发板和电脑都在 192.168.0.1 这个网段内,所以设置开发板的 IP 地址为 192.168.0.10,我的 windows的 主机的地址为 192.168.0.40,因此 serverip 就是192.168.0.40。b\w\l的意思同上,address是要写入内存的地址,value是要写入的值,count是从address开始要写入多少个数,这些都是16进制数。 阅读详情

在分析启动代码之前先看一下S3C2440的NAND启动:
在配置NAND启动模式之后,S3C2440上电会先将NAND中的0x0 - 0x1000共4096字节的数据拷贝到位于Bank0中的Boot Internal SRAM上
Bank0如下图:

 

可以看出Boot Internal SRAM为4KB大小,也正是因为Boot Internal SRAM只有4KB大小,所以只能从NAND中拷贝4K的内容 = 3= 这个Boot Internal SRAM是配置为NAND FLASH启动模式才有的
这4K内容是什么呢?~ 这就要看Uboot的镜像文件中是如何进行连接的了~
连接脚本在board/smdk2440/u-boot.lds中,如下
SECTIONS
{
    . = 0x00000000;
    . = ALIGN(4);
    .text :
    {
     cpu/arm920t/start.o    (.text)
     cpu/arm920t/s3c24x0/nand_read.o (.text)
     *(.text)
    }
    . = ALIGN(4);
    .rodata : { *(.rodata) }
    . = ALIGN(4);
    .data : { *(.data) }
    . = ALIGN(4);
    .got : { *(.got) }
    . = .;
    __u_boot_cmd_start = .;
    .u_boot_cmd : { *(.u_boot_cmd) }
    __u_boot_cmd_end = .;
    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss) }
    _end = .;
}
.text为代码段,可以看出cpu/arm920t/start.o在代码段的最前面,所以会先执行start.o中的代码
连接完成后的镜像文件的前4K如下
cpu/arm920t/start.o(.text)
.text 0x33f80000 0x4e0 cpu/arm920t/start.o
                0x33f80050 IRQ_STACK_START
                0x33f80048 _bss_start
                0x33f8004c _bss_end
                0x33f80044 _armboot_start
                0x33f80000 _start
                0x33f80054 FIQ_STACK_START
cpu/arm920t/s3c24x0/nand_read.o(.text)
.text 0x33f804e0 0x1b8 cpu/arm920t/s3c24x0/nand_read.o
                0x33f804e0 nand_read_ll
*(.text)
.text 0x33f80698 0x64 board/smdk2440/libsmdk2440.a(lowlevel_init.o)
                0x33f8069c lowlevel_init
.text 0x33f806fc 0x280 cpu/arm920t/libarm920t.a(interrupts.o)
                0x33f80934 do_fiq
                0x33f80880 do_undefined_instruction
                0x33f80744 show_regs
                0x33f80958 do_irq
                0x33f80728 bad_mode
                0x33f808c8 do_prefetch_abort
                0x33f8070c disable_interrupts
                0x33f80910 do_not_used
                0x33f808ec do_data_abort
                0x33f808a4 do_software_interrupt
                0x33f806fc enable_interrupts
.text 0x33f8097c 0x250 cpu/arm920t/s3c24x0/libs3c24x0.a(interrupts.o)
                0x33f80aa4 set_timer
                0x33f80a20 reset_timer
                0x33f8097c interrupt_init
                0x33f80ba0 get_tbclk
                0x33f80a90 get_timer
                0x33f809f0 reset_timer_masked
                0x33f80a24 get_timer_masked
                0x33f80ab4 udelay
                0x33f80b10 udelay_masked
                0x33f80bac reset_cpu
                0x33f80b8c get_ticks
.text 0x33f80bcc 0x150 cpu/arm920t/s3c24x0/libs3c24x0.a(speed.o)
                0x33f80c4c get_HCLK
                0x33f80cec get_PCLK
                0x33f80c44 get_FCLK
                0x33f80d14 get_UCLK
.text 0x33f80d1c 0x1e8 cpu/arm920t/s3c24x0/libs3c24x0.a(cmd_s3c24xx.o)
                0x33f80d8c do_s3c24xx
.text 0x33f80f04 0xdc cpu/arm920t/s3c24x0/libs3c24x0.a(serial.o)
                0x33f80f04 serial_setbrg
                0x33f80fa8 serial_tstc
                0x33f80f80 serial_putc
                0x33f80f58 serial_init
                0x33f80fb8 serial_puts
                0x33f80f68 serial_getc
.text 0x33f80fe0 0x140 lib_arm/libarm.a(_divsi3.o)
                0x33f80fe0 __divsi3
如何设置从0x33f80000开始呢?~这是链接的时候指定的
在根目录下面的config.mk中有下面一句
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)
关键就是其中的-Ttext $(TEXT_BASE),这句指明了代码段的起始地址
而TEXT_BASE在board/smdk2440/config.mk中定义 TEXT_BASE = 0x33F8 0000
为什么是0x33F8 0000呢?~
这是将NAND中Uboot拷贝到RAM中的起始地址,所以在代码拷贝到RAM之前不能使用绝对地址来寻址数据,只能用相对地址
在以下将用虚拟地址来指Uboot在RAM中的地址,也就是0x33F8 0000
现在来看代码cpu/arm920t/start.S
_start:    ;异常处理向量表
    b start_code
    ldr    pc, _undefined_instruction   ;未定义指令异常:0x00000004
    ldr    pc, _software_interrupt  ;软中断异常:0x00000008
    ldr    pc, _prefetch_abort  ;预取异常:0x0000000C
    ldr    pc, _data_abort  ;数据异常:0x00000010
    ldr    pc, _not_used  ;未使用:0x00000014
    ldr    pc, _irq  ;外部中断请求IRQ:0x00000018
    ldr    pc, _fiq  ;快束中断请求FIQ:0x0000001C
b  start_code在虚拟地址0x33F8 0000处 , 拷贝到Boot Internal SRAM后则位于0x0处,所以b  start_code是第一条执行的指令,
start_code在cpu/arm920t/start.S中
代码如下:
    //读取CPSR寄存器的内容到R0
    mrs    r0,cpsr
    //清除R0中的0 - 4 这5个位后保存到R0中
    //也就是清除用户模式位
    bic    r0,r0,#0x1f
    //置R0的0 1 4 6 7 位为真
    //也就是选择SVC模式,同时IRQ和FIQ被禁止,处理器处于ARM状态
    //关闭中断和快速中断
    orr    r0,r0,#0xd3
    //将R0中的值保存到CPSR上
    msr    cpsr,r0
# define pWTCON        0x53000000  ;看门狗控制寄存器WTCON
# define INTMSK        0x4A000008  ;中断屏蔽寄存器INTMSK
# define INTSUBMSK    0x4A00001C  ;辅助中断屏蔽寄存器,由于外设中断源太多,要用此寄存器屏蔽剩余的中断源
# define LOCKTIME    0x4c000000  ;PLL锁定时间计数寄存器
# define MPLLCON     0x4c000004  ;主时钟锁相环控制寄存器
# define UPLLCON     0x4c000008
# define CLKDIVN    0x4C000014    ;时钟分频寄存器/* clock divisor register */
# define INTSUBMSK_val    0xffff
# define MPLLCON_val    ((184  12) + (2  4) + 2)    /*406M*/
# define UPLLCON_val     ((60  12) + (4  4) + 2) /* 47M */
# define CLKDIVN_val    7 /* FCLK:HCLK:PCLK = 1:3:6 */
# define CAMDIVN    0x4C000018
    //取得看门狗寄存器的地址
    ldr r0, =pWTCON
    //将R1寄存器清0
    mov r1, #0x0
    //将看门狗寄存器清0,即将看门狗禁止,包括定时器定时,溢出中断及溢出复位等
    str r1, [r0]
    /*
     * mask all IRQs by setting all bits in the INTMR - default
     */
    //设R1寄存器为0xFFFF FFFF
    mov    r1, #0xffffffff
    //读取中断屏蔽寄存器的地址
    ldr    r0, =INTMSK
    //将中断屏蔽寄存器中的位全设1,屏蔽所有中断
    str    r1, [r0]
    //# define INTSUBMSK_val    0xffff
    //设R1寄存器为0xFFFF
    ldr    r1, =INTSUBMSK_val
    //读取辅助中断屏蔽寄存器的地址
    ldr    r0, =INTSUBMSK
    //将辅助中断屏蔽寄中的11个中断信号屏蔽掉,本人觉得INTSUBMS_val应设成7ff
    str    r1, [r0]
    //# define LOCKTIME    0x4c000000
    //读取PLL锁频计数器寄存器地址到R0中
    ldr r0,=LOCKTIME
    //将R1设为0x00FF FFFF
    ldr r1,=0xffffff
    //M_LTIME为最大的0xFFF
    //U_LTIME为最大的0xFFF
    str r1,[r0]    ;0xfff=4096>1800,远远满足锁定要求
    /* FCLK:HCLK:PCLK = 1:2:4 */
    /* default FCLK is 120 MHz ! */
    //# define CLKDIVN    0x4C000014    /* clock divisor register */
    //读取时钟分频寄存器的地址
    ldr    r0, =CLKDIVN
    //# define CLKDIVN_val    7 /* FCLK:HCLK:PCLK = 1:3:6 */
    //将R1设为0x7
    mov    r1, #CLKDIVN_va
    //PDIVN - 1: PCLK has the clock same as the HCLK/2.
    //HDIVN - 11 : HCLK = FCLK/3 when CAMDIVN[8] = 0.
    // HCLK = FCLK/6 when CAMDIVN[8] = 1.
    str    r1, [r0]
    /* Make sure we get FCLK:HCLK:PCLK = 1:3:6 */
    //# define CAMDIVN    0x4C000018
    //读取摄像头时钟分频寄存器的地址
    ldr r0, =CAMDIVN
    //将R1设为0
    mov r1, #0
    //将摄像头时钟分频寄存器清0
    str r1, [r0]
    /* Clock asynchronous mode */
    //MRC p15, 0, Rd, c1, c0, 0 ; read control register
    //读取控制寄存器中的值到R1中
    mrc p15, 0, r1, c1, c0, 0  ;将协处理器p15的寄存器c1和c0的值传到arm处理器的R1寄存器中
    //31 iA bit Asynchronous clock select
    //30 nF bit notFastBus select
    orr r1, r1, #0xc0000000  ;将最高两位置1
    //MCR p15, 0, Rd, c1, c0, 0 ; write control register
    //将R1中的值写到控制寄存器中
    mcr p15, 0, r1, c1, c0, 0 将arm的寄存器R1的32位数据传到协处理器p15的两个16位寄存器c1和c0
    //# define UPLLCON     0x4c000008
    //读取UPLL设置寄存器的地址到R0中
    ldr    r0,=UPLLCON
    //# define UPLLCON_val     ((60
    ldr    r1,=UPLLCON_val
    //将R1中的值写入UPLL设置寄存器中
    str    r1,[r0]
    //ARM920T为5级流水线,需要至少5个周期来让指令生效
    nop   
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    //读取MPLL设置寄存器的地址到R0中   
    ldr    r0,=MPLLCON
    //# define MPLLCON_val    ((184
    ldr    r1,=MPLLCON_val
    //将R1中的值写入MPLL设置寄存器中
    str    r1,[r0]
#define GPJCON 0x560000D0
#define GPJDAT 0x560000D4
#define GPJUP             0x560000D8
    //跳转到cpu_init_crit处执行
    //并将下一条指令的地址写入LR寄存器中
    bl    cpu_init_crit
cpu_init_crit在cpu/arm920t/start.S中
代码如下:
cpu_init_crit:
    /*
     * flush v4 I/D caches
     */
    //将R0寄存器置0
    mov    r0, #0
    //Invalidate ICache and DCache SBZ MCR p15,0,Rd,c7,c7,0
    //禁止指令和数据cache
    mcr    p15, 0, r0, c7, c7, 0    /* flush v3/v4 cache */
    //Invalidate TLB(s) SBZ MCR p15,0,Rd,c8,c7,0
    mcr    p15, 0, r0, c8, c7, 0    /* flush v4 TLB */
    /*
     * disable MMU stuff and caches
     */
    //MRC p15, 0, Rd, c1, c0, 0 ; read control register
    mrc    p15, 0, r0, c1, c0, 0
    //清除[8] [9] [13] 这3个位
    //8 - System protection
    //9 - ROM protection
    //13 - Base location of exception registers - 0 = Low addresses = 0x00000000.
    bic    r0, r0, #0x00002300    // clear bits 13, 9:8 (--V- --RS)
    //清除[0] [1] [2] [7] 这4个位
    // 0 - MMU enable - 0 = MMU disabled.
    // 1 - Alignment fault enable - 0 = Fault checking disabled.
    // 2 - DCache enable - 0 = DCache disabled.
    // 7 - Endianness - 0 = Little-endian operation.
    bic    r0, r0, #0x00000087    // clear bits 7, 2:0 (B--- -CAM)
    //设置位[1]为真
    // 1 - Alignment fault enable - 1 = Fault checking enabled.
    orr    r0, r0, #0x00000002    // set bit 2 (A) Align
    //设置位[12]为真
    //12 - ICache enable - 1 = ICache enabled.
    orr    r0, r0, #0x00001000    // set bit 12 (I) I-Cache
    //MCR p15, 0, Rd, c1, c0, 0 ; write control register
    mcr    p15, 0, r0, c1, c0, 0
    //将返回地址保存到IP中
    mov    ip, lr
    //跳转到lowlevel_init中执行
    bl    lowlevel_init
cpu_init_crit在cpu/arm920t/start.S中
代码如下:
.globl lowlevel_init
    //读取下面标号为SMRDATA处的地址到R0中
    ldr r0, =SMRDATA
    //读取上面标号为_TEXT_BASE处的地址内容到R1中
    //也就是取得TEXT_BASE的值到R1中
    ldr    r1, _TEXT_BASE
    //计算SMRDATA的相对地址保存到R0中
    //SMRDATA为虚拟地址,而TEXT_BASE为虚拟地址的起始地址
    //而现在Uboot的起始地址并不为虚拟地址
    //TEXT_BASE为0x33F8 0000,SMRDATA为0x33F8 06C8
    //而现在程序运行在起始地址为0x0000 0000的地方
    //所以需要计算以0x0000 0000为标准的相对地址
    sub    r0, r0, r1
    //取得带宽与等待状态控制寄存器地址到R1中
    ldr    r1, =BWSCON    /* Bus Width Status Controller */
    //一共需要设置13个寄存器,每个寄存器4字节
    add r2, r0, #13*4
0:
    //读取R0所指的项的值到R3中后R0自加4字节
    ldr r3, [r0], #4
    //将R3中的值保存到R1所指的地址中后R1自加4字节
    str r3, [r1], #4
    //比较R0和R2是否相等,相等则说明13个寄存器全部设置完毕
    cmp r2, r0
    //不等则跳转到上面标号为0处的地址继续执行
    bne 0b
    //跳回到返回地址中继续执行
    mov    pc, lr
    .ltorg
/* the literal pools origin */
SMRDATA:
    .word (0+(B1_BWSCON4)+(B2_BWSCON8)+(B3_BWSCON12)+(B4_BWSCON16)+(B5_BWSCON20)+(B6_BWSCON24)+(B7_BWSCON28))
    .word ((B0_Tacs13)+(B0_Tcos11)+(B0_Tacc8)+(B0_Tcoh6)+(B0_Tah4)+(B0_Tacp2)+(B0_PMC))
    .word ((B1_Tacs13)+(B1_Tcos11)+(B1_Tacc8)+(B1_Tcoh6)+(B1_Tah4)+(B1_Tacp2)+(B1_PMC))
    .word ((B2_Tacs13)+(B2_Tcos11)+(B2_Tacc8)+(B2_Tcoh6)+(B2_Tah4)+(B2_Tacp2)+(B2_PMC))
    .word ((B3_Tacs13)+(B3_Tcos11)+(B3_Tacc8)+(B3_Tcoh6)+(B3_Tah4)+(B3_Tacp2)+(B3_PMC))
    .word ((B4_Tacs13)+(B4_Tcos11)+(B4_Tacc8)+(B4_Tcoh6)+(B4_Tah4)+(B4_Tacp2)+(B4_PMC))
    .word ((B5_Tacs13)+(B5_Tcos11)+(B5_Tacc8)+(B5_Tcoh6)+(B5_Tah4)+(B5_Tacp2)+(B5_PMC))
    .word ((B6_MT15)+(B6_Trcd2)+(B6_SCAN))
    .word ((B7_MT15)+(B7_Trcd2)+(B7_SCAN))
    .word ((REFEN23)+(TREFMD22)+(Trp20)+(Trc18)+(Tchr16)+REFCNT)
    .word 0x32
    .word 0x30
    .word 0x30
执行mov  pc, lr后将返回到cpu_init_crit中
剩下来还有2条指令
    //恢复返回地址到LR
    mov    lr, ip
    //跳转到返回地址
    mov    pc, lr
执行完毕之后将返回到start_code中执行接下来的代码
代码如下:
        //#define GPJCON 0x560000D0
        //取得J端口控制寄存器的地址到R0中
        LDR R0, = GPJCON
        //将R1设置为0x1 5555
        LDR R1, = 0x15555
        //将R1中的值保存到J端口控制寄存器
        //GPJ0 - 01 - Output
        //GPJ1 - 01 - Output
        //GPJ2 - 01 - Output
        //GPJ3 - 01 - Output
        //GPJ4 - 01 - Output
        STR R1, [R0]
        //#define GPJUP        0x560000D8
        //取得J端口上拉功能寄存器的地址到R0中
        LDR R0, = GPJUP
        //将R1设置为0x1F
        LDR R1, = 0x1f
        //将R1中的值保存到J端口上拉功能寄存器
        //禁止GPJ0 - GPJ4的上拉功能
        STR R1, [R0]
        //#define GPJDAT 0x560000D4
        //取得J端口数据寄存器的地址到R0中
        LDR R0, = GPJDAT
        //将R1设为0x0
        LDR R1, = 0x00
        //将R1中的值保存到J端口数据寄存器
        //将J端口数据寄存器清0
        STR R1, [R0]
//下面是NAND数据拷贝过程
//relocate:
copy_myself:
    //#define S3C2440_NAND_BASE        0x4E000000
    //取得Nand Flash设置寄存器的地址
    mov    r1, #S3C2440_NAND_BASE
    //将R2设为0xFFF0
    ldr    r2, =0xfff0        // initial value tacls=3,rph0=7,rph1=7
    //#define oNFCONF            0x00
    //读取Nand Flash设置寄存器中的值到R3中
    ldr    r3, [r1, #oNFCONF]
    //将R3或上R2后保存到R3中
    orr    r3, r3, r2
    //将R3中的值保存到Nand Flash设置寄存器中
    //TWRPH0 - 111 - Duration = HCLK * (TWRPH0 + 1)
    //TACLS - 11 - Duration = HCLK * TACLS
    str    r3, [r1, #oNFCONF]
    //#define oNFCONT            0x04
    //读取Nand Flash控制寄存器中的值到R3中
    ldr    r3, [r1, #oNFCONT]
    //将R3的[0]位置1
    orr    r3, r3, #1        // enable nand controller
    //将R3中的值保存到Nand Flash控制寄存器中
    //Mode - 1:Nand Flash Controller Enable
    str    r3, [r1, #oNFCONT]
    //读取虚拟起始地址到R0中
    ldr    r0, _TEXT_BASE        /* upper 128 KiB: relocated uboot */
    //预留malloc所需要的空间
    sub    r0, r0, #CFG_MALLOC_LEN    /* malloc area */
    //预留bdinfo所需要的空间
    sub    r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
    //预留中断和快速中断向量表空间
    sub    r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
    //预留12字节给中断栈
    sub    sp, r0, #12        /* leave 3 words for abort-stack */
    // copy u-boot to RAM
    //读取虚拟起始地址到R0中,作为目标地址
    ldr    r0, _TEXT_BASE
    //将R1设为0,作为源地址
    mov r1, #0x0
    //将UBOOT大小的值保存在R2中,作为数据大小
    mov    r2, #CFG_UBOOT_SIZE
    //跳转到nand_read_ll处执行
    //并将下一条指令的地址保存在LR中
    bl    nand_read_ll
nand_read_ll的原型为
int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
之前设置的R0 R1 R2为它的3个参数
R0 - buf
R1 - start_addr
R2 - size
nand_read_ll的代码在cpu/arm920t/s3c24x0/nand_read.c中
int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
{
    int i, j;
    //检测源地址和大小是否在NandFlash的边界上
    if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK))
        //不在边界上则返回-1表示出错
        return -1;    /* invalid alignment */
    /* chip Enable */
    // #define nand_select()    (NFCONT &= ~(1
    //置NAND Flash控制寄存器中除Reg_nCE外所有的位为1
    //Reg_nCE - NAND FLASH Memory nFCE signal control
    //0 - Force nFCE to low (Enable chip select)
    nand_select();
    // #define nand_clear_RnB()    (NFSTAT |= (1
    //置NAND Flash操作状态寄存器中的RnB_TransDetect位为1
    //When RnB low to high transition is occurred, this value set and issue interrupt if enabled.
    //To clear this value write '1'
    //1: RnB transition is detected
    nand_clear_RnB();
    for (i=0; i10; i++);
    //从源地址的首地址开始历便所要拷贝的数据大小
    for (i=start_addr; i  (start_addr + size);)
    {
        //检测地址是否在NAND Flash的边界上
        if (start_addr % NAND_BLOCK_SIZE == 0)
        {
            //检测是否为坏块
            if (is_bad_block(i))
            {
                /* Bad block */
                //向后延伸一个存储块
                i += NAND_BLOCK_SIZE;
                size += NAND_BLOCK_SIZE;
                //跳到下一块
                continue;
            }
        }
        j = nand_read_page_ll(buf, i);
        //指向下一块
        i += j;
        buf += j;
    //    LED_FLASH();
    }
    /* chip Disable */
    // #define nand_deselect()    (NFCONT |= (1
    //置Reg_nCE位为1
    //NAND Flash Memory nFCE signal control
    //1: Force nFCE to High(Disable chip select)
    nand_deselect();
    return 0;
}
nand_read_ll将Uboot从NAND中拷贝到RAM中
拷贝完成后将返回到start_code
接下来的代码如下:
    //检测R0是否为0,R0为nand_read_ll的返回值
    tst    r0, #0x0
    //为0则说明无错,跳转到ok_nand_read处执行
    beq    ok_nand_read
ok_nand_read:
    //将R0设为0
    mov    r0, #0
    //ldr    r1, =0x33f00000
    //将R1设为虚拟地址起始处
    ldr    r1, _TEXT_BASE
    //检测0x400个字节
    mov    r2, #0x400    // 4 bytes * 1024 = 4K-bytes
go_next:
    //读取R0处地址的数据到R3中
    //然后R0自加4字节
    ldr    r3, [r0], #4
    //读取R1处地址的数据到R4中
    //然后R1自加4字节
    ldr    r4, [r1], #4
    //比较R3和R4的数据是否相等
    //也就是检测Boot Internal SRAM和RAM中的数据是否相等
    //以保证数据无错
    teq    r3, r4
    //不等则跳转到notmatch
    bne    notmatch
    //相等则R2自减4
    subs    r2, r2, #4
    //当R2为0则跳转到done_nand_read
    beq    done_nand_read
    //R2不为0则跳转回go_next继续检测
    bne    go_next
done_nand_read:
    LDR R0, = GPJDAT
    LDR R1, = 0x2
    STR R1, [R0]
stack_setup:
    //读取虚拟起始地址到R0中
    ldr    r0, _TEXT_BASE        /* upper 128 KiB: relocated uboot */
    //预留malloc所需要的空间
    sub    r0, r0, #CFG_MALLOC_LEN    /* malloc area */
    //预留bdinfo所需要的空间
    sub    r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
    //预留中断和快速中断向量表空间
    sub    r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
    //预留12字节给中断栈
    sub    sp, r0, #12        /* leave 3 words for abort-stack */
clear_bss:
    //读取BSS段的起始地址
    ldr    r0, _bss_start        /* find start of bss segment */
    //读取BSS段的结束地址
    ldr    r1, _bss_end        /* stop here */
    //将R2设为0x0
    mov     r2, #0x00000000        /* clear */
clbss_l:
    //将R2中的值保存在R0所指的地址
    str    r2, [r0]        /* clear loop... */
    //R0自加4字节
    add    r0, r0, #4
    //比较R0和R1是否相等
    cmp    r0, r1
    //不等则说明清0还没结束
    ble    clbss_l
    LDR R0, = GPJDAT
    LDR R1, = 0x1
    STR R1, [R0]
    //跳转到start_armboot处执行
    ldr    pc, _start_armboot
_start_armboot:    .word start_armboot
这里start_armboot是一个绝对地址,在朗成所修改的这个Uboot中为0x33F8 13F4
执行ldr pc, _start_armboot之后将会跳到RAM中的绝对地址继续执行
整理了一个流程图,分为3个存储器:
1 Boot Internal SRAM , 接在BANK0,起始地址为0x0
2 RAM , 接在BANK6,起始地址为0x3000 0000
3 NAND FLASH,为单独寻址
流程如下图:

 

 

 

红字为流程序号:
1. 首先将NAND FLASH中的前0x1000字节内容拷贝到Boot Internal SRAM中
2. 从Boot Internal SRAM的0x0地址处开始执行指令
3. 将Uboot从Flash拷贝到RAM中
4. 执行ldr pc, _start_armboot
从Boot Internal SRAM中跳转到RAM中的绝对地址0x33F8 13F4处继续执行

 

 

深入F1C200S启动流程:图解BROM到Uboot的‘接力’过程 本文深入解析F1C200S芯片从BROM到Uboot的启动流程,详细介绍了硬件初始化、启动介质选择、SPL加载及Uboot配置等关键步骤。通过图解和实战经验,帮助嵌入式开发者掌握uboot移植和系统调试技巧,提升在linux嵌入式系统开发中的效率。 阅读详情

相关推荐

【OpenHarmony4.1 之 U-Boot 2024.07源码深度解析】003 - u-boot-2024.07-rc3 源码目录结构分析

【OpenHarmony4.1 之 U-Boot 2024.07源码深度解析】003 - u-boot-2024.07-rc3 源码目录结构分析

|~~~热爱生活、努力学习的小伙汁~~~| 592

uboot从nand启动

1、在include/configs/apple2440.h头文件中定义Nand要用到的宏和寄存器,如下: gedit include/configs/apple2440.h /*  * Nand flash register and envionment variables  */ #define CONFIG_S3C2440_NAND_BOOT  1 #define NAND_

q123456789098的专栏 831

远程桌面(RDP)共享USB设备全攻略:不止Hyper-V,你的加密狗和CH340串口也能远程用

本文详细介绍了远程桌面(RDP)共享USB设备的完整解决方案,包括加密狗和CH340串口设备的远程使用技巧。通过RemoteFX USB重定向技术,用户可以在远程会话中无缝使用本地USB设备,提升工作效率。文章涵盖企业级配置、典型设备实战案例及高级故障排除方法,是远程工作者的实用指南。

weixin_42522964的博客 334

[NAND]UBOOT从NAND FLASH启动分析

<br />         UBOOT从NAND FLASH启动分析<br />在分析启动代码之前先看一下S3C2440的NAND启动:<br />在配置NAND启动模式之后,S3C2440上电会先将NAND中的0x0 - 0x1000共4096字节的数据拷贝到位于Bank0中的Boot Internal SRAM上<br />Bank0如下图:<br />可以看出Boot Internal SRAM为4KB大小,也正是因为Boot Internal SRAM只有4KB大小,所以只能从NAND中拷贝4K的

figthter_cui的专栏 1499

IC面试常考题 Verilog三分频电路设计(占空比50%,三分之一,三分之二)

实现三分频电路最简单的是: 利用计数器实现。 时序图分析(本人比较懒,平常科研忙,所以直接手画时序图了,懒得用软件画了): 直接上图分析:利用计数器每隔三个周期信号翻转一次,同时在不同的计数下翻转得到的同步信号 clk_1和clk_2,再利用异或即可实现出一个不同占空比的三分频信号(同样的方法也可扩展到其他奇数分频设计中)。 例如占空比50%: module Div_three( input clk, input rst_n, output div_three ); reg

qq_41884662的博客 1万+

ArcGIS基于DEM提取微流域单元(附练习数据下载)

流域作为一个相对独立的地理单元,与行政区的划分有着本质上的区别。流域的划分更注重自然因素,流域作为一个综合的自然整体,在其内部形成了一个天然的物质与能量流动的场所,并通过水流的作用,与周围的区域进行物质和能量的交换。流域的精确划分作为一个研究的基础工作,那么接下来给大家说一下利用DEM提取小流域的案例教程——以贵州省遵义市正安县为例。 1 数据获取 地理空间数据云(http://www.gscloud.cn/),数据分辨率为30m。也可直接点击这篇直接下载→全国30m90mDEM下载。 若想要获得更高精度.

qq_43173805的博客 4838

UBOOT_NAND驱动分析

一、 编写目的 2 二、 驱动分析 2 1. 驱动入口 2 2. NAND相关的结构体 2 3. NAND初始化过程 7 4. NAND操作函数分析 15   一、编写目的 在《NAND_FLASH(K9F1208U0C)驱动分析》一文中,通过分析AT91SAM9260EK开发板Bootstrap中的NAND驱动时,发现该代码为了优化代码体积(4KB限制),没有实现nand

lshiceman的专栏 3037

YOLOv11改进 | 集成FEM、FFM与SCAM即插即用模块:特征增强、融合与空间上下文建模新范式

FEM解决特征表达不足问题,增强关键特征响应FFM优化多尺度融合,提升小目标检测精度SCAM建模空间上下文,改善目标交互预测在保持YOLOv11实时性优势的同时,实现mAP+8.1%的显著提升,特别在复杂光照、密集目标、交互预测场景表现突出。其模块化设计使开发者可根据需求灵活组合,为各种复杂场景提供定制化解决方案。未来,随着跨模态扩展与神经架构搜索的深入,YOLOv11-FSC将进一步推动目标检测技术向更智能、更高效的方向发展。

走向CTO的路上... 989

嵌入式Linux--U-Boot(五)NAND命令使用

uboot 是支持 NAND Flash 的,所以也有 NAND Flash 的操作命令,前提是使用的 NAND 版本的核心板,并且编译 NAND 核心板对应的 uboot,然后使用 imxdownload 软件将 u-boot.bin 烧写到 SD 卡中,最后通过 SD 卡启动。从上面给出了 NAND 的页大小、OOB 域大小,擦除大小等信息。上图中的文件就是我们写到 NAND 中的设备树文件,至于设备树文件的详细内容我们后面会有专门的章节来讲解,这里大家知道这个文件就行了。

u011029104的专栏 1906

【乐企文件生成工程】乐企文件生成支持所有特定要素类型发票

【代码】【乐企文件】乐企文件生成最新更新!!!

九七的博客 851

《UDS协议从入门到精通》系列——图解0x14:清除诊断信息

图解0x14:清除诊断信息,简洁易懂,迅速上手!

车载系统攻城狮 4827

uboot——lowleve_init.S分析

jun2ran的专栏 962

【Cursor+Playwright-MCP 实现 AI 自主功能测试】含实例代码脚本+保姆级教程

本文档详细介绍了如何使用 Cursor(AI 编程助手)和 Playwright-MCP 库实现基于 GPT 模型的自动化功能测试。文档从环境搭建开始,逐步指导读者如何通过 Playwright 进行浏览器自动化,并利用 Playwright-MCP 集成 GPT 模型生成测试计划、执行测试步骤及生成测试报告。核心步骤包括安装依赖、配置 OpenAI API 密钥、编写代码实现 AI 驱动的测试流程,以及生成详细的测试报告。通过本文档,读者可以快速掌握从 0 到 1 实现 AI 自主功能测试的全流程。

Dreams°的博客 2894

Android 9.0 系统启动流程

本篇文章主要介绍Android开发中的部分知识点,通过阅读本篇文章,您将收获以下内容: 一、启动流程概述 二、Android启动分析 三、init 进程启动分析 四、init 启动脚本分析 五、init 进程分析 六、init 脚本执行 七、init 进程守护 八、init rc 脚本启动Zygote 九、启动分析小结 一、 启动流程概述 Android启动流程跟Linux启动类似...

程序员Android 3284

【2025年华为秋招(AI)-11月19日-第二题(200分)- 终端款型聚类识别】(题目+思路+Java&C++&Python解析+在线测试)

该摘要总结了题目要求、输入输出格式、KMeans算法的实现步骤以及关键点。以下是150字内的摘要: 本文提出基于KMeans算法对终端型号进行聚类的方法。通过包间隔时长、连接持续时长、漫游前后信号强度4个特征,使用欧氏距离度量相似度。算法采用固定初始化策略(前k个样本作为初始质心),经过分配-更新迭代,直至质心移动小于1e-8或达到最大迭代次数。最终输出各簇样本数并按升序排列,解决了终端型号自动分类问题。示例显示该方法能有效识别3-4类终端,并准确统计每类数量。

m0_73659489的博客 141

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

RK3588的Uboot启动流程分析--Uboot第一阶段(3)

本文分析了Uboot启动第一阶段的关键源码,主要包括环境变量初始化、串口终端设置、启动信息打印和DRAM初始化四个部分。env_init()函数通过env_driver_lookup_default()确定环境变量存储位置(如EEPROM、Flash、MMC等),并将地址存入gd结构体。串口初始化设置波特率并配置控制台输出。启动信息打印包括Uboot版本、镜像信息和串口参数。最后通过dram_init()初始化DDR内存空间,为后续阶段做好准备。这些步骤为Uboot第二阶段的启动奠定了基础。

m0_71740629的博客 692

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain&#39;s apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath(&#39;DeepLearnToolbox&#39;));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand(&#39;state&#39;,0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W&#39;); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand(&#39;state&#39;,0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = &#39;sigm&#39;;%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, &#39;Too big error&#39;);```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand(&#39;state&#39;,0)sae = saesetup([784 100]);sae.ae{1}.activation_function = &#39;sigm&#39;;sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)&#39;)% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = &#39;sigm&#39;;nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, &#39;Too big error&#39;);```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x&#39;,28,28,60000))/255;test_x = double(reshape(test_x&#39;,28,28,10000))/255;train_y = double(train_y&#39;);test_y = double(test_y&#39;);%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you&#39;ll get around 1.2% errorrand(&#39;state&#39;,0)cnn.layers = { struct(&#39;type&#39;, &#39;i&#39;) %input layer struct(&#39;type&#39;, &#39;c&#39;, &#39;outputmaps&#39;, 6, &#39;kernelsize&#39;, 5) %convolution layer struct(&#39;type&#39;, &#39;s&#39;, &#39;scale&#39;, 2) %sub sampling layer struct(&#39;type&#39;, &#39;c&#39;, &#39;outputmaps&#39;, 12, &#39;kernelsize&#39;, 5) %convolution layer struct(&#39;type&#39;, &#39;s&#39;, &#39;scale&#39;, 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, &#39;Too big error&#39;);```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, &#39;Too big error&#39;);%% ex2 neural net with L2 weight decayrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);%% ex3 neural net with dropoutrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);%% ex4 neural net with sigmoid activation functionrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);nn.activation_function = &#39;sigm&#39;; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);%% ex5 plotting functionalityrand(&#39;state&#39;,0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = &#39;softmax&#39;; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand(&#39;state&#39;,0)nn = nnsetup([784 20 10]); nn.output = &#39;softmax&#39;; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Android系统启动过程-uBoot+Kernel+Android

转载自:http://www.cnblogs.com/pngcui/p/4665106.html 摘要:本文是参考大量网上资源在结合自己查看源代码总结出来的,让自己同时也让大家加深对Android系统启动过程有一个更加深入的了解!再次强调,本文的大多数功劳应归功于那些原创者们,同时一些必要的参考链接我会一一附上。   注:由于本人采用Exynos4412开发板学习,所

coder 1万+

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

【linux 系统移植②】Uboot

Uboot

Jia ming 的日常学习笔记 2073

转载:Android系统启动过程uboot--kernel--Android

一早看到的这篇讲Android启动过程的文章,分析的非常详细。 ———————————————————————————————————————————————————————————————————————————— 摘要:本文是参考大量网上资源在结合自己查看源代码总结出来的,让自己同时也让大家加深对Android系统启动过程有一个更加深入的了解!再次强调,本文的大多数功劳应归功于那些

BlackSmith 2581

linux启动过程

Linux启动过程对于初学者而言有点扑朔迷离,这是因为启动过程关乎处理器配置、内存配置、外 围硬件配置,而不同的处理器和硬件系统会釆用不同的策略,从而具体的启动过程会有所差异。但无论 差异如何,从计算机系统的角度来看,启动过程一般分为三个步骤,如 图 1-1所示。 图 1 - 1 通用系统的启动过程 首先是开机,开机就是给系统开始供电,此时硬件电路会产生一个确定的复位时序,保 证 CPU是...

weixin_42399752的博客 293

Android启动过程深入解析

转自:Android启动过程深入解析 当按下Android设备电源键时究竟发生了什么? Android的启动过程是怎么样的? 什么是Linux内核? 桌面系统linux内核与Android系统linux内核有什么区别? 什么是引导装载程序? 什么是Zygote? 什么是X86以及ARM linux? 什么是init.rc? 什么是系统服务? 当我们想到Android启动过程时,脑...

Yannick Jiang 的专栏 184

android 去重 比较两个list_Android系统启动过程分析

Android系统从接通电源开始1.Power2.Bootloader3.Uboot 初始化外设硬件,引导内核启动 设置启动参数4.Kernel 硬件 驱动 根据uboot参数启动系统5.Android 挂载分区 加载驱动 启动服务 各种Service 配置环境变量 守护系统我们主要关心的还是Android这个模块,这里有两个关键的脚本,Init和Zygote在Android系统启动的时候,ub...

weixin_42317885的博客 163

Linux启动过程详解

转自:http://blog.chinaunix.net/uid-26495963-id-3066282.html 目录: Linux启动概图 Linux启动详图 1. 启动第一步--加载BIOS 2. 启动第二步--读取MBR 3. 启动第三步--Bootloader 4. 启动第四步--加载内核 5. 启动第五步--用户层init依据inittab文件来设定运行等级 6. 启...

qirui24432的专栏 186

eaidk610 uboot

下载本次实验所需文件,分别为rkbin与u-boot。

noah-leo@outlook.com 67
上一篇: u-boot lds文件详解
下一篇: linux进程调度方法(SCHED_OTHER,SCHED_FIFO,SCHED_RR) .
刀客777
博客等级 码龄15年 11粉丝 8原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值