ラズピコ GPIO入力と割り込み

ラズピコ GPIO入力と割り込み

皆さん こんにちは。

今回はGPIOを入力として使ってみます。

回路図

今回はラズピコにスイッチをつないで、スイッチを押してLEDをチカチカさせてみることにします。
前回の記事でLチカさせるには GPIO25 を制御すれば良いことがわかっています。

スイッチを取り付ける必要がありますが、どのGPIOに接続しましょうか。
まず回路図を見てみましょう。

ラズピコのデータシートには回路図が含まれていますので、まずそれをダウンロードしましょう。

ラズピコのデータシート からダウンロードしてください。

私は 5.08mmピッチのタクトスイッチを持っていたので、それを使うことにします。
23番がGNDなので、その2つ隣の21番ピン(GPIO16)と23番ピンをまたいでスイッチをつけることにしました。

そうすると配線は不要です。

スイッチを押した時にGPIO16は lowレベルになりますので離した時に highレベルになるようにしなければなりません。

抵抗をつけても良いのですが、マイコン内部でGPIO端子をプルアップする機能があるので、それを使ってみることにします。

簡単なので回路図は省略します。

GPIO

汎用入出力ピンはGPIOと呼ばれます。
ラズピコに実装されているマイコン RP2040 は 36本のGPIOを備えています。
そのうちの6本はQSPI Flash(プログラムを格納するフラッシュメモリー)とのインターフェースに使われます。

従って私達ユーザーは残りの30本(GPIO0~29)をGPIOとして使うことができます。
更にそのうちの4本(GPIO26~29)はADC(Analog Digital Converter)の入力として使うこともできます。

このあたりについては RP2040データシート の2.19.1項に書かれていますのでご確認ください。

更にラズピコの40ピンの端子には以下の26本だけが割り当てられているようですからご注意ください。

GPIO0~22 の23本と GPIO26~28の3本 (ラズピコの回路図より)

GPIOの機能

GPIO端子はGPIO以外の機能が割り当てられています。これについては RP2040データシート の2.19.2項をご覧ください。
割り当てる機能は、GPIO CTRLレジスタのFUNCSELフィールドに書き込むことで選択します。

各GPIOは一度にひとつの機能だけを選択することができます。

それからある機能(ここでは例えとして UART0 TX)はひとつのGPIOでのみ選択されるべきです。

例えば UART0 TX は GPIOの 0, 12, 16, 28 に割り当てることができるので、UART0 TXを使う場合には、この中のいずれかひとつだけを選択します。

「2つ以上割り当ててはいけない」ということです。

GPIO入力として使う

レジスタに値を設定するためのAPIが用意されているので、それを使います。

gpio_init()
gpio_set_dir()
gpio_pull_up()
gpio_get()

の4つを使えば良さそうです。

GPIOに関するAPIは こちら を参考になさってください。

GPIOなのにAPIの数がたくさんあってビックリしました。

とりあえず上に書いた4つの関数のリンクを貼っておきます。

gpio_init()

gpio_set_dir()

gpio_pull_up()

gpio_get()

gpio_init()の中で先に出てきた機能設定を行っています。
gpio_set_dir()では方向(入力か出力)を決める。
gpio_pull_up()でプルアップ抵抗をつける。
gpio_get()で low または high の状態を読む。

それではプロジェクトをつくり、ソースコードを書いて動作させてみることにします。

プロジェクトをつくる

プロジェクトのつくり方や、VSCodeの起動方法等については省略します。

それぞれについて、わからない方は ラズピコ VSCode + Picoprobe でデバッグ環境を構築する の記事をご覧になってください。

ちょっと手順がややこしいので慣れるまで辛抱してください。

Project Name: picoGpioIn とする
IDE Optionsは
Create VSCode project にチェックを入れる
Debugger: では PicoProbe を選択する

VSCodeを起動する

起動後、何かファイルが開いてたら File – Close Folder で閉じた後、File – Open Folder から picoGpioIn のフォルダーを辿って選択します。

こちらの環境では C:\Users\m3925\Documents\Pico\pico-project-generator\picoGpioIn です。

m3925の部分は皆さんのユーザー名に置き換えてください。

vscode関連の json ファイルは前回作成済のプロジェクトのものをドラッグ&ドロップでコピー&ペーストすると楽ができます。

ソースコードを書く

VSCodeのツリーから picoGpioIn.c を選択して次のように編集し、保存します。

“pico/stdlib.h” ですが、一般的に使われるヘッダを取り込んでいます。
ここで必要なのは hardware/gpio.h になります。GPIOにアクセスするために使われます。

#include "pico/stdlib.h"

#define LED_PIN 25
#define SW_PIN 16

