前言

之前学习嵌入式裸板程序也有一定的时间了,而一个bootloader则是裸板程序的一个集大成者,能包含很多的知识点,所以编写一个bootloader能巩固之前的所学。废话少讲,下面就开始正式的编写。

目标

相信搞嵌入式的都知道bootloader是什么东西,这里我就不作详细介绍,简单说一下我编写的bootloader要做什么工作。bootloader的终极目标就是为了要引导内核,并启动内核,为了达到这个目标我们需要做这么几个工作:

  1. 初始化硬件:关看门狗(否则几秒钟后就会复位整个单板)、设置系统时钟、初始化SDRAM、初始化NAND flash(因为我的内核是烧写在NAND flash的,要初始化才能读)
  2. 如果bootloader比较大,我们还需要把它重定位到SDRAM中(为什么这么做后面会讲)
  3. 把内核从flash中读到SDRAM
  4. 设置要传递给内核的参数
  5. 跳转执行内核

下面的程序我们就会按照这几个步骤进行编写。

Bootloader的编写

我会先给出完整的代码,再分步按照上面的顺序,对每一步做出详细的解释,废话不多讲,请看代码:

#define MEM_CTL_BASE 0x48000000.text
.global _start
_start:/* 1.关看门狗 */ldr r0, =0x53000000  /*当值比较大的时候,不能直接使用mov,只能用ldr*/mov r1, #0str r1, [r0]  /*关看门狗就是把零值写入到固定的地址*//* 2.设置时钟 *//*设置MPLL, FCLK:HCLK:PCLK = 400MHz : 100MHz : 50MHz*//*为了保险先初始化一下locktime*/ldr r0, =0x4C000000ldr r1, =0xFFFFFFFFstr r1, [r0]/*设置CLKDVIN:*HDIVN[2:1] :    10  -  HCLK=FCLK/4*PDVIN[0]    :     1   -   PCLK=HCLK/2*/ldr r0, =0x4C000014ldr r1, =0x5str r1, [r0]/*设置CPU为异步模式*/mrc p15,0,r0,c1,c0,0orr r0,r0,#0xc0000000  //R1_nF:OR:R1_iA mcr p15,0,r0,c1,c0,0/**Mpll = (2 * m * Fin) / (p * 2S) *m = (MDIV + 8), p = (PDIV + 2), s = SDIV*当Fin=12MHz,若要 Mpll=400MHz,则有MDIV=92, PDIV=SDIV=1*/ldr r0, =0x4C000004ldr r1, =(92<<12) | (1<<4) | (1<<0)str r1, [r0]/*一旦设置了PLL,就会锁定locktime直到PLL输出稳定*然后CPU工作于新的频率*//*启动ICACHE*/mrc p15, 0, r0, c1, c0, 0orr r0, r0, #(1<<12)mcr p15, 0, r0, c1, c0, 0/* 3.初始化SDRAM */ldr r0, =MEM_CTL_BASEadr r1, sdram_config   /*sdram_config的当前地址*/add r3, r0, #(13*4)
1:ldr r2, [r1], #4 /*让r1地址的值读到r2,让后r1加4,也就是指向下一个地址*/str r2, [r0], #4 /*让r2的值写入到r0地址的寄存器,r0加上4,指向下一个地址*/cmp r0, r3 /*不断的循环把sdram_config里面的值写入到BWSCON开始的寄存器里面*/bne 1b /*b的含义代表调到这行代码前面的1,如果是1f就代表下面的1*//* 4.重定位: 把bootloader本身的代码从flash复制到链接地址去 *//*查看芯片手册第196页可知,我们的内存为64MB,*基地址为0x30000000,所以sp指向最高地址就可以了,*因为栈是向下增长,0x30000000加上64MB等于0x34000000*/ldr sp, =0x34000000 //设置栈bl nand_init //不管是nor还是nand启动,都需要初始化nand flash,因为内核存在nand flash上/*设置copy_code_to_sdram的三个参数*/mov r0, #0  //src的值就是0地址ldr r1, =_start  //目的地址就是连接地址,就是脚本开始的_start变量ldr r2, =__bss_startsub r2, r2, r1  //bss段开始地址减去链接地址就是二进制文件的长度了bl copy_code_to_sdram/*清除bss段,也就是赋为零值*/bl clean_bss/* 5.执行main函数 */ldr lr, =halt //如果main函数有返回值,则跳入halt循环,避免单板调到未知的地方ldr pc, =main
halt:bl haltsdram_config:.long 0x22000000 //BWSCON.long 0x00000700 //BANKCON0.long 0x00000700 //BANKCON1.long 0x00000700 //BANKCON2.long 0x00000700 //BANKCON3.long 0x00000700 //BANKCON4.long 0x00000700 //BANKCON5.long 0x00018001 //BANKCON6.long 0x00018001 //BANKCON7.long 0x008404f5 //REFRESH.long 0x000000b1 //BANKSIZE.long 0x00000020 //MRSRB6.long 0x00000020 //MRSRB7

