ユーザスイッチ(PushSwitch)による割り込み


20140530 coskx
T.SATOH

1.はじめに

SystemTickTimerを用いたインターバルタイマ割り込みについて,先に述べた。ここでは特定のビット入力信号によって起動する外部割り込みについて述べる。
STM32F3Discoveryの青いユーザスイッチはGPIOのPA0ピンに接続されている が,PA0ピンを外部割り込み受付という「別用途に設定して,青いユーザスイッチが押されると起動する割り込みハンドラ内でLEDの点灯状況を反転させる プログラム例を作成する。
ここではPA0ピンのプッシュボタンによって割り込みハンドラを動かしているが,GPIOの別のピンによって割り込みを発生させることもできる。また,割り込みハンドラ内でLEDのの操作をしているが,割り込みハンドラ内ではもっと他のこともできる。


2.プログラムの要点


PA0ピンによる割り込みを使うには次のような準備が必要である。
(1)PA0ピンによる割り込みを設定する。
(2)割り込みハンドラを用意する。


2.1 PA0ピンによる割り込みの設定
外部ライン割り込みを設定するには,GPIOAにクロックを供給し,GPIOA0ピンを入力設定し,GPIOA0ピンを外部ライン割り込み入力に使うと設定し,割り込み条件を設定しなければならない。

(1)GPIOAにクロックを供給(これはユーザボタンのサンプルプログラムと時と同じ)
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
(2)GPIOA0ピンを入力に設定(これはユーザボタンのサンプルプログラムと時と同じ)
  GPIO_InitStructureを所望の状態に設定し,
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  で設定
(3)GPIOA0ピンを外部ライン割り込み入力に使うと設定
  SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
  これにより,SYSCFG_EXTICR1(DM00043574.pdf(RM0316)の10.1.3)のEXTI0に記述され,
  GPIOA0ピンはEXTI0に割り付けられる。
  そしてGPIOA0ピンはEXTI_Line0と表現される。(stm32f30x_exti.h)
  参考
  SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource3);
  ならば,
  GPIOB3ピンはEXTI3に割り付けられる。
  そしてGPIOB3ピンはEXTI_Line3と表現される。
(4)割り込み条件の設定
  割り込み条件の設定はEXTIレジスタとNVICレジスタに対して行われる。
  (4.1)EXTIレジスタ
  EXTI_InitStructureに記述して,EXTI_Init(&EXTI_InitStructure);で書き込む。
  EXTI_InitStructureの設定
    EXTI_Line (4)で割り付けられたEXTI_Line0
    EXTI_Mode EXTI_Mode_InterruptとEXTI_Mode_Eventの値をのどちらかだが,ここでは前者
    EXTI_Trigger 割り込みトリガ ここでは立ち下がりエッジ(ボタンを押して離した瞬間の意味になる)
      他にはEXTI_Trigger_Rising(立ち上がりエッジ),EXTI_Trigger_Rising_Falling(両エッジ)
      を設定できる
    EXTI_LineCmd ENABLE (有効)
  (4.2)NVICレジスタ
  NVIC_InitStructureに記述して,NVIC_Init(&NVIC_InitStructure);で書き込む。
  NVIC_InitStructureの設定
    NVIC_IRQChannel stm32f30x.hのtypedef enum IRQnから,EXTI Line 0に対応するもの
      を選んでEXTI0_IRQn (割り込み番号を表す)
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
      割り込み優先度 0から15まで設定でき,0が一番優先度が高い
 stm32f30x_misc.h参照
    NVIC_IRQChannelCmd ENABLE (有効)
    


2.2 割り込みハンドラの用意
ハンドラの名前(割り込み関数の名前)は決まっていて,startup_stm32f30x.sのg_pfnVectors:で始まるベクタテーブル中に書いてある。
EXTI0_IRQHandlerがPA0ピンなどによる割り込みを受けた時に動作するハンドラの名前である。
リファレンスマニュアルDM00043574.pdfのTable32. Vector tableからEXTI0_IRQn (割り込み番号)に対応するものを選ぶ。
そうするとEXTI0_1(EXTI Line0 interrupts)というのがあり,addressが58になっている。
割込みベクタテーブルには0,4,8,c,10,...のように4バイトずつ割り込みハンドラのアドレスが並ぶので,23個目がEXTI0_1である。startup_stm32f30x.sのベクタテーブルの23個目に EXTI0_IRQHandlerがあるのと一致する。
割り込みハンドラvoid EXTI0_IRQHandler(void)をファイルmain.c中に作る。
割り込みハンドラ内では,やりたい作業の他に,割り込み作業が終了したことを知らせるために
    EXTI_ClearITPendingBit(EXTI_Line0);
を実行する。



3.プログラム


3.1 プログラム例

2の考え方で作成したプログラムを次に示す。
PA0の信号線は0Vを保っており,はプッシュスイッチを押した瞬間に0Vから電源電圧3Vに立ち上がり,電源電圧3Vを保ち,スイッチを離した時に電源電圧3Vから0Vへと立ち下がることを想定している。


フレームワークプロジェクトを使って,以下のプログラムを行う。
ユーザスイッチ(PushSwitch)による割り込み
/* Includes ------------------------------------------------------------------*/
#include <stm32f30x.h>
#include <stdint.h>
#include "stm32f30x_conf.h"
#include "stm32f3_discovery.h"
#include <stdio.h>

