从ARM裸机看驱动之按键中断方式控制LED(一)
程序员文章站
2022-06-09 16:33:11
...
硬件环境:Samsung Cortex-A9 Exynos4412 BSP + JTAG ARM 仿真器
软件环境:Eclipse
=====================================================
从ARM裸机看驱动相关文章列表:
从ARM裸机看驱动之按键中断方式控制LED(二)http://blog.csdn.net/u010872301/article/details/78526055
=====================================================同志们,我需要大家跟我的思路一起思考:
1、程序是从主函数main开始执行的吗?2、按下按键后程序是怎么到达cpu核的?
3、异常向量表是什么鬼?
我将在从ARM裸机看linux驱动中一一道来!
芯片上电后,ARM处于SVC模式,首先要执行start.s文件,对芯片进行初始化,跳转到restart函数,设置异常向量表的首地址、初始化硬件接口、 初始化栈空间等。最后通过"b main"跳转到程序中的main函数。
.text
.global _start
_start:
@ 异常向量表 偏移地址顺序不能改变
b reset
ldr pc,_undefined_instruction
ldr pc,_software_interrupt
ldr pc,_prefetch_abort
ldr pc,_data_abort
@ 保留的向量表也要填充一条指令,否则后面的向量表地址会出现偏移
ldr pc,_not_used
b irq_handler
ldr pc,_fiq
@ 将各个异常处理函数的入口地址保存在当前位置
_undefined_instruction: .word _undefined_instruction
_software_interrupt: .word _software_interrupt
_prefetch_abort: .word _prefetch_abort
_data_abort: .word _data_abort
_not_used: .word _not_used
_irq: .word irq_handler
_fiq: .word _fiq
reset:
@ 设置异常向量表重新定位在0x40008000地址处存放
ldr r0,=0x40008000
mcr p15,0,r0,c12,c0,0 @ Vector Base Address Register
@ 初始化其他硬件接口
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0 @ Enable svc mode of cpu
mov r0, #0xfffffff
mcr p15, 0, r0, c1, c0, 2 @ Defines access permissions for each coprocessor
@ Privileged and User mode access
/*
* Invalidate L1 I/D
*/
mov r0, #0 @ set up for MCR
mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
@Set the FPEXC EN bit to enable the FPU:
MOV r3, #0x40000000
fmxr FPEXC, r3
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
orr r0, r0, #0x00001000 @ set bit 12 (---I) Icache
orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
mcr p15, 0, r0, c1, c0, 0
/* LED Test Code */
ldr r0, =0x114001E0
ldr r1, [r0]
bic r1, r1, #0xf0000
orr r1, r1, #0x10000
str r1, [r0]
ldr r0, =0x114001E8
ldr r1, [r0]
bic r1, r1, #0x300
str r1, [r0]
ldr r0, =0x114001E4
ldr r1, [r0]
orr r1, r1, #0x10
str r1, [r0]
@ 初始化各个模式的栈空间
init_stack:
@ 将整个栈空间的栈顶赋值给R0
ldr r0,stacktop /*get stack top pointer*/
/********svc mode stack********/
@ 初始化SVC模式下的栈
mov sp,r0
@ 栈顶下降512字节
sub r0,#128*4 /*512 byte for irq mode of stack*/
/****irq mode stack**/
@ 切换模式到IRQ模式
msr cpsr,#0xd2
@ 初始化IRQ模式下的栈顶
mov sp,r0
@ 栈顶下降512字节
sub r0,#128*4 /*512 byte for irq mode of stack*/
/***fiq mode stack***/
msr cpsr,#0xd1
mov sp,r0
sub r0,#0
/***abort mode stack***/
msr cpsr,#0xd7
mov sp,r0
sub r0,#0
/***undefine mode stack***/
msr cpsr,#0xdb
mov sp,r0
sub r0,#0
/*** sys mode and usr mode stack ***/
msr cpsr,#0x10
mov sp,r0 /*1024 byte for user mode of stack*/
@ 跳转到main函数
b main
.align 4
/**** swi_interrupt handler ****/
/**** irq_handler ****/
@ 中断处理函数
irq_handler:
@ 返回地址修正
sub lr,lr,#4
@ 进一步保存现场 将R0-R12寄存器中的值保存到栈空间 同时把返回地址也压栈
stmfd sp!,{r0-r12,lr}
@ 伪操作 do_irq函数没定义是编译时不报错
.weak do_irq
@ 跳转到中断处理函数 保存返回地址到LR中
bl do_irq
@ 出栈恢复现场 同时将LR赋值给PC实现程序的返回 ^表示出栈的同时恢复CPSR
ldmfd sp!,{r0-r12,pc}^
@ 栈顶初始化在了栈空间的最高地址处 因为使用的栈是满减栈
stacktop: .word stack+4*512
@ 将栈空间初始化在数据段内
.data
@ 在当前位置申请了4*512个字节的空间作为栈空间
stack: .space 4*512
设置异常向量表的首地址(0x40008000)?异常向量表在内存当中的向量地址固定,基地址可以通过设置来改变。当一个异常产生时( 比如按键中断),ARM自动将PC的值指向异常向量表当中对应的位置,异常向量表当中只能存储一条指令,所以一般我们将异常向量表中存储一条跳转指令来跳转对应的异常处理函数处。下图每个字都很关键, 在ARM进行模式切换时ARM自动将当前模式下的CPSR保存到需要跳转到的模式下的SPSR当中,当异常处理完成程序返回时将lr-4赋值给PC(sub lr,lr,#4),再将SPSR当中的值恢复到CPSR中,CPSR当中保存的值永远是当前程序运行的状态(“当前程序状态寄存器”的由来)。
为了让FIQ 响应中断尽可能的快,除了 FIQ中断优先级高于IRQ,我们看到右图FIQ 向量位于异常向量表的最末.异常处理程序可从异常量处连续执行,FIQ 模式有5个额外的私有寄存器 (r8-r12),在程序跳转时不需要压栈处理,也不需要保护现场。
中断处理的过程如下图:
通过按键K2发送中断给内核,设置相应的寄存器,分别设置外设(GPIO)、中断管理器(gic)和ARM内核。所以按下K2产生的中断到达cpu核要经过以下三级控制:
1. 外设控制器(GPIO控制器)
2. 中断控制器(GIC)
3. CPU核
1. 外设控制器(GPIO控制器)
2. 中断控制器(GIC)
3. CPU核
1.外设层次
(1)设置引脚。K2连接在GPX1_1引脚上,设置该引脚为中断功能。
(2)配置EXT_INT41_CON寄存器为下降沿触发方式。规定了当按键按下时,为发送中断信号。
(3)配置EXT_INT41_MASK寄存器,设置为使能中断。打开外设通往gic的开关。
GPIO中还有一个EXT_INT41_PEND[1]寄存器,当中断发生时,会自动置1,不断的向GIC中发送信号,告诉GIC有中断信号触发。当中断处理完后要手动置0,停止通知。向寄存器中写入1置0。
2.中断管理器层次
ARM中可以处理160种中断,每一种中断有一个中断号,从0编到159号。
(1)配置ICDDCR寄存器。全局使能GIC,该寄存器是所有中断通往内核的开关。将寄存器置1,即打开开关,中断才可以交给内核处理。
(2)配置ICDISER寄存器。每个ICDISER寄存器为三十二位,GIC共有五个该寄存器,一共有160位,每一位对应一个中断号。相应位的中断号置1,允许该中断打开。
(3)配置ICDIPTR_cpu寄存器。在多核ARM中,gic通过该寄存器设置将中断交给相应的内核处理,该寄存器有32位,每八位管理一种中断,每一位对应一个内核,内核从0编号到7。gic*有40个ICDIPTR寄存器,管理160个中断。相应位置1,就说明交由哪个CPU处理。
3.CPU层次
(1)配置ICCICR_CPUn寄存器。控制gic向CPU发送中断。有四个ICCICR寄存器,对应四个内核。是中断进入内核的开关。
(2)配置ICCPMR_CPUn寄存器。设置一个标准的优先级,等待进入CPU的中断的优先级如果高于此优先级则可以进入内核,否则不可进入。相当于一个过滤器,将低优先级的中断全部过滤掉。可设的值的范围为0~255。
以上三部分是配置中断寄存器步骤,配置好后,等待中断的发生。
当K2按键按下后,PC首先会跳转到异常向量表,在异常向量表中找到相应的中断,然后,跳转到中断程序入口do_irq。进入do_irq后,内核会“询问”gic是哪个中断。
ICCIAR_CPUn寄存器保存的是当前进入内核的中断的中断号,该寄存器位于gic中,执行do_irq函数时,内核会在此寄存器中获取当前中断的中断号。
(1)读取中断号。
(2)清理挂起位。将EXT_INT41_PEND寄存器置0,告诉外设不要再向中断管理器发送中断信号。
(3)配置ICCEOIR_CPUn寄存器,将处理完后的中断的中断号写入ICCEOIR_CPU寄存器。
#include "exynos_4412.h"
/*************
功能:通过中断方式检测按键是否按下并在中断服务函数中写处理函数,灯会闪烁一次
使用中断需要设置三个层次
1.外设层次,打开外设层次的中断功能使中断能够到达中断管理器
2.GIC层次,设置优先级,目标CPU,管理挂起状态等
3.CPU层次,中断优先级屏蔽等功能
中断管理器:统一管理中断主要涉及中断优先级,中断挂起,中断交给哪个CPU去处理等问题
**************/
void Dealy_Ms(unsigned int Time)
{
unsigned int i,j;
for(i=0;i<Time;i++)
for(j=0;j<2500;j++);
}
void do_irq(void)
{
unsigned int irq_num;
/*读取本次中断的中断号*/
irq_num = CPU0.ICCIAR & 0x3FF;
switch(irq_num)
{
case 57:
GPX2.DAT = GPX2.DAT & (~(1 << 7));
/*LEDOFF*/
Dealy_Ms(5000);
/*清除外设层次中断挂起位 告诉外设不要再向中断管理器中送中断信号 写1清零*/
EXT_INT41_PEND = (1 << 1);
break;
case 58:
break;
default:
break;
}
/*将中断号写回到GIC通知GIC可以将新的中断送进来*/
CPU0.ICCEOIR = CPU0.ICCEOIR & ~(0x3FF) | (irq_num);
}
void main(void)
{
/*****外设层次*****/
/*1.将GPX1_1管脚设置成中断功能*/
GPX1.CON = GPX1.CON & ~(0xF << 4) | (0xF << 4);
/*2.设置GPX1_1管脚中断触发方式为下降沿触发*/
EXT_INT41_CON = EXT_INT41_CON & ~(0x7 << 4) | (2 << 4);
/*3.使能GPX1_1管脚的中断使其能够顺利到达GIC中断控制器*/
EXT_INT41_MASK = EXT_INT41_MASK & ~(1 << 1);
/*****GIC层次*****/
/*4.全局使能GIC使GIC的信号能到达CPU接口 ICDDCR*/
ICDDCR = ICDDCR | 1;
/*5.在中断管理器中打开相应的位使能GPX1_1管脚的中断(57)ICDISER*/
ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);
/*6.将57号中断指定给对应的处理器去处理ICDIPTR*/
ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & ~(0xFF << 8) | (1 << 8);
/*****CPU层次*****/
/*7.全局使能中断信号能够通过相应的CPU接口到达相应的处理器ICCICR_CPU0,每个CPU对应一个相应的寄存器*/
CPU0.ICCICR = CPU0.ICCICR | 1;
/*8.设置中断优先级屏蔽寄存器ICCPMR_CPU0,使所有的中断都能通过CUP接口到达处理器*/
CPU0.ICCPMR = CPU0.ICCPMR | 0xFF;
GPX2.CON = GPX2.CON & (~(0xf << 28)) | (1 << 28);
/*将GPX2_7管脚设置成输出功能*/
while(1)
{
GPX2.DAT = GPX2.DAT | (1 << 7);
/*GPX2_7输出高电平*/
Dealy_Ms(500);
/*延时*/
GPX2.DAT = GPX2.DAT & (~(1 << 7));
/*GPX2_7输出低电平*/
Dealy_Ms(500);
/*延时*/
}
}
我写代码的思路,先看原理图,再看看芯片手册,配置相关寄存器:一、看原理图
发现KEY2接在CPU上的GPX1_1引脚上(配置引脚的功能,作为中断功能)
分析发现key2从按下到抬起对应的引脚一共经历了4个状态(设置中断触发方式)
二、看芯片手册,配置相关寄存器
1. 在外设控制器上使能KEY2中断
GPX1CON //引脚的功能配置寄存器
GPX1CON[1] [7:4]:0xF = WAKEUP_INT1[1] //中断功能、
EXT_INT41CON //设置中断的触发方式
EXT_INT41_CON[1] [6:4]: 0x2 = Triggers Falling edge //下降沿触发中断
EXT_INT41_MASK //GPIO控制器上的使能开关
EXT_INT41_MASK[1] [1]: 0x0 = Enables Interrupt //使能KEY2(GPX1_1)
EXT_INT41_PEND //清中断
EXT_INT41_PEND[1] [1]: 写1清中断
2、GIC
ICDISER1 //GIC中,每个中断的小开关,一个位对应一个中断
Set-enable bits [25]: 1 enable
ICDDCR //全局使能,让所有中断都能到达CPU接口
Enable [0]: 1 enable
ICDICPR1 //在GIC的层次上进行清中断操作
Clear-pending bits [25]: 写1清中断
ICDIPTR14
CPU targets, byte offset 1 [15:8]:0x1 //将57号中断分发给CPU0来处理
3、CPU
ICCICR_CPU0 //全局使能,让所以到达CPU接口的中断都能通过CPU接口到达所连接的处理器
Enable [0]: 1 = Enables signaling of interrupts
ICCPMR_CPU0 //设置中断优先级门限
Priority [7:0]: 一共256个优先级,值越大优先级越低 0xff(门限最低,所有中断都能通过)
ICCIAR_CPU0 //当中断发生了之后,我们可以通过读取这个寄存器获取所发生的中断对应的中断号
ACKINTID [9:0]: The interrupt ID //中断号
ICCEOIR_CPU0 //当中断处理完成之后,将读取到的中断号写回到这个寄存器,表示cpu对这个中断已经处理完成,可以处理下一个中断
EOIINTID [9:0]: The ACKINTID value from the corresponding ICCIAR access.
下载
上一篇: 步进电机、伺服电机、舵机的理解
下一篇: zigbee按键中断点亮流水灯