單片機
返回首頁

S3c2440ARM異常與中斷體系詳解8---定時器中斷程序示例

2021-09-10 來源:eefocus

這節課我們來寫一個定時器的中斷服務程序

使用定時器來實現點燈計數

查考資料就是第10章PWM TIMER


我們先把這個結構圖展示出來

在這里插入圖片描述

這個圖的結構很好


這里面肯定有一個clk(時鐘),

1 、每來一個clk(時鐘)這個TCNTn減去1

2、 當TCNTn == TCMPn時,可以產生中斷,也可以讓對應的SPWM引腳反轉,(比如說原來是高電平,發生之后電平轉換成低電平)

3、 TCNTn繼續減1,當TCNTn == 0時,可以產生中斷,pwm引腳再次反轉 TCMPn 和 TCNTn的初始值來自TCMPBn,TCNTBn

4 、TCNTn == 0時,可自動加載初始


怎么使用定時器?

1、 設置時鐘

2 、設置初值

3、 加載初始,啟動Timer

4、 設置為自動加載

5 、中斷相關

由于2440沒有引出pwm引腳,所以pwm功能無法使用,也就無法做pwm相關實驗,所謂pwm是指可調制脈沖

T1高脈沖和T2低脈沖它的時間T1, T2可調整,可以輸出不同頻率不同占控比的波形,在控制電機時特別有用


我們這個程序只做一個實驗,當TCNTn這個計數器到0的時候,就產生中斷,在這個中斷服務程序里我們點燈


寫代碼

打開我們的main函數


int main(void)

{

    led_init();

    interrupt_init();  /* 初始化中斷控制器 */

//我們初始化了中斷源,同樣的,我們初始化timer

    key_eint_init();   /* 初始化按鍵, 設為中斷源 */

//初始化定時器

    timer_init();


我們需要實現定時器初始化函數

新建一個timer.c ,我們肯定需要操作一堆寄存器,添加頭文件


#include "s3c2440_soc.h"


void timer_init(void)


設置TIMER0的時鐘

設置TIMER0的初值

加載初值, 啟動timer0

設置為自動加載并啟動(值到0以后會自動加載)

設置中斷,顯然我們需要提供一個中斷處理函數void timer_irq(void)在這里面我們需要點燈

打開芯片手冊,我們想設置timer0的話

首先設置8-Bit Prescaler

設置5.1 MUX(選擇一個時鐘分頻)

設置TCMPB0和TCNTB0

設置TCONn寄存器

在這里插入圖片描述

看手冊上寫如何初始化timer

在這里插入圖片描述

把初始值寫到TCNTBn 和TCMPBn寄存器

設置手動更新位

設置啟動位

往下看到時鐘配置寄存器

在這里插入圖片描述

有個計算公式

Timer clk = PCLK / {(預分頻數)prescaler value+1} / {divider value(5.1MUX值)}

PCLK是50M

= 50000000/(99+1)/16

= 31250

也就是說我們得TCON是31250的話,從這個值一直減到0


   Prescaler0等于99

  TCFG0 = 99; /* Prescaler 0 = 99, 用于timer0,1 */  


TCFG1 MUX多路復用器的意思,他有多路輸入,我們可以通過MUX選擇其中一路作為輸出

在這里插入圖片描述

根據上面mux的值,我們要把MUX0 設置成0011

只需要設置這4位即可,先清零

再或上 0011 就是3


TCFG1 &= ~0xf;

TCFG1 |= 3; /* MUX0 : 1/16 */


再來看看初始值控制寄存器

在這里插入圖片描述

一秒鐘點燈太慢了 ,就讓0.5秒

TCNTB0 = 15625; /* 0.5s中斷一次 */


這個寄存器是用來觀察里面的計數值的,不需要設置


現在可以設置TCON來設置這個寄存器

在這里插入圖片描述

現在需要設置Timer0

在這里插入圖片描述

開始需要手工更新

TCON |= (1<<1); /* Update from TCNTB0 & TCMPB0 */

把這兩個值放到TCNTB0 和 TCMPB0中

在這里插入圖片描述

注意:這一位必須清楚才能寫下一位


設置為自動加載并啟動,先清掉手動更新位,再或上bit0 bit3


TCON &=~ (1<<1); 

TCON |= (1<<0) | (1<<3); /* bit0: start, bit3: auto reload */


設置中斷,顯然我們需要提供一個中斷處理函數void timer_irq(void)

在Timer里沒有看到中斷相關的控制器,我們需要回到中斷章節去看看中斷控制器,看看有沒有定時器相關的中斷

我們沒有看到更加細致的Timer0寄存器


當TCNTn=TCMPn時,他不會產生中斷,會發生脈沖的反轉,只有當TCNTn等于0的時候才可以產生中斷,我們之前以為這個定時器可以產生兩種中斷,那么肯定有寄存器中斷或者禁止兩種寄存器其中之一,那現在只有一種中斷的話,就相對簡單些

設置中斷的話,我們只需要設置中斷控制器

設置interrupu.c中斷控制器


*初始化中斷控制器 void interrupt_init(void) 

INTMSK &= ~((1<<0) | (1<<2) | (1<<5));


*把定時器相應的位清零就可以了,哪一位呢?


INTPND的哪一位? 

INT_TIMER0第10位即可 

在這里插入圖片描述

INTMSK &= ~(1<<10); /* enable timer0 int */


當定時器減到0的時候就會產生中斷,就會進到start.s這里一路執行do_irq


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}  


    /* 處理irq異常 */

    bl handle_irq_c


    /* 恢復現場 */

    ldmia sp!, {r0-r12, pc}^  /* ^會把spsr_irq的值恢復到cpsr里 */



