お知らせ:10月末まで応募可能な期間限定の無料メンター企画実施中

マイコン入門 LEDの点灯・消灯制御方法【入出力ポート機能】

マイコン
この記事は約22分で読めます。

こんにちは、ナナです。

LEDとはLight Emitting Diode(発光ダイオード)と呼ばれる素子の略称です。

LEDはテレビ、炊飯器、掃除機などの身近な組み込み機器にも搭載されている皆さんにとって大変なじみのあるハードウェアです。

本記事では次の疑問点を解消する内容となっています。

本記事で学習できること
  • LEDってなんで光るの?
  • 回路図ってどうやって見るの?
  • 入出力ポートって何?
  • 入出力ポートとLEDの関係性とは?
  • モジュールの役割を検討する方法とは?
  • インターフェース仕様書って何?

では、LEDを光らせる仕組みを学んでいきましょう。

スポンサーリンク

LEDが点灯する原理を理解しよう

皆さん小学生の頃に豆電球に電池をつなげて光らせた経験があるのではないでしょうか。LEDが光る原理はこれと同じです。

電球とLEDの点灯

電池の+極から電流が流れだし、豆電球を通ることで光ります。電気というエネルギーが光に変換されて光るのです。

LEDの場合は抵抗器を付けることで過剰に電流が流れすぎないように調整はするものの、電流を流すことで光るのは同じです。

電気が流れるとは

電気の特性はよく水に例えられます。水は山の上から下流に流れていきますよね。電気も同じです。電位(電圧)と呼ばれる電気の世界の標高において、電位の高い場所から電位の低い場所に向けて電気は流れていきます。

水と電流

このように電位は電源の+極が最も高く、ー極が最も低くなっています。この間を線でつなぐことで電気が流れるという原理で動いています。

電位はボルトという単位となっており、電池の場合は1.5Vといった単位で表記されます。

スポンサーリンク

LEDを点灯・消灯する制御方法を知ろう

ここからはLEDというハードウェアを制御するための回路を解説します。

LED回路と制御方法

LEDには豆電球とは少し違う特性があります。それは点灯させるための電気を流す方向が決められていることです。回路図を見ることでその方向を確認することができます。

一般的にLEDという素子の回路は次の図で表現されます。

一般的なLED回路図

LEDには極性というものがあり+極をアノード、-極をカソードと呼びます。LEDを点灯させるためにはアノード側からカソード側へ電気を流す必要があります。ダイオードの△記号を矢印と見立てれば方向はわかりやすいですね。

ではビュートローバーの回路図でLEDの部分がどのような回路になっているかを見てみましょう。

ビュートローバーのLED回路図

LEDのアノードが3.3Vの電源につながっており、カソードがマイコンのポートに接続されています。この回路から読み取れることは3.3V電源から流れ出す電流をマイコン側に引き込めばLEDが点灯するということです。

LEDの点灯/消灯とポート電圧の関係性

ここまでの解説を元にポート電圧とLEDの点灯/消灯制御についてまとめましょう。皆さんはプログラムからI/Oポートの電圧をHighかLowへ変化させることが可能です。

LEDを点灯させるためには

LEDの点灯制御

プログラムからポートの電圧をLowへ引き下げることで電源との電位差が生じます。電位差が生まれたことにより電源から電流が放出されマイコン側に流れ込みLEDが点灯します。

LEDを消灯させるためには

LED消灯イメージ

プログラムからポートの電圧をHighに引き上げることで電源との電位差がなくなります。電位差がなくなったことにより電源からの電流放出が停止しLEDが消灯します。

ポート電圧をLowにすることで点灯することに注意!初心者は間違えやすい!

LEDを制御するためのI/Oポートレジスタのまとめ

それではプログラムレベルでLEDを制御する方法をまとめましょう。
オレンジ色LEDを例としてI/Oポートのレジスタ制御方法を示します。

オレンジLEDに対するレジスタ制御方法

オレンジLEDは「P60」のポート番号に接続されているためポート6のビット0番を制御します。

オレンジLEDのレジスタ制御まとめ

LEDは出力系デバイスに所属していますので、PCR6の0番ビットに「1」として出力設定にする必要があります。

