导读:本文是裸机开发的第二篇,介绍裸机开发中断相关,介绍异常,按键中断,定时器中断,实现打印未定义指令异常,打印软中断异常,处理这些异常,然后使用定时器点亮LED灯效果,同时可以使用按键触发中断点亮LED。使用的开发板是基于韦东山老师的JZ2440,SOC是三星的S3C2440芯片,外接了SDRAM,nandFlash和NorFlash。
1、start.S汇编代码
设置异常中断向量表,关看门狗,设置时钟,设置栈,设置串口,初始化SDRAM,重定位
//LED控制寄存器
#define GPFCON 0x56000050
#define GPFDAT 0x56000054
//看门狗寄存器
#define WTCON 0x53000000
//时钟
#define LOCKTIME 0x4C000000
#define CLKDIVN 0x4C000014
#define MPLLCON 0x4C000004
//设置时钟关闭某些模块
#define CLKCON 0x4C00000C
.text
.global _start
_start:
/*异常向量表*/
b reset //vector 0 : reset
ldr pc, und_addr //vector 4 : und,编译器通常do_und标号会放在文件末尾,
ldr pc, swi_addr //vector 8 : swi软件中断,使用ldr pc, und_addr读内存可能超过4k范围而无法读到
b LED //vector 0x0c : prefetch aboot
b LED //vector 0x10 : data abort
b LED //vector 0x14 : reserved
ldr pc, irq_addr //vector 0x18 : irq
b LED //vector. 0x1c : fiq
und_addr:
.word do_und //未重定义就发生异常,do_und位于大于4K后的数据段
swi_addr:
.word do_swi
irq_addr:
.word do_irq
do_und:
/* 执行到这里之前:
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x4的地方执行程序
*/
/* 1、保存现场 */
ldr sp, =0x34000000 //设置栈(undefined模式)
stmdb sp!, {r0-r12, lr} //保存r0~r12,lr是异常处理完后的返回地址, 也要保存
/* 2、处理und异常 */
mrs r0, cpsr //读cpsr到r0,将异常传参给打印函数
ldr r1, =und_string //再传递一个字符串
bl printException //跳转到打印函数,接收r0和r1两个参数
/* 3、恢复现场 */
ldmia sp!, {r0-r12, pc}^ // ^会把spsr的值恢复到cpsr状态寄存器里
und_string:
.string "undefined instruction exception"
.align 4 //4字节对其,字符串容易出现非四字节情况,造成以下代码无法执行
do_swi:
/* 执行到这里之前:
* 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_svc保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 4. 跳到0x08的地方执行程序
*/
/* 1、保存现场 */
ldr sp, =0x33e00000 //设置栈
stmdb sp!, {r0-r12, lr} //保存r0~r12及lr返回地址
/* 2、处理swi异常 */
mov r4, lr //保存函数返回地址,调用函数后lr会被破坏
mrs r0, cpsr //读取cpsr异常地址
ldr r1, =swi_string
bl printException //打印cpsr和字符串
sub r0, r4, #4 //被中断的上一条指令
bl printSWIVal //打印软中断号
/* 3、恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
swi_string:
.string "swi exception"
.align 4 //4字节对其,字符串容易出现非四字节情况,造成以下代码无法执行
do_irq:
/* 执行到这里之前:
* 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_irq保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10010, 进入到irq模式
* 4. 跳到0x18的地方执行程序
*/
/* 1、保存现场 */
ldr sp, =0x33d00000
sub lr, lr, #4 //硬件结构决定lr-4为返回地址
stmdb sp!, {r0-r12, lr}
/* 2、处理irq异常 */
bl handle_irq_c
/* 3、恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr_irq的值恢复到cpsr里 */
reset:
/*1、关看门狗*/
ldr r0, =WTCON
ldr r1, =0
str r1, [r0]
/*2、设置栈*/
ldr sp, =0x40000000+4096 //假设nor启动
mov r1, #0 //SVC管理模式的栈
ldr r0, [r1] //备份0地址的值,以便恢复
str r1, [r1] //将0写入0地址
ldr r2, [r1] //再将0地址值读出来
cmp r1, r2 //判断是否写成功
moveq sp, #4096 //如果相等则证明代码在内部SRAM运行,是nand启动
streq r0, [r1] //写入成功,将备份值写入0地址,恢复原来的数据
/* 3、设置时钟,提高CPU运行速度
* UPLL时钟给UCLK使用,MPLL时钟给FCLK,HCLK,PCLK使用
* FCLK = 400M HCLK = FCLK/4 PCLK = FCLK/8
*/
ldr r0, =LOCKTIME //设置UPLL和MPLL锁定时间
ldr r1, =0xFFFFFFFF //手册默认值
str r1, [r0]
ldr r0, =CLKDIVN //设置DIVN_UPLL、HDIVN和PDIVN分频系数
ldr r1, =0x5 //UCLK = UPLL clock,HCLK = FCLK/4 when CAMDIVN[9]=0,PCLK = HCLK/2.
str r1, [r0] //PLL锁定时间之后才会起效
mrc p15,0,r0,c1,c0,0 //读协处理器
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0 //写协处理器,设置CPU工作于异步模式
ldr r0, =MPLLCON //设置外部晶振12M,CPU主频400M
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0] //HDIVN不为0时,必须设置CPU工作为异步模式,否则使用HCLK
/*4、初始化串口*/
bl uart0_init
/*5、打开icache*/
/*6、初始化SDRAM*/
bl sdram_init
/*7、代码重定位到SDRAM内存*/
bl copysdram
ldr pc, =sdram //将sdram的链接地址赋给PC,跳转到SDRAM中执行
sdram:
/*8、清除BSS段*/
bl clean_bss
/* 9、从SVC模式切换到usr模式 */
mrs r0, cpsr //读出cpsr
bic r0, r0, #0xf //修改M4-M0为0b10000, 进入usr模式
bic r0, r0, #(1<<7) //清除7位, 使能中断
msr cpsr, r0 //写cpsr,打开全局中断
ldr sp, =0x33f00000 //设置usr用户模式栈
//10、初始化LED
ldr r0, =GPFCON
ldr r1, =0x1500 //GPF4 GPF5 GPF6配置LED引脚为输出模式
str r1, [r0]
.word 0xdeadc0de //未定义指令,人工制造异常,会跳转到vector 4处执行,处理异常
swi 0x123 //执行此命令, 触发SWI软中断异常, 进入0x8执行,会跳转到vector 8处执行,处理异常
//11、主函数测试串口
bl main //使用BL命令相对跳转, 程序仍然在NOR/sram执行
LED: //其他类型中断执行LED闪烁程序
ldr r0, =GPFDAT
ldr r1, =((1<<4) | (1<<5) | (1<<6))
str r1, [r0]
bl delay
ldr r1, =((0<<4) | (1<<5) | (1<<6))
str r1, [r0]
bl delay
ldr r1, =((0<<4) | (0<<5) | (1<<6))
str r1, [r0]
bl delay
ldr r1, =((0<<4) | (0<<5) | (0<<6))
str r1, [r0]
bl delay
b LED
delay:
ldr r2, =150000
mov r3, #0
delay_loop:
sub r2, r2, #1 //减1
cmp r2, r3 //cmp会影响Z标志位
bne delay_loop //不相等则跳转
mov pc, lr //函数调用返回
b .
2、main.c
主函数负责初始化中断
#include "uart.h"
#define BWSCON *(volatile unsigned int *)0x48000000
#define BANKCON6 *(volatile unsigned int *)0x4800001C
#define BANKCON7 *(volatile unsigned int *)0x48000020
#define REFRESH *(volatile unsigned int *)0x48000024
#define BANKSIZE *(volatile unsigned int *)0x48000028
#define MRSRB6 *(volatile unsigned int *)0x4800002C
#define MRSRB7 *(volatile unsigned int *)0x48000030
int main(void)
{
interrupt_init(); //初始化中断控制器
key_eint_init(); //初始化按键, 设为中断源
timer_init(); //定时器中断初始化
return 0;
}
//初始化SDRAM
void sdram_init(void)
{
volatile unsigned char *p = (volatile unsigned char *)0x30000000;
BWSCON = 0x22000000; //bank6和bank7使用32位数据位,低于32位操作时不屏蔽某几位数据,直接读32位数据
BANKCON6 = 0x18001; //bank6 bank7内存类型位SDRAM,行地址和列地址发出时间间隔位2个时钟周期(100M)20ns,列地址9位
BANKCON7 = 0x18001; //硬件只连接了bank6
REFRESH = 0x8404f5; //内存刷新Trp=2clock Tsrc=5clock Refresh=64ms/8kb=7.8us
BANKSIZE = 0xb1; //连续突发访问,可以使用时钟使能SDRAM休眠模式,64M空间
MRSRB6 = 0x20; //收到列地址后等两个时钟才返回数据
MRSRB7 = 0x20;
}
//SDRAM重定位
void copysdram(void)
{
extern int __code_start, __bss_start; //获取连接脚本的符号地址
volatile unsigned int *dest = (volatile unsigned int *)&__code_start;
volatile unsigned int *end = (volatile unsigned int *)&__bss_start;
volatile unsigned int *src = (volatile unsigned int *)0; //程序最开始下载到0地址
while (dest < end) //将代码段和数据段从0地址复制到链接脚本指定的链接地址中,也就是SDRAM 0x30000000地址;
{
*dest++ = *src++;
}
}
//清除BSS
void clean_bss(void)
{
extern int _end, __bss_start; //从lds链接脚本文件中获得BSS起始和结束位置
volatile unsigned int *start = (volatile unsigned int *)&__bss_start; //使用指针操作要对符号取地址
volatile unsigned int *end = (volatile unsigned int *)&_end;
while (start <= end)
{
*start++ = 0; //清零
}
}
3、uart.c
在上一篇的基础上加上printHex,printException和printSWIVal三个函数
//打印十六进制数字
void printHex(unsigned int val)
{
int i;
unsigned char arr[8];
for (i = 0; i < 8; i++)
{
arr[i] = val & 0xf;//先取出每一位的值
val >>= 4; //arr[0] = 2, arr[1] = 1, arr[2] = 0xF
}
puts("0x");//打印
for (i = 7; i >=0; i--)
{
if (arr[i] >= 0 && arr[i] <= 9)
putchar(arr[i] + '0');
else if(arr[i] >= 0xA && arr[i] <= 0xF)
putchar(arr[i] - 0xA + 'A');
}
}
//异常信息打印,被start.S汇编代码调用,通过r0和r1传参
void printException(unsigned int cpsr, char *str)
{
puts("Exception! cpsr = ");
printHex(cpsr); //打印异常信息
puts(" ");
puts(str); //打印字符串
puts("\n\r");
}
//打印软中断值,被start.S汇编代码调用,通过r0传参
void printSWIVal(unsigned int *pSWI)
{
puts("SWI val = ");
printHex(*pSWI & ~0xff000000);
puts("\n\r");
}
4、interrupt.c
按键中断和定时器中断处理相关代码
//中断和按键初始化相关
#define INTMSK *(volatile unsigned int *)(0X4A000008)
#define GPFCON *(volatile unsigned int *)(0x56000050)
#define GPGCON *(volatile unsigned int *)(0x56000060)
#define EXTINT0 *(volatile unsigned int *)(0x56000088)
#define EXTINT1 *(volatile unsigned int *)(0x5600008C)
#define EXTINT2 *(volatile unsigned int *)(0x56000090)
#define EINTMASK *(volatile unsigned int *)(0x560000A4)
//中断处理相关
#define EINTPEND *(volatile unsigned int *)(0x560000A8)
#define GPFDAT *(volatile unsigned int *)(0x56000054)
#define GPGDAT *(volatile unsigned int *)(0x56000064)
#define INTOFFSET *(volatile unsigned int *)(0X4A000014)
#define SRCPND *(volatile unsigned int *)(0X4A000000)
#define INTPND *(volatile unsigned int *)(0X4A000010)
//定时器相关
#define TCFG0 *(volatile unsigned int *)(0x51000000)
#define TCFG1 *(volatile unsigned int *)(0x51000004)
#define TCNTB0 *(volatile unsigned int *)(0x5100000C)
#define TCON *(volatile unsigned int *)(0x51000008)
#define GPFDAT *(volatile unsigned int *)(0x56000054)
//中断处理函数声明
typedef void(*irq_func)(int);
irq_func irq_array[32];
/* 初始化中断控制器 */
void interrupt_init(void)
{
/* 用来屏蔽中断, 1-masked
* bit0->eint0
* bit2->eint2
* bit5->eint8_23
*/
INTMSK &= ~((1<<0) | (1<<2) | (1<<5)); //使能S2,S3,S4,S5中断
}
//定时器中断处理函数
void timer_irq(void)
{
/* 点灯计数 */
static int cnt = 0;
int tmp;
cnt++;
tmp = ~cnt;
tmp &= 7;
GPFDAT &= ~(7<<4);
GPFDAT |= (tmp<<4);
}
//按键中断处理函数
void key_irq(int irq)
{
unsigned int val = EINTPEND; //读EINTPEND分辨率哪个EINT产生(eint4~23)清除中断时, 写EINTPEND的相应位
unsigned int val1 = GPFDAT; //LED数据寄存器
unsigned int val2 = GPGDAT;
if (irq == 0) /* eint0 : s2 控制 D12 */
{
if (val1 & (1<<0)) /* s2 --> gpf6 */
{
GPFDAT |= (1<<6);//松开
}
else
{
GPFDAT &= ~(1<<6);//按下
}
}
else if (irq == 2) /* eint2 : s3 控制 D11 */
{
if (val1 & (1<<2)) /* s3 --> gpf5 */
{
GPFDAT |= (1<<5); //松开
}
else
{
GPFDAT &= ~(1<<5); //按下
}
}
else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */
{
if (val & (1<<11)) /* eint11 */
{
if (val2 & (1<<3)) /* s4 --> gpf4 */
{
GPFDAT |= (1<<4);//松开
}
else
{
GPFDAT &= ~(1<<4);//按下
}
}
else if (val & (1<<19)) /* eint19 */
{
if (val2 & (1<<11))
{
GPFDAT |= ((1<<4) | (1<<5) | (1<<6));//松开,熄灭所有LED
}
else
{
GPFDAT &= ~((1<<4) | (1<<5) | (1<<6));//按下,点亮所有LED
}
}
}
EINTPEND = val;
}
//按键中断处理函数
void handle_irq_c(void)
{
int irq = INTOFFSET;//用来显示INTPND中哪一位被设置为1,当清除SRCPND和INTPNED硬件自动清除该位
/* 1、分辨中断源 */
irq_array[irq](irq); //通过函数指针调用中断处理函数
/* 2、清中断 */
SRCPND = (1<<irq); //用来显示哪个中断产生了, 需要写1清除对应位
INTPND = (1<<irq); //用来显示当前优先级最高的、正在发生的中断, 需要写1清除对应位
}
void register_irq(int irq, irq_func fp)
{
irq_array[irq] = fp;
INTMSK &= ~(1<<irq);
}
/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{
/* 配置GPIO为中断引脚 */
GPFCON &= ~((3<<0) | (3<<4));
GPFCON |= ((2<<0) | (2<<4)); /* S2,S3按键被配置为中断引脚 */
GPGCON &= ~((3<<6) | (3<<22));
GPGCON |= ((2<<6) | (2<<22)); /* S4,S5按键被配置为中断引脚 */
/* 设置中断触发方式: 双边沿触发 */
EXTINT0 |= (7<<0) | (7<<8); /* S2,S3 */
EXTINT1 |= (7<<12); /* S4 */
EXTINT2 |= (7<<12); /* S5 */
/* 设置EINTMASK使能eint11,19 */
EINTMASK &= ~((1<<11) | (1<<19)); //eint0和eint2没有对应的屏蔽位,默认打开
register_irq(0, key_irq);
register_irq(2, key_irq);
register_irq(5, key_irq);
}
void timer_init(void)
{
/* 设置TIMER0的时钟 */
//Timer clk = PCLK / {prescaler value+1} / {divider value} = 50000000/(99+1)/16 = 31250
TCFG0 = 99; //Prescaler 0 = 99, 用于timer0,1
TCFG1 &= ~0xf;
TCFG1 |= 3; //MUX0: 1/16分频
/* 设置TIMER0的初值 */
TCNTB0 = 15625; //31250/2 0.5s中断一次
/* 加载初值, 启动timer0 */
TCON |= (1<<1); //设置从TCNTB0 & TCMPB0读自动加载初值
TCON &= ~(1<<1); //写要清零
TCON |= (1<<0) | (1<<3); //设置为自动加载模式,并启动定时器
/* 注册中断 */
register_irq(10, timer_irq);
}
5、Makefile
all: start.o main.o uart.o interrupt.o
arm-linux-ld -T sdram.lds $^ -o int.elf
arm-linux-objcopy -O binary -S int.elf int.bin
clean:
rm *.bin *.o *.elf
%.o : %.c
arm-linux-gcc -c -o $@ $<
%.o : %.S
arm-linux-gcc -c -o $@ $<