14.1.6 中断处理实现
一、中断介绍
中断的概念:外设和CPU进行数据通信
中断的概念:外设和CPU进行数据通信
1)通信的两种方式
a.查询方式:CPU循环查询(轮询)
优点:实现简单
缺点:效率低
b.中断方式:外设主动通知CPU机制
优点:效率高
缺点:实现复杂
2)CPU接受中断后的流程
a.保存现场 stmfd
b.查询中断控制器 check interruptor
c.执行相关中断处理函数 excute function
d.清理中断 clear interruption
e.恢复现场 ldmfd
优点:实现简单
缺点:效率低
b.中断方式:外设主动通知CPU机制
优点:效率高
缺点:实现复杂
2)CPU接受中断后的流程
a.保存现场 stmfd
b.查询中断控制器 check interruptor
c.执行相关中断处理函数 excute function
d.清理中断 clear interruption
e.恢复现场 ldmfd
二、中断控制器工作流程讲解
1)S5PC100_UM_ERV1.02.pdf中解释到,SMART210支持128个中断。
2)中断控制器内部工作流程图(VIC具体流程图)
3)流程图解释
a。按键来一个中断通知
b。通知消息首先到达GPIO,我们需要设置相应的寄存器为中断的功能,至于如何设置,请看下面的代码展示。
GPIO有如下几个寄存器:
CON(管脚功能设置寄存器,设置为输出还是输入模式)
DAT(设置当时的输出状态的寄存器)
INTC(中断配置寄存器,设置管脚是高电平还是低电平触发)
PND(记录中断)
MAS(掩码寄存器,也就是用于设置开关的寄存器
c。消息通过GPIO后来到了VIC,我们需要提前设置VIC里的执行函数以及其他参数。
d。中断消息已经通知CPU,CPU再反过来执行VIC里的执行函数,完成相应的中断处理。
三、外部中断实现
1)Makefile和ARM概述(2)中的基本一致,这里不再赘述。
1)Makefile和ARM概述(2)中的基本一致,这里不再赘述。
2)start.s:
.global _start
_start:
@这里开始地址为0x41000000
b reset
b undef
b swi
b abt_pre
b abt_dat
b reserved
b irq
b fiq
reset:
@CPU启动进入到reset
b reset
b undef
b swi
b abt_pre
b abt_dat
b reserved
b irq
b fiq
reset:
@CPU启动进入到reset
@重新映射异常向量表到0x4000 8000
ldr r0,=0x40008000
mcr p15, 0, r0, c12, c0, 0
@打开CPU的总开关
mrs ro,cpsr
bic r0,r0,#(0x1<<7)
msr cpsr,r0
@跳转到main函数
bl main
bic r0,r0,#(0x1<<7)
msr cpsr,r0
@跳转到main函数
bl main
loop:
b loop
b loop
undef:
swi:
abt_pre:
abt_dat:
reserved:
irq:
@产生中断
@we need to init the stack pointer,because the mode has changed
ldr sp,=0x42000000
stmfd sp!, {r0-r12, lr}
bl irq_handler
@we need to init the stack pointer,because the mode has changed
ldr sp,=0x42000000
stmfd sp!, {r0-r12, lr}
bl irq_handler
//执行中断处理函数
ldmfd sp!,{r0-r12, lr}
subs pc, lr, #4
ldmfd sp!,{r0-r12, lr}
subs pc, lr, #4
//为了防止语句漏执行,我们需要将lr-4后赋给pc
fiq:
3)mian函数:
#define VIC0INTSELECT (*(volatile unsigned int *)0xF200000C)
#define VIC0INTENABLE (*(volatile unsigned int *)0xF2000010)
#define VIC0VECTADDR0 (*(volatile unsigned int *)0xF2000100)
#define GPH0CON (*(volatile unsigned int *)0xE0200C00)
#define EXT_INT_0_CON (*(volatile unsigned int *)0xE0200E00)
#define EXT_INT_0_MASK (*(volatile unsigned int *)0xE0200F00)
#define VIC0INTENABLE (*(volatile unsigned int *)0xF2000010)
#define VIC0VECTADDR0 (*(volatile unsigned int *)0xF2000100)
#define GPH0CON (*(volatile unsigned int *)0xE0200C00)
#define EXT_INT_0_CON (*(volatile unsigned int *)0xE0200E00)
#define EXT_INT_0_MASK (*(volatile unsigned int *)0xE0200F00)
#define EXT_INT_0_PEND (*(volatile unsigned int *)0xE0200F40)
#define VICADDRESS (*(volatile unsigned int *)0xF2000F00)
int key1_handler(){
//利用函数指针调用printf,因为裸板没有该函数
int (*printf)(char *format,...);
printf=(void *)0x3ff13e54;
#define VICADDRESS (*(volatile unsigned int *)0xF2000F00)
int key1_handler(){
//利用函数指针调用printf,因为裸板没有该函数
int (*printf)(char *format,...);
printf=(void *)0x3ff13e54;
printf("hello key0");
EXT_INT_0_PEND = 0x1;//清零gpio的中断记录
}
int main(){
//初始化中断控制器
VIC0INTSELECT= 0;
//注册中断控制函数
VIC0VECTADDR0=(unsigned int)key1_handler();
//开启0号中断,相当于向第一个地址里放一个函数。
VIC0INTSELECT |=0x1;
//初始化Key0,因为这里中断检测的是 GPH0_0管脚,也就是key0
GPH0CON |= 0xf;
//把gph0_0设置成中断功能
EXT_INT_0_CON &= ~0xf;
EXT_INT_0_CON |= 0x3;
//设置为上升沿触发
//设置掩码,将掩码开关打开
EXT_INT_0_MASK &= ~0x1;
while(1){
//设置掩码,将掩码开关打开
EXT_INT_0_MASK &= ~0x1;
while(1){
;
}
}
//while死循环等待key按下发生中断。一旦发生中断,CPU就会切换到异常向量表
return 0;
}
void irq_handler(){
void (*handler)();
handler=( void *)VIC0VECTADDR0;
handler();
return 0;
}
void irq_handler(){
void (*handler)();
handler=( void *)VIC0VECTADDR0;
handler();
VIC0ADDRESS = 0;//清零addr0中的中断记录
}//cpu发生中断后,进入到本函数,然后到指定的ADDR0中取得相应的处理函数
=======================================================================================
四、中断程序优化
这里程序优化主要是将main函数中的开启的中断号进行了改进和完善,并与irq_handler封装成了irq.c。这时我们需要修改相应的Makefile。
1)中断处理函数(irq.c)
#define VIC0INTSELECT(*(volatile unsigned int*)0xF200000C)
#define VIC1INTSELECT(*(volatile unsigned int*)0xF210000C)
#define VIC2INTSELECT(*(volatile unsigned int*)0xF220000C)
#define VIC3INTSELECT(*(volatile unsigned int*)0xF230000C)
#define VIC0VECTADDR (( volatile unsigned int *)0xF2000100)
#define VIC1VECTADDR (( volatile unsigned int *)0xF2100100)
#define VIC2VECTADDR (( volatile unsigned int *)0xF2200100)
#define VIC3VECTADDR (( volatile unsigned int *)0xF2300100)
#define VIC0INTENABLE (*(volatile unsigned int *)0xF2000010)
#define VIC1INTENABLE (*(volatile unsigned int *)0xF2100010)
#define VIC2INTENABLE (*(volatile unsigned int *)0xF2200010)
#define VIC3INTENABLE (*(volatile unsigned int *)0xF2300010)
#define VIC0ADDRESS (*(volatile unsigned int *)0xF2000F00)
#define VIC1ADDRESS (*(volatile unsigned int *)0xF2100F00)
#define VIC2ADDRESS (*(volatile unsigned int *)0xF2200F00)
#define VIC3ADDRESS (*(volatile unsigned int *)0xF2300F00)
int request_irq(int irq,void (*handler)())
{
//初始化中断控制器
//选择irq中断
VIC0INTSELECT = 0;
VIC1INTSELECT = 0;
VIC2INTSELECT = 0;
VIC3INTSELECT = 0;
if(irq>= 0 && irq<= 31)
VIC0VECTADDR[irq]=(unsigned int)handler;
else if(irq>= 32 && irq<= 63){
irq-=32;
VIC1VECTADDR[irq]=(unsigned int)handler;
}
else if(irq>= 64 && irq<= 95){
irq-=64;
VIC2VECTADDR[irq]=(unsigned int)handler;
}
else if(irq>= 96 && irq<= 128){
irq-=96;
VIC3VECTADDR[irq]=(unsigned int)handler;
}
return 0;
}
void irq_handler()
{
void(*handler)();
if(VIC0ADDRESS !=0)//如果C0发生中断
handler=(void*)VIC0ADDRESS();
else if(VIC1ADDRESS !=0)
handler=(void*)VIC1ADDRESS();
else if(VIC2ADDRESS !=0)
handler=(void*)VIC2ADDRESS();
else if(VIC3ADDRESS !=0)
handler=(void*)VIC3ADDRESS();
handler();
VIC0ADDRESS = 0;
VIC1ADDRESS = 0;
VIC2ADDRESS = 0;
VIC3ADDRESS = 0;
}
2)main函数
#define GPH0CON (*(volatile unsigned int *)0xE0200C00)
#define EXT_INT_0_CON (*(volatile unsigned int *)0xE0200E00)
#define EXT_INT_0_MASK (*(volatile unsigned int *)0xE0200F00)
#define EXT_INT_0_PEND (*(volatile unsigned int *)0xE0200F40)
#define VICADDRESS (*(volatile unsigned int *)0xF2000F00)
int key1_handler(){
//利用函数指针调用printf,因为裸板没有该函数
int (*printf)(char *format,...);
printf=(void *)0x3ff13e54;
#define VICADDRESS (*(volatile unsigned int *)0xF2000F00)
int key1_handler(){
//利用函数指针调用printf,因为裸板没有该函数
int (*printf)(char *format,...);
printf=(void *)0x3ff13e54;
printf("hello key0");
EXT_INT_0_PEND = 0x1;//清零gpio的中断记录
}
int main(){
//初始化中断控制器
request_irq(0,key1_handler);
//初始化Key0,因为这里中断检测的是 GPH0_0管脚,也就是key0
GPH0CON |= 0xf;
//初始化Key0,因为这里中断检测的是 GPH0_0管脚,也就是key0
GPH0CON |= 0xf;
//把gph0_0设置成中断功能
EXT_INT_0_CON &= ~0xf;
EXT_INT_0_CON |= 0x3;
//设置为上升沿触发
//设置掩码,将掩码开关打开
EXT_INT_0_MASK &= ~0x1;
while(1){
//设置掩码,将掩码开关打开
EXT_INT_0_MASK &= ~0x1;
while(1){
;
}
}
//while死循环等待key按下发生中断。一旦发生中断,CPU就会切换到异常向量表
return 0;
}
return 0;
}
五、看门狗(WDT)
1)看门狗的简单介绍
CPU的一个外设,保护CPU的,防止其死机。如果死机就会发送有一个复位信号进行复位。
看门狗有2个功能,分别由WTCON【2】和WTCON【0】控制。前者控制是否产生中断;后者控制是否复位。
计数器:(Down Counter)必须有一个数值限制。设定计数器为固定值的操作为喂狗操作。
2)WDT驱动具体实现
vi main.c
#define WTCON (*(volatile unsigned int *)0xE2700000)
#define WTDAT (*(volatile unsigned int *)0xE2700004)
#define WTCNT (*(volatile unsigned int *)0xE2700008)
#define WTCLRINT (*(volatile unsigned int *)0xE270000C)
//828 配置寄存器
void wat_handler(){
int (*printf)(char *format,...);
printf=(void*)0x3ff13e54;
printf("hello watchdog!\n\r");
WTCLRINT = 1;
//清理中断
}
void wdt_init(){
WTCON = (65<<8) | (0x1<<5) | (0x1<<3) | (0x1<<2) | 0
WTCON = (65<<8) | (0x1<<5) | (0x1<<3) | (0x1<<2) | 0
// Prescaler value=65;打开看门狗定时器;clock division factor=32;打开中断开关;禁止复位操作
//- t_watchdog = 1/( PCLK / (Prescaler value + 1) / Division_factor ) 看门狗周期计算
// t= 1/66/66/32=
32us
WTCNT= 1000*1000/32;//1s
WTDAT=
1000*1000/32;
//每次减完1s后,WTDAT自动赋值给WTCNT
}
int main(){
request_irq(27,wat_handler);//526
while(1){
;
}
return 0;
}
===========================================================================================================================
14.1.7 时钟系统
一、时钟的概念
时钟主要是用于同步工作系统的同步信号。SOC内部有很多控制器,例如CPU,串口等外设,这些外设需要协同工作,互相通信,就需要同步的时钟系统来指挥,这个时候就需要SOC的时钟系统。
时钟主要是用于同步工作系统的同步信号。SOC内部有很多控制器,例如CPU,串口等外设,这些外设需要协同工作,互相通信,就需要同步的时钟系统来指挥,这个时候就需要SOC的时钟系统。
时钟源的获得方式:
a)外部直接输入,用于IC传感器。
b)外部晶振+内部时钟发生器产生时钟,一般用于低频单片机。
c)外部晶振+内部时钟发生器+倍频电路+内部分频电路得到各种频率的时钟,用于高速芯片(SOC)。
二、210时钟讲解
APLL、MPLL、EPLL、VPLL:锁相环
DIV:分频器
MUX:或门
XXTI:晶振
三、210时钟编码测试
vi start.s
start.s:
.global _start
_start:
@这里开始地址为0x41000000
b reset
b undef
b swi
b abt_pre
b abt_dat
b reserved
b irq
b fiq
reset:
@CPU启动进入到reset
@重新映射异常向量表到0x4000 8000
ldr r0,=0x40008000
mcr p15, 0, r0, c12, c0, 0
@打开CPU的总开关
mrs ro,cpsr
bic r0,r0,#(0x1<<7)
msr cpsr,r0
@跳转到初始化函数
start.s:
.global _start
_start:
@这里开始地址为0x41000000
b reset
b undef
b swi
b abt_pre
b abt_dat
b reserved
b irq
b fiq
reset:
@CPU启动进入到reset
@重新映射异常向量表到0x4000 8000
ldr r0,=0x40008000
mcr p15, 0, r0, c12, c0, 0
@打开CPU的总开关
mrs ro,cpsr
bic r0,r0,#(0x1<<7)
msr cpsr,r0
@跳转到初始化函数
bl clock_init
@跳转到main函数
bl main
loop:
b loop
undef:
swi:
abt_pre:
abt_dat:
reserved:
irq:
@产生中断
@we need to init the stack pointer,because the mode has changed
ldr sp,=0x42000000
stmfd sp!, {r0-r12, lr}
@跳转到main函数
bl main
loop:
b loop
undef:
swi:
abt_pre:
abt_dat:
reserved:
irq:
@产生中断
@we need to init the stack pointer,because the mode has changed
ldr sp,=0x42000000
stmfd sp!, {r0-r12, lr}
bl irq_handler
//执行中断处理函数
ldmfd sp!,{r0-r12, lr}
subs pc, lr, #4
//为了防止语句漏执行,我们需要将lr-4后赋给pc
fiq:
vi clock.s
.global clock_init
clock_init:
@关闭使用PLL
ldr r0, =0xE0100200
ldr r1, =0x0;
str r1, [r0]
@将0写入0xe……,此时CPU为24M
@设置PLL
@(APLL_LOCK,0xE0100000)
@(MPLL_LOCK,0xE0100008)
@(EPLL_LOCK,0xE0100010)
@(VPLL_LOCK,0xE0100020)
ldr r0, =0xE0100000
ldr r1, =30*24
str r1, [r0]
@每个周期是24分之1us。现在我的locktime是30us,那么要经过多少个周期?其实就是30除以24分之一。
ldr r0, =0xE0100008
ldr r1, =200*24
str r1, [r0]
ldr r0, =0xE0100010
ldr r1, =375*24
str r1, [r0]
ldr r0, =0xE0100020
ldr r1, =100*24
str r1, [r0]
@设置倍频因子
@APLL_CON0
ldr r0, =0xE0100100
ldr r1, =(1<<31)|(125<<16)|(3<<8)|(1<<0)
str r1, [r0]
@MPLL_CON0
ldr r0, =0xE0100108
ldr r1, =(1<<31)|(667<<16)|(12<<8)|(1<<0)
str r1, [r0]
@配置分频器
@CLK_DIV0
ldr r0, =0xE0100300
ldr r1, =(1<<28)|(4<<24)|(1<<20)|(3<<16)|(1<<12)|(4<<8)|(4<<4)|0
str r1, [r0]
@使用PLL
ldr r0, =0xE0100200
ldr r1, =0x1111;
str r1, [r0]
@将0写入0xe……,此时为锁相环输出
delay:
mov r0, 0x100000
d:
subs r0,r0, #1
bne d
mov pc,lr
@回去
四:210时钟设置程序测试程序
测试原理:如果我们使得输出为66MHZ时,而控制串口输出的时钟也是的66MHZ,如果printf能用我们设置的频率 进行数据打印,那么我们就算是设置成功。
四:210时钟设置程序测试程序
测试原理:如果我们使得输出为66MHZ时,而控制串口输出的时钟也是的66MHZ,如果printf能用我们设置的频率 进行数据打印,那么我们就算是设置成功。
我们在上面程序的基础上,再加上之前wtd的main.c和irq.c就可以进行相应测试。