PCRを出力設定にしたため、PDR6のビット0番は次の外部電圧の制御が行えます。

  • PDR6のB0ビットに対し、0(電圧Low)を設定すると点灯  ※0で点灯することに注意
  • PDR6のB0ビットに対し、1(電圧High)を設定すると消灯
スポンサーリンク

LED制御を行うLEDモジュールの作成

LEDの制御方法がわかりましたので具体的なプログラムコードの作成に向かいましょう。

HEWプロジェクトへソース・ヘッダファイルの追加

それではLEDモジュールの処理を記述するためのファイルを追加しましょう。
led.cとled.hの2つのファイルを追加します。

まずは新規ファイルをプロジェクトで作成します。

新規ファイルの追加

次に、作成した新規ファイルに対して名前を付けて保存します。

ファイルの追加

保存したファイルをHEWのプロジェクトに登録します。

ファイルの登録

同手順にてled.hも追加しましょう。ヘッダファイルの場合は、最後のプロジェクトに登録する手順ファイルの種類は「C header file」を選択するようにしてください。

最終的にプロジェクトに次のようにled.cとled.hが追加されていれば成功です。

登録されたファイル

次章以降もモジュールのファイル作成時には同手順で作成すること!

モジュールの役割検討

LEDモジュールの役割は他のモジュールからLEDというハードウェア制御を隠蔽することです。「LEDを点灯/消灯したければ私に任せて!」という役割を担ってもらいます。

LEDモジュールの役割イメージ

私の場合はモジュールの役割を検討するうえで、モジュールを擬人化してイメージすることで役割が見えてきたりします。LEDモジュールに対して他者からどのような要望がありそうかを考えるとよいでしょう。

LED制御に対する考察と要望

  • LEDの種類にはオレンジと緑が2つある
  • LEDは点灯と消灯の状態をもつ
  • システム起動時は両LEDは消灯状態としたい
  • 任意のタイミングでLEDを点灯/消灯したい

次はこれらを実現するためのインターフェースとなる関数構成を検討します。

インターフェースの設計

他のモジュールからLEDモジュールへの要求をどのような形にするかを検討する作業がインターフェースの設計になります。

インターフェース設計の考え方は、対象モジュールにどのような機能が必要で、他のモジュールに対してどのような形のサービスを提供すると便利なのかを考えます。サービスというのは常に使う側ファーストなのです。

初期化インターフェース

最低限、システム起動直後に対象モジュールの状態を初期状態にするための初期化インターフェースを必ず持たせます。インターフェースの関数仕様は次のものとして定義しましょう。

インターフェースの名前は「モジュール名+処理概要」のルールで統一します。initとはinitialize(初期化)の略であり、ソフトウェア業界ではよく使われる名前です。

Led_init関数仕様

システム起動直後にLEDを消灯したい要望はこのインターフェースで実現しましょう。

点灯・消灯インターフェース

LEDの色にはオレンジとグリーン、状態として点灯と消灯があるため、利用したいモジュールが任意の色と状態を指定できるような関数仕様にしたいと思います。

Led_setLight

引数で色と状態を指定できるように設計しました。LEDを点灯・消灯させたいモジュールは引数で指示を与えることができるインターフェースになっています。

引数で指定する数値はマクロ定数として定義します。LEDの色種別と状態種別を下記の名前と値で定数化しましょう。

LEDの種別定義

実際の開発ではこのようなインターフェースに関わる情報をインターフェース仕様書としてドキュメントに起こします。そうすることでLEDモジュールを利用したい人はドキュメントから使い方を知ることができるようになります。

IF仕様書の位置づけ

インターフェイス仕様書例

1.提供ヘッダファイル名

#include “led.h”

2.定数定義

LEDの種別定義

3.インターフェース定義

3.1 初期化

Led_init関数仕様2

3.2 点灯・消灯の設定

Led_setLight2

インターフェース仕様書はインターフェースを使う側の人が必要とする情報をまとめる。必要以上にモジュール内部の情報を記述するのはよくないことに注意!

スポンサーリンク

課題:LEDモジュールを作って点灯・消灯させてみよう

長らくお待たせいたしました。ここからは皆さんに課題形式でシステムを構築していただきます。まずは本章にて語られたLEDモジュールを仕様通りに実装いただきます。

