u-boot分析与使用—u-boot编译体验

  • 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
  • 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
  • 参考资料:《嵌入式Linux应用开发手册》、《嵌入式Linux应用开发手册第2版》
  • 开发环境:Linux 2.6.22.6 内核、arm-linux-gcc-3.4.5-glibc-2.3.6工具链、u-boot-1.1.6

    目录

    • u-boot分析与使用—u-boot编译体验
      • 一、前言
      • 二、编写bootloader
        • 1、编写`start.S`文件
          • 1.1 关看门狗
          • 1.2 设置时钟
          • 1.3 初始化SDRAM
          • 1.4 重定位 : 把bootloader本身的代码从flash复制到它的链接地址去
          • 1.5 执行`main()`
          • 1.6 完整的程序
        • 2、编写链接脚本`boot.lds`
        • 3、编写`boot.c`主函数文件
          • 3.1 帮内核设置串口
          • 3.2 从NAND FLASH里把内核读入内存
          • 3.3 设置参数
          • 3.4 跳转执行
          • 3.5 完整的文件
        • 4、编写相关初始化`init.c`文件
        • 5、编写`Makefile`文件
      • 三、编译与测试
        • 1、编译
        • 2、测试

一、前言

在嵌入式Linux开发板中,u-boot的目的是为了启动内核,大致流程如下图:

  1. 开发板一上电启动bootloader
  2. bootloader首先从Nand Flash上把内核加载到内存中,
  3. bootloader随后初始化内存、时钟等
  4. bootloader之后进行参数的设置,如启动参数、内存参数、命令参数、结束参数
  5. bootloader最后跳转启动内核

下面就来从0开始编写bootloader,实现上述功能。

二、编写bootloader

  • 在【5.1 u-boot分析与使用—u-boot编译体验】有提到过,bootloader可以类比成一个复杂的裸机程序。对于一个裸机程序,根据第一期的学习经验,可以知道程序首先执行的是.S文件中的指令,所以第一个需要编写的文件是start.S
  • 整个程序的流程:先执行start.S中的程序,下面的第五步,进入到main()内进行串口的初始化、传递给内核的参数的一些设置、把内核读到内存、以及跳转去执行内核部分。

1、编写start.S文件

实现的功能如下:

  1. 关看门狗
  2. 设置时钟
  3. 初始化SDRAM
  4. 重定位 : 把bootloader本身的代码从flash复制到它的链接地址去
  5. 执行main()

1.1 关看门狗

查s3c2440可以往0x53000000地址写0可以关闭看门狗

 ldr r0, =0x53000000mov r1, #0str r1, [r0]

1.2 设置时钟

  • 这里的时钟设置为:FCLK:HCLK:PCLK=1:4:8,主要是一个优化措施:提高时钟频率
  • 对于CPU总线模式设置为asynchronous bus mode,这里是手册上规定的
  • 启动ICACHE:主要是一个优化措施:提高从Flash上读出内核的速度
 ldr r0, =0x4c000014//  mov r1, #0x03;          // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1mov r1, #0x05;               // FCLK:HCLK:PCLK=1:4:8str r1, [r0]/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */mrc   p15, 0, r1, c1, c0, 0       //读出控制寄存器orr    r1, r1, #0xc0000000         //设置为“asynchronous bus modemcr  p15, 0, r1, c1, c0, 0       //写入控制寄存器/* MPLLCON = S3C2440_MPLL_200MHZ */ldr r0, =0x4c000004ldr r1, =S3C2440_MPLL_400MHZstr r1, [r0]/* 启动ICACHE */mrc p15, 0, r0, c1, c0, 0   @ read control regorr r0, r0, #(1<<12)mcr    p15, 0, r0, c1, c0, 0   @ write it back

1.3 初始化SDRAM

 ldr r0, =MEM_CTL_BASEadr r1, sdram_config    //sdram_config的当前地址add r3, r0, #(13*4)        //r3等于r0 + 52,即sdram_config的末地址
