#9 インテリジェントブロックの機能を使おう【もっと!後輩たちのためのEV3rt講座】

# 目次

| 章 | タイトル | 内容 |
| :-: | :-: | :-: |
| 1 | [はじめに](#1-はじめに) | 今回やることの説明 |
| 2 | [LEDライト](#2-ledライト) | ボタン回りのLEDライトの使い方について |
| 3 | [ボタン](#3-ボタン) | 上面の6つのボタンの使い方について |
| 4 | [スピーカー](#4-スピーカー) | 側面のスピーカーの使い方について |
| 5 | [LCD (液晶ディスプレイ)](#5-lcd-液晶ディスプレイ) | LCDの使い方について |
| 6 | [バッテリ](#6-バッテリ) | バッテリの状態を取得する方法について |
| 7 | [まとめ](#7-まとめ) | 今回のまとめ |

# 1. はじめに

前回までの2回で、センサの使い方は一通り解説することが出来ました。
これでほぼ、ロボットを動かせる状態になったとも言えますが、あと一つ、重要なパーツがありますね。
それが今回のテーマ、EV3 インテリジェントブロックです。

![ev3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/a196d5a0-6106-ccf7-ad9c-9bed0ebe358c.png)

EV3インテリジェントブロックとはいわばロボットの核となるパーツで、中にはプロセッサ、要するにコンピュータが入っています。
今までに解説してきた通り、モータ用のポートが4つ(`A`~`D`)、センサ用のポートが4つ(`1`~`4`)搭載されており、通信方式としてはUARTとI2Cをサポートしています。

又、USBタイプAポート、USBタイプmicro Bポート、microSDカードスロットが備わっています。
このうちUSB-Aポートは、EV3rtではサポートされていません。
microSDカードはEV3rtを起動するために、USB micro BポートはmicroSDカードの中身を閲覧・書き込みするため、もっぱら実行ファイルを転送するために使ってきましたね。

ここまでは、既に解説し使用してきた機能です。
今回は、いまだ登場していない以下の5つの機能の使い方について解説していきます。

- LEDライト
- ボタン
- スピーカー
- LCD (液晶ディスプレイ)
- バッテリー

APIの説明は以下のページに書かれていますので、そちらもあわせてご覧ください。

[EV3RT C API Reference / EV3本体機能](https://www.toppers.jp/ev3pf/EV3RT_C_API_Reference/group__ev3api-brick.html)

# 2. LEDライト

インテリジェントブロックの上面中央部にはボタンが並んでいますが、そのボタンの内部にはLEDライトが搭載されており、プログラムにより自由に光らせることが出来ます。
このLEDライトの使い方から解説していきます。

## 光らせることが出来る色

まずは、LEDに設定できる色について整理しておきましょう。
EV3ソフトウェアにもLEDライトを制御するボタンがあり、そこでは以下のような設定項目がありました。

![led_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/52542f7c-9f48-06cf-6199-aeb57f974edb.png)

![led_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/8538c07e-93a9-dd99-d1ca-eb05307c194f.png)

上の画像はLED「オフ」と「オン」の切り替え、下の画像は「オン」の時に設定できる色を表しており、緑・オレンジ・赤から選ぶことが出来ます。
EV3rtでも、この4つから状態を選ぶことになります。又、その状態については、列挙型により既に定数が定義されています。

```c
typedef enum {
	LED_OFF    = 0,
	LED_RED    = 1 << 0,
	LED_GREEN  = 1 << 1,
	LED_ORANGE = LED_RED | LED_GREEN,
} ledcolor_t;
```

| 状態 | 定数 | 値 |
| :-: | :-: | :-: |
| OFF | LED_OFF | 0 |
| 赤 | LED_RED | 1 |
| 緑 | LED_GREEN | 2 |
| オレンジ | LED_ORANGE | 3 |

カラーセンサの色の時と同じように、基本的にはこの定義された定数を使っていけば良いかと思います。

## LEDライトの設定関数

それでは、LEDライトの設定を行う関数を紹介します。

```c
ER ev3_led_set_color(ledcolor_t color);
```

関数名は`ev3_led_set_color`で、LEDライトの色を設定します。
引数には、先ほど紹介したLEDカラーの定数を指定します。
(戻り値はエラーコードで基本的には使いません。)

簡単な使い方を示しておきたいと思います。

*Ex 1. LEDの色を順番に変えていく*

```c:app.c
void main_task(intptr_t unused)
{
    while(1){
        ev3_led_set_color(LED_OFF); //LEDをOFFにする
        tslp_tsk(1*1000*1000); // 1s待機
  
        ev3_led_set_color(LED_RED); //LEDを赤色にする
        tslp_tsk(1*1000*1000); // 1s待機
  
        ev3_led_set_color(LED_GREEN); //LEDを緑色にする
        tslp_tsk(1*1000*1000); // 1s待機
  
        ev3_led_set_color(LED_ORANGE); //LEDをオレンジ色にする
        tslp_tsk(1*1000*1000); // 1s待機
    }
}
```

1秒ごとにオフ⇒赤⇒緑⇒オレンジと切り替わるはずです。
ロボットプログラミングにおいて、LEDライトは今の状態を表すインジケータとして非常にわかりやすいですし、簡単に使えるので活用していきましょう。

# 3. ボタン

インテリジェントブロックの上面に、上・下・左・右・中央の5つのボタン、さらに左上に戻るボタンもあるため、計6つのボタンを使用することが出来ます。

## ボタンの定義

各ボタンについても、プログラミングしやすいよう列挙型により定数が定義されています。

```c
typedef enum {
    LEFT_BUTTON  = 0,
    RIGHT_BUTTON = 1,
    UP_BUTTON    = 2,
    DOWN_BUTTON  = 3,
    ENTER_BUTTON = 4,
    BACK_BUTTON  = 5,
    TNUM_BUTTON  = 6,
} button_t;
```

| ボタン | 定数 | 値 |
| :-: | :-: | :-: |
| 左 | LEFT_BUTTON | 0 |
| 右 | RIGHT_BUTTON | 1 |
| 上 | UP_BUTTON | 2 |
| 下 | DOWN_BUTTON | 3 |
| 中央 | ENTER_BUTTON | 4 |
| 戻る | BACK_BUTTON | 5 |

ボタンを指定するときも、基本的にはこの定義された定数を使っていけば良いでしょう。

## ボタンが押されているかを取得する

指定したボタンが今押されているかを取得する関数を紹介します。
EV3ソフトでは以下のボタンにあたります。

![btn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/1385cef7-bd68-9131-00ee-02df6aa4c61a.png)

```c
bool_t ev3_button_is_pressed(button_t button);
```

関数名は`ev3_button_is_pressed`で、指定したボタンの押下状態(押されているか否か)を取得します。
引数には、先ほど紹介したボタンの定数を指定します。
戻り値として、ボタンが押されている場合は`True`が、押されていない場合は`False`が返ってきます。

ここでも、簡単な使い方を示しておきます。

*Ex 2. ボタンの状態でLEDを切り替える*

```c:app.c
void main_task(intptr_t unused)
{
    while(1){
        if(ev3_button_is_pressed(LEFT_BUTTON)){ //左ボタンが押されているとき
            ev3_led_set_color(LED_RED); //LEDを赤色にする
        }
        else if(ev3_button_is_pressed(RIGHT_BUTTON)){ //右ボタンが押されているとき
            ev3_led_set_color(LED_GREEN); //LEDを緑色にする
        }
        else if(ev3_button_is_pressed(ENTER_BUTTON)){ //中央ボタンが押されているとき
            ev3_led_set_color(LED_ORANGE); //LEDをオレンジ色にする
        }
        else{ 
            ev3_led_set_color(LED_OFF); //LEDをオフにする
        }
        tslp_tsk(100*1000); // 100ms待機
    }
}
```

実行するとLEDが消灯しますが、左ボタンを押すと赤色、右ボタンを押すと緑色、中央ボタンを押すとオレンジ色にLEDが光ります。

尚、戻るボタンも使うことは出来るのですが、戻るボタン長押しは一番初めの画面である「EV3RT Console」への遷移に割り当てられているため、不要な誤作動を避けるためにも戻るボタンはあまり使わない方が無難でしょう。

## ボタンを押したときに関数を実行する

EV3rtでは、ボタンを押したときに特定の関数を実行できるように設定することが出来ます。
これを「クリックイベントハンドラ」と言います。
例えば、先ほど無限ループでボタンを押したかどうかを常時判定していましたが、「左ボタンを押したときはこの関数を実行しろ」と先に決定しておけば、常時チェックをかける必要がなくなります。

```c
ER ev3_button_set_on_clicked(button_t button, ISR handler, intptr_t exinf);
```

関数名は`ev3_button_set_on_clicked`で、指定されたボタンで実行する関数を設定することが出来ます。
第一引数は設定するボタンで、先ほども使用したボタンの定数を使います。
第二引数は実行する関数を指定します。
第三引数は、第二引数で指定した関数に一つだけ整数値の引数を渡すことが出来、それを指定します。

百聞は一見に如かずなので、説明はこれくらいにして例を紹介します。

*Ex 3. ボタンが押されたときのLEDの光り方を決めておく*

```c:app.c
#include "ev3api.h"
#include "app.h"
#include <stdio.h>

void btn_task(intptr_t button); // 関数のプロトタイプ宣言

void main_task(intptr_t unused)
{
    // クリックイベントハンドラの設定
    ev3_button_set_on_clicked(LEFT_BUTTON, btn_task, LEFT_BUTTON); //左ボタンを押したときの設定
    ev3_button_set_on_clicked(RIGHT_BUTTON, btn_task, RIGHT_BUTTON); //右ボタンを押したときの設定
    ev3_button_set_on_clicked(ENTER_BUTTON, btn_task, ENTER_BUTTON); //中央ボタンを押したときの設定

    ev3_motor_config(EV3_PORT_A, LARGE_MOTOR);
    ev3_motor_config(EV3_PORT_D, LARGE_MOTOR);

    while(1){
        // 500ms 前進
        ev3_motor_set_power(EV3_PORT_A, 25);
        ev3_motor_set_power(EV3_PORT_D, 25);
        tslp_tsk(500*1000);

        // 500ms 後退
        ev3_motor_set_power(EV3_PORT_A, -25);
        ev3_motor_set_power(EV3_PORT_D, -25);
        tslp_tsk(500*1000);
    }
}

void btn_task(intptr_t button)
{
    // どのボタンが押されたかは引数「button」から得られる
    switch (button){
        case LEFT_BUTTON:
            ev3_led_set_color(LED_RED); //LEDを赤色にする
            break;
        case RIGHT_BUTTON:
            ev3_led_set_color(LED_GREEN); //LEDを緑色にする
            break;
        case ENTER_BUTTON:
            ev3_led_set_color(LED_ORANGE); //LEDを緑色にする
            break;
        default:
            ev3_led_set_color(LED_OFF); //LEDをオフにする
            break;
    }
}
```

(※関数のプロトタイプ宣言の話をまだしていないので、app.cファイル上に書いています。本来はapp.hに書くのが通例です。)

メインタスクの一番最初でクリックイベントハンドラを指定しており、各ボタンが押されたら`btn_task`という関数が動くよう設定しています。
又、`ev3_button_set_on_clicked`の第三引数に、押されたボタンの定数を指定しており、これが`btn_task`の引数`button`に渡されます。

関数`btn_task`では、受け取った引数`button`に応じて、LEDの色を設定しています。

話をメインタスクに戻すと、クリックイベントハンドラ設定後は無限ループに入り、ロボットは前進と後退を繰り返します。
この状態で、左・右・中央ボタンを押してみてください。LEDが対応した色に切り替わるはずです。

ちなみに今回、すべてのボタンに同じ関数を実行するよう指定していますが、当然別の関数を実行するよう指定することも可能です。

この設定をしておけば、「ボタンが押されたときに○○する」というプログラムを簡単に組むことが出来るのでとても便利ですね。
ただし、WROなどのロボコンでは、原則的に押せるボタンは真ん中ボタンだけ、しかも競技開始後はロボットに触れることは出来ないので、こういった競技の場面ではあまり使え無さそうです。
(FLLのような何度も発進が可能なロボコンでは有効化も…？)

最後にこの関数の注意点ですが、クリックイベントハンドラにより実行される関数よりも、メインタスクの関数の方が優先度が高いようです。
したがって、重複するような動き(どちらもLEDの色を設定するとか)をプログラムすると、メインタスクの方しか実行されないので注意してください。

# 4. スピーカー

インテリジェントブロックの上面に対して、右側面にあるスピーカーの使い方です。
スピーカーといっても単音が出るだけなので、電子工作的に言えばブザーといった方が正しいでしょうか。
何はともあれ、使い方を説明していきます。

## 音量の設定

使用する前に、スピーカーから出る音の音量を設定していおきましょう。
単純に音量設定をしておかないと、100%で鳴らすとかなりうるさいですし、音量が0%になっていて「音が出ない!」と勘違いすることを防ぐ効果もあります。

```c
ER ev3_speaker_set_volume(uint8_t volume);
```

関数名は`ev3_speaker_set_volume`で、スピーカーの音量設定を行います。
引数には`0`~`100`の整数値を指定します。これがスピーカー音量のパーセンテージになります。

使い方は後ほど示します。

## 指定の周波数でトーン出力

次に、音の周波数を指定して鳴らす方法を紹介します。
周波数とは簡単に言えば音の高さのことですね。

EV3ソフトでは以下のブロックにあたります。
(尚、EV3ソフトでは音量の設定もこれらのブロックに含まれています。)

![sound.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/ed3aa917-6397-3d3c-a081-b3bc2d3d514e.png)


関数の紹介に入る前に、いきなり周波数なんて言われても、一般の人には何の音がどれくらいの周波数だとかはピンとこないと思います。
そこで、事前に定義されている「音階」と周波数を結び付けた定数を使いたいと思います。

```c
#define SOUND_MANUAL_STOP (-1)

#define NOTE_C4  (261.63)
#define NOTE_CS4 (277.18)
#define NOTE_D4  (293.66)
#define NOTE_DS4 (311.13)
#define NOTE_E4  (329.63)
#define NOTE_F4  (349.23)
#define NOTE_FS4 (369.99)
#define NOTE_G4  (392.00)
#define NOTE_GS4 (415.30)
#define NOTE_A4  (440.00)
#define NOTE_AS4 (466.16)
#define NOTE_B4  (493.88)
#define NOTE_C5  (523.25)
#define NOTE_CS5 (554.37)
#define NOTE_D5  (587.33)
#define NOTE_DS5 (622.25)
#define NOTE_E5  (659.25)
#define NOTE_F5  (698.46)
#define NOTE_FS5 (739.99)
#define NOTE_G5  (783.99)
#define NOTE_GS5 (830.61)
#define NOTE_A5  (880.00)
#define NOTE_AS5 (932.33)
#define NOTE_B5  (987.77)
#define NOTE_C6  (1046.50)
#define NOTE_CS6 (1108.73)
#define NOTE_D6  (1174.66)
#define NOTE_DS6 (1244.51)
#define NOTE_E6  (1318.51)
#define NOTE_F6  (1396.91)
#define NOTE_FS6 (1479.98)
#define NOTE_G6  (1567.98)
#define NOTE_GS6 (1661.22)
#define NOTE_A6  (1760.00)
#define NOTE_AS6 (1864.66)
#define NOTE_B6  (1975.53)
```

Cはド、Dはレ、Eはミ、以降F(ファ)、G(ソ)、A(ラ)、B(シ)を表します。
又、Sがついているのは#シャープ(半音上げ)で、例えばCSはド#です。
さらに、後ろについている数字はオクターブを表します。
これで、幾分か使いやすくなったことでしょう。

ちなみに、`SOUND_MANUAL_STOP`は音階ではなく、後ほど紹介する再生停止関数が実行されない限り、音を鳴らし続けることを指定する定数です。

では、本題の関数の紹介に移ります。

```c
ER ev3_speaker_play_tone(uint16_t frequency, int32_t duration);
```

関数名は`ev3_speaker_play_tone`で、周波数と鳴らす時間を指定して音を鳴らします。
第一引数は周波数、第二引数は鳴らす時間(ミリ秒)で、どちらも整数値で指定します。

では、簡単な使い方を示します。

*EX 3. ド・レ・ミ・ファ・ソと音を鳴らす*

```c:app.c
void main_task(intptr_t unused)
{
    ev3_speaker_set_volume(10); // 音量の設定

    ev3_speaker_play_tone(NOTE_C5, 1000); //ド
    tslp_tsk(1*1000*1000);

    ev3_speaker_play_tone(NOTE_D5, 1000); //レ
    tslp_tsk(1*1000*1000);

    ev3_speaker_play_tone(NOTE_E5, 1000); //ミ
    tslp_tsk(1*1000*1000);

    ev3_speaker_play_tone(NOTE_F5, 1000); //ファ
    tslp_tsk(1*1000*1000);

    ev3_speaker_play_tone(NOTE_G5, 1000); //ソ
    tslp_tsk(1*1000*1000);
}
```

これで、ド・レ・ミ・ファ・ソと音を鳴らすことが出来ます。
ちなみに、私の手元にあるEV3で鳴らしてみたところ、実際には「レ・ミ・ファ#・ソ・ラ」と、Dのコードのように鳴ってしまいました。
これはEV3のスピーカーの性能によるもの、あるいは個体差が原因かと思われます。

関数の使い方の注意点として、1秒鳴らすと指定したとしても、1秒待ってくれるわけではありません。
メインタスクでは、音声の再生完了を待たずに次の関数の実行に移ります。
従って、音を順番に鳴らしたい時は上記のように待機を入れてあげる必要があります。

ちなみに、この周波数の指定は音階の定数でなくとも、任意の周波数を指定することも出来ます。
例えばこんなことも可能です👇

*EX 4. 周波数をどんどん上げていく*

```c:app.c
void main_task(intptr_t unused)
{
    int i;
    
    ev3_speaker_set_volume(10); // 音量の設定

    // どんどん周波数を上げていく
    for(i=250; i<10000; i++){
        ev3_speaker_play_tone(i, 100); //周波数 i Hz で 1ms鳴らす
        tslp_tsk(100*1000); // 1ms待機
    }
}
```

実行すると、音の高さがどんどん上がっていくはずです。

## 音を停止する

鳴らしている音を、任意のタイミングで停止させることも出来ます。

```c
ER ev3_speaker_stop();
```

関数は`ev3_speaker_stop`で、スピーカーでの再生を停止します。
引数はありません。

こちらも例を示します。

*EX 5. 音が鳴り終わる前に強制終了する*

```c:app.c
void main_task(intptr_t unused)
{
    ev3_speaker_set_volume(10); // 音量の設定

    ev3_speaker_play_tone(NOTE_C5, 3000); // C5で3秒鳴らす
    tslp_tsk(500*1000); // 500ms待機
    ev3_speaker_stop(); // 再生停止
}
```

初め、C5(ド)の音で、3000msすなわち3秒鳴らすよう設定しました。
ところが、500msの待機を入れたのち、再生停止の関数を実行すると、最初に設定した3秒を待たずに再生が停止します。
これにより、任意のタイミングで音を停止することが出来ます。

## WAVファイルを再生する

EV3rtでは、microSDカードに保存したWAVファイル(音声ファイル)を再生することができます。
ただし、WAVファイルは **「8bit 8kHz モノラル」** で作成されてなければなりません。
音声編集ソフト等での編集作業が必要になるかと思います。

### WAVファイルを作る

EV3ソフトにも入っているような、良く使われる音声のファイルについては以下のページに沢山掲載されており、ダウウンロードも可能です。
ただし、上述の形式に自分で合わせる必要はあります。

https://pybricks.com/ev3-micropython/media.html

こんな形で配布されています。
![sound_file.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/32feb1b5-5306-1ba3-ecb0-7e2335fee7e7.png)

ではこれを、再生できる形にしてみましょう。
今回はフリーの音声編集ソフト、「Audacity」を使ってみます。

https://www.audacityteam.org/

インストールして、ソフトを起動したら、上記のサイトからダウンロードしてきた音声ファイルをドラッグ&ドロップで取り込みましょう。

![wav_make.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/483e48b3-48cc-f34a-b682-ea070337d06c.png)

何も編集することがなければ、そのまま「ファイル」から「オーディオをエクスポート」を押しましょう。
すると、以下のような書き出し設定が表示されます。

![wav_make2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/eba01e26-0c4a-3109-8a59-cb8d54e2ae17.png)

上の画像に示すように、設定を行います。

- 形式: WAV
- チャンネル: モノラル
- サンプリング周波数: 8000 Hz
(選択肢に無ければ、カスタム周波数から指定してください)
- エンコーディング: Unsigned 8-bit PCM

これで、条件を満たすWAVファイルを作成することが出来ます。
設定が完了したらエクスポートを押し、生成されたWAVファイルをEV3rtのmicroSDカード内に保存してください。

### WAVファイルを再生する関数

それでは、WAVファイルを再生する関数を紹介します。

```c
ER ev3_speaker_play_file(const memfile_t *p_memfile, int32_t duration);
```

関数名は`ev3_speaker_play_file`で、音声ファイルを再生します。
第一引数には「メモリファイルのポインタ」、第二引数には「再生時間(ミリ秒・整数値)」を指定します。

### メモリファイルの扱い

ここで、第一引数に指定する「メモリファイルのポインタ」を得るための関数も、ここで紹介しておきます。

```c
ER ev3_memfile_load(const char *path, memfile_t *p_memfile);
```

関数名は`ev3_memfile_load`で、sdカード上のディレクトリからファイルをロードし、メモリ上にメモリファイルとして保持します。
第一引数には「ファイルの場所のパス」、第二引数には「メモリファイルのポインタ」を指定します。

又、使ったメモリファイルは使い終わったら開放するようにすべきです。
その関数についても紹介しておきます。

```c
ER ev3_memfile_free(memfile_t *p_memfile);
```

関数名は`ev3_memfile_free`で、メモリファイルを開放する関数です。
引数には「メモリファイルのポインタ」を指定します。

### WAVファイルを再生する

では、これらの関数の簡単な使い方を示します。
今回は、microSDカード内の「res」フォルダにある、「test.wav」ファイルを使用することとします。

*EX 6. 音声ファイルを再生する*

```c:app.c
void main_task(intptr_t unused)
{
    memfile_t memfile; // メモリファイルの構造体を作成
    ev3_memfile_load("ev3rt/res/test.wav", &memfile); //SDカード内の"test.wav"をメモリファイルとしてロード

    ev3_speaker_set_volume(10); //音量の設定
    ev3_speaker_play_file(&memfile, SOUND_MANUAL_STOP); // 音声ファイルを再生
}
```

まず、上2行でmicroSDカードに保存されたWAVファイルを読み込んでいます。
WAVファイルがメモリファイルとして取り込まれるので、それを`ev3_speaker_play_file`にアドレスとして指定してあげます。
尚、音声ファイルは最後まで再生して欲しいので、再生時間のところには`SOUND_MANUAL_STOP`を指定しています。

この関数については少し使い方が難しいですが、マスターすれば好きな音声ファイルを流すことが出来るようになります。
興味のある方はぜひチャレンジしてみてください。

# 5. LCD (液晶ディスプレイ)

インテリジェントブロック上面、かなり大きな領域を取っているLCD(液晶ディスプレイ)についても、自由に文字や図形、画像をを表示することが出来ます。

## LCD上の座標

LCD上に何かを描くときは、必ず**座標**を設定します。
その座標軸がどのように設定されているかを以下に示します。

![lcd_x_y (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183170/7bde31c2-9f7c-5c43-d692-80a7607a25f3.png)

又、座標軸の最大値、すなわち画面領域の大きさについては、定数が定義されていますので、こちらも紹介しておきます。

```c
#define EV3_LCD_WIDTH  (178)
#define EV3_LCD_HEIGHT (128)
```

| サイズ | 定数 | 値 |
| :-: | :-: | :-: |
| 横幅 | EV3_LCD_WIDTH | 178 |
| 高さ | EV3_LCD_HEIGHT | 128 |

## 文字を描く

まずは最も基本的な、LCDに文字を描く方法を解説します。

### フォントサイズ

まずはLCDに文字列を表示してみようと思いますが、先に表示する文字列のフォントサイズを指定しておきます。
フォントサイズは「小」と「普通」の2種類が用意されています。
これも、以下のように列挙型により定義がされています。

```c
typedef enum {
	EV3_FONT_SMALL,
	EV3_FONT_MEDIUM,
} lcdfont_t;
```

| サイズ | 定数 | 値 |
| :-: | :-: | :-: |
| 小 | EV3_FONT_SMALL | 0 |
| 普通 | EV3_FONT_MEDIUM | 1 |

デフォルトでは「小」サイズが指定されています。

では、この定数を元に、LCDのフォントサイズを設定する関数を紹介します。

```c
ER ev3_lcd_set_font(lcdfont_t font);
```

関数名は`ev3_lcd_set_font`で、LCDで表示する文字列のフォントサイズを設定する関数です。
引数には先ほどのフォントサイズの定数を指定します。

使い方については後ほど例を示します。

### 文字列を描く

フォントサイズの設定が出来たら、いよいよLCD上に文字列を描いていきましょう。

```c
ER ev3_lcd_draw_string(const char *str, int32_t x, int32_t y);
```

関数名は`ev3_lcd_draw_string`で、指定した文字列を、指定した座標に描きます。
引数として、第一引数には「書きたい文字列 (char型配列のポインタ)」を、第二・三引数にはそれぞれ文字列を表示する「x座標」、「y座標」を整数値で指定します。

では、簡単な使い方を示します。

*EX 7. LCDに「Hello EV3」と描く*

```c:app.c
void main_task(intptr_t unused)
{
    ev3_lcd_set_font(EV3_FONT_MEDIUM); // フォントを普通サイズに変更
    ev3_lcd_draw_string("Hello EV3", 0, 0); // (x:0, y:0) に「Hello EV3」を描く
}
```

フォントサイズを中サイズに変更した後、一番左上に文字列「Hello EV3」を描きます。
上の例では、関数に「描きたい文字列」を直接指定していますが、もちろんchar型配列を先に作っておき、それを表示することも出来ます。

*EX 8. LCDにchar型配列の文字列を描く*

```c:app.c
void main_task(intptr_t unused)
{
    char str[64] = "This is char array.";
    ev3_lcd_set_font(EV3_FONT_MEDIUM); // フォントを普通サイズに変更
    ev3_lcd_draw_string(str , 0, 0); // (x:0, y:0) にchar型配列str内の文字列を描く
}
```

さらに、C言語にはchar型配列に任意の文字を格納することが出来る`sprintf`関数があります。
これを使えば、測定したセンサ値や変数の値なども、char型配列に文字列として保存し、LCDに描くことが出来ます。

printf系の詳しい使い方については別の回に詳しく説明する予定ですが、とりあえずの解説ページを紹介しておきます。

https://bituse.info/c_func/40

では、カラーセンサで測定した値をLCDに表示させてみたいと思います。

*EX 9. LCDにカラーセンサでの反射光測定値を表示する*

```c:app.c
void main_task(intptr_t unused)
{
    char str[16]; //長さ16のchar型配列を定義
    ev3_sensor_config(EV3_PORT_1, COLOR_SENSOR); // ポート1にカラーセンサを設定
    ev3_lcd_set_font(EV3_FONT_MEDIUM); // フォントを普通サイズに変更

    while(1){
        int ref = ev3_color_sensor_get_reflect(EV3_PORT_1); // 測定した反射光の値を変数に格納
        sprintf(str, "Value: %3d", ref); // char型配列strに測定値を格納
        ev3_lcd_draw_string(str, 0, 0); // (x:0, y:0) にchar型配列str内の文字列を描く
    }
}
```

初めに諸々の設定を行い、無限ループに突入します。
ポート1のカラーセンサから得た反射光の測定値を、変数`ref`に格納します。
次に、`sprintf`関数を用いて、char型配列`str`に"Value: (反射光の値)"というフォーマットで文字列を格納します。`%d`のところに、変数`ref`の値が入ります。`%3d`となっているのは、3桁で表示してね、という意味です。
最後に、先ほど同様LCDにchar型配列`str`の文字列を表示させます。

これで、今センサでどれくらいの値が得られているのか、簡単に確認することが出来るようになりましたね。

### フォントサイズを取得

先ほど紹介したフォントサイズの定数について、それぞれのフォントの横幅と高さを取得することが出来ます。
厳密に座標を指定して文字列を描きたい時には有効かもしれません。

```c
ER ev3_font_get_size(lcdfont_t font, int32_t *width, int32_t *height);
```

関数名は`ev3_font_get_size`で、フォントサイズに応じて横幅、高さを返します。
引数として、第一引数にはフォントサイズの定数、第二・三引数にはそれぞれ横幅と高さを格納するための変数のポインタを指定します。

ここは基本的な使い方だけ示しておきます。

*EX 10. 高さを揃えて文字列を表示する*

```c:app.c
void main_task(intptr_t unused)
{
    int medium_width;
    int medium_height;

    ev3_font_get_size(EV3_FONT_MEDIUM, &medium_width, &medium_height);
    ev3_lcd_set_font(EV3_FONT_MEDIUM);

    for(int i=0; i<EV3_LCD_HEIGHT; i+=medium_height){
        ev3_lcd_draw_string("HELLO EV3", 0, i);
    }
}
```

これで、フォントが「普通」サイズのときの横幅が変数`medium_width`に、高さが変数`medium_height`に格納されます。
その後、for文を利用することで座標を順にずらし、縦方向に文字を綺麗に描画しています。

## 図形を描く

次に、LCD上に直線や四角形といった図形を描いてみます。

### 直線

LCD上の指定した座標$(x_0, y_0)$から$(x_1, y_1)$への直線を描く方法です。

```c
ER ev3_lcd_draw_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1);
```

関数名は`ev3_lcd_draw_line`で、LCD上に直線を描きます。
引数として順番に、$x_0, y_0, x_1, y_1$の4つを指定します。

使い方は後ほど示します。

### 四角形を描く

次はLCD上に四角形を描く方法です。
4つの頂点のうち、左上に来る座標のみを指定し、そこからの横幅、および高さを指定して四角形を描きます。

```c
ER ev3_lcd_fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, lcdcolor_t color);
```

関数名は`ev3_lcd_fill_rect`で、LCD上に四角形を描きます。
引数として順番に、左上の頂点のx座標、y座標、横幅、高さを指定します。
又、第五引数はその四角形をどの色で塗りつぶすかを指定することができ、色については以下のように列挙型により定義されています。

```c
typedef enum {
	EV3_LCD_WHITE = 0,
	EV3_LCD_BLACK = 1,
} lcdcolor_t;
```

| 色 | 定数 | 値 |
| :-: | :-: | :-: |
| 白 | EV3_LCD_WHITE | 0 |
| 黒 | EV3_LCD_BLACK | 1 |

では、これらの使い方を示しておきます。

*EX 11. LCD上に図形を表示する*

```c:app.c
void main_task(intptr_t unused)
{
    while(1){
        ev3_lcd_draw_line(0, 0, EV3_LCD_WIDTH, EV3_LCD_HEIGHT); // 左上から右下方向の斜め線
        tslp_tsk(1*1000*1000); // 1s待機

        ev3_lcd_draw_line(0, EV3_LCD_HEIGHT, EV3_LCD_WIDTH, 0); // 右下から左上方向の斜め線
        tslp_tsk(1*1000*1000); // 1s待機

        ev3_lcd_fill_rect(0, 0, EV3_LCD_WIDTH, EV3_LCD_HEIGHT, EV3_LCD_BLACK); // 黒で塗りつぶし
        tslp_tsk(1*1000*1000); // 1s待機

        ev3_lcd_fill_rect(0, 0, EV3_LCD_WIDTH, EV3_LCD_HEIGHT, EV3_LCD_WHITE); // 白で塗りつぶし
        tslp_tsk(1*1000*1000); // 1s待機
    }
}
```

この例では、左上から右下方向、右下から左上方向に斜め線を引き、そのあと黒で塗りつぶし、最後に白で塗りつぶし(実質的に消しゴムの役割)を行います。

## 画像を表示する

LCDの章の最後として、画像ファイルをLCD上に表示する方法を解説したいと思います。
ただし、音声の時と同じように **「BMP形式 モノクロ画像」** という制限がありますので、注意して画像ファイルを作成してください。

### 画像ファイルをロード

音声ファイルの時と同じように、bmpファイルはメモリファイルとして一度読み込まれます。
さらに、そのメモリファイルから画像をロードします。
従って、次に紹介する画像表示関数までに、ファイルロード系の関数を2つ実行することになります。

では、メモリファイルから画像をロードする関数を紹介します。

```c
ER ev3_image_load(const memfile_t *p_memfile, image_t *p_image);
```

関数名は`ev3_image_load`で、メモリファイルを元に画像ファイルをロードする関数です。
第一引数には「メモリファイルのポインタ」、第二引数には「イメージ構造体のポインタ」を指定します。

### 画像の表示

次に本題の、ロードした画像をLCDに表示する関数についてです。

```c
ER ev3_lcd_draw_image(const image_t *p_image, int32_t x, int32_t y);
```

関数名は`ev3_lcd_draw_image`で、イメージ構造体に格納された画像をLCD上に表示する関数です。
第一引数には「イメージ構造体のポインタ」、第二・三引数にはそれぞれ「x座標」「y座標」を指定します。

### イメージ構造体の解放

使い終わった画像のイメージ構造体は、その都度開放していきましょう。
あまりにもメモリを使いすぎると、オーバーフローに陥る可能性があります。
そうなると、言うまでもなくロボットは正常に動かなくなるので、セットで実行するようにしておきましょう。

```c
ER ev3_image_free(image_t *p_image);
```

関数名は`ev3_image_load`で、画像用に確保したメモリを解放する関数です。
引数には「ファイルの場所を示すポインタ」を指定します。

### ロードから表示⇒解放までの流れ

ではこれらの関数を用いて、画像をロードし、イメージ構造体に格納、LCDに表示して、メモリを開放するまでの流れを示したいと思います。

*EX 12. LCD上に画像を表示する*

```c:app.c
void main_task(intptr_t unused)
{
    memfile_t memfile; // ファイル構造体を作成
    ev3_memfile_load("ev3rt/res/test.bmp", &memfile); // SDカード内の"test.bpm"をメモリファイルとしてロード

    image_t image; // イメージ構造体を作成
    ev3_image_load(&memfile, &image); // イメージ構造体にファイル構造体を紐づけてろロード
    ev3_lcd_draw_image(&image,0,0); // 座標(x:0, y:0)に画像を表示

    ev3_image_free(&image); // イメージ構造体を解放
}
```

上手く行けば、左上にTOPPERSのロゴが表示されるはずです。
対応している画像ファイルを作成するところが少し難しいですが、この機能により好きな画像を表示できるようになるので、ぜひチャレンジしてみてください。

これにて、LCD系の関数は以上です。

# 6. バッテリ

最後に、バッテリに関わる関数を紹介します。
これは、競技においては電源管理に非常に役立つので、是非マスターしましょう。

## 電流値の取得

まずは、流れている電流値を取得する関数です。

```c
int ev3_battery_current_mA();
```

関数名は`ev3_battery_current_mA`で、電流値をmA単位で取得する関数です。
引数は無く、戻り値として電流値が整数値で得られます。

## 電圧値の取得

次に、かかっている電圧値を取得する関数です。

```c
int ev3_battery_voltage_mV();
```

関数名は`ev3_battery_voltage_mV`で、電圧値をmV単位で取得する関数です。
引数は無く、戻り値として電圧値が整数値で得られます。

では、これらの例を示します。

*EX 13. LCD上に電流値・電圧値を表示する*

```c:app.c
void main_task(intptr_t unused)
{
    char str[64]; // char型配列の定義

    int mA = ev3_battery_current_mA(); // 電流値の取得
    sprintf(str, "mA: %d", mA); // char型配列strに測定値を格納
    ev3_lcd_draw_string(str, 0, 0); // LCD上に表示

    int mV = ev3_battery_voltage_mV(); // 電圧値の取得
    sprintf(str, "mV: %d", mV); // char型配列strに測定値を格納
    ev3_lcd_draw_string(str, 0, 30); // LCD上に表示
}
```

これで、LCD上に今の電流値、電圧値が表示されたはずです。
競技等で使用する際には、 **「いつもこれくらいの電圧値で調整する」** というような値を自分で決めておくと、バッテリー残量に依存しないモータの調整などが出来ると思います。
又、「バッテリー残量が規定値を下回ったら警告を出す」とか、「バッテリー残量に比例してモータパワーを変更する」なども出来るかもしれませんね。

# 7. まとめ

かなり長い記事となってしまいましたが、これにてインテリジェントブロックの機能も使えるようになりました。
又、今回をもって、ハードに関わるAPIは全て解説完了しました!
今後は、ソフトに関わる部分が細々とありますので、その辺りを紹介できたらと思います。

次回については

前回: [#8 超音波・ジャイロ・タッチセンサを使おう](https://qiita.com/koushiro/items/d9c69c9c73ff9d54aee2)
次回: [#10 関数を使おう](https://qiita.com/koushiro/items/2ba33587ea25faf3b8e5)