いきなり全てを作ろうとすると皆さんも困惑しますので、3つの課題に分けて順に解いていきましょう。

解答を見るべきタイミングについて

各課題には見開き形式で解答が付いています。しかし、可能な限り解答を見ずに自分で頑張って解いてみてください。本章や関連の章を見直すのもよいです。

中々できずに試行錯誤するかもしれませんが、試行錯誤した分だけ必ず成長します。安易に答えを見てしまうとせっかくの成長する機会を奪ってしまうのです。

解答を見るのは自分で動かせた時の答え合わせとして見ることを推奨します。このカリキュラムは皆さんの技術スキルを向上させるために作られています。

課題1

課題内容

※main.cは下記ベースのプログラムに全て差し替えてから変更を加えよ。

main.cに対して次の処理を追加せよ。

  • MAINモジュールからLEDモジュールを利用するためled.hをインクルードせよ
  • main_init関数にてオレンジとグリーンのLEDに対するPCRを出力に設定せよ
  • main_init関数にてLEDモジュールを初期化するLed_init関数を呼べ

main.c

#include "iodefine.h"


//------------------------------------------------
//  内部プロトタイプ宣言
//------------------------------------------------
void main_init(void);

//------------------------------------------------
//  概  要:エントリーポイント
//------------------------------------------------
void main(void)
{
    //  システム初期化処理
    main_init();

    while(1)
    {

    }
}

//------------------------------------------------
//  概  要:システム初期化
//------------------------------------------------
void main_init(void)
{
    //---------------------------------------------------
    //  ウォッチドッグタイマの停止(消さないこと)
    //---------------------------------------------------
    WDT.TCSRWD.BYTE = 0x92;
    WDT.TCSRWD.BYTE = 0x92;


    //---------------------------------------------------
    //  PCRの設定(PCRは書き込み専用レジスタ)
    //---------------------------------------------------


    //---------------------------------------------------
    //  モジュールの初期化(各モジュールの初期化を実施)
    //---------------------------------------------------

}

PCRの設定とLed_init関数は記載してあるmain_init関数のコメントに従い処理を追加せよ。

main.c

#include "iodefine.h"
#include "led.h"        //  LEDモジュールを使用するためインクルード

//------------------------------------------------
//  内部プロトタイプ宣言
//------------------------------------------------
void main_init(void);

//------------------------------------------------
//  概  要:エントリーポイント
//------------------------------------------------
void main(void)
{
    //  システム初期化処理
    main_init();

    while(1)
    {

    }
}

//------------------------------------------------
//  概  要:システム初期化
//------------------------------------------------
void main_init(void)
{
    //---------------------------------------------------
    //  ウォッチドッグタイマの停止(消さないこと)
    //---------------------------------------------------
    WDT.TCSRWD.BYTE = 0x92;
    WDT.TCSRWD.BYTE = 0x92;


    //---------------------------------------------------
    //  PCRの設定(PCRは書き込み専用レジスタ)
    //---------------------------------------------------
    IO.PCR6         = 0x11;     //  P60,P64を出力


    //---------------------------------------------------
    //  モジュールの初期化(各モジュールの初期化を実施)
    //---------------------------------------------------
    Led_init();
    
}
  • PCR6レジスタの設定はオレンジLEDがP60、グリーンLEDがP64のため0x11を設定することにより出力設定となる。レジスタの設定は常に2進数をイメージしながら行う必要があることに注意。
PCR6の設定イメージ

課題2

課題内容

インターフェース仕様に従いLEDモジュールを作成せよ。led.cとled.hは次のプログラムをベースにして修正を加えよ。

led.h

#ifndef LED_H
#define LED_H
//------------------------------------------------


//------------------------------------------------
//  マクロ定義(Macro definition)
//------------------------------------------------


//------------------------------------------------
//  型定義(Type definition)
//------------------------------------------------


//------------------------------------------------
//  プロトタイプ宣言(Prototype declaration)
//------------------------------------------------


//------------------------------------------------
#endif  // LED_H

led.hに追記すべきこと

  • LEDの色種別と点灯・消灯の定数をマクロ定義にて実装せよ。
  • インターフェースのプロトタイプ宣言を追加せよ。

led.c

#include "iodefine.h"
#include "led.h"

