嵌入式入门6(异常和中断)

1、概述

1.一、异常

CPU一般采用顺序执行的方式执行代码,假如须要在某个时刻,打断CPU当前执行流程,去执行一些特定操做,执行完后,再返回到以前的程序继续执行,这一系列操做就涉及到异常linux

ARM处理器中定义了7种类型的异常(Exception):编程

异常 优先级 注释
复位异常(Reset) 1
数据异常(Data Abort) 2
快速中断异常(FIQ) 3
外部中断异常(IRQ) 4
预取异常(Prefetch Abort) 5
软中断异常(SWI) 6
未定义指令异常(Undefined interrupt) 6

因为正在执行的指令不可能既是一条软中断指令,又是一条未定义指令,因此软中断异常和未定义指令异常享有相同的优先级。markdown

发生异常时,CPU会跳转到特定的地址执行,这个地址被称为异常向量,每种异常对应着不一样的异常向量,用于它们紧密相邻,因此统称为异常向量表网络

image.png

每一个异常向量中,都保存着对应的异常处理函数地址,这样当异常发生时,就能自动调用到异常处理函数。ide

如:u-boot-1.1.6\cpu\arm920t\start.S函数

.globl _start
_start:
    b	reset
    ldr	pc, _undefined_instruction
    ldr	pc, _software_interrupt
    ldr	pc, _prefetch_abort
    ldr	pc, _data_abort
    ldr	pc, _not_used
    ldr	pc, _irq
    ldr	pc, _fiq
复制代码

1.二、中断

中断(Interrupt)也是异常的一种。oop

  • 中断源:

能够引发中断的信号源,如按键、定时器、网络数据等。fetch

中断控制器能够发信号给CPU告诉它发生了哪些中断。ui

中断处理过程重点在于保存现场以及恢复现场:spa

一、保存现场(各类寄存器)
二、处理异常(中断属于一种异常)
三、恢复现场

1.三、arm对异常(中断)处理过程

  • 一、初始化:

一、设置中断源,让它能够产生中断
二、设置中断控制器(能够屏蔽某个中断,优先级)
三、设置CPU总开关,使能中断

  • 二、执行其余程序:正常程序
  • 三、产生中断:按下按键--->中断控制器--->CPU
  • 四、cpu每执行完一条指令都会检查有无中断/异常产生
  • 五、发现有中断/异常产生,开始处理。

2、CPU模式、状态和寄存器

能够参考书籍 《ARM体系结构与编程》做者:杜春雷

  • 7种Mode:

image.png

用户模式不可直接进入其余模式,而其他6个特权模式(privileged mode),能够经过编程操做CPSR寄存器直接进入其余模式。

在终止模式中

指令预取终止:读某条错误的指令致使终止运行
数据访问终止:读写某个地址,这个过程出错

  • 2种State:

ARM state:使用ARM指令集,每一个指令4byte。
Thumb state:使用的是Thumb指令集,每一个指令2byte。

ARM指令和Thumb指令的区别

  • 寄存器:

通用寄存器
备份寄存器(banked register)
当前程序状态寄存器:CPSR(Current Program Status Register)
CPSR的备份寄存器:SPSR(Save Program Status Register)

image.png

其中,带灰色三角形表示改寄存器为备份寄存器,它是该模式下所特有的。好比:r13(sp)寄存器和r14(lr)寄存器,它们虽然名字与System/User模式下r1三、r14名字一致,但实际上它们是物理上不一样的寄存器。

lr寄存器保存了发生异常时的指令地址。

image.png

Bit位 含义 接收
M4 ~ M0 Mode bits 表示当前CPU处于哪种异常模式(Mode)
T State bits 表示CPU工做于Thumb State仍是ARM State
F FIQ disable 禁止全部的FIQ中断
I IRQ disable 禁止全部的IRQ中断,这个位是IRQ的总开关
Bit8 ~ Bit27 保留位
Bit28 ~ Bit31 状态位

M4~M0位的取值以下:

image.png

咱们能够读取这5位来判断CPU处于哪种模式,若是咱们在特权模式下,能够经过修改这些模式位,让ARM运行在不一样的异常模式下;