1:ldr r2, [r1], #4      //r2从r1的地址读到一个值,后r1+4str r2, [r0], #4       //把r2的值存到r0所指向的地方,后r0+4cmp r0, r3               //比较r0与r3,不等则跳回1bne 1bsdram_config:.long 0x22011110   //BWSCON.long 0x00000700    //BANKCON0.long 0x00000700  //BANKCON1.long 0x00000700  //BANKCON2.long 0x00000700  //BANKCON3  .long 0x00000700    //BANKCON4.long 0x00000700  //BANKCON5.long 0x00018005  //BANKCON6.long 0x00018005  //BANKCON7.long 0x008C04F4  //REFRESH.long 0x000000B1   //BANKSIZE.long 0x00000030  //MRSRB6.long 0x00000030    //MRSRB7

1.4 重定位 : 把bootloader本身的代码从flash复制到它的链接地址去

对于copy_code_to_sdram()clean bss()是通过C语言实现的。
为什么需要重定位呢?(具体可以看这篇博客【009 数据段重定位】)
问题:

  • 首先:CPU能直接访问的地方有:NOR FLASH、SDRAM、SRAM和各种控制器(包括NAND flash控制器)。所以当我们的程序烧写到SDRAM或者NOR FALSH的时候,程序能直接运行
  • 如果烧写到NAND FLASH,芯片会把程序的头4K先拷贝到SRAM中执行,如果NAND flash中的程序小于4K的话,程序还能正常运行,如果大于4K,那大于4K的这部分就运行不了

解决:

  • 所以我们就引入了重定位,NAND FLASH的代码中的前4K的代码需要把整个代码拷贝到SDRAM去执行。

  • 另外,对于NOR FLASH来说,我们无法简单的去写NOR FLASH,所以一旦程序中有需要写的变量,比如全局变量和静态变量,我们在无法在NOR FLASH上直接修改它们的值。因此,我们还是需要将NOR FLASH代码重定位到SDRAM中去执行。

 ldr sp, =0x34000000        //设置栈bl nand_init           //因为无论是何种方式启动,内核都是在nand flash上,需要读出来mov r0, #0ldr r1, =_start            //链接地址 ldr r2, =__bss_start    //链接脚本的地址sub r2, r2, r1         //得到程序的大小 bl copy_code_to_sdram //执行重定位代码bl clear_bss           //清楚bss段

1.5 执行main()

对于main()是通过C语言实现

 ldr lr, =halt          //返回地址死循环ldr pc, =main
halt:b halt

1.6 完整的程序


#define S3C2440_MPLL_200MHZ     ((0x5c<<12)|(0x01<<4)|(0x02))
#define S3C2440_MPLL_400MHZ     ((0x5c<<12)|(0x01<<4)|(0x01))
#define MEM_CTL_BASE    0x48000000.text
.global _start
_start:/* 1. 关看门狗 */ldr r0, =0x53000000mov r1, #0str r1, [r0]/* 2. 设置时钟 */ldr r0, =0x4c000014//   mov r1, #0x03;          // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1mov r1, #0x05;               // FCLK:HCLK:PCLK=1:4:8str r1, [r0]/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */mrc   p15, 0, r1, c1, c0, 0       //读出控制寄存器orr    r1, r1, #0xc0000000         //设置为“asynchronous bus modemcr  p15, 0, r1, c1, c0, 0       //写入控制寄存器/* MPLLCON = S3C2440_MPLL_200MHZ */ldr r0, =0x4c000004ldr r1, =S3C2440_MPLL_400MHZstr r1, [r0]/* 启动ICACHE */mrc p15, 0, r0, c1, c0, 0   @ read control regorr r0, r0, #(1<<12)mcr    p15, 0, r0, c1, c0, 0   @ write it back/* 3. 初始化SDRAM */ldr r0, =MEM_CTL_BASEadr r1, sdram_config    //sdram_config的当前地址add r3, r0, #(13*4)       //r3等于r0 + 52,即sdram_config的末地址
1:ldr r2, [r1], #4      //r2从r1的地址读到一个值,后r1+4str r2, [r0], #4       //把r2的值存到r0所指向的地方,后r0+4cmp r0, r3               //比较r0与r3,不等则跳回1bne 1b/* 4. 重定位 : 把bootloader本身的代码从flash复制到它的链接地址去 */ldr sp, =0x34000000        //设置栈bl nand_init           //因为无论是何种方式启动,内核都是在nand flash上,需要读出来mov r0, #0ldr r1, =_start            //链接地址 ldr r2, =__bss_start    //链接脚本的地址sub r2, r2, r1         //得到程序的大小 bl copy_code_to_sdram //执行重定位代码bl clear_bss           //清楚bss段/* 5. 执行main */ldr lr, =halt           //返回地址死循环ldr pc, =main
halt:b haltsdram_config:.long 0x22011110     //BWSCON.long 0x00000700    //BANKCON0.long 0x00000700  //BANKCON1.long 0x00000700  //BANKCON2.long 0x00000700  //BANKCON3  .long 0x00000700    //BANKCON4.long 0x00000700  //BANKCON5.long 0x00018005  //BANKCON6.long 0x00018005  //BANKCON7.long 0x008C04F4  //REFRESH.long 0x000000B1   //BANKSIZE.long 0x00000030  //MRSRB6.long 0x00000030    //MRSRB7

