嵌入式入門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
複製代碼
相關文章
相關標籤/搜索