機械系二学科 夏学期演習 マイコンプログラミング演習

目次

機械系二学科 夏学期演習 マイコンプログラミング演習#

_images/arduino-uno-r4-wifi.jpg

図 1 Arduino UNO R4 WiFi#

担当教員・TA

担当

二瓶研究室 M2

  • 植田歴

  • 家門慶人

  • 長塚一真

  • 長谷川恭平

M1

  • 佐藤颯真

  • 正光紘大

1. 本演習について#

1.1 本演習の進め方#

  1. 資料を UTOL または Slack からダウンロードする

  2. 資料に従って課題を進める.班ごとに分かれ,理解を深めるために積極的に議論すること.課題は個人で取り組むが,ソースコードの直接のやりとり以外,例えばソースコードを画面で表示する,エラーメッセージを表示する,情報源の URL をチャットで共有する等,積極的な情報交換を行うとよい.質問がある場合はTAを呼ぶこと

  3. 演習課題1, 2, 3, 4が完了したらそれぞれTAが巡回してチェックするので,作成したプログラムを実行している様子を見せること

  4. 発展課題5以降(モータ制御,シリアル通信,Wi-Fi)が出来た者はTAを呼んでチェックを受けること

  5. 取り組んだ課題の画面キャプチャ,実機の写真,ソースコード,並びに考察を記したpdfファイルをUTOLに提出すること

1.2 講義や他の演習と本演習の関連#

既にソフトウェア第1や第2の講義で基本的なプログラミングについて学んでいるが,ソフトウェアの授業で主に扱っているのは計算機(所謂コンピュータ)で動作するソフトウェアである.それに対して本演習で取り組むのはマイコンで動作するソフトウェアであり,そのようなソフトウェアを組み込みソフトウェアと呼ぶ.

マイコンは計算機と比較して計算資源が少ないため,組み込みプログラミングを行う際にはこれまで計算機プログラミングでは意識してこなかったようなハードウェアの振る舞いや制約を考慮する必要がある.しかし,組み込みプログラミングによって様々なモータやセンサなどを扱えるようになり実際のものを動かせるようになる.

実際,組み込みソフトウェアは家電などの電気製品だけでなく自動車などの機械製品にも不可欠な要素であり,モータの制御やセンサとの通信などの重要な役割を担っている.

先日の電子回路演習においてステッピングモータやDCモータの原理や駆動方法も既に学んできているが,本演習においてマイコンからそれらのモータを制御することで,電子回路演習で取り組んだアナログ・デジタル回路よりも汎用性と拡張性の高いシステムを設計することができるようになる.

2. マイコン#

2.1 マイコンボードArduino UNO R4 WiFi#

本演習で用いるArduino UNO R4 WiFiは,32ビットマイコンとESP32-S3 Wi-Fiモジュール(ESP32-S3-MINI-1-N8)を搭載した最新のUNOボードである.Renesas社のRA4M1シリーズマイコン(R7FA4M1AB3CFM#AA0)を搭載し,48MHz Arm Cortex-M4マイクロプロセッサをベースとしている.

機材提供について

本演習で使用する「Arduino UNO R4 WiFi」は,ルネサス エレクトロニクス株式会社よりご寄付いただいたものです.

_images/arduino-uno-r4-wifi-block-diagram.jpg

図 2 Arduino UNO R4 WiFi ブロック図#

Arduino UNO R4 WiFiの主な特徴#

項目

仕様

マイコン

Renesas RA4M1 (R7FA4M1AB3CFM#AA0)

プロセッサ

48MHz Arm Cortex-M4(浮動小数点演算ユニット付き)

動作電圧

5V

フラッシュメモリ

256kB

SRAM

32kB

EEPROM

8kB

デジタルI/Oピン

14本

アナログ入力

6チャンネル(14ビットADC)

DAC

最大12ビット(A0ピン)

PWM出力

D3, D5, D6, D9, D10, D11

通信

UART×1, SPI×1, I2C×2, CAN×1

Wi-Fi

ESP32-S3モジュール(802.11 b/g/n)

Bluetooth

Bluetooth 5 LE

LEDマトリクス

12×8 赤色LED

USB

USB-C

推奨入力電圧

6-24V(VINピン)

_images/arduino-uno-r4-wifi-pinout.jpg

図 3 Arduino UNO R4 WiFi ピン配置図#

Arduinoにはマイコン初心者にも容易に開発ができるよう各ボード専用のソフトウェア開発環境とそれに付属するライブラリなどが用意されている.そのため本来ならばタイマーやA/D変換などの機能を用いるために行わなければならないレジスタの操作の大部分を隠蔽した容易なプログラミングが可能となる.

Note

Arduino UNO R4 WiFiの特徴的な機能

  • 12×8 LEDマトリクス: アニメーション,テキストスクロール,ミニゲームなど様々な表示が可能

  • Wi-Fi/Bluetooth接続: ESP32-S3モジュールによるIoTアプリケーション開発が可能

  • DAC出力: A0ピンで最大12ビットのデジタル-アナログ変換が可能

  • Qwiic I2Cコネクタ: I2Cデバイスの簡単な接続が可能

2.2 組み込みプログラミングと計算機プログラミングの主な違い#

2.2.1 クロス開発とその開発環境#

クロス開発とはアプリケーションの実行環境とは異なる環境でシステムを開発することである.マイコンにおける組み込みソフトウェアの開発もクロス開発に該当する.

コンピュータ上でコンパイルしたマイコン用の実行ファイルをマイコンのプログラムメモリに転送して書き込む処理は煩雑になりがちだが,Arduinoは初心者向けの開発環境が用意されているので,マイコンとコンピュータをケーブルで接続しボードを選択してボタンをクリックするだけでクロス開発の難しさを意識することなく組み込みプログラミングを行うことができる.

Arduinoでクロス開発を行う際にはArduino IDEというマイコンボード用の初心者向けプログラム開発環境ソフトウェアを用いることができる.一般に計算機プログラミングでC言語のプログラム開発を行う際は.cファイルにプログラムを書くが,Arduino IDEで開発を行う際にはSketch.inoファイルにプログラムを書く.

プログラムの文法は実質的にC言語と同様であるが,main関数の代わりにsetup関数loop関数を書くという点が大きく異なる:

  • setup関数: Arduinoが起動したときに最初の1回だけ実行される関数で,各ピンの状態を設定したりシリアル通信を開始したりする

  • loop関数: setup関数が実行された後に電源が切れるまで繰り返し実行される関数

void setup() {
    // 最初に一回だけ実行される
}

void loop() {
    // 毎周期実行される
}

Tip

Arduinoのsetup()loop()の仕組みは,実際には内部のmain()関数から呼び出されている. Arduino UNO R4 WiFi(Renesas RA4M1)の場合,その実装はmain.cppで確認できる.

2.2.2 動作確認・デバッグ方法#

マイコンや電子回路におけるデバッグ方法には以下のようなものが挙げられる:

A) シリアル通信を介したプリントデバッグ

計算機プログラミングにおいてコード内にprintf()関数を挿入することで処理の実行状況や変数の値を確認する方法に相当する.

void setup() {
    Serial.begin(9600);  // シリアル通信の初期化
}

void loop() {
    Serial.println("This is Serial Print");
    delay(1000);  // 1000ms待機
}

Serialクラスの実装はSerial.hで定義されている.

B) LEDにより状態を表示する方法

マイコンの出力とはoutputに設定したピンからHIGHとLOWの値を信号として書き出すことである.そのHIGH/LOWに合わせてLEDを点滅させることでマイコンのピンの出力状態を確認する.pinModedigitalWriteなどのピン操作関数はdigital.cppで実装されている.

外部LEDを使用する例:

void setup() {
    pinMode(2, OUTPUT);  // D2ピンをoutputに設定
}

void loop() {
    digitalWrite(2, HIGH);  // D2ピンをHIGHに出力
    delay(1000);
}

内蔵LED(LED_BUILTIN)を使用する例:

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);  // 内蔵LEDをoutputに設定
}

void loop() {
    digitalWrite(LED_BUILTIN, HIGH);  // 内蔵LEDを点灯
    delay(1000);
    digitalWrite(LED_BUILTIN, LOW);   // 内蔵LEDを消灯
    delay(1000);
}

Tip

LED_BUILTINを使ったデバッグのすすめ

LED_BUILTINは,Arduinoボード上に内蔵されているLEDを制御するための定義済み定数です(pins_arduino.hで定義). 外部にLEDを接続しなくても,基板上のLEDでプログラムの動作確認ができます.

  • 配線不要: 外部LEDや抵抗を接続する必要がない

  • デバッグに便利: プログラムの動作状態を視覚的に確認できる

  • 可搬性: ボードを変更してもコードを書き換える必要がない

詳しくはArduinoのLED_BUILTINとはを参照.

Note

Arduino UNO R4 WiFiの内蔵LEDマトリクス

Arduino UNO R4 WiFiにはLED_BUILTIN(D13)に加えて,12×8のLEDマトリクスも内蔵されています. より高度な表示が必要な場合は,Arduino_LED_Matrixライブラリを使用してLEDマトリクスを制御することもできます.

C) 外部計測機器を用いる方法

電圧計や電流計,オシロスコープによる外部計測機器を用いて,LEDの点灯だけでは確認できないより詳細な情報を確認する方法である.

2.2.3 割り込み処理#

割り込みとはマイコンがメインルーティンの一連の処理を行っている最中に,緊急な処理を要求された場合に緊急な処理を先に実行し,緊急な処理の終了後に作業途中の処理を再開することである.

多くの場合,マイコンはCPUコアを一つしか持たないため,様々な処理を並行して行うためには割り込みという概念が必要になる.割り込みを使わずに緊急な処理が必要かどうかを常に確認する方式も考えられ,その監視処理をポーリングと呼ぶが,ポーリングは煩雑かつ非効率であるため,計算資源が潤沢ではないマイコンには不向きである.以下のような場合に割り込み処理が有効・必須となる:

  • 一定時間毎に処理を行いたい

  • 外部からの入力を受け取るタイミングが分からない

Note

従来のArduino UNO R3ではD2とD3のみが外部割り込みに対応していたが,Arduino UNO R4 WiFi(Renesas RA4M1, ARM Cortex-M4)ではより多くのピンでattachInterrupt()が使用できる.ただし,すべてのピンが対応しているわけではなく,またIRQチャンネルを共有するピン同士は同時に使用できない制約がある.詳細はArduino公式ドキュメントのattachInterrupt()を参照すること.