2、编写链接脚本boot.lds

SECTIONS {. = 0x33f80000;.text : { *(.text) }. = ALIGN(4);.rodata : {*(.rodata*)} . = ALIGN(4);.data : { *(.data) }. = ALIGN(4);__bss_start = .;.bss : { *(.bss)  *(COMMON) }__bss_end = .;
}

3、编写boot.c主函数文件

实现的功能如下:

  1. 帮内核设置串口: 内核启动的开始部分会从串口打印一些信息
  2. NAND FLASH里把内核读入内存
  3. 设置参数
  4. 跳转执行

3.1 帮内核设置串口

uart0_init()是在init.c文件中实现的,这里先把代码贴出来

/** 初始化UART0* 115200,8N1,无流控*/
void uart0_init(void)
{GPHCON  |= 0xa0;    // GPH2,GPH3用作TXD0,RXD0GPHUP   = 0x0c;     // GPH2,GPH3内部上拉ULCON0  = 0x03;     // 8N1(8个数据位,无较验,1个停止位)UCON0   = 0x05;     // 查询方式,UART时钟源为PCLKUFCON0  = 0x00;     // 不使用FIFOUMCON0  = 0x00;     // 不使用流控UBRDIV0 = UART_BRD; // 波特率为115200
}

3.2 从NAND FLASH里把内核读入内存

这里的nand_read()、puthex()、puts()也是在init.c文件中实现

 /* 1、从NAND FLASH里把内核读入内存 * uImage = 64 + zImage,通过uboot执行mtd可以找到zImage烧写地址*/puts("Copy kernel from nand\n\r");nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000);/* 调试代码 */puthex(0x1234ABCD);puts("\n\r");puthex(*p);  puts("\n\r");

3.3 设置参数

 /* 2、设置参数 */puts("Set boot params\n\r");setup_start_tag();        //启动参数setup_memory_tags();  //内存参数setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200");  //命令行参数setup_end_tag();     //结束参数

对于4个参数的设置,我画了下面的图以供理解:

  1. 根据start_tag的参数tag:0x30000100,在地址为0x30000100的内存中进行入栈

  2. 每设置完一个参数后params指针移动到下一个tag的位置,进行下一个参数的入栈

3.4 跳转执行

最后则会根据传入的参数,跳去执行theKernel()函数

 /* 3、跳转执行 */puts("Boot kernel\n\r");theKernel = (void (*)(int, int, unsigned int))0x30008000;/* *  mov r0, #0*  ldr r1, =362 —> 机器ID*  ldr r2, =0x30000100*  mov pc, #0x30008000 */theKernel(0, 362, 0x30000100);  /* 如果一切正常, 不会执行到这里 */puts("Error!\n\r");