(1)关看门狗

关看门狗这步没什么讲的,按照芯片手册,给特定的寄存器写0值

(2)设置时钟

时钟的设置也不难,也就是根据芯片手册,设置一些寄存器的值。可以参考我的这篇文章:S3C2440芯片的时钟体系结构

(3)启动ICACHE

上面的汇编代码中有个启动ICACHE的设置,这个作用主要是为了加快指令的执行,从而加快bootloader的启动

(4)初始化SDRAM

SDRAM的初始化其实也是根据自己的需求,参考芯片手册,去设置一些寄存器的值,至于每个寄存器设置的值的含义,可以参考我这篇文章:S3C2440芯片的SDRAM初始化设置

这里需要解释一下的就是MEM_CTL_BASE(0x48000000)的值就是寄存器BWSCON的地址

(5)重定位

这里我先解释一下为什么需要重定位:

我们板子中有nor flash、SDRAM和nand flash,还有一个4k的片内内存SRAM。

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

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

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

这里重定位用到了链接脚本的知识,下面我先给出这次bootloader的链接脚本:

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

一开始的值0x33f80000,表示的是链接的地址,也就是代码运行时地址的起始地址,这个地址也可以是别的值,只要它在我们的内存地址中,且加上bootloader的大小后不会超出内存的最大地址即可。

由一开始给出汇编代码可以看出,我们把所有从地址0开始的代码都拷贝到地址0x33f80000开始的内存中:

 mov r0, #0  //src的值就是0地址ldr r1, =_start  //目的地址就是连接地址,就是脚本开始的_start变量ldr r2, =__bss_startsub r2, r2, r1  //bss段开始地址减去链接地址就是二进制文件的长度了bl copy_code_to_sdram

简单解释一下汇编代码的含义:

1、把地址0作为第一个参数(如果是NAND启动,就是NAND FLASH零地址的位置,如果是NOR启动,就是NOR FLASH 零地址的位置),为拷贝代码的源地址;

2、_start(为代码一开始的地址,也就是链接脚本中定义的0x33f80000)作为第二个参数,为代码拷贝的目的地址;

3、而要拷贝的代码有多长呢?这里就要用到有关ELF文件中BSS段的知识。我们都知道编译出来的bin文件是不包含BSS段的,BSS段存放的是未初始化的全局变量和静态变量,所以我们可以把它想象为初始化为0值,如果bin文件中存放一堆0值的变量是很浪费空间的。至于更加深层的原因可以参考这篇文章:bss段不占据磁盘空间的理解。这里我们就知道,拷贝的代码长度为BSS段开始的地址减去代码的起始地址,也就是__bss_start - _start 。

现在,我们来看一下copy_code_to_sdram函数怎么对代码进行重定位,也就是代码的拷贝

/*知识背景:*对于nand flash: 开机启动的时候,从0地址开始的前4k内容会被拷贝到*芯片的片内0地址开始的RAM里面,并在RAM的0地址开始执行,所以*我们可以读写0地址开始的内容。*而对于nor flash : 是能直接在nor flash读的,但是不能写,开机启动*是在nor flash的0地址处开始执行,所以我们能读但是写不了。*/
int isBootFromNorFlash(void)
{volatile int *p = (volatile int *)0;int val;val = *p;*p = 0x12345678;if(*p == 0x12345678){/*nand flash启动*/*p = val;return 0;}else{/*nor flash启动*/return 1;}
}void copy_code_to_sdram(unsigned char *src, unsigned char *dest, unsigned int len)
{int i = 0;/*如果是NOR启动*/if(isBootFromNorFlash()){while(i < len){dest[i] = src[i];i++;}}else{nand_read((unsigned int)src, dest, len);}
}

上面的代码都有注释,从代码来看也比较直观。先判断是NOR启动还是NAND启动,如果是NOR启动就很简单,直接对内容进行赋值。如果是NAND启动,则篇幅会比较长,涉及到NAND FLASH的读操作。为了保持整个讲解流程的简洁,就不在这里展开,放到我的这篇文章里面:NAND FLASH的读操作及原理。

(6)清除BSS段

清除BSS段其实就是直接赋零0,不多讲,直接上代码

void clean_bss(void)
{extern int __bss_start, __bss_end;int *p = &__bss_start;for (; p<&__bss_end; p++)*p = 0;
}

后续