什么是状态位?好比说执行如下两条指令:

cmp R0, R1
beq xxx
复制代码

当执行第一条指令时,会比较R0与R1是否相等,其结果会影响到Z位的值:若是相等,那么Z位等于1,不然等于0。

第二句指令,会去判断Z位的值,若是Z位等于1则跳转,不然继续执行。

SPSR:程序状态备份寄存器,当发生异常时,它保存被中断的模式下的CPSR。假如咱们当前在系统模式下运行,CPSR是某个值,当发生中断时,会进入irq模式,这个CPSR_irq就保存系统模式下的CPSR。

image.png

咱们来翻译一下:

发生异常时,咱们的CPU会作什么事情

  • 一、把下一条指令的地址保存在LR寄存器里(某种异常模式的LR等于被中断的下一条指令的地址)

它有多是PC + 4有多是PC + 8,究竟是那种取决于不一样的状况

  • 二、把CPSR保存在SPSR里面(某一种异常模式下SPSR里面的值等于CPSR)
  • 三、修改CPSR的模式为进入异常模式(修改CPSR的M4 ~ M0进入异常模式)
  • 四、跳到向量表

退出异常怎么作?

  • 一、让LR减去某个值,让后赋值给PC(PC = 某个异常LR寄存器减去 offset)
  • 二、把CPSR的值恢复(CPSR 值等于某一个异常模式下的SPSR)
  • 三、清中断(若是是中断的话,对于其余异常不用设置)

退出异常时,LR减去什么值呢?也就是咱们如何返回到原来的程序继续执行呢?能够根据下面这个表来取值:

image.png

好比: 发生了SWI异常,能够把 R14_svc复制给PC。 发生了IRQ异常,能够把R14_irq的值减去4赋值给PC。

2、Thumb指令示例程序

Makefile

all: init.o uart.o main.o start.o
	#arm-linux-ld -Ttext 0 -Tdata 0x700 start.o uart.o main.o -o sdram.elf
	arm-linux-ld -T sdram.lds start.o init.o uart.o main.o -o sdram.elf
	arm-linux-objcopy -O binary -S sdram.elf sdram.bin
	arm-linux-objdump -D sdram.elf > sdram.dis
clean:
	rm *.bin *.o *.elf *.dis

%.o: %.c
	arm-linux-gcc -mthumb -c -o $@ $<

%.o: %.S
	arm-linux-gcc -c -o $@ $<
复制代码

start.S

.text
.global _start

_start:
    /* 省略如下代码:
     一、关闭看门狗
     二、设置时钟
     三、设置栈指针
    */

    /* 怎么从ARM State切换到Thumb State */
    adr r0, thumb_func
    add r0, r0, #1  /* bit0=1时,bx就会切换cpu State*/
    bx  r0

.code 16
thumb_func:
    bl sdram_init

    bl copy2sdram

    bl clean_bss

    bl uart0_init

    //bl main  /*bl相对跳转,程序仍在NOR/sram执行*/
    ldr r0, =main  /* 绝对跳转, 跳到SDRAM ,先把main的地址赋值给R0 */
    mov pc, r0  /*让后再移动到PC*/

halt:
    b halt
复制代码

在编写Thumb指令时,先要使用伪指令CODE16声明,并且在ARM指令中要使用BX指令跳转到Thumb指令。

若目标地址的bit[0]为0,则跳转时自动将CPSR中的标志位T复位,即把目标地址的代码解释为ARM代码。
若目标地址的bit[0]为1,则跳转时自动将CPSR中的标志位T置位,即把目标地址的代码解释为Thumb代码。

3、und异常示例程序

exception.c

#include "uart.h"

void printException(unsigned int cpsr, char* str)
{
    puts("Exception! cpsr = ");
    printHex(cpsr);
    puts(" ");
    puts(str);
    puts("\n\r");
}
复制代码

start.S

.text
.global _start

_start:
    b reset     /* vector 0: reset*/
    b do_und    /* vector 4: und*/