3.5 完整的文件

#include "setup.h"extern void uart0_init(void);
extern void nand_read(unsigned int addr, unsigned char *buf, unsigned int len);
extern void puts(char *str);
extern void puthex(unsigned int val);static struct tag *params;/* 设置启动参数 */
void setup_start_tag(void)
{params = (struct tag *)0x30000100;params->hdr.tag = ATAG_CORE;params->hdr.size = tag_size (tag_core);/* 未用到 */params->u.core.flags = 0;params->u.core.pagesize = 0;params->u.core.rootdev = 0;/* 移动指针指向下一个tag */params = tag_next (params);
}/* 设置内存参数 */
void setup_memory_tags(void)
{params->hdr.tag = ATAG_MEM;/* ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)*         struct tag_header {          struct tag_mem32 {  *          __u32 size;                 __u32   size;*          __u32 tag;                  __u32   start;*     };                          };*     字节: (8 + 8) >> 2 = 4*/params->hdr.size = tag_size (tag_mem32);  params->u.mem.start = 0x30000000;       //内存起始地址params->u.mem.size  = 64*1024*1024;     //内存大小64M   /* 移动指针指向下一个tag */params = tag_next (params);
}int strlen(char *str)
{int i = 0;while (str[i])i++;return i;
}void strcpy(char *dest, char *src)
{while ((*dest++ = *src++) != '\0');
}/* 设置命令行参数 */
void setup_commandline_tag(char *cmdline)
{int len = strlen(cmdline) + 1;params->hdr.tag  = ATAG_CMDLINE;params->hdr.size = (sizeof (struct tag_header) + len + 3) >> 2;    //向四取整  strcpy (params->u.cmdline.cmdline, cmdline);/* 移动指针指向下一个tag */params = tag_next (params);
}/* 设置结束函数 */
void setup_end_tag(void)
{params->hdr.tag = ATAG_NONE;params->hdr.size = 0;
}int main(void)
{void (*theKernel)(int zero, int arch, unsigned int params);volatile unsigned int *p = (volatile unsigned int *)0x30008000;/* 0、帮内核设置串口: 内核启动的开始部分会从串口打印一些信息,但是内核一开始没有初始化串口 */uart0_init();/* 1、从NAND FLASH里把内核读入内存 * uImage = 64 + zImage,通过uboot执行mtd可以找到zImage烧写地址*/puts("Copy kernel from nand\n\r");nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000);puthex(0x1234ABCD);puts("\n\r");puthex(*p);  puts("\n\r");/* 2、设置参数 */puts("Set boot params\n\r");setup_start_tag();       //启动参数setup_memory_tags();  //内存参数setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200");  //命令行参数setup_end_tag();     //结束参数/* 3、跳转执行 */puts("Boot kernel\n\r");theKernel = (void (*)(int, int, unsigned int))0x30008000;/* *  mov r0, #0*  ldr r1, =362 —> 机器ID*  ldr r2, =0x30000100*  mov pc, #0x30008000 */theKernel(0, 362, 0x30000100);  /* 如果一切正常, 不会执行到这里 */puts("Error!\n\r");return -1;
}

4、编写相关初始化init.c文件

