ARM嵌入式Linux裸机开发---异常、按键中断和定时器中断

导读:本文是裸机开发的第二篇,介绍裸机开发中断相关,介绍异常,按键中断,定时器中断,实现打印未定义指令异常,打印软中断异常,处理这些异常,然后使用定时器点亮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 $@ $<

6、编译结果

异常

源码下载

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值