do_und:
    /* 执行到这里以前:
     * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
     * 2. SPSR_und保存有被中断模式的CPSR
     * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
     * 4. 跳到0x4的地方执行程序 
     */
    //设置sp_und
    ldr sp, =0x34000000

    //保存现场
    stmdb sp!, {r0-r12, lr}

    //处理und异常
    mrs r0, cpsr
    ldr r1, =und_string
    bl printException

    //恢复现场
    ldmia sp!, {r0-r12, pc}^ //^会把spsr的值恢复到cpsr里

und_string:
    .string "undefined instruction exception"

reset:
    /* 省略如下代码:
     一、关闭看门狗
     二、设置时钟
     三、设置栈指针
    */

    bl sdram_init

    bl copy2sdram

    bl clean_bss

    bl uart0_init

und_code:
    .word 0xdeadc0de // 故意加入一条未定义指令

    //bl main  /*bl相对跳转,程序仍在NOR/sram执行*/
    ldr pc, =main  /*绝对跳转,跳到SDRAM*/

halt:
    b halt
复制代码

编译烧写运行,发现确实发生了未定义指令异常。

可是上面的代码仍是存在问题:

  • 一、b do_und指令,会让后续代码在启动芯片上运行,而不是SDRAM上运行。
  • 二、因为string占据内存不固定,可能会形成下一行代码未进行4字节对齐,致使程序执行不正确。

改进以下:

.text
.global _start

_start:
    b reset            /* vector 0: reset*/
    ldr pc, und_addr   /* vector 4: und*/

und_addr:           //这里防止Nand启动时,去4k以外的地方读取do_und的地址。
    .word do_und

do_und:
    /* 执行到这里以前:
     * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
     * 2. SPSR_und保存有被中断模式的CPSR
     * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
     * 4. 跳到0x4的地方执行程序 
     */

    //设置sp_und
    ldr sp, =0x34000000

    //保存现场
    stmdb sp!, {r0-r12, lr}

    //处理und异常
    mrs r0, cpsr
    ldr r1, =und_string
    bl printException

    //恢复现场
    ldmia sp!, {r0-r12, pc}^ //^会把spsr的值恢复到cpsr里

und_string:
    .string "undefined instruction exception"

.align 4  // string可能致使reset函数没有4字节对齐

reset:
    /* 省略如下代码:
     一、关闭看门狗
     二、设置时钟
     三、设置栈指针
    */

    bl sdram_init

    bl copy2sdram

    bl clean_bss

    // 重定位完成后,跳转到SDRAM执行
    ldr pc, =sdram
sdram:

    bl uart0_init

und_code:
    .word 0xdeadc0de //故意加入一条未定义指令

    //bl main  /*bl相对跳转,程序仍在NOR/sram执行*/
    ldr pc, =main  /*绝对跳转,跳到SDRAM*/

halt:
    b halt
复制代码

4、swi异常示例程序

在Linux系统中,App通常运行在用户模式,这是一种受限的模式,如:不能访问硬件。而若是APP想访问硬件,只能经过异常,来切换到特权模式。

而中断、und异常可遇不可求,此时就只能使用swi软中断。

exception.c

#include "uart.h"

void printException(unsigned int cpsr, char* str) {
    puts("Exception! cpsr = ");
    printHex(cpsr);
    puts(" ");
    puts(str);
    puts("\n\r");
}

void printSWIVal(unsigned int* pSWI) {
    unsigned int val = *pSWI & (~0xFF000000);
    puts("SWI Val: ");
    printHex(val);
    puts("\n\r");
}

void printMode(unsigned int cpsr) {
    unsigned int mode = cpsr & 0x001f;
    puts("cpsr = ");
    printHex(cpsr);
    if(mode == 0x10) {
        puts(" User mode");
    } else if(mode == 0x11) {
        puts(" FIQ mode");
    } else if(mode == 0x12) {
        puts(" IRQ mode");
    } else if(mode == 0x13) {
        puts(" Supervisor mode");
    } else if(mode == 0x17) {
        puts(" Abort mode");
    } else if(mode == 0x1b) {
        puts(" Underfined mode");
    } else if(mode == 0x1f) {
        puts(" System mode");
    }
    puts("\n\r");
}
复制代码