Warning

割り込み処理の中でグローバル変数を使う場合はvolatile修飾子をつけて宣言する必要がある.volatileをつけないと,コンパイラの最適化により変数の変更が反映されない場合がある. 例: volatile int count = 0;

外部割り込みの例: ボタンでLEDをトグル

以下は,D2ピンに接続したボタンを押すたびにLED_BUILTINの点灯/消灯を切り替える例である.attachInterruptにより,ボタンが押された瞬間(FALLINGエッジ)に割り込み関数toggleLEDが呼ばれる.loop()内でポーリングする必要がなく,他の処理を行いながらボタン入力を検出できる.

#define BUTTON_PIN 2

volatile bool led_state = false;

void toggleLED() {
    led_state = !led_state;
    digitalWrite(LED_BUILTIN, led_state ? HIGH : LOW);
}

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);
    pinMode(BUTTON_PIN, INPUT_PULLUP);  // 内蔵プルアップ抵抗を使用
    attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), toggleLED, FALLING);
}

void loop() {
    // loop内では他の処理を自由に行える
    // ボタン押下は割り込みで処理される
}

Tip

INPUT_PULLUPを使うと外部にプルアップ抵抗を接続する必要がない.ボタンの片方をD2ピンに,もう片方をGNDに接続するだけで動作する. ボタンを押すとD2ピンがHIGHからLOWに変化し(FALLING),割り込みが発生する.

2.3 マイコンによるモータ制御#

DCモータは一般にPWM制御によってモータに送る電力を制御する.PWM (Pulse Width Modulation) とは日本語ではパルス幅変調という.電圧のパルスの大きさと周期は一定で,オンのときのパルス幅(時間)を調整することで平均電圧を制御する.周期に対するオンの時間の割合をデューティー比と呼ぶ.analogWriteによるPWM出力の実装はanalog.cppで確認できる.

_images/pwm-duty-cycle.jpg

図 4 PWM信号の概念図#

サーボモータもPWM信号により制御することができるが,PWM信号によりモータの角度を制御するという点でPWMの大きさにより電力を制御するDCモータとは異なる.

3. 演習課題1: Arduinoを用いたLEDの点灯#

Arduinoには,デジタル/アナログのIOが複数ポート用意されており,各種モータやセンサなどを接続し,入出力を制御することができる.また,ライブラリも充実し世界中にユーザも多いため,メカトロ設計制作やIoTデバイスのプラットフォームとしてよく用いられている.

LED_BUILTINを使った動作確認#

外部にLEDを接続する前に,まずArduinoボード上に内蔵されているLED(LED_BUILTIN)を使ってプログラムの動作確認を行うことを推奨する.これにより,配線ミスのリスクなしにプログラムが正しく書き込めているかを確認できる.

Tip

段階的な開発アプローチ

  1. まずLED_BUILTINを使って最小限の動作するプログラムを作成する

  2. 動作を確認する

  3. 少しずつ機能を追加する(外部LED,センサなど)

  4. 各段階で動作を確認する

このアプローチにより,問題が発生した際にどの変更が原因かを特定しやすくなる. 詳しくはArduinoのLED_BUILTINとはを参照.

Arduino IDEの起動と使い方:

Tip

初回利用時にはArduino IDEのインストール,および「ボードマネージャでArduino UNO R4のサポートをインストール」する必要がある.本ページ末尾の付録: Arduino IDEの使い方,特に2. ボードマネージャでArduino UNO R4のサポートをインストールの節,およびArduino IDEのインストール方法Arduinoへのプログラム書き込み方法を参照すること.

Windowsキーを押して「arduino」と入力し,Arduino IDEを起動する.起動すると以下のような画面が表示される.

_images/arduino-ide-led-builtin-upload.png

図 5 Arduino IDEの画面.setup()関数とloop()関数にプログラムを記述する.#

Arduino IDEの基本操作:

  • 画面上部左側のチェックマーク(Verify)ボタンでプログラムをコンパイルできる

  • その隣の矢印(Upload)ボタンでプログラムをArduinoに書き込むことができる

  • Arduino UNO R4 WiFiをUSBケーブルでPCに接続した状態で,画面上部のボード選択欄からArduino UNO R4 WiFiを選択する

LED_BUILTINを使った基本的なLチカ(LED点滅):

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);  // 内蔵LEDをoutputに設定
}

void loop() {
    digitalWrite(LED_BUILTIN, HIGH);  // LEDを点灯
    delay(1000);                      // 1秒待機
    digitalWrite(LED_BUILTIN, LOW);   // LEDを消灯
    delay(1000);                      // 1秒待機
}

Note

LED_BUILTINはArduino UNO R4 WiFiではD13ピンに接続されている. ピン番号を直接書く(pinMode(13, OUTPUT))よりもLED_BUILTINを使う方が,コードの意図が明確になり,他のボードへの移植性も高くなる.

演習課題1: LED点滅回路#

Arduinoのデジタル信号HIGH/LOWを周期的に切り替えてLEDを点滅させる.実物のブレッドボードとArduino UNO R4 WiFiに実装すること.

void setup() {
    pinMode(2, OUTPUT);  // D2をoutputに設定
}

void loop() {
    digitalWrite(2, HIGH);  // D2をHIGHに切り替え
    delay(1000);            // 1000ms待機
    digitalWrite(2, LOW);   // D2をLOWに切り替え
    delay(1000);            // 1000ms待機
}
_images/led-blink.png

図 7 実物ブレッドボードでのLED点滅回路の実装#

チェックポイント: LED点滅の確認#

4. 演習課題2: LEDマトリクスを使った表示演習#

Arduino UNO R4 WiFiに内蔵されている12×8 LEDマトリクスを活用し,パターン表示やテキストスクロールを行う.

_images/led-matrix-schematic.jpg

図 8 LEDマトリクス回路図#

LEDマトリクスの仕組み: チャーリプレクシング#

Arduino UNO R4 WiFiの12×8 LEDマトリクスは合計96個のLEDで構成されているが,96本のピンを使っているわけではない.チャーリプレクシング(Charlieplexing) という手法により,わずか11本のピンで96個すべてのLEDを個別に制御している.

なぜ11本のピンで96個のLEDを制御できるのか#

通常,1個のLEDを点灯するにはHIGH(電流の供給元)とLOW(電流の吸い込み先)の2本のピンが必要である.チャーリプレクシングでは,マイコンのピンが取りうる3つの状態を活用する:

ピンの状態

意味

HIGH(出力)

電流を供給する

LOW(出力)

電流を吸い込む

ハイインピーダンス(入力)

回路から切り離された状態

N本のピンがあるとき,HIGHにするピンとLOWにするピンの組み合わせは N × (N - 1) 通りある.つまり:

  • 11本のピン → 11 × 10 = 110通り の組み合わせ

  • 96個のLEDを制御するには十分

動作原理#

2本のピン間に,互いに逆向きに2個のLEDを並列接続する.電流の向きによってどちらのLEDが点灯するかが決まる.

        +--->|---+        (LED1: A -> B 方向)
        |        |
PinA ---+        +--- PinB
        |        |
        +---|<---+        (LED2: B -> A 方向)
  • PinA = HIGH, PinB = LOW → LED1 が点灯(PinAからPinBに電流が流れる)

  • PinA = LOW, PinB = HIGH → LED2 が点灯(PinBからPinAに電流が流れる)

  • 他のピンはすべてハイインピーダンス(入力モード)にして回路から切り離す

実際には人間の目に見えない速さで1個ずつ順番にLEDを点灯させる(ダイナミック駆動)ことで,すべてのLEDが同時に光っているように見せている.

Note

チャーリプレクシングは,少ないピン数で多数のLEDを制御できる一方,同時に点灯できるLEDは1個だけである. そのため,多くのLEDを同時に光らせると1個あたりの点灯時間が短くなり,輝度が下がるというトレードオフがある.

2-1: LEDマトリクスの基本表示#

まずはLEDマトリクスの基本的な使い方を確認する.Arduino UNO R4 WiFiには12×8のLEDマトリクスが内蔵されており(図 1 参照),Arduino_LED_Matrixライブラリを使用して制御できる.

#include "Arduino_LED_Matrix.h"

ArduinoLEDMatrix matrix;

void setup() {
    matrix.begin();

    uint8_t frame[8][12] = {
        {1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1},
        {1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1},
        {1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0},
        {1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0},
        {1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0},
        {1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0},
        {1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0},
        {0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0},
    };

    matrix.renderBitmap(frame, 8, 12);
}

void loop() {
    delay(1000);
}

Tip

LEDマトリクスのフレームデータは8行×12列の2次元配列で表現する.1がLED点灯,0が消灯に対応する. 配列の内容を変更して,自分だけのパターンを作ってみよう.

チェックポイント: LEDマトリクスの基本表示#

チェックポイント 2 (LEDマトリクスの基本表示)

  1. 上記のサンプルコードを書き込み,LEDマトリクスにパターンが表示されることを確認せよ

  2. フレームデータを変更して,自分だけのオリジナルパターンを表示せよ

  3. TAに動作を見せてチェックを受けること

2-2: テキストのスクロール表示#

ArduinoGraphicsライブラリを使用すると,LEDマトリクスにテキストをスクロール表示できる.

Warning

インクルード順序に注意: ArduinoGraphics.hは必ずArduino_LED_Matrix.hより先にインクルードすること. 順序が逆だとbeginDraw()textFont()等のメソッドが使えずコンパイルエラーになる. これはArduino_LED_Matrix.h内の条件付きインクルードArduinoGraphics.hが先に読み込まれているかどうかを判定しているためである.

#include "ArduinoGraphics.h"
#include "Arduino_LED_Matrix.h"

ArduinoLEDMatrix matrix;

void setup() {
    matrix.begin();
}

const char text[] = "Hello!";

void loop() {
    matrix.beginDraw();
    matrix.stroke(0xFFFFFFFF);
    matrix.textScrollSpeed(50);
    matrix.textFont(Font_5x7);
    matrix.beginText(matrix.width() - 1, 1, 0xFFFFFF);
    matrix.println(text);
    matrix.endText(SCROLL_LEFT);
    matrix.endDraw();
}

Note

