一、2440按键中断编程
1. 程序结构优化
将main.c中的mmu_init分开成独立文件
-
/*
-
* 用于段描述符的一些宏定义
-
*/
-
#define MMU_DOMAIN (0<<5) /*属于哪个域*/
-
#define MMU_FULL_ACCESS (3<<10) /*访问权限*/
-
#define MMU_SPECIAL (1<<4) /*必须使1*/
-
#define MMU_CACHEABLE (1<<3) /*使能cache*/
-
#define MMU_BUFFERABLE (1<<2) /*使能buffer*/
-
#define MMU_SECTION (2<<0) /*段描述符*/
-
-
#define MMU_SECDESC (MMU_SECTION|MMU_SPECIAL|MMU_DOMAIN|MMU_FULL_ACCESS)
-
#define MMU_SECDESC_WB (MMU_SECTION|MMU_SPECIAL|MMU_DOMAIN|MMU_FULL_ACCESS|MMU_CACHEABLE|MMU_BUFFERABLE)
-
-
void create_page_table()
-
{
-
//设置ttb的起始位置
-
unsigned long *ttb = (unsigned long *)0x30000000;
-
//定义虚拟地址、物理地址
-
unsigned long vaddr, paddr;
-
//定义虚拟地址起始地址、物理起始地址
-
vaddr = 0xA0000000;
-
paddr = 0x56000000;
-
/* ttb加上虚拟地址高12位所指的地址就是PA的基地址位置,物理地址取前12位,依次设置后面的值(见AMR9核手册)
-
* 然后我们就建立的从vaddr的地址对应于paddr
-
*/
-
-
*(ttb + (vaddr>>20) ) = (paddr & 0xfff00000)|MMU_SECDESC;
-
-
// 上面是对GPIO的映射,这里是对内存的映射
-
vaddr = 0x30000000;
-
paddr = 0x30000000;
-
//循环了64M的内存空间
-
while(vaddr < 0x34000000)
-
{
-
//建立内存映射表
-
*(ttb + (vaddr>>20)) = (paddr & 0xfff00000)|MMU_SECDESC_WB;
-
//加上1M,这个1M使手册中MVA[19:0]的大小
-
vaddr += 0x100000;
-
paddr += 0x100000;
-
}
-
}
-
-
void mmu_enable()
-
{
-
__asm__(
-
/*设置TTB*/
-
"ldr r0, =0x30000000\n"
-
"mcr p15, 0, r0, c2, c0, 0\n"
-
/*不进行权限检查*/
-
"mvn r0, #0 \n"
-
"mcr p15, 0, r0, c3, c0, 0\n"
-
/*使能mmu*/
-
"mrc p15, 0, r0, c1, c0, 0\n"
-
"orr r0, r0, #0x0001\n"
-
"mcr p15, 0, r0, c1, c0, 0\n"
-
:
-
:
-
);
-
}
-
-
void mmu_init()
-
{
-
//建立页表、写入ttb
-
create_page_table();
-
//使能mmu
-
mmu_enable();
-
}
同时将led提取成led.c
-
#define GPBCON (volatile unsigned long*) 0x56000010 //这里设置成了0x56000010
-
#define GPBDAT (volatile unsigned long*) 0x56000014 //这里设置成了0x56000014
-
-
void led_init()
-
{
-
*(GPBCON) = 0x15400; //对控制寄存器初始化
-
}
-
-
void led_on()
-
{
-
*(GPBDAT) = 0x6bf; //点亮led1和led3
-
}
-
-
void led_off()
-
{
-
*(GPBDAT) = 0x7ff; //熄灭所有led
-
}
Makefile中增加led.o和mmu.o
-
#ifdef MMU_ON
-
mmu_init(); //在main.c中关闭mmu
-
#endif
2. 按键初始化
从原理图中可知,EINT0、EINT1、EINT2、EINT4是GPIOF口
-
#define GPFCON (volatile unsigned long*) 0X56000050
-
#define GPFDAT (volatile unsigned long*) 0X56000054
-
//开启
-
#define GPF0_msk (3<<(0*2))
-
#define GPF1_msk (3<<(1*2))
-
#define GPF2_msk (3<<(2*2))
-
#define GPF4_msk (3<<(4*2))
-
-
#define GPF0_int (0x2<<(0*2))
-
#define GPF1_int (0x2<<(1*2))
-
#define GPF2_int (0x2<<(2*2))
-
#define GPF4_int (0x2<<(4*2))
-
-
void button_init()
-
{
-
*(GPFCON) &= ~(GPF0_msk|GPF1_msk|GPF2_msk|GPF4_msk); //先全部清零
-
*(GPFCON) |= (GPF0_int|GPF1_int|GPF2_int|GPF4_int); //设置为中断口
-
}
3. 初始化中断控制器
1.设置中断掩码控制器
2.开启CPSR中的中断
参考
中断处理流程分析,所以要设置Mask寄存器(0X4A000008),但是mask中EINT4-7是一起的,所以还有一个EINTMASK寄存器细分4-7位的中断。设置0就可以开启中断位,设置1为屏蔽中断。
-
#define INTMSK (volatile unsigned long*) 0x4A000008
-
#define EINTMSK (volatile unsigned long*) 0x560000A4
-
-
void init_irq()
-
{
-
//取消屏蔽
-
*(EINTMSK) &= (~(1<<4)); 必须先设置
-
*(INTMSK) &= (~(1<<0)) & (~(1<<1)) & (~(1<<2)) & (~(1<<4));
-
//操作cpsr寄存器,I位
-
__asm__(
-
"mrs r0, cpsr\n" 读取cpsr中的内容
-
"bic r0, r0, #0x80\n" bic设置第7位是0,开启irq
-
"msr cpsr_c, r0\n" 写进cpsr_controlbit
-
:
-
:
-
);
-
}
芯片手册中写如果不开启I位,产生中断时也会强制置1。但是我取消了开启中断位,并没有实现中断。
4. 中断处理
1.中断程序总入口设置(start.S):
-
irq:
-
sub lr, lr, #4 /*保存环境*/ 这里不是很懂
-
stmfd {r0-r12, lr} 保存环境
-
bl handle_int 跳转到中断处理函数
-
ldmfd {r0-r12, pc}^ 恢复环境,^表示把spsr恢复到cpsr
关于这里为什么lr需要减4:
ARM处理器使用流水线来增加处理器指令流的速度,这样可使几个操作同时进行,并使处理与存储器系统之间的操作更加流畅,连续,能提供0.9MIPS/MHZ的指令执行速度。
PC代表程序计数器,流水线使用三个阶段,因此指令分为三个阶段执行:1.取指(从存储器装载一条指令);2.译码(识别将要被执行的指令);3.执行(处理指令并将结果写回寄存器)。而R15(PC)总是指向“正在取指”的指令,而不是指向“正在执行”的指令或正在“译码”的指令。一般来说,人们习惯性约定将“正在执行的指令作为参考点”,称之为当前第一条指令,因此PC总是指向第三条指令。当ARM状态时,每条指令为4字节长,所以PC始终指向该指令地址加8字节的地址,即:PC值=当前程序执行位置+8;
ARM指令是三级流水线,取指,译指,执行时同时执行的,现在PC指向的是正在取指的地址,那么cpu正在译指的指令地址是PC-4(假设在ARM状态下,一个指令占4个字节),cpu正在执行的指令地址是PC-8,也就是说PC所指向的地址和现在所执行的指令地址相差8。
当突然发生中断的时候,保存的是PC的地址即lr的内容
这样你就知道了,如果返回的时候返回PC,那么中间就有一个指令没有执行,所以用SUB pc lr-irq #4。
参考:
2.中断处理函数(interrupt.c)
-
#define SRCPND (volatile unsigned long*) 0x4A000000
-
#define INTPND (volatile unsigned long*) 0x4A000010
-
#define INTOFFSET (volatile unsigned long*) 0x4A000014
-
-
#define EINTPEND (volatile unsigned long*) 0x560000A8
-
-
-
void handle_int()
-
{
-
//判断产生中断的中断源
-
unsigned long value = *(INTOFFSET); //读取寄存器中的值,用于判断哪个中断发生了
-
-
//根据中断源,执行不同的中断处理
-
switch(value)
-
{
-
case 1: //k1
-
led_on();
-
break;
-
-
case 4: //k2
-
led_on();
-
break;
-
-
case 2: //k3
-
led_off();
-
break;
-
-
case 0: //k4
-
led_off();
-
break;
-
-
default:
-
break;
-
}
-
//中断清除
-
if(value == 4)
-
{
-
*(EINTPEND) = 1<<4; //4号中断和4-7中在一起,要特殊处理
-
}
-
*(SRCPND) = 1<<value; //清除SRCPND中寄存器中断,需要写入1才能清0寄存器。清空中断源标志
-
*(INTPND) = 1<<value; //清除INTPND中寄存器的值,同样需要写入1才能清0。表明已经处理
-
}
5.最后遗漏
完成上面后依然没有实现中断控制。
原因:初始化栈中的SP是在svc模式下的。但是irq模式下的sp指针没有实现初始化。所以需要初始化IRQ模式下的svc
-
init_stack:
-
msr cpsr_c, #0xd2
-
ldr sp, =0x33000000 //初始化R13_irq
-
msr cpsr_c, #0xd3
-
ldr sp, =0x34000000 //初始化R13_SVC
-
mov pc, lr
二、6410按键中断编程
主要部分还是interrupt.c
-
/*
-
* interrupt.c
-
*
-
* Created on: 2016-10-2
-
* Author: root
-
*/
-
-
#define EINT0CON0 (volatile unsigned long*) 0x7F008900
-
#define EINT0CON1 (volatile unsigned long*) 0x7F008904
-
#define EINT0MASK (volatile unsigned long*) 0x7F008920
-
-
#define VIC0INTENABLE (volatile unsigned long*) 0x71200010
-
#define VIC1INTENABLE (volatile unsigned long*) 0x71300010
-
#define EINT0_VICADDR (volatile unsigned long*) 0x71200100
-
#define EINT20_VICADDR (volatile unsigned long*) 0x71300104
-
#define EINT0PEND (volatile unsigned long*) 0x7F008924
-
#define VIC0ADDRESS (volatile unsigned long*) 0x71200F00
-
#define VIC1ADDRESS (volatile unsigned long*) 0x71300F00
-
-
void key1_isr()
-
{
-
//1.保存环境
-
__asm__(
-
"sub lr, lr, #4\n"
-
"stmfd sp!, {r0-r12,lr}\n"
-
:
-
:
-
);
-
//2.中断处理
-
led_on();
-
//3.清除中断
-
*(EINT0PEND) = ~(0x0);
-
*(VIC0ADDRESS) = 0;
-
*(VIC1ADDRESS) = 0;
-
//4.恢复环境
-
__asm__(
-
"ltmfd sp!, {r0-r12,pc}^\n"
-
:
-
:
-
);
-
}
-
-
void key8_isr()
-
{
-
//1.保存环境
-
__asm__(
-
"sub lr, lr, #4\n"
-
"stmfd sp!, {r0-r12,lr}\n"
-
:
-
:
-
);
-
//2.中断处理
-
led_off();
-
//3.清除中断
-
*(EINT0PEND) = ~(0x0);
-
*(VIC0ADDRESS) = 0;
-
*(VIC1ADDRESS) = 0;
-
//4.恢复环境
-
__asm__(
-
"ltmfd sp!, {r0-r12,pc}^\n"
-
:
-
:
-
);
-
}
-
-
void init_irq()
-
{
-
//配置按键下降沿
-
*(EINT0CON0) = 0b010; 设置EINT0和EINT1为下降沿触发
-
*(EINT0CON1) = 0b010<<8; 设置EINT21, 20为下降沿触发
-
-
*(EINT0MASK) = 0; 关闭EINT0屏蔽寄存器
-
-
*(VIC0INTENABLE) |= 0b1; 使能中断
-
*(VIC1INTENABLE) |= 0b10; 从INTERRUPT SOURCE表中了解到External interrupt 20 ~ 27在VIC1第2位。
-
-
*(EINT0_VICADDR) = key1_isr; 设置中断向量地址,中断0是0x71200100寄存器中,写入EIN0地址
-
*(EINT1_VICADDR) = key8_isr; 和使能类似,在EINT1中第二个0x71300104寄存器中,写入EINT20的地址
-
-
__asm__(
-
"mcr p15,0,r0,c1,c0,0\n" 使能向量中断功能,210不需要
-
"orr r0, r0, #(1<<24)\n"
-
"mcr p15,0,r0,c1,c0,0\n"
-
-
"mrs r0, cpsr\n" 同样是开启总中断的使能
-
"bic r0, r0, #0x80\n"
-
"msr cpsr_c, r0\n"
-
:
-
:
-
);
-
-
}
SRCPND:中断发生时,处理完后置1清除标志。0-32代表不同的中断类型
INTMSK:中断屏蔽位
INTPND:SRCPND说明了有什么中断被触发了,INTPND说明了CPU即将或已经在对某一个中断进行处理。
INTOFFSET:寄存器的功能则很简单,它的作用只是用于表明哪个中断正在被处理。
SUBSRCPND:有效的只有0—14位,SRCPND的细分类。
阅读(702) | 评论(0) | 转发(0) |