start.S

.text
.global _start

_start:
    b reset	   	   /* vector 0: reset*/
    ldr pc, und_addr   /* vector 4: und*/
    ldr pc, swi_addr   /* vector 8: swi*/

und_addr:
    .word do_und

swi_addr:
    .word do_swi

do_und:
    /* 执行到这里以前:
     * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
     * 2. SPSR_und保存有被中断模式的CPSR
     * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
     * 4. 跳到0x4的地方执行程序 
     */

    //设置sp_und
    ldr sp, =0x34000000

    //保存现场
    stmdb sp!, {r0-r12, lr}

    //处理und异常

    //处理und异常
    mrs r0, cpsr
    ldr r1, =und_string
    bl printException

    //恢复现场
    ldmia sp!, {r0-r12, pc}^ //^会把spsr的值恢复到cpsr里

und_string:
    .string "undefined instruction exception"

.align 4  // string可能致使reset函数没有4字节对齐

do_swi:
    /* 执行到这里以前:
     * 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
     * 2. SPSR_svc保存有被中断模式的CPSR
     * 3. CPSR中的M4-M0被设置为11011, 进入到svc模式
     * 4. 跳到0x4的地方执行程序 
     */

    //设置sp_und
    ldr sp, =0x33e00000

    //保存现场
    stmdb sp!, {r0-r12, lr}
    //ATPCS规则:C函数会保存r4 ~ r11这几个寄存器,这些寄存器不会被c函数破坏
    mov r4, lr

    //处理svc异常
    mrs r0, cpsr
    bl printMode

    sub r0, r4, #4
    bl printSWIVal

    //恢复现场
    ldmia sp!, {r0-r12, pc}^ //^会把spsr的值恢复到cpsr里

reset:
    /* 省略如下代码:
     一、关闭看门狗
     二、设置时钟
     三、设置栈指针
    */

    bl sdram_init

    bl copy2sdram

    bl clean_bss

    // 重定位完成后,跳转到SDRAM执行
    ldr pc, =sdram
sdram:

    bl uart0_init

und_code:
    .word 0xdeadc0de //故意加入一条未定义指令

    //上电后是supervisor模式0b10011
    mrs r0, cpsr
    bl printMode //Spervisor mode

    mrs r0, cpsr      /* 读出cpsr 读到r0 */
    //使用bic命令 bitclean 把低4位清零
    bic r0, r0, #0xf  /* 修改M4-M0为0b10000, 进入usr模式 */
    msr cpsr, r0

    mrs r0, cpsr
    bl printMode //User mode

    swi 0x123 //执行此命令,会触发swi异常

    // bl main  /*bl相对跳转,程序仍在NOR/sram执行*/
    ldr pc, =main  /*绝对跳转,跳到SDRAM*/

halt:
    b halt
复制代码

5、按键中断示例程序

本示例程序中,将经过按键中断,控制LED。

整个流程以下:

  • 一、初始化:
一、设置中断源,让它能够发出中断
二、设置中断控制器,让它能够把中断信号发送给CPU
三、设置CPU,CPSR的I位,它是中断总开关
复制代码
  • 二、处理时:要分辨中断源,执行不一样的中断处理函数
  • 三、处理完:清中断

咱们想经过4个按键,控制3个LED灯。

image.png

具体控制信息以下:

按键 按键GPIO引脚 按键中断源 控制的LED LED GPIO引脚
EINT0 GPF0 EINT[0] nLED_1 GPF4
EINT2 GPF2 EINT[2] nLED_2 GPF5
EINT11 GPG3 EINT[11] nLED_4 GPF6
EINT19 GPG11 EINT[19] nLED_1\2\4 GPF4\5\6

5.一、配置LED GPIO

image.png

led.c

void init_led() {
    /* 设置GPFCON让GPF4/5/6配置为输出引脚 */
    GPFCON &= ~((3<<8) | (3<<10) | (3<<12));
    GPFCON |=  ((1<<8) | (1<<10) | (1<<12));
}
复制代码

5.二、配置按键中断

  • 一、配置按键引脚为中断引脚

image.png

  • 二、配置中断触发方式