ArduinoGraphicsライブラリは,Arduino IDEのライブラリマネージャから別途インストールする必要がある. [スケッチ] -> [ライブラリをインクルード] -> [ライブラリを管理] から「ArduinoGraphics」を検索してインストールすること.

_images/install-arduino-graphics.png

図 9 Arduino IDEのライブラリマネージャからArduinoGraphicsをインストールする#

チェックポイント: テキストのスクロール表示#

チェックポイント 3 (テキストのスクロール表示)

  1. ArduinoGraphicsライブラリをインストールし,LEDマトリクスにテキストがスクロール表示されることを確認せよ

  2. 表示するテキストを自分の好きな文字列に変更して動作を確認せよ

  3. TAに動作を見せてチェックを受けること

5. 演習課題3: 超音波距離センサとLEDマトリクスによる距離の可視化#

Arduinoで超音波距離センサ(HC-SR04)の値を読み,前方物体との距離を計測する.ここで使用するpulseIn関数はpulse.cppで実装されている.

_images/hc-sr04-front.jpg

図 10 超音波距離センサ HC-SR04#

HC-SR04のピンアサイン:

Pin

Symbol

Description

1

VCC

5V電源

2

Trig

トリガー入力ピン

3

Echo

レシーバー出力ピン

4

GND

グランド

_images/hc-sr04.png

図 11 超音波距離センサの動作タイミング#

_images/hc-sr04-connection.jpg

図 12 超音波距離センサ HC-SR04 の接続図#

3-1: シリアル通信による距離の確認#

Tip

シリアルモニタを初めて使う場合は,Arduino IDEのシリアルモニタの使い方 と本ページ末尾の付録5. シリアルモニタでのプリント文の確認を参照すること.ボーレートをArduino側(Serial.begin(9600))と一致させる必要がある.

まずはシリアル通信で距離の値を確認する.

#define TRIG_PIN 2
#define ECHO_PIN 3

const float time_gain = 1.0 / 58;  // 時間[us]から距離[cm]への変換係数

float read_sensor() {
    // トリガー送信
    digitalWrite(TRIG_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIG_PIN, LOW);
    // エコーがHIGHの時間を計測
    return time_gain * pulseIn(ECHO_PIN, HIGH);
}

void setup() {
    pinMode(TRIG_PIN, OUTPUT);
    pinMode(ECHO_PIN, INPUT);
    Serial.begin(9600);
}

float distance;

void loop() {
    distance = read_sensor();
    Serial.println(distance);
    delay(500);
}

演習課題3: LEDマトリクスによる距離の時系列表示#

超音波距離センサで計測した距離を,LEDマトリクスに時系列のバーグラフとして表示する.12列のLEDマトリクスを左にスクロールしながら,最新の計測値を右端の列に縦方向のバーとして描画する.距離が近いほどバーが高くなる.

_images/led-matrix-distance.png

図 13 LEDマトリクスによる距離の時系列表示#

#include "Arduino_LED_Matrix.h"

#define TRIG_PIN 2
#define ECHO_PIN 3

ArduinoLEDMatrix matrix;
uint8_t frame[8][12] = {0};

const float time_gain = 1.0 / 58;  // 時間[us]から距離[cm]への変換係数
const float max_distance = 100.0;   // 表示上の最大距離 [cm]

float read_sensor() {
    digitalWrite(TRIG_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIG_PIN, LOW);
    return time_gain * pulseIn(ECHO_PIN, HIGH);
}

void setup() {
    pinMode(TRIG_PIN, OUTPUT);
    pinMode(ECHO_PIN, INPUT);
    Serial.begin(9600);
    matrix.begin();
}

void loop() {
    float distance = read_sensor();
    Serial.println(distance);

    // 全列を左に1列シフト(時系列スクロール)
    for (int row = 0; row < 8; row++) {
        for (int col = 0; col < 11; col++) {
            frame[row][col] = frame[row][col + 1];
        }
    }

    // 距離をバーの高さ(0〜8)に変換(近いほど高い)
    float clamped = constrain(distance, 0, max_distance);
    int height = 8 - (int)(clamped / max_distance * 8);

    // 右端の列にバーを描画(下から点灯)
    for (int row = 0; row < 8; row++) {
        frame[row][11] = ((7 - row) < height) ? 1 : 0;
    }

    matrix.renderBitmap(frame, 8, 12);
    delay(200);
}

Tip

LEDマトリクスの12列が時間軸,8行が距離軸に対応している. 手をセンサに近づけたり遠ざけたりすると,波形のようなパターンがリアルタイムにスクロール表示される. max_distanceの値を変更すると,表示する距離の範囲を調整できる.

超音波距離センサの計測誤差について

シリアル通信で計測した距離を表示すると,値に微小な変化や実際の距離との差があることに気づくだろう.HC-SR04のデータシートによると距離の計測精度は3mmであり,シリアルコンソールで確認される誤差はこの範囲内であれば正常動作である.また,pulseIn関数の時間計測の精度も影響している.

本演習ではセンサの計測値に応じてLEDマトリクスに表示するだけなので,これらの誤差がシステムに大きな影響を及ぼすことはない.しかし,センサの計測値に応じて自動車や機械,ロボット等をフィードバック制御する際は,これらの誤差により振動したり暴走したりする場合がある.従ってセンサの計測値を扱う際には,その精度分解能ヒステリシス応答性等の特性により誤差が生じることを意識して回路・プログラム・機械を組み上げる必要がある.

チェックポイント: 超音波距離センサとLEDマトリクスによる距離の可視化#

チェックポイント 4 (超音波距離センサとLEDマトリクスによる距離の可視化)

  1. 超音波距離センサ(HC-SR04)を配線し,シリアルモニタで距離の値が正しく表示されることを確認せよ

  2. LEDマトリクスに距離の時系列バーグラフがスクロール表示されることを確認せよ

  3. 手をセンサに近づけたり遠ざけたりして,LEDマトリクスの表示がリアルタイムに変化することを確認せよ

  4. TAに動作を見せてチェックを受けること

6. 演習課題4: Arduinoを用いたステッピングモータ駆動#

Arduinoで複数のステップ波形を生成しステッピングモータを駆動する.

_images/stepper-motor-waveform.jpg

図 14 ステッピングモータを駆動する波形#

#define MOTOR_PIN1 5
#define MOTOR_PIN2 6
#define MOTOR_PIN3 7
#define MOTOR_PIN4 8

const float step_period = 10;  // step入力の時間幅 [msec]

void setup() {
    Serial.begin(9600);
    pinMode(MOTOR_PIN1, OUTPUT);
    pinMode(MOTOR_PIN2, OUTPUT);
    pinMode(MOTOR_PIN3, OUTPUT);
    pinMode(MOTOR_PIN4, OUTPUT);
}

void loop() {
    // 1001
    digitalWrite(MOTOR_PIN1, HIGH);
    digitalWrite(MOTOR_PIN2, LOW);
    digitalWrite(MOTOR_PIN3, LOW);
    digitalWrite(MOTOR_PIN4, HIGH);
    Serial.println("1001");
    delay(step_period);

    // 1100
    digitalWrite(MOTOR_PIN1, HIGH);
    digitalWrite(MOTOR_PIN2, HIGH);
    digitalWrite(MOTOR_PIN3, LOW);
    digitalWrite(MOTOR_PIN4, LOW);
    Serial.println("1100");
    delay(step_period);

    // 0110
    digitalWrite(MOTOR_PIN1, LOW);
    digitalWrite(MOTOR_PIN2, HIGH);
    digitalWrite(MOTOR_PIN3, HIGH);
    digitalWrite(MOTOR_PIN4, LOW);
    Serial.println("0110");
    delay(step_period);

    // 0011
    digitalWrite(MOTOR_PIN1, LOW);
    digitalWrite(MOTOR_PIN2, LOW);
    digitalWrite(MOTOR_PIN3, HIGH);
    digitalWrite(MOTOR_PIN4, HIGH);
    Serial.println("0011");
    delay(step_period);
}

Arduinoには**Stepper**ライブラリが用意されているので,これを用いることもできる:

#include <Stepper.h>

#define MOTOR_PIN1 5
#define MOTOR_PIN2 6
#define MOTOR_PIN3 7
#define MOTOR_PIN4 8
#define STEPS_PER_ROTATE_28BYJ48 2048  // 1回転に必要なステップ数

const int StepsPerRotate = STEPS_PER_ROTATE_28BYJ48;
int rpm = 15;    // 毎分の回転数 (1-15rpmでないと動かない)
int Steps = 512; // モータに与えるステップ数 (90度回転)

Stepper myStepper(StepsPerRotate, MOTOR_PIN1, MOTOR_PIN3, MOTOR_PIN2, MOTOR_PIN4);

void setup() {
    Serial.begin(9600);
    myStepper.setSpeed(rpm);
}

void loop() {
    Serial.println("Rotate");
    myStepper.step(Steps);
    delay(500);
}

演習課題4: ステッピングモータの駆動#

上記のプログラムを参考に,実物のブレッドボードとArduino UNO R4 WiFiでステッピングモータを駆動する.

モータドライバには大電流ダーリントン・トランジスタ・アレイULN2003(TEXAS INSTRUMENTS)を使用する.ULN2003は入力信号を増幅し,ステッピングモータを駆動するのに十分な電力を供給する.ArduinoのD5〜D8ピンからの信号をULN2003のIN1〜IN4に接続し,出力ポートにステッピングモータのコネクタを接続する.

Warning

ArduinoのGNDとULN2003ボードの5V・GNDを接続する必要があることに注意する.

_images/stepper-motor-uln2003-wiring.jpg

図 15 ステッピングモータの駆動回路#

_images/stepping-motor-port.png

図 16 ステッピングモータの5V・GND・Arduino配線#

_images/stepping-motor-with-arduino.png

図 17 Arduinoの配線例#

デジタル回路演習で作成した回路との比較

デジタル回路演習の課題4で作成したステッピングモータ駆動回路と,本演習の課題4でマイコンを用いて作成した機能はほぼ等価である.しかし,両者には以下のような違いがある:

  • マイコンによる実装: ソースコードを書き換えるだけで機能を拡張できる.例えば演習課題3のように超音波距離センサと連携させるなど,ソフトウェアの変更のみで新しい機能を追加できる

  • ICによる実装: マイコンのように拡張は容易ではないが,マイコンのハードウェア制約に影響されない回路を実現できる.例えばマイコンの動作速度よりも高速な信号処理を行う場合や,マイコンのデジタル入出力ポートが不足する場合には,ICとマイコンを組み合わせて実装する場合がある