到这里,我们的汇编代码部分就大概梳理了一遍,最后会跳转到main函数里面去执行,里面会做一些串口的初始化、传递给内核的参数的一些设置、把内核读到内存、以及跳转去执行内核部分。

从零开始写一个简单的bootloader(1)相关推荐

  1. 如何搭建python框架_从零开始:写一个简单的Python框架

    原标题:从零开始:写一个简单的Python框架 Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. 你为什么想搭建一个Web框架?我想有下面几个原因: 有一个 ...

  2. 自己动手写一个简单的bootloader

    自己动手写一个简单的bootloader 15年10月31日19:44:27 (一) start.S 写这一段代码前,先要清楚bootloader开始的时候都做什么了.无非就是硬件的初始化,我们想要写 ...

  3. python解释器用什么写的_用 Python 从零开始写一个简单的解释器(3)

    到目前为止,我们已经为解释器写了一个词法分析器和 一个解析器组合子库.在这里,我们会创建抽象语法树(AST)的数据结构,使用组合子库写一个解析器,组合子库可以实现将词法分析器返回的标记列表转换为一个抽 ...

  4. 用python写一个简单的爬虫_用Python从零开始写一个简单爬虫

    import requests from bs4 import BeautifulSoup url = "https://tieba.baidu.com/f?kw=王者荣耀&fr=h ...

  5. 从零开始写一个武侠冒险游戏-3-地图生成

    2019独角兽企业重金招聘Python工程师标准>>> 从零开始写一个武侠冒险游戏-3-地图生成 概述 前面两章我们设计了角色的状态, 绘制出了角色, 并且赋予角色动作, 现在是时候 ...

  6. 从零开始写一个武侠冒险游戏-6-用GPU提升性能(1)

    从零开始写一个武侠冒险游戏-6-用GPU提升性能(1) ----把帧动画的实现放在GPU上 作者:FreeBlues 修订记录 2016.06.19 初稿完成. 2016.08.05 增加对 XCod ...

  7. 从零开始写一个武侠冒险游戏-8-用GPU提升性能(3)

    从零开始写一个武侠冒险游戏-8-用GPU提升性能(3) ----解决因绘制雷达图导致的帧速下降问题 作者:FreeBlues 修订记录 2016.06.23 初稿完成. 2016.08.07 增加对 ...

  8. 怎样写一个简单的操作系统?(原文标题:How to write a simple operating system) 分类: 翻译 2011-01-26 01:10 3175人阅读 评论(3) 收藏

    怎样写一个简单的操作系统?(原文标题:How to write a simple operating system) 分类: 翻译2011-01-26 01:10 3175人阅读 评论(3) 收藏 举 ...

  9. dotnet 从零开始写一个人工智能 从一个神经元开始

    现在小伙伴说的人工智能都是弱智能,可以基于神经网络来做.而神经网络是有多层网络,每一层网络都有多个神经元.那么最简单的神经网络就是只有一层,而这一层只有一个神经元,也就是整个神经网络只是有一个神经元. ...

最新文章

  1. 资源 | Yann LeCun最新演讲:大脑是如何高效学习的?(附PPT+视频)
  2. 变换域隐写术检测分析
  3. Ubuntu下git使用教程
  4. Python中常用的文本转义及编码
  5. pycharm Debug问题
  6. 【英语学习】【Level 08】U03 My Choice L5 The star that shines the brightest
  7. AI驱动智能化日志分析 : 通过决策树给日志做聚类分析
  8. 【Java】编程思想汇总ing
  9. timesat数据如何读取_CMPR软件安装教程和如何转换XRD格式
  10. 稻盛和夫《干法》读后感
  11. 错误No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbala
  12. malloc与calloc的区别及实例
  13. 我们如何学习:学会学习再学习
  14. Linux5.9下DHCP服务器的配搭建
  15. 微信小程序播放视频 禁止快进
  16. ueditor粘贴word图片无法显示的问题
  17. React16、17、18版本新特性
  18. 《经济学通识课》读书笔记
  19. 民生银行加入R3区块链联盟
  20. 8通道16位DAC芯片LTC2600编程使用

热门文章

  1. 基于微软平台IIS/ASP.NET开发的大型网站有哪些?
  2. AIX pv vg lv fs 文件系统
  3. 用Jquey实现双击图片放大和触摸放大的功能。
  4. 我,32岁零基础转大数据,不需要别人怎么看!
  5. 如何在Win7中安装使用超级终端Hyper Terminal
  6. 某基于DEDECMS5.5网站的安全检测初步报告
  7. 新手学网站建设解疑与技巧1200例
  8. Web前端学完后薪资怎么样?取决你技术好坏
  9. 磁编码器MT6835_SPI读取位置信息
  10. Office 2007兼容工具包