image.png

这里EINT设置为111,表示双边缘触发,即按键按下抬起都会产生中断。
FLTEN表示是否打开滤波功能。 对于一些波形不规整的外部中断信号,能够经过滤波电路让其变成规整,这样会简化软件的编写。

  • 三、开启GPIO引脚中断

image.png

void key_eint_init() {
    puts("key_eint_init\r\n");
    //设置gpio引脚为中断引脚
    GPFCON &= ~((3<<0)|(3<<4));
    GPFCON |=  ((2<<0)|(2<<4));
    GPGCON &= ~((3<<6)|(3<<22));
    GPGCON |=  ((2<<6)|(2<<22));

    //设置外部中断触发方式:双边缘触发
    EXTINT0 |= ((7<<0)|(7<<8));
    EXTINT1 |= (7<<12);
    EXTINT2 |= (7<<12);

    //设置外部中断屏蔽寄存器EINTMASK,使能eint11,19
    EINTMASK &= ~((1<<11)|(1<<19));
}
复制代码

外部中断EINTPEND寄存器,经过读这个寄存器,能够知道发生了哪一个外部中断(eint4~23),使用完成后,须要清除改寄存器中对应的中断位。。

image.png

5.三、配置中断控制器

image.png

MASK:屏蔽寄存器
INTPND:等待处理,读取看哪一个中断产生的

有些中断,能够直接到达SRCPND。
有些中断,必须先经过SUBSRCPND,再到SUBMASK,才能到达SRCPND。

image.png

这里能够看到外部中断0~3分别使用各自的中断线,4~7合用一条中断线,8~23合用一条中断线。
而且这些中断是Sources,而不是Sub Sources,即它们能够直接到达SRCPND寄存器。因此咱们只需设置SRCPNDMASKINTPND三个寄存器便可。

SRCPND寄存器,表示发生了哪一个中断,执行完中断处理程序后,须要清除该寄存器。

image.png

INTMASK,中断屏蔽寄存器,若是某个中断位设置为1,即便该中断发生了,CPU也不会处理该中断。

image.png

INTPND寄存器,用来显示当前优先级最高的、正在发生的中断源。

当多个中断同时产生,在SRCPND中,有多个位被设置1,它们通过优先级后,只会有一个中断通知CPU。执行完中断处理程序后,也须要清除该寄存器。

image.png

INTOFFSET寄存器,表示INTPND寄存器中,哪一个中断正在被处理,能够显示INTPND寄存器哪一位被设置为1。

image.png

void interrupt_init() {
    puts("interrupt_init\r\n");
    INTMSK &= ~((1<<0)|(1<<2)|(1<<5));
}
复制代码

5.四、其他代码

.text
.global _start

_start:
    b reset            /* vector 0: reset*/
    ldr pc, und_addr   /* vector 4: und*/
    ldr pc, swi_addr   /* vector 8: swi*/
    b halt             /* vector 0x0c : prefetch aboot */
    b halt             /* vector 0x10 : data abort */
    b halt             /* vector 0x14 : reserved */
    ldr pc, irq_addr   /* vector 0x18 : irq */
    b halt             /* vector 0x1c : fiq */

und_addr:
    .word do_und

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的地方执行程序 
     */

    //设置sp_und
    ldr sp, =0x34000000

    //保存现场
    stmdb sp!, {r0-r12, lr}

    //处理und异常
    mrs r0, cpsr
    ldr r1, =und_string
    bl printException

    //恢复现场
    ldmia sp!, {r0-r12, pc}^ //^会把spsr的值恢复到cpsr里

und_string:
    .string "undefined instruction exception"

.align 4  // string可能致使reset函数没有4字节对齐

do_swi:
    /* 执行到这里以前:
     * 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
     * 2. SPSR_svc保存有被中断模式的CPSR
     * 3. CPSR中的M4-M0被设置为11011, 进入到svc模式
     * 4. 跳到0x4的地方执行程序 
     */

    //设置sp_und
    ldr sp, =0x33e00000

    //保存现场
    stmdb sp!, {r0-r12, lr}
    //ATPCS规则:C函数会保存r4 ~ r11这几个寄存器
    mov r4, lr

    //处理svc异常
    mrs r0, cpsr
    bl printMode

    sub r0, r4, #4
    bl printSWIVal

    //恢复现场
    ldmia sp!, {r0-r12, pc}^ //^会把spsr的值恢复到cpsr里