//------------------------------------------------
//  概 要:初期化
//  引  数:なし
//  戻り値:なし
//------------------------------------------------
void Led_init(void)
{

}

//------------------------------------------------
//  概 要:LEDの点灯・消灯制御
//  引  数:kind    LEDの色種別
//          onoff   点灯・消灯状態
//  戻り値:0   正常
//          -1  異常
//------------------------------------------------
int Led_setLight(int kind, int onoff)
{

}

led.cに追記すべきこと

  • Led_init関数でPDRレジスタを制御し、オレンジとグリーンのLEDを消灯する
  • Led_setLight関数で引数の指定に従い、オレンジとグリーンのLEDを点灯・消灯する

led.h

#ifndef LED_H
#define LED_H
//------------------------------------------------

//------------------------------------------------
//  マクロ定義(Macro definition)
//------------------------------------------------
#define D_LED_KIND_ORANGE (0) //  オレンジLED
#define D_LED_KIND_GREEN  (1) //  グリーンLED

#define D_LED_LIGHT_OFF   (0) //  消灯
#define D_LED_LIGHT_ON    (1) //  点灯

//------------------------------------------------
//  型定義(Type definition)
//------------------------------------------------


//------------------------------------------------
//  プロトタイプ宣言(Prototype declaration)
//------------------------------------------------
void Led_init(void);
int Led_setLight(int kind, int onoff);

//------------------------------------------------
#endif  // LED_H
  • マクロ定義とプロトタイプ宣言を仕様に従い定義する。

led.c

#include "iodefine.h"
#include "led.h"

//------------------------------------------------
//  概 要:初期化
//  引  数:なし
//  戻り値:なし
//------------------------------------------------
void Led_init(void)
{
    IO.PDR6.BIT.B0 = 1; // オレンジLEDの消灯
    IO.PDR6.BIT.B4 = 1; // グリーンLEDの消灯
}

//-------------------------------------------------
//  概 要:LEDの点灯・消灯制御
//  引  数:kind    LEDの色種別
//          onoff   点灯・消灯状態
//  戻り値:0   正常
//          -1  異常
//-------------------------------------------------
int Led_setLight(int kind, int onoff)
{
    if (kind == D_LED_KIND_ORANGE)
    {
        if (onoff == D_LED_LIGHT_OFF)
        {
            IO.PDR6.BIT.B0  = 1;  // 消灯
        }
        else
        {
            IO.PDR6.BIT.B0  = 0;  // 点灯
        }       
    }
    else if (kind == D_LED_KIND_GREEN)
    {
        if (onoff == D_LED_LIGHT_OFF)
        {
            IO.PDR6.BIT.B4  = 1;  // 消灯
        }
        else
        {
            IO.PDR6.BIT.B4  = 0;  // 点灯
        }           
    }
    else
    {
        //  色指定が規定外の場合異常終了
        return -1;
    }
    
    return 0;
}
  • Led_init関数ではP60とP64につながるオレンジLEDとグリーンLEDに対して、PDRのビットを1(電圧High)に設定することで消灯している。1で消灯になることに注意。
  • Led_setLight関数では引数で指定された色種別と状態種別に応じてPDR6のビットを制御している。色種別に関しては規定の色以外は異常終了という動作仕様とした。

課題3

課題内容

MAINモジュールのmain関数を変更し、ビュートローバー上のLEDを次の通りに点灯・消灯せよ。while(1)の中に処理を追加することで1~4を無限に繰り返すようにせよ。

  1. オレンジ点灯 → 一定時間待つ
  2. グリーン点灯 → 一定時間待つ
  3. オレンジ消灯 → 一定時間待つ
  4. グリーン消灯 → 一定時間待つ

一定時間の待ち処理は次のプログラムにて実施すること。

for (i=0 ; i < 1000000; i++);

main.c

//------------------------------------------------
//  概  要:エントリーポイント
//------------------------------------------------
void main(void)
{
    //  システム初期化処理
    main_init();

    while(1)
    {

    }
}

プログラムが完成したらビルドを行い、機器上で期待動作となるか確認せよ。


期待動作

ビュートローバー上のLEDが下記1~4に従い点灯・消灯し繰り返されることを確認。

  1. オレンジ点灯
  2. グリーン点灯
  3. オレンジ消灯
  4. グリーン消灯