/* NAND FLASH控制器 */
#define NFCONF          (*((volatile unsigned long *)0x4E000000))
#define NFCONT          (*((volatile unsigned long *)0x4E000004))
#define NFCMMD          (*((volatile unsigned char *)0x4E000008))
#define NFADDR          (*((volatile unsigned char *)0x4E00000C))
#define NFDATA          (*((volatile unsigned char *)0x4E000010))
#define NFSTAT          (*((volatile unsigned char *)0x4E000020))/* GPIO */
#define GPHCON          (*(volatile unsigned long *)0x56000070)
#define GPHUP           (*(volatile unsigned long *)0x56000078)/* UART registers*/
#define ULCON0          (*(volatile unsigned long *)0x50000000)
#define UCON0           (*(volatile unsigned long *)0x50000004)
#define UFCON0          (*(volatile unsigned long *)0x50000008)
#define UMCON0          (*(volatile unsigned long *)0x5000000c)
#define UTRSTAT0        (*(volatile unsigned long *)0x50000010)
#define UTXH0           (*(volatile unsigned char *)0x50000020)
#define URXH0           (*(volatile unsigned char *)0x50000024)
#define UBRDIV0         (*(volatile unsigned long *)0x50000028)#define TXD0READY    (1<<2)#define PCLK            50000000    // init.c中的clock_init函数设置PCLK为50MHz
#define UART_CLK        PCLK        //  UART0的时钟源设为PCLK
#define UART_BAUD_RATE  115200      // 波特率
#define UART_BRD        ((UART_CLK  / (UART_BAUD_RATE * 16)) - 1)/** 初始化UART0* 115200,8N1,无流控*/
void uart0_init(void)
{GPHCON  |= 0xa0;    // GPH2,GPH3用作TXD0,RXD0GPHUP   = 0x0c;     // GPH2,GPH3内部上拉ULCON0  = 0x03;     // 8N1(8个数据位,无较验,1个停止位)UCON0   = 0x05;     // 查询方式,UART时钟源为PCLKUFCON0  = 0x00;     // 不使用FIFOUMCON0  = 0x00;     // 不使用流控UBRDIV0 = UART_BRD; // 波特率为115200
}/* 发送一个字符 */
void putc(unsigned char c)
{/* 等待,直到发送缓冲区中的数据已经全部发送出去 */while (!(UTRSTAT0 & TXD0READY));/* 向UTXH0寄存器中写入数据,UART即自动将它发送出去 */UTXH0 = c;
}void puts(char *str)
{int i = 0;while (str[i]){putc(str[i]);i++;}
}void puthex(unsigned int val)
{/* 0x1234abcd */int i;int j;puts("0x");for (i = 0; i < 8; i++) {j = (val >> ((7-i)*4)) & 0xf;if ((j >= 0) && (j <= 9))putc('0' + j);elseputc('A' + j - 0xa);}
}/* nor flash启动,0地址是nor flash的0地址,可以像内存一样读,但是不能像内存一样写* nand flash启动,0地址是片内内存,内存可以写*/
int isBootFromNorFlash(void)
{volatile int *p = (volatile int *)0;  //旧值int val;val = *p;      //缓冲旧值*p = 0x12345;    //往0地址写入新值/* 判断新值是否写成功 */if (*p == 0x12345) { /* 写成功,nand启动       */*p = val; return 0;} else {/* 写失败,nor启动 */return 1;}
}void nand_init(void)
{#define TACLS   0
#define TWRPH0  1
#define TWRPH1  0/* 设置时序 */NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);/* 使能NAND Flash控制器, 初始化ECC, 禁止片选 */NFCONT = (1<<4)|(1<<1)|(1<<0);
}void nand_select(void)
{NFCONT &= ~(1<<1);
}void nand_deselect(void)
{NFCONT |= (1<<1);
}void nand_cmd(unsigned char cmd)
{volatile int i;NFCMMD = cmd;for (i = 0; i < 10; i++);
}void nand_addr(unsigned int addr)
{unsigned int col  = addr % 2048;unsigned int page = addr / 2048;volatile int i;NFADDR = col & 0xff;for (i = 0; i < 10; i++);NFADDR = (col >> 8) & 0xff;for (i = 0; i < 10; i++);NFADDR  = page & 0xff;for (i = 0; i < 10; i++);NFADDR  = (page >> 8) & 0xff;for (i = 0; i < 10; i++);NFADDR  = (page >> 16) & 0xff;for (i = 0; i < 10; i++);
}void nand_page(unsigned int page)
{volatile int i;NFADDR  = page & 0xff;for (i = 0; i < 10; i++);NFADDR  = (page >> 8) & 0xff;for (i = 0; i < 10; i++);NFADDR  = (page >> 16) & 0xff;for (i = 0; i < 10; i++);
}void nand_col(unsigned int col)
{volatile int i;NFADDR = col & 0xff;for (i = 0; i < 10; i++);NFADDR = (col >> 8) & 0xff;for (i = 0; i < 10; i++);
}void nand_wait_ready(void)
{while (!(NFSTAT & 1));
}unsigned char nand_data(void)
{return NFDATA;
}int nand_bad(unsigned int addr)
{unsigned int col  = 2048;unsigned int page = addr / 2048;unsigned char val;/* 1. 选中 */nand_select();/* 2. 发出读命令00h */nand_cmd(0x00);/* 3. 发出地址(分5步发出) */nand_col(col);nand_page(page);/* 4. 发出读命令30h */nand_cmd(0x30);/* 5. 判断状态 */nand_wait_ready();/* 6. 读数据 */val = nand_data();/* 7. 取消选中 */        nand_deselect();if (val != 0xff)return 1;  /* bad blcok */elsereturn 0;
}void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
{int col = addr % 2048;int i = 0;while (i < len) {/* 一个block只判断一次 */if (!(addr & 0x1FFFF) && nand_bad(addr)) {addr += (128*1024);  /* 跳过当前block */continue;}/* 1. 选中 */nand_select();    /* 2. 发出读命令00h */nand_cmd(0x00);/* 3. 发出地址(分5步发出) */nand_addr(addr);/* 4. 发出读命令30h */nand_cmd(0x30);/* 5. 判断状态 */nand_wait_ready();/* 6. 读数据 */for (; (col < 2048) && (i < len); col++) {buf[i] = nand_data();i++;addr++;}col = 0;/* 7. 取消选中 */       nand_deselect();    }
}void copy_code_to_sdram(unsigned char *src, unsigned char *dest,unsigned int length)
{int i = 0;/* 如果是NOR   启动 */if (isBootFromNorFlash()) {while (i < length) {dest[i] = src[i];i++;}} else {//nand_init();nand_read((unsigned int)src, dest, length);}
}void clear_bss(void)
{/* 引用链接脚本的变量方法:定义外部变量,取地址 */extern int __bss_start, __bss_end;int *p = &__bss_start;for (; p < &__bss_end; p++)*p = 0;
}

5、编写Makefile文件

CC      = arm-linux-gcc
LD      = arm-linux-ld
AR      = arm-linux-ar
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdumpCFLAGS      := -Wall -O2
CPPFLAGS    := -nostdinc -nostdlib -fno-builtinobjs := start.o init.o boot.oboot.bin: $(objs)${LD} -Tboot.lds -o boot.elf $^${OBJCOPY} -O binary -S boot.elf $@${OBJDUMP} -D -m arm boot.elf > boot.dis%.o:%.c${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<%.o:%.S${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<clean:rm -f *.o *.bin *.elf *.dis

三、编译与测试

1、编译

执行make之后会生成boot.bin文件

2、测试

boot.bin文件通过OpenJtag分别烧写到Nor FlashNand Flash中,可以看到开发板成功启动bootloader

第一、二期衔接——6.1 从0写bootloader相关推荐

  1. 韦东山第一二期衔接课程内容概要

    韦东山第一二期衔接课程内容概要 0 使得一个裸板Jz2440能运行linux应用程序的过程 1 uboot启动内核总结 1.1 u-boot分析之编译体验 1.2 u-boot分析之Makefile结 ...

  2. 第三阶段应用层——2.4 视频监控—从0写USB摄像头驱动(1)-描述符的分析与打印

    视频监控-从0写USB摄像头驱动(1)-描述符的分析与打印 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3) 软件平台:运行于VMware Workstation 12 Player下U ...

  3. 【吉大刘大有数据结构绿皮书】已知非空线性链表第一个结点由list指出,写一算法,交换p所指结点与其下一个结点在链表中的位置(设p指向的不是链表最后的那个结点)。

    题目 已知非空线性链表第一个结点由list指出,写一算法,交换p所指结点与其下一个结点在链表中的位置(设p指向的不是链表最后的那个结点). 思路及解答 本题要求的是在无头链表中交换p结点和p-> ...

  4. 201671010135 2016--2017java程序设计对java的初步认识和对第一,二章的总结(0)

    201671010135  2016--2017<java程序设计>对java的初步认识和对第一,二章的总结(0) java是一种程序语言设计.html是一种描述网页结构的方式.除了用于在 ...

  5. 【产品实战-乘风游旅游App】0.0 写在前面

    [产品实战-乘风游旅游App]0.0 写在前面 个人背景: 作为一名从事2年服务端研发的程序媛,对于项目设计与研发比较熟悉,同时因部门主管也倾向于培养我们综合能力(不局限于写出优秀的代码与项目框架). ...

  6. c语言c52数码管,数码管(STC89C52): 第一个数码管循环显示0~F

    原标题:数码管(STC89C52): 第一个数码管循环显示0~F 一. 硬件设计 说明: 选通一个分两步, 第一步是位选,即选择哪个数码管亮, 这里位选是通过锁存器U2的WE1~WE6来选择第一个数码 ...

  7. mini2440通过JLink烧写BootLoader到Nor Flash

    开发板:友善之臂mini2440,64M Nand Flash 操作系统:Win7 电脑:笔记本Lenovo Y450 连接器:由于我的笔记本没有并口,所有买了个J-Link和转接板 软件:JLink ...

  8. jflash烧录教程_【参赛手记3】JLINK烧写bootloader

    JLINK是segger公司推出的专业烧写工具(interface).我们主要用它来烧写bootloader. 此次烧写所用板子为mini2440,烧写的是100ask的uboot.我所用的板子如下: ...

  9. 裸板烧写 bootloader

    [转] 有读者要求介绍一下裸板下Uboot的烧写:所以就简单的说明一下.这里主要是参考的TQ2440的出厂使用手册中介绍的,并做一下具体说明. 在裸板(没有Uboot)情况下,只用通过jtag调试接口 ...

最新文章

  1. matlab 博客,matlab
  2. Linux-进程、进程组、作业、会话、控制终端详解
  3. 自然语言处理顶会 ACL 2018 参会见闻
  4. LiveVideoStackCon 2020 首届音视频线上峰会【优秀出品人与讲师】
  5. Angular4的QuickStart—— ES6 而非TypeScript
  6. 类与类加载器---《深入理解java虚拟机》
  7. UI基础UIView常见属性及方法
  8. pandas读取csv文件数据并对指定字段分类使用matplotlib在一张图里画四张折线图子图
  9. 淘宝全屏代码天猫首页全屏代码不显示全屏怎么做设置自适应通栏990布局 全屏代码1920
  10. 为什么公司宁可高薪招一个新员工,也不愿意给老员工涨一点工资?
  11. w10需要计算机管理员才能删除,如何解决删除文件需要管理员权限win10_win10你需要提供管理员权限才能删除的解决方法...
  12. uni-app项目配置UrlSchemes在外部打开APP
  13. ECMAScript - GrammarⅠ
  14. XCTF新手练习区 writeup
  15. STM32 SWD 只能下载一次的问题
  16. android 语音播报(通过手说tts 实现中文语音播报)
  17. Docker学习资源汇总
  18. Spark 调优技巧总结
  19. 解决Echarts的toolbox只显示英文的问题
  20. 一个json传参的错误:JSON parse error: Unrecognized token ‘xxx‘{ “timestamp“: “2022-03-06T16:06:29.866

热门文章

  1. 高速信号与高频信号区分与解释
  2. VBA关于声音的多种实现方法
  3. Java的Io模型你了解多少?工信部java证书
  4. mysql 5528安装_windows 下mysql 解压方式安装
  5. 从100万个数中找出最大的前100个数
  6. cocoscreator摄像机跟随玩家移动及地图边界的设置
  7. KuCoin与链安科技达成深度战略合作
  8. 十套精品钢琴音色-Native Instruments Pianos Bundle Kontakt
  9. Photoshop CS6 简体中文版
  10. 中国移动又迈出一大步