do_irq:
    /* 执行到这里以前:
     * 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址
     * 2. SPSR_irq保存有被中断模式的CPSR
     * 3. CPSR中的M4-M0被设置为10010, 进入到irq模式
     * 4. 跳到0x18的地方执行程序 
     */

    //设置sp_irq
    ldr sp, =0x33d00000

    //保存现场
    // 在irq异常处理函数中有可能会修改r0-r12, 因此先保存
    // lr-4是异常处理完后的返回地址, 也要保存
    sub lr, lr, #4
    stmdb sp!, {r0-r12, lr}

    bl handle_irq_c

    //恢复现场
    ldmia sp!, {r0-r12, pc}^ //^会把spsr_irq的值恢复到cpsr里

reset:
    /* 省略如下代码:
     一、关闭看门狗
     二、设置时钟
     三、设置栈指针
    */

    bl sdram_init

    bl copy2sdram

    bl clean_bss

    // 重定位完成后,跳转到SDRAM执行
    ldr pc, =sdram
sdram:

    bl uart0_init

und_code:
    .word 0xdeadc0de //故意加入一条未定义指令

    //上电后是supervisor模式0b10011
    mrs r0, cpsr
    bl printMode  //Spervisor mode

    mrs r0, cpsr      /* 读出cpsr 读到r0 */
    //使用bic命令 bitclean 把低4位清零
    bic r0, r0, #0xf  /* 修改M4-M0为0b10000, 进入usr模式 */
    bic r0, r0, #(1<<7) /* 清除I位,使能中断 */
    msr cpsr, r0

    mrs r0, cpsr
    bl printMode  //User mode

    swi 0x123 //执行此命令,会触发swi异常

    bl init_led        /* 初始化LED引脚 */
    bl interrupt_init  /* 初始化中断控制器 */
    bl key_eint_init   /* 初始化按键,设为中断源 */

    bl main

halt:
    b halt
复制代码

interrupt.c

#include "s3c2440_soc.h"

void key_eint_irq(int irq) {
    unsigned int val = EINTPEND;
    unsigned int val1 = GPFDAT;
    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() {
    puts("handle_irq_c\r\n");
    // 一、分辨中断源
    // 读INTOFFSET在芯片手册里找到这个寄存器,它里面的值表示INTPND中哪一位被设置成1
    int bit = INTOFFSET;
    // 二、调用对应的处理函数
    if (bit == 0 || bit == 2 || bit == 5)  /* 对应eint0,2,eint8_23 */
    {
        /*咱们会调用一个按键处理函数*/
        key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */
    }

    /** * 三、清中断 : 从源头开始清 * 先清除掉中断源里面的某些寄存器 * 再清 SRCPND * 再清 INTPND */
    SRCPND = (1<<bit);
    INTPND = (1<<bit);	
}
复制代码

6、定时器中断示例程序

  • S3C2440A有5个16位定时器。其中定时器0、一、2和3具备脉宽调制(PWM)功能。定时器4是一个无输出引脚的内部定时器。定时器0还包含用于大电流驱动的死区发生器。

  • 定时器0和1共用一个8位预分频器,定时器二、3和4共用另外的8位预分频器。每一个定时器都有一个能够生成5种不一样分频信号(1/2,1/4,1/8,1/16和TCLK)的时钟分频器。每一个定时器模块从相应8位预分频器获得时钟的时钟分频器中获得其本身的时钟信号。8位预分频器是可编程的,而且按存储在TCFG0和TCFG1寄存器中的加载值来分频PCLK。

  • 定时计数缓冲寄存器(TCNTBn)包含了一个当使能了定时器时的被加载到递减计数器中的初始值。定时比较缓冲寄存器(TCMPBn)包含了一个被加载到比较寄存器中的与递减计数器相比较的初始值。这种TCNTBn和TCMPBn的双缓冲特征保证了改变频率和占空比时定时器产生稳定的输出。

  • 每一个定时器有它本身的由定时器时钟驱动的16位递减计数器。当递减计数器到达零时,产生定时器中断请求通知CPU定时器操做已经完成。当定时器计数器到达零时,相应的TCNTBn的值将自动被加载到递减计数器以继续下一次操做。然而,若是定时器中止了,例如,在定时器运行模式期间清除TCONn的定时器使能位,TCNTBn的值将不会被从新加载到计数器中。

  • TCMPBn的值是用于脉宽调制(PWM)。当递减计数器的值与定时器控制逻辑中的比较寄存器的值相匹配时定时器控制逻辑改变输出电平。所以,比较寄存器决定PWM输出的开启时间(或关闭时间)。

image.png

image.png

一、每来一个CLK,TCNTn减1
二、当TCNTn等于0是,能够产生中断,PWM引脚翻转。
四、TCNTn等于0时,可设置自动加载初值。

怎么使用Timer?

  • 一、设置时钟
  • 二、设置初值
  • 三、加载初值,启动Timer
  • 四、设置为自动加载
  • 五、中断相关

6.一、设置Timer0

image.png

PWM时钟控制寄存器,这里涉及一个公式:

Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}

