Blog on embedded programming

The trip to the dark side

Triggering External Interrupt on Stm32f4discovery

Hello everyone! This post introduces stm32f4xx interrupts and their usage from assembly language. The goal is to react on user-button push (a blue one) using external interrupt. To reach this goal without involving the interrupt we would write a loop “listening” the input level on the pin. That’s a really bad idea because our application should be able to do other useful stuff.

A usual application configures all needed interrupts, peripherals etc. at init stage an then just falls into infinite loop that does nothing - or even it may go to a sleep mode, waking up just to process events and after that go to sleep again.

To introduce an external interrupt I configure port’s A zero pin as input with pulldown resistor. Connecting pulldown resistor guarantees low level at the pin when button is not pressed. Then I configure this pin as the source for external interrupt line 0 - stm32f4xx has a rather flexible external interrupt system (please, refer the user manual). After that additionally I need to mask which lines can generate an interrupt request - in our case it is line 0.

The “master” of all interrupts and events inside stm32 is the NVIC core peripheral (please refer to stm32 programming manual - which is a very useful resource). To get things work I should enable the interrupt coming from channel EXT0_IRQn = 6 (this information is summarized in the interrupt vector table, see stm32 reference manual, page 371. Finally I associate some routine to be an interrupt handler by placing it’s address to appropriate offset within interrupt vector table.

Let’s beging coding. Code for startup and init_clock routines I’ve taken from clock control on stm32f4discovery post. So, the clock source is HSE with 8 MHz crystal. No PLL is used.

I’ve decided to share for you two variants of code working with the user button. While I was struggling to make the external interrupt work I wrote a code which works with the user button through a usual loop as a starting point.

Here it is. Now, I’m trying to be modular while writing code. Defined constants are included below.

(external_interrupt.S) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
    .thumb
    .syntax     unified
    .cpu        cortex-m4

    .include    "registers_and_bits.inc.S"
    .equ        DELAY,                  0x1


.section        .text
    .type       _reset, %function
_reset:
    bl          startup
    bl          init_clock
    bl          gpio_setup
    b           run
    .size       _reset, . - _reset


    .type       run, %function
run:
    ldr         r0, =GPIOD_ODR          /* port D output register */
    ldr         r3, =GPIOA_IDR          /* port A input register */
2:
    eor         r1, r1                  /* elegant way to clean register */
    str         r1, [r0]                /* all LEDs on GPIOD_ODR off */
1:
    ldr         r4, [r3]                /* reading port A */
    ands        r4, IDR0                /* when button is pressed - high level appears at pin 0 */
    beq         1b
1:
    bl          super_flash           /* make a flash */
    ldr         r4, [r3]                /* and wait for button release */
    ands        r4, IDR0
    bne         1b
    b           2b
    .size       run, . - run


    .type       super_flash, %function
super_flash:
    push        {lr}                    /* we need to preserve the return address */
    mov         r5, 5                   /* as this routine is going to call a subroutine */
1:
    mov         r1, LED_GREEN           /* flashing one by one */
    bl          flash_led
    mov         r1, LED_BLUE
    bl          flash_led
    mov         r1, LED_RED
    bl          flash_led
    mov         r1, LED_ORANGE
    bl          flash_led
    subs        r5, 1                   /* <r5> times */
    bne         1b
    pop         {lr}                    /* restore the return address and jump to it */
    bx          lr
    .size       super_flash, . - super_flash


    .type       flash_led, %function
flash_led:
    push        {lr}                    /* in this function it is assumed that */
    str         r1, [r0]                /* r0 contains port D address */
    bl          delay                 /* and r1 contains the LED bit to be flashed */
    eor         r1, r1
    str         r1, [r0]                /* LED fade out */
    bl          delay
    pop         {lr}
    bx          lr
    .size       flash_led, . - flash_led


    .type       delay, %function
delay:
    movt        r2, DELAY
1:
    subs        r2, r2, 1               /* spend time substracting DELAY value by 1 */
    bne         1b                      /* '1b' means `look backward for label '1'` */

    bx          lr
    .size       delay, . - delay


    .type       gpio_setup, %function
gpio_setup:
    ldr         r0, =RCC_AHB1ENR        /* this register controls some amount of peripherals' clock */
    ldr         r1, [r0]
    orr         r1, GPIODEN | GPIOAEN   /* enabling GPIOD AND GPIOA clock */
    str         r1, [r0]

    ldr         r0, =GPIOA_MODER        /* configure GPIOA as input */
    ldr         r1, [r0]
    bic         r1, MODER0_IN
    str         r1, [r0]

    ldr         r0, =GPIOA_PUPDR        /* connect pulldown resistor to the pin 0 */
    ldr         r1, [r0]
    orr         r1, PUPDR0_PD
    str         r1, [r0]

    ldr         r0, =GPIOD_MODER        /* GPIOD mode configuration register - all LED pins are outputs*/
    ldr         r1, =MODER12_OUT | MODER13_OUT | MODER14_OUT | MODER15_OUT
    str         r1, [r0]

    bx          lr
    .size       gpio_setup, . - gpio_setup


    .type       startup, %function
startup:
    ldr         r0, =RCC_CR             /* resetting clock configuration to it's defaults */
    ldr         r1, [r0]                /* clearing HSEON, CSSON, PLLON bits - directly disabling */
                                        /* HSE oscillator, clock security system, main PLL */
    orr         r1, HSION               /* setting HSION bit - directly selecting HSI oscillator*/
    ldr         r2, =(HSEON) | (CSSON) | (PLLON)
    bic         r1, r2
    str         r1, [r0]
    ldr         r0, =RCC_CFGR           /* clearing RCC_CFGR - clock configuration register */
    movw        r1, 0
    str         r1, [r0]
    ldr         r0, =RCC_CIR            /* disabling all interrupts */
    str         r1, [r0]
    ldr         r0, =RCC_PLLCFGR        /* setting the default value from datasheet */
    ldr         r1, =0x24003010         /* for PLL configuration register */
    str         r1, [r0]
    bx          lr
    .size       startup, . - startup


    .type       init_clock, %function
init_clock:
    ldr         r0, =RCC_CR             /* enabling HSE clock */
    ldr         r1, [r0]
    bic         r1, HSEBYP              /* clearing HSEBYP bit - not bypassing HSE oscillator*/
    orr         r1, HSEON
    str         r1, [r0]
1:
    ldr         r1, [r0]                /* waiting for HSE to be ready */
    ands        r1, HSERDY
    beq         1b
    ldr         r0, =RCC_CFGR           /* selecting HSE as the source */
    movw        r1, SW0
    str         r1, [r0]
    eor         r2, r2
    movt        r2, HSE_STARTUP_TIMEOUT
1:
    ldr         r1, [r0]                /* waiting for source being selected */
    subs        r2, 1                   /* counting some HSE_STARTUP_TIMEOUT value */
    beq         2f                      /* if HSE doesn't start - remain with HSI */
    and         r1, RCC_CFGR_SWS
    cmp         r1, SWS0
    bne         1b
2:
    bx          lr
    .size       init_clock, . - init_clock


.section        .int_vector_table, "a", %progbits
    .type       basic_vectors, %object
basic_vectors:
    .word       _estack
    .word       _reset
    .size       basic_vectors, . - basic_vectors

The executable is free to download. Hope you will get that nice flashing on button pushed.

But as I said earlier that is a bad idea of application design. This code idea is narrow and tied up. We need to use a mini operating system approach not to be tied up. What is it about? The system boots, enables and configures some services needed and then just waits for events. The next code example is exactly about that. Note the b . line which means “jump at current address” - the endless loop. After button is pushed the core will jump to _exti_0 and after execution return back to this loop. Whenever a new functionality is needed - it’s easy enough to attach it.

Here is the code and defined constants.

(external_interrupt_1.S) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
    .thumb
    .syntax     unified
    .cpu        cortex-m4

    .include    "registers_and_bits.inc.S"

    .equ        DELAY,                  0x1


.section        .text
    .type       _reset, %function
_reset:
    bl          startup
    bl          init_clock
    bl          gpio_setup
    bl          exti_setup
    b           .
    .size       _reset, . - _reset


    .type       _exti_0, %function
_exti_0:
    push        {r0,r1,lr}              /* preserve registers being used by routine */
    ldr         r0, =GPIOD_ODR
    eor         r1, r1
    bl          super_flash
    ldr         r0, =EXTI_PR            /* after we do nececcary stuff pending bit for current interrupt */
    ldr         r1, [r0]                /* should be cleared and this is done by setting that bit to 1*/
    orr         r1, PR0
    str         r1, [r0]
    pop         {r0,r1,lr}              /* restore registers before leave */
    bx          lr
    .size       _exti_0, . - _exti_0


    .type       exti_setup, %function
exti_setup:
    ldr         r0, =RCC_APB2ENR        /* enable syscfg to be able to configure external interrupt line source */
    ldr         r1, [r0]
    orr         r1, SYSCFGEN
    str         r1, [r0]

    ldr         r0, =SYSCFG_EXTICR1     /* choosing PA0 as a source for ext. interrupt */
    ldr         r1, [r0]
    bic         r1, SYSCFG_EXTICR1_EXTI0
    str         r1, [r0]

    ldr         r0, =EXTI_IMR           /* setting interrupt mask for line 0 */
    movw        r1, MR0
    str         r1, [r0]

    ldr         r0, =EXTI_RTSR          /* configuring interrupt to be generated on rising edge */
    movw        r1, TR0
    str         r1, [r0]

    ldr         r0, =NVIC_ISER0         /* enable enterrupt from EXTI0_IRQn channel */
    ldr         r1, =SETENA6
    str         r1, [r0]

    bx          lr
    .size       exti_setup, . - exti_setup


    .type       super_flash, %function
super_flash:
    push        {r1, r5, lr}            /* we need to preserve the return address */
    mov         r5, 5                   /* as this routine is going to call a subroutine */
1:
    mov         r1, LED_GREEN           /* flashing one by one */
    bl          flash_led
    mov         r1, LED_BLUE
    bl          flash_led
    mov         r1, LED_RED
    bl          flash_led
    mov         r1, LED_ORANGE
    bl          flash_led
    subs        r5, 1                   /* <r5> times */
    bne         1b
    pop         {r1, r5, lr}            /* restore the return address and jump to it */
    bx          lr
    .size       super_flash, . - super_flash


    .type       flash_led, %function
flash_led:
    push        {r1, lr}                /* in this function it is assumed that */
    str         r1, [r0]                /* r0 contains port D output register address */
    bl          delay                   /* and r1 contains the LED bit to be flashed */
    eor         r1, r1
    str         r1, [r0]                /* LED fade out */
    bl          delay
    pop         {r1, lr}
    bx          lr
    .size       flash_led, . - flash_led


    .type       delay, %function
delay:
    push        {r2}
    movt        r2, DELAY
1:
    subs        r2, r2, 1               /* spend time substracting DELAY value by 1 */
    bne         1b                      /* '1b' means `look backward for label '1'` */

    pop         {r2}
    bx          lr
    .size       delay, . - delay


    .type       gpio_setup, %function
gpio_setup:
    ldr         r0, =RCC_AHB1ENR        /* this register controls some amount of peripherals' clock */
    ldr         r1, [r0]
    orr         r1, GPIODEN | GPIOAEN   /* enabling GPIOD AND GPIOA clock */
    str         r1, [r0]

    ldr         r0, =GPIOA_MODER        /* configure GPIOA as input */
    ldr         r1, [r0]
    bic         r1, MODER0_IN
    str         r1, [r0]

    ldr         r0, =GPIOA_PUPDR        /* connect pulldown resistor to the pin 0 */
    ldr         r1, [r0]
    orr         r1, PUPDR0_PD
    str         r1, [r0]

    ldr         r0, =GPIOD_MODER        /* GPIOD mode configuration register - all LED pins are outputs*/
    ldr         r1, =MODER12_OUT | MODER13_OUT | MODER14_OUT | MODER15_OUT
    str         r1, [r0]

    bx          lr
    .size       gpio_setup, . - gpio_setup


    .type       startup, %function
startup:
    ldr         r0, =RCC_CR             /* resetting clock configuration to it's defaults */
    ldr         r1, [r0]                /* clearing HSEON, CSSON, PLLON bits - directly disabling */
                                        /* HSE oscillator, clock security system, main PLL */
    orr         r1, HSION               /* setting HSION bit - directly selecting HSI oscillator*/
    ldr         r2, =HSEON | CSSON | PLLON
    bic         r1, r2
    str         r1, [r0]
    ldr         r0, =RCC_CFGR           /* clearing RCC_CFGR - clock configuration register */
    movw        r1, 0
    str         r1, [r0]
    ldr         r0, =RCC_CIR            /* disabling all interrupts */
    str         r1, [r0]
    ldr         r0, =RCC_PLLCFGR        /* setting the default value from datasheet */
    ldr         r1, =0x24003010         /* for PLL configuration register */
    str         r1, [r0]
    bx          lr
    .size       startup, . - startup


    .type       init_clock, %function
init_clock:
    ldr         r0, =RCC_CR             /* enabling HSE clock */
    ldr         r1, [r0]
    bic         r1, HSEBYP              /* clearing HSEBYP bit - not bypassing HSE oscillator*/
    orr         r1, HSEON
    str         r1, [r0]
1:
    ldr         r1, [r0]                /* waiting for HSE to be ready */
    ands        r1, HSERDY
    beq         1b
    ldr         r0, =RCC_CFGR           /* selecting HSE as the source */
    movw        r1, SW0
    str         r1, [r0]
    eor         r2, r2
    movt        r2, HSE_STARTUP_TIMEOUT
1:
    ldr         r1, [r0]                /* waiting for source being selected */
    subs        r2, 1                   /* counting some HSE_STARTUP_TIMEOUT value */
    beq         2f                      /* if HSE doesn't start - remain with HSI */
    and         r1, RCC_CFGR_SWS
    cmp         r1, SWS0
    bne         1b
2:
    bx          lr
    .size       init_clock, . - init_clock


.section        .int_vector_table, "a", %progbits
    .type       basic_vectors, %object
basic_vectors:
    .word       _estack
    .word       _reset
    .org        0x58
    .word       _exti_0
    .size       basic_vectors, . - basic_vectors
(registers_and_bits.inc.S) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    .equ        RCC_BASE,       0x40023800
    .equ        RCC_CR  ,       RCC_BASE
    .equ        RCC_CFGR,       RCC_BASE + 0x08
    .equ        RCC_PLLCFGR,    RCC_BASE + 0x04
    .equ        RCC_AHB1ENR,    RCC_BASE + 0x30
    .equ        RCC_APB2ENR,    RCC_BASE + 0x44
    .equ        RCC_CIR,        RCC_BASE + 0x0C
    .equ        GPIOD_BASE,     0x40020C00
    .equ        GPIOD_MODER,    GPIOD_BASE
    .equ        GPIOD_ODR,      GPIOD_BASE + 0x14
    .equ        GPIOA_BASE,     0x40020000
    .equ        GPIOA_MODER,    GPIOA_BASE
    .equ        GPIOA_PUPDR,    GPIOA_BASE + 0x0C
    .equ        GPIOA_IDR,      GPIOA_BASE + 0x10
    .equ        SYSCFG_BASE,    0x40013800
    .equ        SYSCFG_EXTICR1, SYSCFG_BASE + 0x08
    .equ        EXTI_BASE,      0x40013C00
    .equ        EXTI_IMR,       EXTI_BASE
    .equ        EXTI_RTSR,      EXTI_BASE + 0x08
    .equ        EXTI_PR,        EXTI_BASE + 0x14
    .equ        NVIC_BASE,      0xE000E100
    .equ        NVIC_ISER0,     NVIC_BASE

    .equ        GPIODEN,        1 << 3
    .equ        GPIOAEN,        1
    .equ        SYSCFGEN,       1 << 14
    .equ        MODER12_OUT,    1 << 24
    .equ        MODER13_OUT,    1 << 26
    .equ        MODER14_OUT,    1 << 28
    .equ        MODER15_OUT,    1 << 30
    .equ        MODER0_IN,      0x3
    .equ        LED_GREEN,      1 << 12
    .equ        LED_ORANGE,     1 << 13
    .equ        LED_RED,        1 << 14
    .equ        LED_BLUE,       1 << 15
    .equ        PLLON,          1 << 24
    .equ        HSION,          1
    .equ        HSEBYP,         1 << 18
    .equ        HSEON,          1 << 16
    .equ        HSERDY,         1 << 17
    .equ        CSSON,          1 << 19
    .equ        SW0,            1
    .equ        SWS0,           1 << 2
    .equ        PUPDR0_PD,      1 << 1
    .equ        IDR0,           1
    .equ        MR0,            1
    .equ        TR0,            1
    .equ        PR0,            1
    .equ        EXTI0_IRQn,     6
    .equ        SETENA6,        1 << EXTI0_IRQn

    .equ        HSE_STARTUP_TIMEOUT,    0x1

    .equ        RCC_CFGR_SWS,           3 << 2
    .equ        GPIOx_PUPDR0,           3
    .equ        SYSCFG_EXTICR1_EXTI0,   0xF

Ready executable: download.

Note the stack usage in these examples. The routine preserves registers’ contents by pushing it into the stack at the very start of execution and popping it before leave. I think that is absolutely necessary in interrupt handle routines because of randomity of accesses to them. Though, I may be wrong and there may be better aproaches to that.

Feel free to write for conversations.