このように,マイコンは汎用性と拡張性に優れるが,ICによる回路はハードウェアレベルでの高速処理に適しており,用途に応じて使い分けることが重要である.

チェックポイント: ステッピングモータの駆動#

チェックポイント 5 (ステッピングモータの駆動)

  1. ULN2003モータドライバを介してステッピングモータ(28BYJ-48)をArduinoのD5〜D8ピンに配線せよ

  2. ArduinoのGNDとULN2003ボードの5V・GNDが正しく接続されていることを確認せよ

  3. 直接波形を生成する方法digitalWriteで1001 / 1100 / 0110 / 0011のパターンを出力するプログラム)でステッピングモータが回転することを確認せよ

  4. Stepperライブラリを用いる方法でも同じくステッピングモータが回転することを確認せよ

  5. シリアルモニタで現在のステップ値(Serial.println("1001")などの出力)が確認できることを確認せよ

  6. step_periodrpm の値を変えて回転速度が変化することを確認せよ

  7. TAに動作を見せてチェックを受けること

7. 発展課題5: Arduinoを用いた様々なモータ制御#

発展課題5-1: サーボモータの駆動#

ArduinoでServoライブラリを使ってサーボモータ(SG90)を駆動する.詳細な仕様はデータシートを参照すること.

_images/servo-motor-with-arduino.jpg

図 18 サーボモータのための接続#

Warning

SG90の配線に注意

SG90のケーブルは3本あり,それぞれ以下の通りである:

  • オレンジ: PWM信号線(Arduinoのデジタルピンに接続)

  • 赤(真ん中): VCC(5Vに接続)

  • 茶色: GND(グランドに接続)

配線を間違えるとサーボモータが破損する可能性があるため,接続前に必ず確認すること.

Tip

3線サーボの配線規則

SG90に限らず,3線式サーボモータでは一般的に真ん中の線がVCCになっている. これは,コネクタを逆向きに挿してしまった場合でもVCCとGNDが直結せず,ショートによる破損を防ぐための設計上の配慮である.

#define SERVO_PIN 2
#include <Servo.h>

Servo servo;

void setup() {
    servo.attach(SERVO_PIN);
}

void loop() {
    servo.write(45);
    delay(1000);
    servo.write(90);
    delay(1000);
    servo.write(135);
    delay(1000);
    servo.write(90);
    delay(1000);
}

発展課題5-2: 超音波センサとサーボモータの連携#

超音波距離センサ(HC-SR04)で計測した距離に応じてサーボモータ(SG90)の角度を変化させるシステムを作成する.距離が近いほどサーボモータの角度が大きくなるようにする.

_images/servo-motor-with-distance.jpg

図 19 超音波センサとサーボモータの接続#

#define SERVO_PIN 2
#define TRIG_PIN 3
#define ECHO_PIN 4

#include <Servo.h>

Servo servo;

const float max_distance = 50.0;  // 最大計測距離 [cm]

void setup() {
    Serial.begin(9600);
    servo.attach(SERVO_PIN);
    pinMode(TRIG_PIN, OUTPUT);
    pinMode(ECHO_PIN, INPUT);
}

void loop() {
    // 超音波センサで距離を計測
    digitalWrite(TRIG_PIN, LOW);
    delayMicroseconds(2);
    digitalWrite(TRIG_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIG_PIN, LOW);

    long duration = pulseIn(ECHO_PIN, HIGH);
    float distance = duration * 0.0343 / 2.0;  // 距離 [cm]

    // 距離をサーボの角度(0〜180度)にマッピング
    // 距離が近いほど角度が大きくなる
    int angle = 0;
    if (distance < max_distance) {
        angle = (int)((1.0 - distance / max_distance) * 180.0);
    }
    angle = constrain(angle, 0, 180);

    servo.write(angle);

    Serial.print("Distance: ");
    Serial.print(distance);
    Serial.print(" cm, Angle: ");
    Serial.println(angle);

    delay(200);
}

超音波距離センサ(HC-SR04)で計測した距離に応じて,サーボモータ(SG90)の角度を変化させると同時に,LEDマトリクスにバーメーターとして距離を表示する.距離が近いほどサーボモータの角度が大きくなり,LEDマトリクスの点灯行数も増える.

#define SERVO_PIN 2
#define TRIG_PIN 3
#define ECHO_PIN 4

#include <Servo.h>
#include "Arduino_LED_Matrix.h"

Servo servo;
ArduinoLEDMatrix matrix;

const float max_distance = 50.0;  // 最大計測距離 [cm]
uint8_t frame[8][12] = {0};       // LEDマトリクスのフレームバッファ

void setup() {
    Serial.begin(9600);
    servo.attach(SERVO_PIN);
    pinMode(TRIG_PIN, OUTPUT);
    pinMode(ECHO_PIN, INPUT);
    matrix.begin();
}

void loop() {
    // 超音波センサで距離を計測
    digitalWrite(TRIG_PIN, LOW);
    delayMicroseconds(2);
    digitalWrite(TRIG_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIG_PIN, LOW);

    long duration = pulseIn(ECHO_PIN, HIGH);
    float distance = duration * 0.0343 / 2.0;  // 距離 [cm]

    // 距離をサーボの角度(0〜180度)にマッピング
    int angle = 0;
    if (distance < max_distance) {
        angle = (int)((1.0 - distance / max_distance) * 180.0);
    }
    angle = constrain(angle, 0, 180);
    servo.write(angle);

    // 距離をLEDマトリクスの点灯行数(0〜8)にマッピング
    int bars = 0;
    if (distance < max_distance) {
        bars = (int)((1.0 - distance / max_distance) * 8.0) + 1;
    }
    bars = constrain(bars, 0, 8);

    // LEDマトリクスを左にスクロール
    for (int row = 0; row < 8; row++) {
        for (int col = 0; col < 11; col++) {
            frame[row][col] = frame[row][col + 1];
        }
    }
    // 最新の計測値を右端の列に描画
    for (int row = 0; row < 8; row++) {
        frame[row][11] = (row >= 8 - bars) ? 1 : 0;
    }
    matrix.renderBitmap(frame, 8, 12);

    Serial.print("Distance: ");
    Serial.print(distance);
    Serial.print(" cm, Angle: ");
    Serial.print(angle);
    Serial.print(", Bars: ");
    Serial.println(bars);

    delay(200);
}

Tip

max_distanceの値を変更すると,反応する距離の範囲を調整できる. LEDマトリクスには距離の時系列変化がスクロール表示され,同時にサーボモータも距離に応じて動く.

発展課題5-3: DCモータの駆動#

DCモータをマイコンで駆動するには,Arduinoのデジタル出力ピン(数十mA)では電流が足りないため,モータドライバICを介して大電流をスイッチングする必要がある.本演習では東芝のフルブリッジドライバ TB6643KQ(最大4.5 A)を実機で用いる.

ここでは新しいデバイスを扱うときの基本パターン 「(1) データシートを読む → (2) 回路を作る → (3) コードで動かす」 の順に進める.この流れは組み込みエンジニアとして身につけるべき手順である.

_images/tb6643kq-package.jpg

図 20 TB6643KQ(HSIP7-P-2.54A パッケージ)の外観.7本のリード端子を持つ#

(1) データシートを読む#

データシートは「そのICを正しく安全に使うための一次資料」である.インターネット上の作例や記事だけを頼りに配線すると,ピンアサインを間違えたり定格を超えてICを破壊することがある.TB6643KQのデータシート(秋月電子)を例に,最低限ここだけは読むべきという項目を順番に見ていこう.

① 特長 (Features)

データシートの先頭にはICの主要な仕様が箇条書きで書かれている.まずここを読んで「自分の用途で使えるか」を判断する.TB6643KQの場合:

  • 電源電圧 (VM): 最大 50 V

  • 出力電流: 最大 4.5 A

  • 出力ON抵抗: 0.55 Ω (標準)

  • PWM制御可能(IN1, IN2でスイッチング)

  • 正転 / 逆転 / ショートブレーキ / ストップ の4モード

  • 過電流検出 (ISD) / 過電圧検出 (VSD) / 熱遮断 (TSD) / 低下電圧検出 (UVLO) の保護回路内蔵

この欄を見るだけで「5 Vロジックで制御でき,最大4.5 A流せて,保護回路もある」という用途への適合性が判断できる.

② ブロック図 / 応用回路例 (Block Diagram)

ICの内部構成と「メーカが推奨する典型的な配線例」が描かれている.データシートを参考に回路を組むときの出発点になる図.

_images/tb6643kq-block-diagram.jpg

図 21 TB6643KQ のブロック図(応用回路例).IN1, IN2の入力からControl/Predriverを経て4つのMOSFETによるフルブリッジでモータを駆動する.VM端子の近くに3個のコンデンサが描かれていることに注目#

このブロック図から次のことが読み取れる:

  • IN1, IN2 はICの内部でプルダウンされており,未接続だとLOW扱いになる

  • 出力段は4つのMOSFETと逆並列ダイオードで構成されたフルブリッジ

  • 各MOSFETに過電流検出 (ISD detection) が付いている

  • VM端子の近くにバイパスコンデンサ(パスコン)を配置するのがメーカ推奨

③ 端子説明・外形図 (Pin Description / Outline)

ピン番号と機能の対応はここで確認する.配線前に必ず読むべき最重要項目.TB6643KQ (HSIP7-P-2.54A) は7本ピン:

ピン番号

名称

機能

1

IN1

制御信号入力 1(Arduinoから)

2

IN2

制御信号入力 2(Arduinoから)

3

OUT1

モータ出力 1

4

GND

グランド

5

OUT2

モータ出力 2

6

N.C.

接続なし (No Connection)

7

VM

モータ電源 (10〜45 V)

_images/tb6643kq-outline.jpg

図 22 TB6643KQ の外形図とピン配置.型番が印字された面から見て左端が1番ピン (IN1),右端が7番ピン (VM) になる#

Note

「HSIP7-P-2.54A」の読み方

データシートの型番に出てくる HSIP7-P-2.54A はICのパッケージ形状を表す型番で,次のように分解できる:

記号

意味

HSIP

Heat-sink Single In-line Package(背面に放熱タブが付いた,ピンが一列に並んだパッケージ)

7

ピン数(7本)

P

Plastic(樹脂モールド)

2.54

ピンピッチ 2.54 mm(= 0.1インチ.ブレッドボードや一般的なユニバーサル基板の穴間隔と同じ)

A

細かいバリエーション・改訂

見た目はTO-220にピンが増えたような板状のICで,足が1列に並び背面に金属の放熱タブが付いている.ピンピッチが2.54 mm なのでそのままブレッドボードに挿して使える.背面の金属タブはGNDではなくICチップ裏面に繋がっているため,ヒートシンクを付けるときは絶縁シートを挟むかGNDに落とす必要がある(後述の放熱tip参照).

ステッピングモータで使った ULN2003 は16ピン DIP(2列)パッケージだったのに対し,TB6643KQ はこのように1列で,ブレッドボードへの挿し方が異なることに注意.

Warning

ピン番号の数え方を取り違えないこと

HSIP7-P-2.54Aは,型番のシール(印字)面から見て左から1, 2, 3, …, 7番である.裏返すと左右が逆になるので注意.ブレッドボードに挿す前に必ずどちらが1番ピンかを確認すること.また**6番ピンは N.C.(接続なし)**なので,どこにも繋がない(誤って繋いでも害はないが,誤配線の原因になる).

④ 絶対最大定格 (Absolute Maximum Ratings)

瞬時たりとも超えてはならない」値が書かれている.これを超えるとIC破壊・発煙・発火の原因となる.

項目

記号

定格

電源電圧

VM

50 V

出力電圧

\(V_O\)

50 V

出力電流 (ピーク)

\(I_O(peak)\)

4.5 A

入力電圧

\(V_{IN}\)

-0.3 〜 5.5 V

許容損失(放熱板なし)

\(P_D\)

1.25 W

動作温度

\(T_{opr}\)

-40 〜 85 °C

ここから「IN1, IN2 には 5.5 V を超える電圧を絶対に印加してはならない」ことが読み取れる.Arduino UNO R4 WiFi のロジック電圧は 5 V なので問題ないが,もし他の3.3V系マイコン(ESP32など)と混在させる場合は,HIGH時に5Vが出ないことを確認すること.

⑤ 動作範囲 (Operating Range)

「絶対最大定格」が壊れる境界線であるのに対し,「動作範囲」は設計時に守るべき推奨範囲である.通常はこちらの範囲内で使う.

項目

記号

定格

電源電圧

\(VM_{opr}\)

10 〜 45 V

PWM 周波数

\(f_{PWM}\)

〜 100 kHz

出力電流 (平均)

\(I_O\) (平均)

〜 1.5 A (放熱板なし)

Important

TB6643KQのVMは最低でも 10 V が必要である.Arduino UNO R4 WiFiの5V電源では駆動できない.モータ用に 12 VのACアダプタ乾電池ボックス(単3×8本=12 V等) などの外部電源を別途用意すること.analogWriteのPWM周波数(Arduino UNO R4 WiFi標準で約490 Hz)はTB6643KQの100 kHz上限に十分収まっている.

⑥ 入出力ファンクション表 (Truth Table)

ICを「どう操作すればどう動くか」を示す表.データシートの中で最もよく参照する項目

IN1

IN2

OUT1

OUT2

モード

H

H

L

L

ショートブレーキ(急停止)

L

H

L

H

正転(または逆転)

H

L

H

L

逆転(または正転)

L

L

Hi-Z

Hi-Z

ストップ(出力オフ・惰性回転)

ArduinoのD9, D10ピンをIN1, IN2に繋ぎ,HIGH/LOWを切り替えるだけで4モードを使い分けられる.PWM制御は,例えば IN2をLOW固定にして IN1にPWMを入れる方式で実現する:

  • PWM ON(IN1=H, IN2=L): 正転

  • PWM OFF(IN1=L, IN2=L): ストップ(Hi-Z)

_images/tb6643kq-pwm-on-off.jpg

図 23 TB6643KQ の PWM 制御時の電流経路.PWM ON期間(左)→OFFへの遷移(中)→PWM OFF期間(右)と推移する.上下のMOSFETが同時にONにならないよう200 ns / 500 nsのデッドタイムがIC内部で生成されるため,外部から OFFタイムを挿入する必要はない#

⑦ 保護機能 (UVLO / VSD / TSD / ISD)

TB6643KQには各種保護回路が内蔵されている:

保護機能

動作条件

動作内容

UVLO(低下電圧検出)

VM < 8 V

出力を Hi-Z にして停止

VSD(過電圧検出)

VM > 53 V

出力を Hi-Z にして停止

TSD(熱遮断)

Tj > 170°C

出力を Hi-Z にして停止

ISD(過電流検出)

4.5〜8 A を 5.1 µs 以上

全出力を Hi-Z にして停止

Warning

これらの保護機能は補助的なものであり,「絶対最大定格を超えても保護してくれる」ものではない.データシートにも「いかなる場合でも IC を保護するというものではありません」と明記されている.設計段階で動作範囲内に収まるようにすることが大前提.

⑧ 許容損失と熱設計 (Power Dissipation)

ICが連続的に消費できる電力の上限.TB6643KQは放熱板なしで Ta = 25°C のとき \(P_D\) = 1.25 W しか許容されない.消費電力 \(P = R_{ON} \times I^2\) で,例えば 1.5 A 連続で流すと \(P = 0.55 \times 1.5^2 \approx 1.24\) W となり,放熱板なしではほぼ上限に達する.

_images/tb6643kq-pd-ta.jpg

図 24 TB6643KQ の許容損失 - 周囲温度特性.(1) 10°C/Wの放熱板を使用した場合 7.8 W,(2) 放熱板なしの場合 1.25 W.温度が高くなると許容損失は直線的に減少する#

Tip

データシート末尾の「使用上の注意事項」「使用上の留意点」も必ず読むこと.「適切な電源ヒューズを使用」「逆起電力対策」など,現場で起きる事故を防ぐ知見が詰まっている.

(2) 回路を作る#

データシートで読み取った情報を元に,Arduino UNO R4 WiFi と TB6643KQ の配線を決める.

配線表:

TB6643KQ ピン

接続先

備考

1 (IN1)

Arduino D9(PWM対応)

方向 or PWM入力

2 (IN2)

Arduino D10(PWM対応)

方向 or PWM入力

3 (OUT1)

DCモータ端子A

4 (GND)

Arduino GND かつ モータ電源 (−)

共通GND

5 (OUT2)

DCモータ端子B

6 (N.C.)

未接続

7 (VM)

モータ用外部電源 (+)

10〜45 V.パスコン要

結線図(テキスト版):

                         +12V (モータ用外部電源)
                          |
                          +--+ 100μF 電解 + 0.1μF セラミック (パスコン)
                          |     |        |
                          |     GND      GND
                          |
        Arduino           |
        D9  ───────►  IN1(1)        VM(7)
        D10 ───────►  IN2(2)
                        OUT1(3) ──[DCモータ]── OUT2(5)
                        GND(4) ───────┐
                                      |
        Arduino GND ──────────────────+── モータ電源 GND
                            (共通GND)

Warning

GNDの共通化を忘れない

Arduino の GND,TB6643KQ の 4番ピン GND,モータ外部電源の (−) を必ず同じ電位にすること(共通GND).これがないと IN1/IN2 のHIGH/LOWの基準が定まらず,誤動作やIC破壊の原因になる.モータ電源とArduino電源を別系統で取る場合は,GND同士をジャンパで必ず繋ぐ.

Important

バイパスコンデンサ(パスコン)について

本演習ではコンデンサを用意していないため,今回は省略して構わない.ただし実際に製品や装置に組み込む際は必ず入れること.TB6643KQの7番ピン (VM) のすぐ近くに,以下を並列でGNDに接続するのがメーカ推奨:

  • セラミックコンデンサ 0.1 µF(高周波ノイズの除去)

  • 電解コンデンサ 100 µF 以上(VM電圧の2倍以上の耐圧.12V電源なら25V耐圧以上を推奨)

データシートのブロック図(図 21)でも VM端子近傍に3個のコンデンサが描かれている.これがないとモータの起動・反転時のサージで TB6643KQ や Arduino が破損する恐れがある.電解コンデンサは極性を間違えないこと(長いリードが+,缶の白帯側が−).

Tip

放熱について

平均1 A を超えるような連続駆動を行う場合は,TB6643KQの背面金属タブにヒートシンクをネジ留めする.データシートに「IC裏面の金属部分は ICチップ裏面と電気的に接続されている」とあるため,ヒートシンクは絶縁シートを挟むか,GNDに落とすこと(他の電位に接触させない).

ブレッドボード上での実装例

実際にブレッドボード上で配線した例を 図 25図 26 に示す.TB6643KQ をブレッドボードに挿し,モータ用外部電源(単3電池×8本=12V のボックス)を VM と GND に接続し,OUT1/OUT2 を DCモータに繋いでいる.

_images/dc-motor-tb6643kq-wiring-detail.webp

図 25 TB6643KQ とモータ・電池ボックスの配線(近接ショット).赤・黒の太い線が VM (+12V) と GND,黄・橙の細い線が Arduino からの IN1/IN2 信号線#

_images/dc-motor-tb6643kq-wiring-full.webp

図 26 Arduino UNO R4 WiFi も含めた全体構成.Arduino の GND と電池ボックスの GND が同じノードに繋がっている(共通GND)ことを確認すること#

(3) コードで動かす#

回路ができたらArduinoから制御する.まずは analogWrite を使った最も基本的な例から始めて,より細かい制御が必要な場合は FspTimer を使った自作PWMに進む.

a. analogWrite を使った基本制御

IN1, IN2 の両方を analogWrite で制御し,正転・ショートブレーキ・逆転・ストップを順に切り替える例.analogWrite(pin, 0) は常時LOW(デューティー比0%),analogWrite(pin, 255) は常時HIGH(デューティー比100%)に相当するため,両ピンを統一してanalogWriteで扱える:

#define IN1_PIN 9   // TB6643KQ IN1 (PWM対応ピン)
#define IN2_PIN 10  // TB6643KQ IN2 (PWM対応ピン)