int main()
{
    stdio_init_all();
    gpio_init(LED_PIN);
    gpio_init(SW_PIN);
    gpio_set_dir(LED_PIN, true);    // out
    gpio_set_dir(SW_PIN, false);    // in
    gpio_pull_up(SW_PIN);

    while (true) {
        if (!gpio_get(SW_PIN)) {
            gpio_put(LED_PIN, 1);
        } else {
            gpio_put(LED_PIN, 0);
        }
    }
    return 0;
}

デバッグ開始

それでは接続に問題がないことを確認した後、Run – Start Debugging を選択します。

以下のようにmain()のところでカーソルが停止します。

F5キーを押してプログラムを動作させ、スイッチを押している間だけLEDが点灯すれば成功です。

割り込みを使ってみる

割り込みを使うには gpio_set_irq_enabled_with_callback() を使うのが便利です。
ちょっと長ったらしいですけれど、割り込みを有効にしてイベント発生時の関数をコールバック登録するものです。

コールバックとは割り込みの要因(条件)を指定し、その条件が成立すると、指定した関数に処理が来るしくみです。

APIのリンクを貼っておきます。

gpio_set_irq_enabled_with_callback()

ソースコードを編集する

VSCodeのツリーから picoGpioIn.c を選択して次のように編集し、保存します。

#include "pico/stdlib.h"

#define LED_PIN 25
#define SW_PIN 16

void gpio_callback(uint gpio, uint32_t events) {
    static int event_count = 0;
    event_count++;
    if (event_count & 1) {
        gpio_put(LED_PIN, 1);
    } else {
        gpio_put(LED_PIN, 0);
    }
}

int main()
{
    stdio_init_all();
    gpio_init(LED_PIN);
    gpio_init(SW_PIN);
    gpio_set_dir(LED_PIN, true);
    gpio_set_dir(SW_PIN, false);
    gpio_pull_up(SW_PIN);
    gpio_set_irq_enabled_with_callback(SW_PIN, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &gpio_callback);
    while (true) {
    }
    return 0;
}

プログラムの説明

割り込みとは

人間社会における割り込みは悪いイメージがありますが、マイコンを使ったシステムにおいて割り込みは重要な役割を果たします。
内容的にはどちらも一緒で、今やっていることを中断して割り込んだものを先に処理します。

GPIOの割り込みは例えば入力の状態変化に使います。
あるポートの入力信号が H から L にかわったり、 L から H にかわることをいち早く検出したい場合に割り込みを使うことができます。

例えば100msec周期のポーリングで入力の信号変化をチェックしているとします。
たまたまチェックし終えた直後に信号が変化したら、次のチェックまで約100msecあるのでそれだけ検出が遅れることになります。

そうは言っても 1msec周期のポーリングにすると検出は速くなりますが、その分忙しくなります。

そこでGPIOの割り込みを使います。
割り込みを使えば入力ポートの状態をサンプリングする必要がなくなり、信号が変化したことをすぐに検出することができます。

それではAPIを見ていきましょう。

gpio_set_irq_enabled_with_callback() は複雑ですけど設定をまとめて処理できそうです。

第1引数:入力するGPIOピンを指定します。
第2引数:要因を指定します。

GPIO_IRQ_EDGE_RISE : 立ち上がりエッジ
GPIO_IRQ_EDGE_FALL : 立ち下がりエッジ
GPIO_IRQ_LEVEL_HIGH : ハイレベル
GPIO_IRQ_LEVEL_LOW : ローレベル

要因は | でつなぐことにより、OR で割り込みが発生します。
上のコード例では、立ち上がりエッジか立下りエッジのどちらかで割り込みが発生することになります。

第3引数:trueで割り込みイネーブル、falseでディセーブル
第4引数:割り込みが発生した時に呼ばれるコールバック関数を指定します。

第4引数は gpio_irq_callback_t という型になっています。

enabled と書かれているので、設定ついでに割り込みを許可してくれるようです。

次の宣言は関数ポインタを示しています。

typedef void(* gpio_irq_callback_t) (uint gpio, uint32_t event_mask)

gpio_irq_callback_t が

void function(uint, uint32_t) というシグネチャ(関数の形式)の関数へのポインタになります。

従ってこれに対応する関数もこの形式でなければいけません。

ソースコードの void gpio_callback(uint gpio, uint32_t events) の部分とシグネチャが一致していることがわかります。

要因を複数設定した場合に、どのGPIOがどの状態でイベントを発生させたのかを引数として渡したいから関数がこのような形になっているのだと思います。

この例では割り込みが入るたびにLEDを点滅するコードを書きました。

チャタリングがあるとスイッチを押した時や離した時の点滅の状態がかわることがあるので注意が必要です。

ですから私個人的には、GPIO入力の割り込みは使いませんが、ここでは割り込みの例として参考までに紹介しました。

うまく使えばGPIOの状態をポーリングする必要がないので割り込みは効率的で便利だと思います。

いかがでしたか?
通常の入力、割り込み処理ともに動作を確認できたでしょうか?

お疲れさまでした。

GPIOカテゴリの最新記事