main.c

//------------------------------------------------
//  概  要:エントリーポイント
//------------------------------------------------
void main(void)
{
    //  システム初期化処理
    main_init();

    while(1)
    {
        long i;

        //  オレンジLED点灯
        Led_setLight(D_LED_KIND_ORANGE, D_LED_LIGHT_ON);
        for (i=0 ; i < 1000000; i++);

        //  グリーンLED点灯
        Led_setLight(D_LED_KIND_GREEN, D_LED_LIGHT_ON);
        for (i=0 ; i < 1000000; i++);

        //  オレンジLED消灯
        Led_setLight(D_LED_KIND_ORANGE, D_LED_LIGHT_OFF);
        for (i=0 ; i < 1000000; i++);

        //  グリーンLED消灯
        Led_setLight(D_LED_KIND_GREEN, D_LED_LIGHT_OFF);
        for (i=0 ; i < 1000000; i++);
    }
}
  • LEDの点灯・消灯のインターフェースを呼び出すことで実現する。PDR6のレジスタをここでは制御せずにLEDのインターフェースを活用することに意味がある。引数はマクロ定数で指定すること。

スポンサーリンク

Q&A:LEDの点灯・消灯制御でよくある質問

組み込み機器においてLEDを点灯させるためには接続されたポートの電圧を下げればよいということですね。

それは少し違います。ビュートローバーの場合は確かにそうですが、他の機器の場合は異なるかもしれませんので注意してください。ポートの電圧を下げて点灯するのは、ビュートローバーのLED回路がそのようにハードウェア設計されているからにすぎません。

例えば次のようにLEDを接続する回路を持つ製品もあるのです。

電圧を上げると点灯するLED

この場合はマイコンのポート電圧を上げることでポートから電流を放出しLEDを点灯します。放出された電流はグランド(GND)と呼ばれる-極に回収されます。

このようにLEDを点灯/消灯させる制御とはハードウェア回路に依存して変化します。ですので、常にポートの電圧を下げれば点灯するという認識はもたないでください。

LEDの点灯/消灯のためのポート制御は回路に依存して変化することに注意!

main関数の中にwhile(1)という処理があります。これって無限ループでしたよね。こんなことしてよいのでしょうか?

はい、むしろ実施しなければならないのです。

C言語学習編ではこのような処理は出てきませんでしたが、組み込み機器というのはプログラムが終わらないという特徴があります。

例えば、皆さんの近くにあるリモコンの中にもマイコンが搭載されており、プログラムが動いています。リモコンのスイッチを押すといつでも反応しますよね。それはプログラムが常にスイッチが押されたかをチェックし続けているからです。

無限ループのプログラム

組み込み開発のソフトウェアとはこのように動き続けることが必要なため、無限ループを必ず持つものなのです。

main関数の中で実施している次の処理はいったい何なのでしょうか?
for (i=0 ; i < 1000000; i++);

この処理は一定時間の経過を待つための処理です。LEDの点灯・消灯の区間に少し時間を設けたいために何もしないループ処理を入れることで時間経過を無理やり実施しているのです。

正確な時間を待つわけではないので暫定ではありますがこのような形で時間を作り出しています。ループ回数を変化させることでLEDの点滅時間が変化するので実験してみるとよいでしょう。

LEDの制御ですがポートコントロールレジスタの設定がmain.cのmain_init関数の中で実施するようになっています。led.cの中で実施した方がよいのではないでしょうか?

これはするどい質問です。

本来ポートの入出力方向を決定する処理もLEDモジュールに実施させたいのはやまやまなのですが、PCRレジスタは書き込み専用で読み取りができないレジスタとなっています。

この制約があるため、PCRは一部のビットだけを書き換えることができません。そのためPCRの設定を全体を統括するMAINモジュールにて一括処理するような設計にしてあります。

main.cのmain_init関数の中にウォッチドッグタイマを停止するとの処理がありますがこれはなんでしょうか?

ウォッチドッグタイマに関してはもう少し後の章にて解説します。今の段階では気にしなくてよいでしょう。

この処理を消してしまうとプログラムが動かなくなっていしまうため、今後も消さないように注意してください。

ウォッチドッグタイマの停止処理は消さないように注意!動かなくなるぞ!