void setup() {
    pinMode(IN1_PIN, OUTPUT);
    pinMode(IN2_PIN, OUTPUT);
    Serial.begin(9600);
}

void loop() {
    // 正転 (IN1=PWM, IN2=LOW) : デューティー比 50%
    Serial.println("Forward (duty 50%)");
    analogWrite(IN1_PIN, 128);
    analogWrite(IN2_PIN, 0);
    delay(2000);

    // ショートブレーキ (IN1=HIGH, IN2=HIGH) : 急停止
    Serial.println("Short brake");
    analogWrite(IN1_PIN, 255);
    analogWrite(IN2_PIN, 255);
    delay(500);

    // 逆転 (IN1=LOW, IN2=PWM) : デューティー比 50%
    Serial.println("Reverse (duty 50%)");
    analogWrite(IN1_PIN, 0);
    analogWrite(IN2_PIN, 128);
    delay(2000);

    // ストップ (IN1=LOW, IN2=LOW) : Hi-Z で惰性回転
    Serial.println("Stop (Hi-Z)");
    analogWrite(IN1_PIN, 0);
    analogWrite(IN2_PIN, 0);
    delay(500);
}

Warning

Arduino UNO R4 WiFi で analogWritedigitalWrite を混在させない

Arduino UNO R4 WiFi (Renesas RA4M1) では,一度 analogWrite を呼んだピンはGPT (汎用PWMタイマ) に接続される.その後に同じピンへ digitalWrite(pin, LOW) を呼んでもタイマからピンが切り離されないことがあり,PWMが鳴り続けて意図したHIGH/LOWにならない

例えば「正転 (IN2=digitalWrite LOW) → 逆転 (IN2=analogWrite) → ストップ (IN2=digitalWrite LOW)」のように切り替えると,2周目以降は IN2 にPWMが残ったままになり「ずっと同じ方向に回り続ける」現象が起きる.

回避策は2つ:

  1. analogWrite だけで統一する(上記サンプルコードの方式):analogWrite(pin, 0) をLOW,analogWrite(pin, 255) をHIGHとして使う

  2. digitalWrite を呼ぶ前に pinMode(pin, OUTPUT) を再度呼ぶ:これによりピンをGPIOモードに戻してからLOW/HIGHを書く

Tip

「ショートブレーキ」と「ストップ (Hi-Z)」の違い

  • ショートブレーキ (IN1=H, IN2=H): 両出力をLOWに短絡し,モータの慣性で生じる起電力を回生して急速に減衰させる → 急停止

  • ストップ (IN1=L, IN2=L): 両出力をハイインピーダンスにする → モータは惰性で回り続けてやがて摩擦で止まる

用途によって使い分ける(例: ロボットの位置制御では急停止が必要なのでショートブレーキ).

b. FspTimer (タイマー割り込み) によるPWM自作

Arduino UNO R4 WiFiに内蔵されている FspTimer を使ってタイマー割り込みでPWM波形を自作することもできる.analogWrite では実現しにくい細かなPWM制御(周波数を変える,特殊な波形を出すなど)が必要なときに使う.

Note

FspTimer はArduino UNO R4 WiFiのコアライブラリに含まれているため,追加インストールは不要. 従来のArduino UNO R3で使われていた MsTimer2 はAVR専用であり,Arduino UNO R4 WiFi(Renesas RA4M1)では使用できない. 割り込み処理の中でグローバル変数を使う場合は volatile 修飾子をつけて宣言すること.

Warning

PWM周波数はモータがガクガクしない程度に高くする

タイマー周波数を低くすると(例えば1kHzタイマーで100ステップ=PWM 10Hz)モータが「カクカク」「ガクガク」と振動する.これは電流のオン/オフ周期が長すぎて回転速度が安定しないため.DCモータでは少なくとも 1kHz以上のPWM周波数 が目安で,可聴域を避けたい場合は20kHz以上にする.TB6643KQの仕様上限は100kHz.

下のサンプルではタイマー周波数を100kHzに設定し,100ステップで1周期=PWM 1kHz としている.

#define IN1_PIN 9   // TB6643KQ IN1
#define IN2_PIN 10  // TB6643KQ IN2

#include "FspTimer.h"

FspTimer pwm_timer;

volatile int duty;
volatile int pwm_count;

// タイマー割り込みで呼ばれるコールバック関数
// IN1にPWM, IN2はLOW固定 (正転方向)
void pwm_cycle(timer_callback_args_t __attribute((unused)) *args) {
    if (pwm_count < 100) {
        if (pwm_count == 0) {
            digitalWrite(IN1_PIN, HIGH);
        } else if (pwm_count == duty) {
            digitalWrite(IN1_PIN, LOW);
        }
        ++pwm_count;
    } else {
        pwm_count = 0;
    }
    digitalWrite(IN2_PIN, LOW);
}

void setup() {
    pinMode(IN1_PIN, OUTPUT);
    pinMode(IN2_PIN, OUTPUT);
    duty = 0;
    pwm_count = 0;

    // 10usec (100kHz) 毎に pwm_cycle を呼ぶタイマーを設定
    // 100ステップで1周期なので PWM周波数 = 100kHz / 100 = 1kHz
    uint8_t timer_type = GPT_TIMER;
    int8_t timer_ch = FspTimer::get_available_timer(timer_type);
    pwm_timer.begin(TIMER_MODE_PERIODIC, timer_type, timer_ch, 100000.0, 0.0, pwm_cycle);
    pwm_timer.setup_overflow_irq();
    pwm_timer.open();
    pwm_timer.start();
}

void loop() {
    duty = 20;   // デューティー比 20%
    delay(2000);
    duty = 50;   // デューティー比 50%
    delay(2000);
    duty = 80;   // デューティー比 80%
    delay(2000);
}

Note

Arduino UNO R4 WiFiのPWM出力ポートについて

Arduino UNO R4 WiFiでは,以下のピンでPWM出力が可能です:

  • D3, D5, D6, D9, D10, D11 (PWM対応ピン)

analogWrite 関数を用いてPWM出力ができるピンはこれらのPWM対応ピンのみです.

マイコンのピンの役割

Arduinoのピンをよく見ると,D3, D5, D6, D9, D10, D11ピンには「~」の表記がある.これはPWM波形を出力できるピンであることを意味している.analogWrite関数でPWM出力ができるのはこれらのPWM対応ピンのみであり,上のコード例でも IN1_PIND9IN2_PIND10 を指定していることに注意したい.

また,一般にマイコンのピンはそれぞれ機能が決まっているが,ArduinoのPWM出力ピンがデジタル入出力の機能も兼ねているように,一つのポートを複数の機能で兼用していることがある.回路を設計する際には,利用したい機能同士が兼用ピンとなって重複していないかの確認が必要である.

参考: シミュレーション用のドライバIC (L293D)

Tinkercad等のシミュレータで実機が無い環境で試す場合は,Texas Instruments の L293D(最大600 mA)も使える.L293DはTB6643KQと異なり ENABLE端子 (EN1, EN2) を持ち,これをHIGHにした上で IN1, IN2 を制御する2チャンネルH-ブリッジである.ピンアサインや動作モードはL293Dのデータシートで確認すること.「データシートを読んでから配線する」という流れは,どのドライバICでも変わらない

_images/dc-motor-l293d-circuit.jpg

図 27 L293Dを用いたDCモータ駆動回路の例(参考)#

チェックポイント: DCモータの駆動#

チェックポイント 6 (DCモータの駆動 (TB6643KQ))

  1. TB6643KQのデータシート(PDF)の 特長・端子説明・絶対最大定格・動作範囲・入出力ファンクション表 に目を通せ

  2. ArduinoのD9 → IN1,D10 → IN2,OUT1/OUT2 → DCモータ,VM → 外部電源 (+),GND → Arduino GND および 外部電源 (−),として配線せよ.本演習ではパスコンは省略してよいが,実際に製品や装置として組み込む際は VM-GND間にパスコン(0.1 µF + 100 µF)を入れることを忘れないこと

  3. analogWrite 版のサンプルスケッチを書き込み,正転 → 急停止 → 逆転 → 惰性停止のサイクルが動作することを確認せよ

  4. analogWrite の値を変えてデューティー比と回転速度の関係を観察せよ

  5. 余裕があれば FspTimer 版も試し,タイマー割り込みによるPWM自作の動作を確認せよ

  6. TAに動作を見せてチェックを受けること

8. 発展課題6: シリアル通信を介した計算機とマイコン間の通信#

_images/serial-communication.png

図 28 マイコン・計算機間の文字列の送受信の概念図#

ボーレートを合わせる理由

シリアル通信では,送信側と受信側でボーレート(1秒間に送受信するビット数)を一致させる必要がある.ボーレートが一致していないと,受信側がビットの境界を正しく認識できず,データが化けたり,フレーミングエラーが発生する.図 29 に示すように,送信側が速すぎる場合はビットの読み取りタイミングがずれてフレーミングエラーとなり,遅すぎる場合は同じビットを二重に読み取ってデータの重複が起こる.Arduino側のSerial.begin(9600)と計算機側の設定(例:baudrate=9600)を必ず同じ値に揃えること.

_images/baudrate.png

図 29 ボーレートの不一致によるエラーの例.送信側と受信側のボーレートが一致している場合(Panel 1)は正常に通信できるが,不一致の場合(Panel 2, 3)はフレーミングエラーやデータの重複が発生する.#

発展課題6-1: シリアル通信コマンドtioを用いた文字列の表示#

計算機でUSBにより接続したArduinoが計算機で/dev/ttyACM0として認識されていてボーレート9600 bpsの場合:

tio -b 9600 /dev/ttyACM0

tioは軽量で使いやすいシリアル端末ツールである.-bオプションでボーレートを指定する(指定しない場合のデフォルトは115200 bps).

tioの終了方法: Ctrl-T を押した直後に q を押す.

Note

Arduino UNO R4 WiFiはUSB-Cで接続し,OSによって次のように認識される:

  • Linux: /dev/ttyACM0

  • macOS: /dev/cu.usbmodem**にはシリアル番号が入る)

  • Windows: COM*

以降の例では/dev/ttyACM0を使うが,macOS/Windowsでは自分の環境のデバイス名に読み替えること.デバイスが見つからない場合はArduinoが認識されないときの確認方法を参照.

