/*====================================================
*
* 标题:实作你的第一个嵌入式系统
*
* xue, 2012、05、18 15:45
*
* 说明:读书笔记
*
*===================================================*/
1、嵌入式系统开发流程
a、在PC上安装开发环境(包含cross-compiler、debugging、tool、editor、ICE驱动程序等)
b、程序编写
c、编写makefile
d、build程序(执行makefile)
e、利用ICE或其他工具将程序下载到机器上,并测试程序的执行结果
f、如程序无误,用烧录器将程序烧写到ROM,则以后机器上电,就会执行新的程序
2、先别急着写程序,首先要了解嵌入式系统开发环境思想,然后先看CPU厂商提供的
sample code 和makefile。
3、一个完整的嵌入式系统至少包含多少种类的文件,那些文件是程序员必须编写的,那些是由
工具产生的?
例:简单的LED亮灭project中包含以下文件
程序部分(由程序员编写)
vector.c:中断矢量表
bot.c:启动程序
main.c:应用程序主程序
drv_timer0.c:Timer(定时器)的驱动程序
drv_LED.c:LED驱动程序
其他文件(由程序员编写)
test.mak:makefile
test.lds:Link Script(提供给Linker的信息,描述程序要被寻址到那个地址去)
build.bat:执行makefile
执行build.bat后产生:
test.elf:build后产生之ELF格式的可执行文件
test.bin:由ELF格式转换来的binary文件
test.err:错误信息文件(存储编译及连接时期的输出信息)
vetor.o,boot.o,main.o,drv_timer0.o,drv_LED.o:.c文件编译后的object文件
4、第一步:弄清楚CPU的规定流程,简单来说,就是CPU reset后,会到哪个地址执行第一条指令?
回答这个问题前,我们先来看一下一般的嵌入式系统执行的流程
a、CPU会到特定的地址处获取第一条指令,实现的细节有两种。
1、CPU重新启动后,将其PC寄存器设为特定的地址,只要user的程序确实存储于这个地址,
就可以正确被执行。
2、CPU重新启动后,会将CPU存储的中断矢量表地址的寄存器设为某特定的地址,接着引发
RESET中断,所以程序员只要把中断矢量表存储于这个地址,经指定RESET中断的处理程序为
自己写的某个函数(如:boot()),则该函数就可以在开机后被CPU执行。
b、user程序开始运行后,会对CPU进行初始化
c、将程序的数据段从存储器(ROM或FLASH)载入到RAM中
d、初始化CPU后,接着初始化应用程序所用到的硬件设备
e、初始化各个子系统,如嵌入式操作系统(RTOS),动态存储管理、图形界面系统等
f、执行应用程序
首先,我们先不管b、c、d、e、f,我们先把目光放到a上面,假设我们的CPU使用手册上规定我们程序的开头必须
被寻址到0xc0000,它是采用方式2的。即是CPU启动后,会将0xc0000当做存放中断矢量表的地址,并引发RESET中断,
因此,我们只要把启动程序所在的地址存放在中断矢量表中代表RESET中断的entry即可。
第二步:我们来聊一下中断矢量表
首先我们来看一下一般程序和中断处理程序(简称ISR)的区别?
我们都知道一般程序是循序执行的,而中断程序则是可能在任何时间点发生,而且一般情况下(CPU不屏蔽中断),
当中断发生时,CPU先记录目前的状态,然后去执行中断处理程序,执行完毕后,再回来取回刚才的状态,然后
返回被中断的地址继续循序执行。
来看一下中断向量表是咋回事,程序如下:
/*================================================
vetor.c
=================================================*/
void dummy(void);
//定义其他程序中的functions
extern void boot(void);
extern void drv_isr_timer0 (void);
//中断矢量表定义
const unsigned long vetor[] =
{
(unsigned long) boot, //00 Reset
(unsigned long) dummy, //Division by zero
(unsigned long) dunmy,
(unsigned long) dunmy,
(unsigned long) dunmy,
(unsigned long) dunmy,
(unsigned long) dunmy,
(unsigned long) dunmy,
(unsigned long) dunmy,
(unsigned long) drv_isr_timer0, //Timer 0
(unsigned long) dunmy,
(unsigned long) dunmy,
........
}
从上面我们可以看到,一般的,中断矢量表就是一个C数组,当然也可以用汇编来编写,不过意义
都是一样的,实际上CPU只有存储地址的思想,当然不知道C数组是啥东东,说穿了,所谓的中断矢
量表就是从某个地址开始,每4个BYTE为一个单位(entry),每一个entry记录了一个函数的地址,就这么简单。
现在,我们来理清一下思路:在上述的中断矢量表中,只有两个不是dummy()的中断矢量,第一个指到
boot(),CPU启动后会产生RESET中断,而boot()就是RESET中断的ISR程序,所以CPU启动后第一个执行的
程序就是boot(),另一个不是dummy()的中断矢量则是指向drv_isr_timer0的,当Timer0溢出时,CPU就会引发
Timer0中断,因此,drv_isr_timer0就会被调用,明白否?
第三步:现在,我们已经聊完了中断矢量表了,接下来就是boot()了。先看一下boot()负责干些什么:
a、设定某些重要的CPU寄存器,特别是堆栈指针寄存器与状态寄存器。
b、CPU各部分初始化
c、系统初始化
d、调用应用程序的主程序
e、结束
例:boot()程序范例
/*====================================
boot.c
====================================*/
_interrupt void boot(void)
{
//设定SP寄存器
asm("xld.w %15,0x2000");
asm("ld.w %sp,%15");
//设置CPU状态寄存器
asm("xld.w %15,0x200010");
asm("ld.w %psr,%15");
//CPU初始化
_init_pull();
_init_bbcu();
_init_ebcu();
_init_cache();
_init_int();
//系统初始化
_init_sys();
//调用主程序
main();
//一般的嵌入式系统,主程序不会返回boot程序
exit();
}
对于以上的这个程序,我们来解剖一下
Q1:函数定义前面为何有‘_interrupt’这个东西,是C关键字吗?貌似标准C关键字没这个家伙吧!
A1:'_interrupt'并非C语言的标准,它是这个Cross_Compiler自定的关键字,写在代表中断处理
程序的函数定义前面。这个关键字并非只是用来区分一般函数和ISR那么简单,实际上,ISR的
开头和结尾要做的动作和一般的C语言函数不同,具体不同表现在一般函数开头只需将返回地
址存放在堆栈中,而ISR函数必须把返回地址(发生中断的地址)以及CPU的状态寄存器存放在堆
栈中,而且,CPU的所有寄存器的值也要存放到堆栈中,而在结尾的时候,一般函数使用CPU
的ret指令,而ISR函数首先要从堆栈中返回所有的CPU寄存器的值,其次它使用的指令是iret.
因此,Compiler会对加上'_interrupt'的函数进行特殊的处理,即产生不同的汇编语言指令。其实,
对于我们初学者来说,我们只需要记住,写ISR时一应要记住加上这个关键字就行了,至于其中
的道理,以后等我懂了之后再告诉你,哈哈,我菜鸟一个,等我懂这些东西,不知道要等到猴
年马月,上面文字说了一大堆,咯里啰嗦,不过没办法,我是照着书上写的..别怪我
第四步:接下来,让我们一起来看一下main(),程序范例如下:
/*=====================================================
main.c
=====================================================*/
void main(void)
{
//设定定时器0,每100ms就timeout一次
drv_init_timer0(100);
//LED驱动程序初始化
drv_init_LED();
//启动Timer 0
drv_start_timer0();
while(1);
}
以上程序很简单,在此我们不必过多讨论它了
现在,我们来对这个范例程序的运行流程来归纳一下
step1:程序的中断矢量表是一个数组,它会被寻址到CPU指定的启动地址,中断矢量表
Reset ISR的中断矢量指到我们的boot程序中。
step2:CPU启动后,会引发Reset中断,所以boot程序会被执行。
step3:boot程序负责初始化CPU,并把控制权交给应用程序的主程序
step4:主程序初始化它会用到的硬件设备
step5:在本例中,每100ms会产生一次Timer0中断。
第四步:下面,我们来看一下LED驱动程序,范例如下
/*=================================================
drv_LED.c
=================================================*/
void drv_init_LED(void)
{
//设定P10为output PIN
*(volatile unsigned char *)0x300023 |= 0x02;
//关闭LED
*(volatile unsigned char *)0x300022 &= 0xfd;
}
...... //以下略
以上程序中,我们需要关注的是volatile这个关键字,这里不再做详细的分析,
感兴趣的同学可以上网查一下,另外,有些同学对
*(volatile unsigned char *)0x300023 |= 0x02;这个语句有些不明白,下面我们来讲解一下
其实相当于
volatile unsigned char data; //存储寄存器器的值
volatile unsigned char *reg; //指向寄存器所在地址的指针
reg = (volatile unsigned char *)0x300023; //设定指针所指向的地址,以及指针指向的地址存放的数据类型
data = *reg; //取出0x300023寄存器的值
data = data | 0x02;
*reg = data;
现在是不是容易理解了啊,如果你还看不懂的话,那你应该去复习一下C语言了,哈哈
5、架设开发环境是第一步,看懂程序是第二步,那接下来呢?我们应该干些什么?
对,是测试,我们如何测试我们的程序是否正确无误执行?
利用示波器等等...我不大懂...