PCLK为50MHz,为了便于计算,将prescaler设置为99,divider value设置为1/16。可获得Timer0的设置频率为31250。也就是说,当TCNTn从31250减到0,才过去一秒钟。

image.png

因此这里TCFG1[3:0] = 3

image.png

再设置时钟控制寄存器TCON:

一、先设置TCON[1]=1,表示更新TCNTB0的值。
二、再设置TCON[1]=0,由于在写这个寄存器时,须要清除该位。
三、设置自动加载:TCON[3]=1
四、启动Timer0:TCON[0]=1

image.png

timer.c

void timer_init() {
    /* 设置TIMER0的时钟 * * Timer clock = PCLK / {prescaler value+1} / {divider value} * = 50000000 / (99 + 1) / 16 * = 31250 * 也就是说,当TCNTn从31250减到0,才过去一秒钟 */
    TCFG0 = 99;
    TCFG1 = 3;
    
    /* 设置TIMER0的初值 */
    TCNTB0 = 15625; /* 0.5s中断一次 */
    
    /* 设置手动更新 */
    TCON |= (1<<1); //update from TCNTB0 & TCMPB0
    
    /* 设置为自动加载, 并启动*/
    TCON &= ~(1<<1);
    TCON |= (1<<0) | (1<<3);
}
复制代码

6.二、设置中断

前面已经配置好了Timer0中断源,如今,须要配置中断控制器中的Timer0屏蔽寄存器,让CPU可以响应Timer0的中断。

image.png

interrupt.c

void interrupt_init() {
    puts("interrupt_init\r\n");

    /* 设置按键中断 */
    INTMSK &= ~((1<<0)|(1<<2)|(1<<5));

    /* 设置TIMER0中断 */
    INTMSK &= ~(1<<10);
}

// 省略部分代码
复制代码

设置完成后,Timer0将会每隔0.5秒,就会产生一个中断。

6.三、响应中断

interrupt.c

// 省略部分代码

void handle_irq_c() {
    puts("handle_irq_c\r\n");
    /*1 分辨中断源 */
    /*读INTOFFSET在芯片手册里找到这个寄存器,它里面的值表示INTPND中哪一位被设置成1*/
    int bit = INTOFFSET;
    /*2 调用对应的处理函数 */
    if (bit == 0 || bit == 2 || bit == 5)  /* 对应eint0,2,eint8_23 */
    {
        /*咱们会调用一个按键处理函数*/
        key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */
    } else if (bit == 10) /* 发生TIMER0时钟中断 */
    {
        timer_irq();
    }

    /*3 清中断 : 从源头开始清 *先清除掉中断源里面的某些寄存器 *再清 SRCPND *再清 INTPND */
    SRCPND = (1<<bit);
    INTPND = (1<<bit);	
}
复制代码

timer.c

// 省略部分代码