讓后進入irq處理函數中處理,處理這個irq

void handle_irq_c(void)

{

    /* 分辨中斷源 */

    int bit = INTOFFSET;


    /* 調用對應的處理函數 */


if(bit ==0 || bit == 2 || bit == 5)/*eint0,2,rint8_23*/

{

    key_eint_irq(bit);/*處理中斷,清中斷源EINTPEND*/

}else if(bit == 10)//如果等于10的話說明發生的是定時器中斷,這時候就調用我們得timer_irq

{

    timer_irq();

}


    /* 清中斷 : 從源頭開始清 */

    SRCPND = (1<    INTPND = (1<}


回到timer.c文件中,在這個定時器處理函數中我們需要點燈

void timer_irq(void)

{

    /* 點燈計數 循環點燈*/

    static int cnt = 0;

    int tmp;


    cnt++;


    tmp = ~cnt;

    tmp &= 7;

    GPFDAT &= ~(7<<4);

    GPFDAT |= (tmp<<4);

}


代碼寫完我們實驗一下,上傳代碼,在Makefile中添加timer.o,進行編譯

編譯后進行燒寫

發現燈已經開始閃

對程序進行改進


進入main函數中執行 timer_init();

還需要修改interrupt.c

初始化函數


void interrupt_init(void) 


還需要調用中斷處理函數


void handle_irq_c(void) 


每次添加一個中斷我都需要修改handle_irq這個函數,這樣太麻煩,我能不能保證這個interrupt文件不變,只需要在timer.c中引用即可,這里我們使用指針數組

在interrupt.c中定義函數指針數組


typedef void(*irq_func)(int); 


定義一個數組,我們來卡看下這里有多少項,一共32位,我們想把每一個中斷的處理函數都放在這個數組里面來,當發生中斷時,我們可以得到這個中斷號,讓后我從數組里面調用對應的中斷號就可以了


irq_func irq_array[32];


那么我們得提供一個注冊函數



void register_irq (int irq, irq_func fp)

{

    irq_array[irq] = fp;

    INTMASK &= ~(1 << irq)

}


以后我直接調用對應的處理函數


void handle_irq_c(void)

{

    /* 分辨中斷源 */

    int bit = INTOFFSET;



/* 調用對應的處理函數 */

irq_array[bit](bit);


    /* 清中斷 : 從源頭開始清 */

    SRCPND = (1<    INTPND = (1<}


//按鍵中斷初始化函數需要注冊


    /* 初始化按鍵, 設為中斷源 */

    void key_eint_init(void)

    {

    /* 配置GPIO為中斷引腳 */

    GPFCON &= ~((3<<0) | (3<<4));

    GPFCON |= ((2<<0) | (2<<4));   /* S2,S3被配置為中斷引腳 */


    GPGCON &= ~((3<<6) | (3<<22));

    GPGCON |= ((2<<6) | (2<<22));   /* S4,S5被配置為中斷引腳 */



    /* 設置中斷觸發方式: 雙邊沿觸發 */

    EXTINT0 |= (7<<0) | (7<<8);     /* S2,S3 */

    EXTINT1 |= (7<<12);             /* S4 */

    EXTINT2 |= (7<<12);             /* S5 */


    /* 設置EINTMASK使能eint11,19 */

    EINTMASK &= ~((1<<11) | (1<<19));


register_irq(0, key_eint_irq);

register_irq(2, key_eint_irq);

register_irq(5, key_eint_irq);

    }


在timer.c中也需要設置中斷


    void timer_init(void)

    {

    /* 設置TIMER0的時鐘 */

    /* Timer clk = PCLK / {prescaler value+1} / {divider value} 

                 = 50000000/(99+1)/16

                 = 31250

     */

    TCFG0 = 99;  /* Prescaler 0 = 99, 用于timer0,1 */

    TCFG1 &= ~0xf;

    TCFG1 |= 3;  /* MUX0 : 1/16 */


    /* 設置TIMER0的初值 */

    TCNTB0 = 15625;  /* 0.5s中斷一次 */


    /* 加載初值, 啟動timer0 */

    TCON |= (1<<1);   /* Update from TCNTB0 & TCMPB0 */


    /* 設置為自動加載并啟動 */

     TCON &= ~(1<<1);

     TCON |= (1<<0) | (1<<3);  /* bit0: start, bit3: auto reload */


       /* 設置中斷 */

        register_irq(10, timer_irq);

    }


把interrupt.c中按鍵的初始化放在最后面


我們來看看我們做了什么事情,

<1>我們定義了一個指針數組

typedef void(*irq_func)(int);

注:這里看不懂請參考typedef函數指針用法

這個指針數組里面放有各個指針的處理函數

irq_func irq_array[32];


當我們去初始化按鍵中斷時,我們給這按鍵注冊中斷函數

register_irq(0, key_eint_irq);

register_irq(2, key_eint_irq);

register_irq(5, key_eint_irq);


這個注冊函數會做什么事情,他會把這個數組放在注冊函數里面,同時使能中斷


void register_irq(int irq, irq_func fp)

{

    irq_array[irq] = fp;


    INTMSK &= ~(1<}

//我們的timer.c中


timer_init();

//也會注冊這個函數

    /* 設置中斷 */

    register_irq(10, timer_irq);


把這個中斷irq放在第10項里同時使能中斷,以后我們只需要添加中斷號,和處理函數即可,再也不需要修改函數

燒寫執行


我們從start.s開始看,

一上電從 b reset運行做一列初始化


.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 */



reset:

    /* 關閉看門狗 */

    ldr r0, =0x53000000

    ldr r1, =0

    str r1, [r0]


    /* 設置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */

    /* LOCKTIME(0x4C000000) = 0xFFFFFFFF */

    ldr r0, =0x4C000000

    ldr r1, =0xFFFFFFFF

    str r1, [r0]


    /* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */

    ldr r0, =0x4C000014

    ldr r1, =0x5

    str r1, [r0]


    /* 設置CPU工作于異步模式 */

    mrc p15,0,r0,c1,c0,0

    orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA

    mcr p15,0,r0,c1,c0,0


    /* 設置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 

     *  m = MDIV+8 = 92+8=100

     *  p = PDIV+2 = 1+2 = 3

     *  s = SDIV = 1

     *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M

     */

    ldr r0, =0x4C000004

    ldr r1, =(92<<12)|(1<<4)|(1<<0)

    str r1, [r0]


    /* 一旦設置PLL, 就會鎖定lock time直到PLL輸出穩定

     * 然后CPU工作于新的頻率FCLK

     */




    /* 設置內存: sp 棧 */

    /* 分辨是nor/nand啟動

     * 寫0到0地址, 再讀出來

     * 如果得到0, 表示0地址上的內容被修改了, 它對應ram, 這就是nand啟動

     * 否則就是nor啟動

     */

    mov r1, #0

    ldr r0, [r1] /* 讀出原來的值備份 */

    str r1, [r1] /* 0->[0] */ 

    ldr r2, [r1] /* r2=[0] */

    cmp r1, r2   /* r1==r2? 如果相等表示是NAND啟動 */

    ldr sp, =0x40000000+4096 /* 先假設是nor啟動 */

    moveq sp, #4096  /* nand啟動 */

    streq r0, [r1]   /* 恢復原來的值 */


    bl sdram_init

    //bl sdram_init2     /* 用到有初始值的數組, 不是位置無關碼 */


    /* 重定位text, rodata, data段整個程序 */

    bl copy2sdram


    /* 清除BSS段 */

    bl clean_bss


    /* 復位之后, cpu處于svc模式

     * 現在, 切換到usr模式

     */

    mrs r0, cpsr         /* 讀出cpsr */

    bic r0, r0, #0xf     /* 修改M4-M0為0b10000, 進入usr模式 */

    bic r0, r0, #(1<<7)  /* 清除I位, 使能中斷 */

    msr cpsr, r0


    /* 設置 sp_usr */

    ldr sp, =0x33f00000


    ldr pc, =sdram

sdram:

    bl uart0_init


    bl print1

    /* 故意加入一條未定義指令 */

und_code:

    .word 0xdeadc0de  /* 未定義指令 */

    bl print2


    swi 0x123  /* 執行此命令, 觸發SWI異常, 進入0x8執行 */


/***最后執行main函數***/

    //bl main  /* 使用BL命令相對跳轉, 程序仍然在NOR/sram執行 */

    ldr pc, =main  /* 絕對跳轉, 跳到SDRAM */


halt:

    b halt


進入main.c做一系列初始化



int main(void)

{

    led_init();

    //interrupt_init();  /* 初始化中斷控制器 */

    key_eint_init();   /* 初始化按鍵, 設為中斷源 */

    timer_init();


    puts("nrg_A = ");

    printHex(g_A);

    puts("nr");

}


進入按鍵初始化程序 interrupt.c

初始化按鍵, 設為中斷源


void key_eint_init(void)

{

    /* 配置GPIO為中斷引腳 */

    GPFCON &= ~((3<<0) | (3<<4));

    GPFCON |= ((2<<0) | (2<<4));   /* S2,S3被配置為中斷引腳 */


    GPGCON &= ~((3<<6) | (3<<22));

    GPGCON |= ((2<<6) | (2<<22));   /* S4,S5被配置為中斷引腳 */



    /* 設置中斷觸發方式: 雙邊沿觸發 */

    EXTINT0 |= (7<<0) | (7<<8);     /* S2,S3 */

    EXTINT1 |= (7<<12);             /* S4 */

    EXTINT2 |= (7<<12);             /* S5 */


    /* 設置EINTMASK使能eint11,19 */

     EINTMASK &= ~((1<<11) | (1<<19));


/*注冊中斷控制器*/

    register_irq(0, key_eint_irq);

    register_irq(2, key_eint_irq);

    register_irq(5, key_eint_irq);

}


時鐘初始化程序 timer_init();


void timer_init(void)

{

    /* 設置TIMER0的時鐘 */

    /* Timer clk = PCLK / {prescaler value+1} / {divider value} 

                 = 50000000/(99+1)/16

                 = 31250

     */

    TCFG0 = 99;  /* Prescaler 0 = 99, 用于timer0,1 */

    TCFG1 &= ~0xf;

    TCFG1 |= 3;  /* MUX0 : 1/16 */


    /* 設置TIMER0的初值 */

    TCNTB0 = 15625;  /* 0.5s中斷一次 */


    /* 加載初值, 啟動timer0 */

    TCON |= (1<<1);   /* Update from TCNTB0 & TCMPB0 */


    /* 設置為自動加載并啟動 */

    TCON &= ~(1<<1);

    TCON |= (1<<0) | (1<<3);  /* bit0: start, bit3: auto reload */


    /* 設置中斷 */

    register_irq(10, timer_irq);

}


讓后main.c函數一直循環執行

輸出串口信息


 while (1)

 {

    putchar(g_Char);

    g_Char++;


    putchar(g_Char3);

    g_Char3++;

    delay(1000000);

    //printHex(TCNTO0);

 }


定時器減到0的時候就會產生中斷,start.S

跳到 0x18的地方執行


    ldr pc, irq_addr /* vector 0x18 : irq */

    b halt           /* vector 0x1c : fiq */

.align 4


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}  


    /* 處理irq異常 */

    bl handle_irq_c


    /* 恢復現場 */

    ldmia sp!, {r0-r12, pc}^  /* ^會把spsr_irq的值恢復到cpsr里 */


ldr pc, irq_addr /* vector 0x18 : irq */

b halt           /* vector 0x1c : fiq */


.align 4


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}  


/* 處理irq異常 */

bl handle_irq_c


/* 恢復現場 */

ldmia sp!, {r0-r12, pc}^  /* ^會把spsr_irq的值恢復到cpsr里 */


看看怎么處理irq


void handle_irq_c(void)

{

    /* 分辨中斷源 */

    int bit = INTOFFSET;


    /* 調用對應的處理函數執行 */

    irq_array[bit](bit);


    /* 清中斷 : 從源頭開始清 */

    SRCPND = (1<    INTPND = (1<}

進入單片機查看更多內容>>
相關視頻
  • TI 新一代 C2000? 微控制器:全方位助力伺服及馬達驅動應用

  • MSP430電容觸摸技術 - 防水Demo演示

  • 直播回放: Microchip Timberwolf? 音頻處理器在線研討會

  • 新唐 8051單片機教程

  • 基于靈動MM32W0系列MCU的指夾血氧儀控制及OTA升級應用方案分享

  • 基于靈動MM32SPIN系列MCU的無感FOC便攜冰箱應用方案分享

    相關電子頭條文章
萝卜大香蕉