tioのインストール

  • Ubuntu/Debian: sudo apt install tio

  • macOS (Homebrew): brew install tio

  • Windows: WSL を使うか,GitHubのリリースページからバイナリを取得

tio --help で利用可能なオプションを確認できる.

tioがうまくいかない場合

以下を順に確認すること:

  1. Arduino IDEのシリアルモニタが開いていないか確認:一つのシリアルポートに同時アクセスできるのは1プログラムだけなので,IDEのシリアルモニタは閉じる

  2. 権限エラー(Permission denied)の場合sudo usermod -a -G dialout $USER を実行後にログアウト・再ログイン(macOSでは不要)

  3. tioが利用できないときの代替コマンド:以下のいずれかを使う

    # cu を使う(終了は ~ → Ctrl-D.Ubuntuでは sudo apt install cu)
    cu -l /dev/ttyACM0 -s 9600
    # screen を使う(終了は Ctrl-A → K → y)
    screen /dev/ttyACM0 9600
    # picocom を使う(終了は Ctrl-A → Ctrl-X)
    sudo apt install picocom
    picocom -b 9600 /dev/ttyACM0
    # minicom を使う
    sudo apt install minicom
    minicom -D /dev/ttyACM0 -b 9600
    
  4. PythonのpyserialやArduino IDEのシリアルモニタで代替:後述のPythonによる方法(pyserial)でも文字列を確認できる

tioコマンドの代わりに,C言語で直接シリアルポートを読み取ることもできる:

C言語によるシリアル読み取りプログラム:

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    const char *port_name = "/dev/ttyACM0";
    if (argc > 1) {
        port_name = argv[1];
    }

    int serial_port = open(port_name, O_RDWR);
    if (serial_port < 0) {
        printf("Error %i from open: %s\n", errno, strerror(errno));
        return 1;
    }

    struct termios tty;
    if (tcgetattr(serial_port, &tty) != 0) {
        printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
        return 1;
    }

    cfsetispeed(&tty, B9600);
    cfsetospeed(&tty, B9600);

    tty.c_cflag &= ~PARENB;   // パリティなし
    tty.c_cflag &= ~CSTOPB;   // ストップビット1
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;       // 8ビットデータ
    tty.c_cflag &= ~CRTSCTS;  // ハードウェアフロー制御なし
    tty.c_cflag |= CREAD | CLOCAL;

    tty.c_lflag &= ~ICANON;
    tty.c_lflag &= ~ECHO;
    tty.c_lflag &= ~ECHOE;
    tty.c_lflag &= ~ECHONL;
    tty.c_lflag &= ~ISIG;

    tty.c_iflag &= ~(IXON | IXOFF | IXANY);
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);

    tty.c_oflag &= ~OPOST;
    tty.c_oflag &= ~ONLCR;

    tty.c_cc[VTIME] = 10;  // 1秒タイムアウト
    tty.c_cc[VMIN] = 0;

    if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
        printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
        return 1;
    }

    char buf[256];
    while (1) {
        memset(buf, '\0', sizeof(buf));
        int n = read(serial_port, buf, sizeof(buf));
        if (n > 0) {
            printf("%s", buf);
            fflush(stdout);
        }
    }

    close(serial_port);
    return 0;
}

コンパイルと実行:

Note

Ubuntuでgccが入っていない場合は事前にsudo apt install build-essentialでCコンパイラ一式を導入する.

gcc -o read_serial read_serial.c
./read_serial /dev/ttyACM0

C++(boost::asio)によるシリアル読み取りプログラム:

Boostライブラリのインストール

<boost/asio.hpp> は標準C++ライブラリには含まれていないため,事前にBoostライブラリのインストールが必要:

  • Ubuntu/Debian:

    sudo apt install libboost-all-dev
    
  • macOS (Homebrew):

    brew install boost
    

    コンパイル時は -I/opt/homebrew/include -L/opt/homebrew/lib(Apple Silicon)または -I/usr/local/include -L/usr/local/lib(Intel Mac)を追加する

  • Windows: Boost公式サイト からダウンロード,もしくは WSL/MSYS2 を利用するのが簡単

Boostを使わずに済ませたい場合は,前掲のC言語版(termiosを使う)またはPython版(pyserial)を用いる.

#include <boost/asio.hpp>
#include <iostream>

int main(int argc, char *argv[]) {
    const char *port_name = "/dev/ttyACM0";
    if (argc > 1) {
        port_name = argv[1];
    }

    try {
        boost::asio::io_context io;
        boost::asio::serial_port port(io, port_name);
        port.set_option(boost::asio::serial_port_base::baud_rate(9600));

        while (1) {
            boost::asio::streambuf buf;
            boost::asio::read_until(port, buf, "\n");
            std::istream is(&buf);
            std::string line;
            std::getline(is, line);
            std::cout << ">>" << line << std::endl;
        }
    } catch (const std::exception &e) {
        std::cerr << e.what() << std::endl;
        return 1;
    }
    return 0;
}

コンパイルと実行:

g++ -std=c++17 -o read_serial read_serial.cpp -lboost_system
./read_serial /dev/ttyACM0

発展課題6-2: 計算機側からの文字列の入力#

_images/pyserial.png

図 30 Pythonによるシリアル通信の実行例#

Arduino側のプログラム:

void setup() {
    Serial.begin(9600);
    while (!Serial) {
        delay(100);  // シリアルポートが接続されるのを待つ
    }
}

int delay_ms = 500;
int count = 0;

void loop() {
    Serial.print("[");
    Serial.print(count);
    Serial.print("] Hello world!");
    Serial.println();
    count++;
    delay(delay_ms);

    if (Serial.available() > 0) {
        Serial.print("Change delay to ");
        delay_ms = Serial.readString().toInt();
        Serial.println(delay_ms, DEC);
    }
}

Python環境のセットアップ:

Pythonでシリアル通信を行うにはpyserialライブラリが必要である.以下の手順でvenv(仮想環境)を作成し,インストールする:

Note

Ubuntuでpython3 -m venvが「The virtual environment was not created successfully because ensurepip is not available」と失敗する場合は,事前にsudo apt install python3-venvを実行する.

python3 -m venv ~/arduino_venv
source ~/arduino_venv/bin/activate
pip install pyserial

以降,Pythonのシリアル通信プログラムを実行する際は,事前に仮想環境を有効化しておくこと:

source ~/arduino_venv/bin/activate

Python側のプログラム:

import serial

ser = serial.Serial(port='/dev/ttyACM0', baudrate=9600)
loop = 0

while ser.is_open:
    print(">>" + ser.readline().decode().strip())
    if loop == 10:
        ser.write(b"100\n")
    elif loop == 50:
        ser.write(b"500\n")
    elif loop == 70:
        break
    loop = loop + 1

発展課題6-3: シリアル通信を介したLEDマトリクスへのテキスト表示#

計算機からシリアル通信で送信したテキストを,LEDマトリクスにスクロール表示する.

Arduino側のプログラム:

#include "ArduinoGraphics.h"
#include "Arduino_LED_Matrix.h"

ArduinoLEDMatrix matrix;
String message = "";

void setup() {
    Serial.begin(9600);
    matrix.begin();
    while (!Serial) {
        delay(100);
    }
    Serial.println("Ready. Send text to display on LED matrix.");
}

void loop() {
    if (Serial.available() > 0) {
        message = Serial.readStringUntil('\n');
        Serial.print("Displaying: ");
        Serial.println(message);

        matrix.beginDraw();
        matrix.stroke(0xFFFFFFFF);
        matrix.textScrollSpeed(50);
        matrix.textFont(Font_5x7);
        matrix.beginText(matrix.width() - 1, 1, 0xFFFFFF);
        matrix.println(message);
        matrix.endText(SCROLL_LEFT);
        matrix.endDraw();
    }
}

Python側のプログラム:

import serial

ser = serial.Serial(port='/dev/ttyACM0', baudrate=9600)

# Arduinoの起動を待つ
print(ser.readline().decode().strip())

while True:
    text = input("表示するテキストを入力 (Ctrl+Cで終了): ")
    ser.write((text + "\n").encode())
    print(">>" + ser.readline().decode().strip())

Tip

Pythonプログラムを実行したら,Arduino UNO R4 WiFiの基板上にある白いリセットボタンを押してArduinoを再起動すると,シリアル通信の接続が確実に確立される.

Note

ArduinoGraphicsライブラリが必要です.インストールしていない場合は,Arduino IDEのライブラリマネージャからインストールすること(図 9 参照).

発展課題6-4: シリアル通信を介した計算機側からのサーボモータの制御#

Arduino側のプログラム:

#define SERVO_PIN 2
#include <Servo.h>

Servo servo;

void setup() {
    Serial.begin(9600);
    servo.attach(SERVO_PIN);
    while (!Serial) {
        delay(100);  // シリアルポートが接続されるのを待つ
    }
    Serial.println("Ready. Send angle (0-180).");
}

int angle = 0;

void loop() {
    if (Serial.available() > 0) {
        angle = Serial.readStringUntil('\n').toInt();
        servo.write(angle);
        Serial.print("Change angle to ");
        Serial.println(angle, DEC);
    }
    delay(10);  // CPU負荷軽減のための短い待機
}

Python側のプログラム:

import serial

ser = serial.Serial(port='/dev/ttyACM0', baudrate=9600)

# Arduinoの起動を待つ
print(ser.readline().decode().strip())

while True:
    angle = input("角度を入力 (0-180, Ctrl+Cで終了): ")
    ser.write((angle + "\n").encode())
    print(">>" + ser.readline().decode().strip())

