Bootloader是嵌入式系统加电执行的第一段代码,代码初始化cpu和相关硬件资源,最终实现引导内核加载,启动操作系统运行。Bootloader引导完内核,资源操作权便交给了内核,这时内核就像个永不退出的while(1)循环执行着。正因为这种隔离交付,内核开发者会疑惑内核怎么就能跑起来了,代码堆栈环境是如何建立起来的?等
uboot
bootloader并不所属于linux内核任何子系统,但既然是理解linux内核框架,内核引导肯定是要了解一下的,下面就以一个arm平台uboot的设计要点描述,来感性的认知一下,同时也给出一个驱动实例来做具体的讲解。BTW,uboot是bootloader的一种,有关简介可自行了解。
图示,为针对S3c2440处理器的uboot简单构造流程。对于不同的处理器芯片,uboot的设计实现是需要考虑的。若是S3c6410的开发板,在核心初始化关闭mmu和cache后,应该进行外设基地址的初始化。而若是S3c210的板子,内存初始化时后需要取消存储保护区,还要初始化iram-irom。
上图示代码化,一个简单可引导内核的uboot就实现了。知道了bootloader需要干些什么东西,那具体是怎么实现的,需要掌握些什么知识呢,对每个需实现的的小模块,能不能试着概括出一个较为标准化的实现流程?
init_stack:
ldr sp, =0x34000000
mov pc, lr
上示设置堆栈起始地址,并保存当前地址至lr寄存器,其实就基本实现了C语言编程堆栈环境的初始化,清除bss段仅是为防错而已,因为bss段中值多而杂,若不清零初始化,取值可能报错。代码就几行,可知对于一个开发者来说,首先需要掌握的是处理器硬件及架构方面的知识,是否能做一个通用的uboot,就要掌握不同处理器的特性(可能需具体到芯片的io引脚),0x34000000是S3c2440的堆栈起始地址,但在S3c6410上是0x54000000。其实有些还真没多大合不合理的,就像x86 0x7c00这个魔值一样,记住就行。然后当然就是,掌握编程语言,能够根据芯片手册实现代码逻辑了。如下裸驱:RS-232串口驱动实例,来讲解一下该怎么来实现一个裸驱。
明确串口驱动实现:1,串口的初始化;2,数据发送及数据接收;3,验证。首先需要了解一下硬件的电气特性,进而得知需要什么硬件资源再做对应的初始化,先来看RS-232 9帧串口的电气特性:
逻辑1:-3 — -15v
逻辑0:+3 — +15v
RS-232正负代表01逻辑,我们知道处理器使用TTL电平(无负电平),因为这个特性,处理器连接时需要加入电平转换芯片:实现RS-232与TTL电平转换
图2
串口初始化包括:引脚初始化,帧格式设置,工作模式设置,波特率设置。如图可知初始化相应引脚,主要关注2,3,5,分别是代表接收,发送及地io引脚;帧格式主要设置多少奇偶位及多少停止位;工作模式设置双工传输方式;波特率设置标准的115200即可。针对串口驱动,如帧格式设置,这时你需要查看芯片手册,做相应定制。主要可能需要了解:
一帧数据是怎样的?一般包括起始位,数据位,奇偶校验位以及停止位。TTL起始位空闲时为高电平,一出现下降沿则视为起始位;数据位即所需传输数据;奇偶校验位检验数据的合法安全;停止位表数据传输结束。BTW,起始位空闲时为高电平是很有道理的,可防因抖动引起的数据错误,低电平变高可比高电平变低来的容易啊。还有就是具体的简单时序图要没问题。
例如,对于16进制数据55aaH,采用8数据位,1停止位传输时,信号线TTL波形图3,和RS-232波形图4
如TTL图3:波形与数据格式一致
55H=01010101B,55H的数据格式为1010101010B,起始位0,停止位1
aaH=10101010B,aaH的数据格式为1101010100B,起始位0,停止位0
如RS232图4:相反
55H=01010101B,取反后10101010B,加入一个起始位1,一个停止位0
aaH=10101010B,取反后01010101B,加入一个起始位1,一个停止位0
图3
图4
如下是具体的代码实现:
//寄存器定义
#define GPHCON (*(volatile unsigned long*)0x56000070)
#define ULCON0 (*(volatile unsigned long*)0x50000000)
#define UCON0 (*(volatile unsigned long*)0x50000004)
#define UTRSTAT0 (*(volatile unsigned long*)0x50000010)
#define UTXH0 (*(volatile unsigned long*)0x50000020)
#define URXH0 (*(volatile unsigned long*)0x50000024)
#define UBRDIV0 (*(volatile unsigned long*)0x50000028)
#define PCLK 5000000 //串口时钟来源
#define BAUD 115200 //波特率
void uart_init()
{
//配置引脚功能
GPHCON &= ~(0xf << 4);
GPHCON |= (0xa << 4);
//设置数据格式
ULCON0 = 0b11;
//设置工作模式
UCON0 = 0b0101;
//设置波特率
UBRDIV0 = (int)(PCLK / (BAUD*16) – 1);
}
void putc(unsigned char ch)
{
while(!(UTRSTAT0 & (1<<1))); //等待寄存器变化,数据被取走
UTXH0 = ch; //往寄存器赋值
}
unsigned char getc(void)
{
unsigned char ret;
while(!(UTRSTAT0 & (1<<0))); //等待寄存器变化,数据发送过来
//取数据
ret = TRXH0; //取值
if((ret ==0x0d) || (ret == 0x0a)){ //异常处理
putc(0x0d);
putc(0x0a);
}else{
putc(ret);
}
return ret;
}
//验证:查看串口输出,两次输出均为字符A即验证成功
void test()
{
uart_init();
putc(‘A’);
getc();
}
如上代码,代码并不多,主要是要理解其中的原理
小结
是不是感觉bootloader不再那么的神秘了。当然可称为是一个简单操作系统的东西还是很复杂的,只是大概知道了框架是怎样的,也对构成模块的代码化有了个直观的理解。
参考资料:
百度文库:RS-232串口通讯详解
成都国嵌相关资料