void timer_irq() {
    /* 电灯计数 */
    static int i = 4;
    //对数据寄存器4~6位取反
    GPFDAT ^= (1<<i);
    i++;
    if (i >= 7) {
        i=4;
    }
}
复制代码

start.S

.text
.global _start

_start:
    b reset            /* vector 0: reset*/
    ldr pc, und_addr   /* vector 4: und*/
    ldr pc, swi_addr   /* vector 8: swi*/
    b halt             /* vector 0x0c : prefetch aboot */
    b halt             /* vector 0x10 : data abort */
    b halt             /* vector 0x14 : reserved */
    ldr pc, irq_addr   /* vector 0x18 : irq */
    b halt             /* vector 0x1c : fiq */

und_addr:
    .word do_und

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的地方执行程序 
     */

    //设置sp_und
    ldr sp, =0x34000000

    //保存现场
    stmdb sp!, {r0-r12, lr}

    //处理und异常
    mrs r0, cpsr
    ldr r1, =und_string
    bl printException

    //恢复现场
    ldmia sp!, {r0-r12, pc}^ //^会把spsr的值恢复到cpsr里

und_string:
    .string "undefined instruction exception"

.align 4  // string可能致使reset函数没有4字节对齐

do_swi:
    /* 执行到这里以前:
     * 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
     * 2. SPSR_svc保存有被中断模式的CPSR
     * 3. CPSR中的M4-M0被设置为11011, 进入到svc模式
     * 4. 跳到0x4的地方执行程序 
     */

    //设置sp_und
    ldr sp, =0x33e00000

    //保存现场
    stmdb sp!, {r0-r12, lr}
    //ATPCS规则:C函数会保存r4 ~ r11这几个寄存器
    mov r4, lr

    //处理svc异常
    mrs r0, cpsr
    bl printMode

    sub r0, r4, #4
    bl printSWIVal

    //恢复现场
    ldmia sp!, {r0-r12, pc}^ //^会把spsr的值恢复到cpsr里

do_irq:
    /* 执行到这里以前:
     * 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址
     * 2. SPSR_irq保存有被中断模式的CPSR
     * 3. CPSR中的M4-M0被设置为10010, 进入到irq模式
     * 4. 跳到0x18的地方执行程序 
     */

    //设置sp_irq
    ldr sp, =0x33d00000

    //保存现场
    // 在irq异常处理函数中有可能会修改r0-r12, 因此先保存
    // lr-4是异常处理完后的返回地址, 也要保存
    sub lr, lr, #4
    stmdb sp!, {r0-r12, lr}

    bl handle_irq_c

    //恢复现场
    ldmia sp!, {r0-r12, pc}^ //^会把spsr_irq的值恢复到cpsr里

reset:
    /* 省略如下代码:
     一、关闭看门狗
     二、设置时钟
     三、设置栈指针
    */

    bl sdram_init

    bl copy2sdram

    bl clean_bss

    // 重定位完成后,跳转到SDRAM执行
    ldr pc, =sdram
sdram:

    bl uart0_init

und_code:
    .word 0xdeadc0de //故意加入一条未定义指令

    //上电后是supervisor模式0b10011
    mrs r0, cpsr
    bl printMode  //Spervisor mode

    mrs r0, cpsr      /* 读出cpsr 读到r0 */
    //使用bic命令 bitclean 把低4位清零
    bic r0, r0, #0xf  /* 修改M4-M0为0b10000, 进入usr模式 */
    bic r0, r0, #(1<<7) /* 清除I位,使能中断 */
    msr cpsr, r0
	
    mrs r0, cpsr
    bl printMode  //User mode

    swi 0x123 //执行此命令,会触发swi异常

    bl init_led /* 初始化LED引脚 */
    bl interrupt_init /* 初始化中断控制器 */
    bl key_eint_init  /* 初始化按键,设为中断源 */
    
    bl timer_init  /* 初始化时钟中断源 */
    
    bl main  /*bl相对跳转,程序仍在NOR/sram执行*/
    //ldr pc, =main  /*绝对跳转,跳到SDRAM*/

halt:
    b halt
复制代码
相关文章
相关标签/搜索