C言語による計算機側のプログラム:

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    const char *port_name = "/dev/ttyACM0";
    if (argc > 1) {
        port_name = argv[1];
    }

    int serial_port = open(port_name, O_RDWR);
    if (serial_port < 0) {
        printf("Error %i from open: %s\n", errno, strerror(errno));
        return 1;
    }

    struct termios tty;
    if (tcgetattr(serial_port, &tty) != 0) {
        printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
        return 1;
    }

    cfsetispeed(&tty, B9600);
    cfsetospeed(&tty, B9600);

    tty.c_cflag &= ~PARENB;
    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;
    tty.c_cflag &= ~CRTSCTS;
    tty.c_cflag |= CREAD | CLOCAL;

    tty.c_lflag &= ~ICANON;
    tty.c_lflag &= ~ECHO;
    tty.c_lflag &= ~ISIG;

    tty.c_iflag &= ~(IXON | IXOFF | IXANY);
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);

    tty.c_oflag &= ~OPOST;
    tty.c_oflag &= ~ONLCR;

    tty.c_cc[VTIME] = 10;
    tty.c_cc[VMIN] = 0;

    if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
        printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
        return 1;
    }

    printf("begin loop\n");
    char buf[256];
    for (int i = 0; ; i++) {
        memset(buf, '\0', sizeof(buf));
        int n = read(serial_port, buf, sizeof(buf));
        if (n > 0) {
            printf("%s", buf);
            fflush(stdout);
        }
        if (i % 2 == 0) {
            write(serial_port, "10\n", strlen("10\n"));
            printf("changed angle to 10\n");
        } else {
            write(serial_port, "100\n", strlen("100\n"));
            printf("changed angle to 100\n");
        }
        sleep(1);
    }

    close(serial_port);
    return 0;
}

C++(boost::asio)による計算機側のプログラム:

Note

Boostライブラリのインストールが必要.インストール手順は前述の「Boostライブラリのインストール」を参照.

#include <boost/asio.hpp>
#include <iostream>
#include <unistd.h>

int main(int argc, char *argv[]) {
    const char *port_name = "/dev/ttyACM0";
    if (argc > 1) {
        port_name = argv[1];
    }

    try {
        boost::asio::io_context io;
        boost::asio::serial_port port(io, port_name);
        port.set_option(boost::asio::serial_port_base::baud_rate(9600));

        while (true) {
            sleep(1);
            boost::asio::write(port, boost::asio::buffer("10\n", 3));
            std::cout << "changed angle to 10" << std::endl;
            sleep(1);
            boost::asio::write(port, boost::asio::buffer("100\n", 4));
            std::cout << "changed angle to 100" << std::endl;
        }
    } catch (const std::exception &e) {
        std::cerr << e.what() << std::endl;
        return 1;
    }
    return 0;
}

コンパイルと実行:

g++ -std=c++17 -o serial_servo serial_servo.cpp -lboost_system
./serial_servo /dev/ttyACM0

9. 発展課題7: Wi-Fiを使ったスマートフォンからLEDマトリクスへのメッセージ送信#

Arduino UNO R4 WiFiをWebサーバとして動作させ,同じWi-Fiネットワークに接続したスマートフォンのブラウザからメッセージを送信し,LEDマトリクスにスクロール表示するシステムを作成する.Wi-Fi機能はWiFiS3ライブラリを使用する.

_images/arduino-led-matrix-web.png

図 31 スマートフォンからLEDマトリクスへのメッセージ送信#

手順:

  1. Arduino UNO R4 WiFiをWi-Fiアクセスポイントとして設定する

  2. Webサーバを起動し,メッセージ入力用のHTMLページを配信する

  3. スマートフォンからArduinoのアクセスポイントに接続し,ブラウザでメッセージを送信する

  4. 受信したメッセージをLEDマトリクスにスクロール表示する

#include "ArduinoGraphics.h"
#include "Arduino_LED_Matrix.h"
#include "WiFiS3.h"

// アクセスポイントのSSIDとパスワード
char ap_ssid[] = "Arduino-LED";
char ap_pass[] = "12345678";

WiFiServer server(80);
ArduinoLEDMatrix matrix;
String currentMessage = "Hello!";
bool messageUpdated = true;

void setup() {
    Serial.begin(9600);
    matrix.begin();

    // アクセスポイントモードで起動
    Serial.print("Creating access point: ");
    Serial.println(ap_ssid);
    int status = WiFi.beginAP(ap_ssid, ap_pass);
    if (status != WL_AP_LISTENING) {
        Serial.println("Failed to create AP");
        while (true);
    }

    server.begin();
    IPAddress ip = WiFi.localIP();
    Serial.print("AP IP address: ");
    Serial.println(ip);
    Serial.println("Connect to the AP and open http://192.168.4.1");
}

void scrollText(const char* text) {
    matrix.beginDraw();
    matrix.stroke(0xFFFFFFFF);
    matrix.textScrollSpeed(50);
    matrix.textFont(Font_5x7);
    matrix.beginText(matrix.width() - 1, 1, 0xFFFFFF);
    matrix.println(text);
    matrix.endText(SCROLL_LEFT);
    matrix.endDraw();
}

void loop() {
    if (messageUpdated) {
        scrollText(currentMessage.c_str());
        messageUpdated = false;
    }

    WiFiClient client = server.available();
    if (client) {
        String request = "";
        while (client.connected()) {
            if (client.available()) {
                char c = client.read();
                request += c;
                if (c == '\n' && request.endsWith("\r\n\r\n")) {
                    break;
                }
            }
        }

        // メッセージの取得(GETパラメータから)
        if (request.indexOf("GET /send?msg=") >= 0) {
            int start = request.indexOf("msg=") + 4;
            int end = request.indexOf(" ", start);
            String msg = request.substring(start, end);
            // URLデコード(スペースの+変換)
            msg.replace("+", " ");
            currentMessage = msg;
            messageUpdated = true;
            Serial.print("New message: ");
            Serial.println(currentMessage);
        }

        // HTMLページの送信
        client.println("HTTP/1.1 200 OK");
        client.println("Content-type:text/html; charset=UTF-8");
        client.println();
        client.println("<!DOCTYPE html><html><head>");
        client.println("<meta name='viewport' content='width=device-width, initial-scale=1'>");
        client.println("<style>");
        client.println("body{font-family:sans-serif;text-align:center;padding:20px;}");
        client.println("input[type=text]{font-size:24px;padding:10px;width:80%;}");
        client.println("input[type=submit]{font-size:24px;padding:10px 30px;margin:10px;}");
        client.println("</style></head><body>");
        client.println("<h1>Arduino LED Matrix</h1>");
        client.println("<form action='/send'>");
        client.println("<input type='text' name='msg' placeholder='Message'><br>");
        client.println("<input type='submit' value='Send'>");
        client.println("</form>");
        client.print("<p>Current: ");
        client.print(currentMessage);
        client.println("</p></body></html>");
        client.stop();
    }
}

Note

接続方法

  1. スケッチをArduino UNO R4 WiFiに書き込む

  2. スマートフォンのWi-Fi設定から「Arduino-LED」に接続する(パスワード: 12345678

  3. ブラウザで http://192.168.4.1 を開く

  4. テキストボックスにメッセージを入力して「Send」を押す

  5. LEDマトリクスにメッセージがスクロール表示される

Warning

アクセスポイントモードを使用するため,演習室のWi-Fiとは別のネットワークになる. スマートフォンがArduinoのアクセスポイントに接続している間は,インターネットには接続できない. また,ap_ssidは他の人と重複しないように,自分の名前や班番号などを含めた固有の名前に変更すること(例: "Arduino-LED-GroupA").

付録: Arduino IDEの使い方#

Arduino IDEはArduinoの統合開発環境である.プログラムエディタ,プログラムのコンパイル,マイコンへの焼きこみ,シリアル通信の表示機能などが統合されている.

1. ArduinoとPCの接続準備#

Arduino UNO R4 WiFiとPCをUSB-Cケーブルで接続する.

Ubuntuの場合の注意事項:

"can't open device "/dev/ttyACM0": Permission denied"のようなエラーがでる場合は以下を実行する:

sudo usermod -a -G dialout <user-name>

場合によっては再起動する必要がある.

2. ボードマネージャでArduino UNO R4のサポートをインストール#

Arduino IDEを起動し,以下の手順でArduino UNO R4 WiFiのサポートをインストールする:

  1. [ツール] -> [ボード] -> [ボードマネージャ]を開く

  2. 「Arduino UNO R4」を検索

  3. 「Arduino UNO R4 Boards」をインストール

3. 焼きこみ設定#

Arduinoマイコンへプログラムを焼きこむための設定:

  1. [ツール] -> [ボード] -> [Arduino UNO R4 Boards] -> [Arduino UNO R4 WiFi]

  2. [ツール] -> [シリアルポート] -> [/dev/ttyACM0](または認識されているポート)

4. プログラムの検証及び書き込み#

  1. IDEの[検証](チェックマークボタン)をクリックするとプログラムのコンパイルが始まる

  2. エラーがない場合は「コンパイルが完了しました.」と表示される

  3. [マイコンボードへ書き込む](右矢印ボタン)を押すとマイコンへの書き込みが行われる

_images/arduino-uno-r4-wifi-uploading.png

図 32 プログラムの書き込み#

5. シリアルモニタでのプリント文の確認#

[ツール] -> [シリアルモニタ]でシリアルモニタを起動できる.

Warning

一つのArduinoと同時にシリアル通信できるPC上のプログラムは一つに限られる. シリアルモニタを起動していると発展課題6(シリアル通信)で他のプログラム(cuやC言語・Python)からArduinoへ接続できないので,発展課題6に取り組むときはIDEのシリアルモニタを終了する必要がある.

6. 書き込みや通信ができなくなった場合#

書き込みやシリアル通信ができなくなった場合,まずUSBケーブルを抜き差しする.それでも解決しない場合は,まずデバイスファイルの有無を確認する:

ls /dev/ttyACM*

/dev/ttyACM0 が表示されていれば,OS側はArduinoを認識している.表示されない場合は,ターミナルで以下のコマンドを実行してカーネルログから認識状況を確認する:

sudo dmesg --follow

Note

Ubuntu 24.04 以降は kernel.dmesg_restrict=1 がデフォルトのため,dmesg の閲覧には sudo が必要である(adm グループに所属していれば不要).sudo が使えない場合は journalctl -kf でも同じカーネルログをリアルタイムに確認できる.

この状態でUSBケーブルを抜き差しし,Arduinoが正しく認識されたログ(usb 1-1: new full-speed USB deviceidVendor=2341, idProduct=1002cdc_acm ... ttyACM0など)が表示されることを確認してから,再度書き込みや通信を試みる.ログが一切出ない場合はUSBケーブル(充電専用ケーブルになっていないか)やUSBハブを疑う.

Ubuntu・macOS・Windowsでの確認方法を含めた詳細はArduinoが認識されないときの確認方法を参照.

スケッチがプロセッサをロックしてUSB経由でボードにアクセスできなくなった場合は,電源投入直後にリセットボタンを2回素早く押すことでブートローダーモードに入ることができる.