/* Private function prototypes -----------------------------------------------*/
void EXTI0_Config(void);

//ソフトウェアで割り込みエミュレーション
int main1(void)
{
    /* Initialize LEDs mounted on STM32F0-Discovery kit */
    STM_EVAL_LEDInit(LED3);
    STM_EVAL_LEDInit(LED4);
    STM_EVAL_LEDToggle(LED4);

    /* Configure PA0 in interrupt mode */
    EXTI0_Config();

    /* Infinite loop */
    while (1)
    {
        volatile int i=0;
        for (i=0;i<1000000;i++);
        /* Generate software interrupt: simulate a falling edge applied on EXTI0 line */
        EXTI_GenerateSWInterrupt(EXTI_Line0);
    }
}

int main(void)
{
    /* Initialize LEDs mounted on STM32F0-Discovery kit */
    STM_EVAL_LEDInit(LED3);
    STM_EVAL_LEDInit(LED4);
    STM_EVAL_LEDToggle(LED4);
    printf("aa %x\n",RCC->APB2ENR);

    /* Configure PA0 in interrupt mode */
    EXTI0_Config();
    printf("bb %x\n",RCC->APB2ENR);

    /* Infinite loop */
    while (1)
    {
    }
}

void EXTI0_Config(void)
{
    /* Enable GPIOA clock */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    /* Configure PA0 pin as input floating */
    GPIO_InitTypeDef   GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* Connect EXTI0 Line to PA0 pin */
    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);

    /* Configure EXTI0 line */
    EXTI_InitTypeDef   EXTI_InitStructure;
    EXTI_InitStructure.EXTI_Line = EXTI_Line0;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    /* Enable and set EXTI0 Interrupt */
    NVIC_InitTypeDef   NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

void EXTI0_IRQHandler__old(void)
{
    /* Toggle LED3 and LED4 */
    STM_EVAL_LEDToggle(LED3);
    STM_EVAL_LEDToggle(LED4);

    /* Clear the EXTI line 0 pending bit */
    EXTI_ClearITPendingBit(EXTI_Line0);
}


3.2 チャタリング

3.1のプログラムの動作としては,青のボタンを押すたびに,2つのLEDの点灯消灯状態が交互に変化することを期待している。
ところが,実際には動作が不安定である。
原因はスイッチのチャタリングと言って,スイッチがOFFからON状態になる時とONからOFF状態になる時に,高速に複数回ON-OFFを繰り返してしまう現象である。
PA0の信号線は0Vを保っており,はプッシュスイッチを押した瞬間に0Vから電源電圧3Vに立ち上が り,電源電圧3Vを保ち,スイッチを離した時に電源電圧3Vから0Vへと立ち下がることを想定している。よって,スイッチを離す瞬間にPA0において立ち 下がりエッジが観測され割り込みハンドラが1回だけ動作することを想定している。ところがチャタリングが起きると,プッシュボタンを押した瞬間と離した瞬 間に複数回の立ち下がりエッジを生じ,誤動作となる。

防ぎ方は色いろあると思うが,次のようにするとソフトウェアで対処した例を次に示す。
チャタリングによる意図しない割り込みは,プッシュボタンを押した時と,離した時に起こる可能性がある。
割り込みハンドラが起動したら,0.1秒ほどなにもしない時間帯を設け,LED操作を行い,割り込みフラ グ(ハードウェアに割り込み作業終了を伝える)を消して,割り込みハンドラを終了すようにすると,高速なチャタリングには反応しなくなる。それでもプッ シュボタンを押した時に1回誤起動が起こる可能性がある。そこで,割り込みハンドラが起動したら,0.1秒ほどなにもしない時間帯を設け,PA0が0Vの 時だけLED操作を行う。そして,割り込みフラグ(ハードウェアに割り込み作業終了を伝える)を消して,割り込みハンドラを終了すようにする。
この方法でかなりのチャタリングによる誤動作を防ぐことができる。

チャタリング対策した割り込みハンドラ
void EXTI0_IRQHandler(void)
{
    volatile int i=0;
    for (i=0;i<100000;i++);
    if (STM_EVAL_PBGetState(BUTTON_USER)!= SET) {
        /* Toggle LED3 and LED4 */
        STM_EVAL_LEDToggle(LED3);
        STM_EVAL_LEDToggle(LED4);
    }

    /* Clear the EXTI line 0 pending bit */
    EXTI_ClearITPendingBit(EXTI_Line0);
}


3.3 割り込みシミュレーション関数

PA0に信号の立ち下がりエッジが観測されなくても,割り込みハンドラを起動させる方法がある。
EXTI_GenerateSWInterrupt(EXTI_Line0);の関数を用いると,あたかもPA0に立ち下がりエッジが観測されたかのように振る舞う。
main関数内の無限ループ内で,周期的に実行させる例を次に示す。
実行すると規則的にLEDが点滅する。


割り込みシミュレーション関数
int main(void)
{
    /* Initialize LEDs mounted on STM32F0-Discovery kit */
    STM_EVAL_LEDInit(LED3);
    STM_EVAL_LEDInit(LED4);

    /* Configure PA0 in interrupt mode */
    EXTI0_Config();

    /* Infinite loop */
    while (1)
    {
        volatile int i=0;
        for (i=0;i<1000000;i++);
        /* Generate software interrupt: simulate a falling edge applied on EXTI0 line */
        EXTI_GenerateSWInterrupt(EXTI_Line0);
    }
}