SDメモリカードを抜き差しせずイメージを書き換えRaspberry Piをブートするツール(ソフトウェア作成編)
前回の記事
前回、PCBをKiCad使って設計、部品の実装し、SDカードスロットに差して、Raspberry Piが起動するまでの試行錯誤を書きました。今回は、このツールのもう一つの機能である赤外線コントロールの部分のソフトウェアの実装について書きたいと思います。
どんなものかについては前回の記事を参照してみてください。
Raspberry Piの電源をON/OFFするには
Raspberry Piの電源はUSB Type-Cで供給していますが、このUSBケーブルを切ってリレー等でON/OFFする方法もありますが、Raspberry Pi以外の機器でもON/OFFしたく、100vの電源をON/OFFできるようにしたいと思いました。100vのON/OFFをリレーで行うのは、ちょっと電圧が高くハードルが高かったので、IoTでやるようなスイッチを探していると、赤外線でON/OFFができるのを見つけました。
大きさも小さく、価格もそんなに高くないので、これを使えると何かと便利です。これを、赤外線でコントロールして、100vの電源がON/OFFできれば、Raspberry PiへのUSBケーブルを切らなくても、ON/OFFできます。
赤外線コントロールのやり方
昔からある専用ICによる赤外線送信の方法では、この機器に合うかどうかわかりません。一般的な赤外線リモコンのフォーマットを調べてみましたが、これと決まった方法はなく、複数のフォーマットがあるようです。また、このリモコンがどのフォーマットなのかわからないので、実際を調べて見ました。
下記の赤外線リモコン受信モジュール(SPS-445-1:写真右)を用意し、下記の回路をブレッドボードで結線し、リモコンからのON/OFFを簡易ロジアナで信号を取ってみました。
ONボタンを押した時の信号です。家電のフォーマットとまったく違うことがわかりました。これでは、専用ICでは送信できないのですが、この簡易ロジアナはUSBシリアルコンバータFTDIのFT232Hを使用したものです。これで波形が取れるなら、この波形のデータを赤外線LEDに送信すればできるのではないかと思いました。
波形の立下りの部分は、実際は38KHzで変調されています。赤外線リモコン受信モジュールがこの変調された部分を1つのLow区間にまとめています。ですので、送信する際は、このLowの部分を38KHzの矩形波で埋めて送信すれば良いはずです。(実際のリモコンの矩形波は1/3dutyで、1周期の1/3がHighで2/3がLowですが、電池の電力消費を抑えるためそうしているとのことですので、今回はLEDは電池駆動ではないのでDutyは1/2にしました。)
受信モジュールの出力をFT232Hで受け、データを取り出します。ADBUS0のビットのみを順次取り出します。HIGHの部分が1となり、LOWの部分が0になります。送信時は、1の部分を0に反転し、0の部分を交互に1010となるようにエンコード(赤字部分)します。この送信データを赤外線LEDのADBUS1に38KHzのボーレートで順次送信することでコントロールします。
赤外線リモコンの信号を送受信するUSBシリアルコンバータ(FT232H)のコード
ソースコードの主要部分です。
すべてのコードは以下にあります。
FT232HのUSBポートをオープンします。
#include <ftdi.h>
#define USB_VENDER_ID 0x0403 // FTDI
#define USB_PRODUCT_ID 0x6014 // FT232H
#define CLOCK_RATE_FOR_IFR 3800 // 38kHz
#define CLOCK_RATE_FOR_IFR_TX 3960 // 38kHz * 1.04
static FT_STATUS ftdi_open(FT_HANDLE *ftdi){
FT_STATUS fts;
if((*ftdi = ftdi_new()) == 0){
fprintf(stderr, "ftdi_new() failed.");
exit(1);
}
fts = ftdi_usb_open(*ftdi, USB_VENDER_ID, USB_PRODUCT_ID);
if (fts < 0 && fts != 5)
{
fprintf(stderr, "FT_Open failed (error %d).\n", (int)fts);
exit(1);
}
return fts;
}
赤外線リモコン受信モジュールの信号を受信し、ファイルにデータを保存部分です。
// リモコンコマンド受信
int command_capture(char *cmdname, int dumpbit){
FT_STATUS fts = FT_OK;
FT_HANDLE fth;
// FT232Hポートをオープン
fts = ftdi_open(&fth);
if (fts != FT_OK){
return fts;
}
// 受信ボーレートを設定 38KHz
fts = ftdi_set_baudrate(fth, CLOCK_RATE_FOR_IFR);
if (fts != FT_OK)
{
printf("FT_SetBaudRate failed (error %d).\n", (int)fts);
goto exit;
}
// 受信開始
DWORD cmdlen;
UCHAR buf[CAPTURE_BUFFER_SIZE];
fts = ifr_cmd_capture(fth, buf, CAPTURE_BUFFER_SIZE, &cmdlen);
if(fts != FT_OK){
goto exit;
}
if(dumpbit){
bit_show(buf, cmdlen, 0);
}
// 受信完了後、コマンドを送信できるようにエンコード
encode_ifr(buf, cmdlen);
// コマンドを保存
save_ifr_cmd(buf, cmdlen, cmdname);
if(cmdlen > 0)
fts = FT_OK;
printf("Receive IFR data (%dB)\n", cmdlen);
exit:
// ポートをクローズ
ftdi_usb_close(fth);
ftdi_free(fth);
return fts;
}
// 受信処理
int ifr_cmd_capture(FT_HANDLE fth, UCHAR *buf, int maxbuf, DWORD *prcvbuf)
{
UCHAR tmpbuf[TMP_BUF_SIZE];
FT_STATUS fts;
DWORD bytesRead = 0, outlen = 0, outofs = 0, room;
int wait;
if (maxbuf < MIN_COMMAND_BUF_LEN){
printf("Invalid buffer length %d\n", maxbuf);
return -1;
}
// FT232HのADBUSポートをBITBANGモードにセットする。
fts = ftdi_set_bitmode(fth, 0x00, BITMODE_BITBANG);
if (fts != FT_OK)
{
printf("FT_SetBitMode failed (error %d).\n", (int)fts);
return fts;
}
printf("Capture Start.. \n");
//フィルタ初期値セット
ifr_filter(NULL, 0, 1);
ifr_lowpass_filter(NULL, 0, 1);
outofs = PREAMBLE_BIT_SIZE;
room = maxbuf - PREAMBLE_BIT_SIZE;
memset(buf, 0x01, outofs);
for(wait=0;wait < MAX_CAPTURE_WAIT;wait++){
// データを受信 ADBUS0 01:HIGH, 00:LOW
fts = ftdi_read_data(fth, tmpbuf, TMP_BUF_SIZE);
if(fts < 0){
return fts;
}
bytesRead = fts;
// ノイズを除去
ifr_lowpass_filter(tmpbuf, bytesRead, 0);
// コマンド列を抽出
outlen = ifr_filter(tmpbuf, bytesRead, 0);
if(outlen == 0){
if(outofs > PREAMBLE_BIT_SIZE){
*prcvbuf = outofs;
return FT_OK;
}
continue;
}
if(room < outlen){
printf("overflow buffer room=%d outlen=%d\n", room, outlen);
outlen = room;
}
memcpy(buf+outofs, tmpbuf, outlen);
outofs += outlen;
room -= outlen;
}
printf("expired wait time.\n");
*prcvbuf = outofs;
return FT_OK;
}
赤外線LEDから送信する部分です。
// 赤外線コマンドの送信
int command_tx(char *cmdname, int n_tx, int dumpbit){
FT_STATUS fts = FT_OK;
FT_HANDLE fth;
// FT232Hポートをオープン
fts = ftdi_open(&fth);
if (fts != FT_OK){
return fts;
}
// 送信ボーレートを設定 38KHz * 1.02倍
fts = ftdi_set_baudrate(fth, CLOCK_RATE_FOR_IFR_TX);
if (fts != FT_OK)
{
printf("FT_SetBaudRate failed (error %d).\n", (int)fts);
goto exit;
}
DWORD cmdlen;
UCHAR buf[CAPTURE_BUFFER_SIZE];
// 送信するコマンドのデータをファイルからリード
cmdlen = load_ifr_cmd(buf, CAPTURE_BUFFER_SIZE, cmdname);
if(dumpbit){
bit_show(buf, cmdlen, 1);
}
for(int j=0;j<n_tx;j++){
// コマンドビット列を送信
tx_ifr_cmd(fth, buf, cmdlen);
}
printf("Tx Command %s (%d)\n", cmdname, cmdlen);
exit:
// ポートのクローズ
ftdi_usb_close(fth);
ftdi_free(fth);
return fts;
}
// 送信処理
DWORD tx_ifr_cmd(FT_HANDLE fth, UCHAR *buf, DWORD cmdlen){
DWORD bytesWritten;
FT_STATUS fts;
// ADBUSポートのモードをBITBANGモードにセット
fts = ftdi_set_bitmode(fth, 0xFF, BITMODE_BITBANG);
if (fts != FT_OK)
{
printf("FT_SetBitMode failed (error %d).\n", fts);
return fts;
}
// コマンドデータを送信 ASBUS1に送信 02: HIGH, 00: LOW
fts = ftdi_write_data(fth, buf, cmdlen);
if (fts < 0)
{
printf("FT_Write failed (error %d).\n", fts);
}
bytesWritten = (DWORD)fts;
return bytesWritten;
}
メモリカードMUXの切替信号を出す部分です。
// val: 1:ホスト側, 0:ターゲット側
int write_to_tbctl(UCHAR val){
FT_STATUS fts = FT_OK;
FT_HANDLE fth;
fts = ftdi_open(&fth);
if (fts != FT_OK){
return fts;
}
// ACBUS5(C5)を1または0にコントロールすることでメモリカードの方向を切替
// 1: Host side, 0: Target side
fts = ftdi_set_bitmode(fth,
0xF0 | (val & 0x0F), // C9,C8,C6,C5
BITMODE_CBUS);
if (fts != FT_OK)
{
printf("FT_SetBitMode failed (error %d).\n", (int)fts);
}
ftdi_usb_close(fth);
ftdi_free(fth);
return 0;
}
使い方(2023.7.23追記)
Githubからソースコードを取り出し、ビルドします。Ubuntu 22.04 LTSを使用しています。
$ sudo apt -y install build-essential libftdi-dev
$ git clone https://github.com/hnz1102/sdcdmux-emmc.git
$ cd sdcdmux-emmc/src
$ make
接続確認します。
$ ./sdcdmux
Memory Device is connected to : HOST.
リモコンのONコマンドをツールに覚えさせます。
$ ./sdcdmux -c on
Capture Start..
リモコンのONを押します。
以下のように受信しました。
Receive IFR data (10776B)
Captured command for on
同様にリモコンのOFFコマンドをツールに覚えさせます。
$ ./sdcdmux -c off
Capture Start..
リモコンのOFFを押します。以下のように受信しました。
Receive IFR data (10135B)
Captured command for off
今度は、覚えたコマンドで、電源ON/OFF操作できるか確認します。
$ ./sdcdmux -x on
Tx Command on (10776)
カチッといってONできました。
次はOFFにしてみます。
$ ./sdcdmux -x off
Tx Command off (10135)
カチッといってOFFできました。
SDカードをターゲットに切り替えて、Raspberry Piを起動してみます。
$ ./sdcdmux -s target
Memory Device is connected to : TARGET.
$ ./sdcdmux -x on
Tx Command on (10776)
カチッといってRaspberry Piの電源が入り、OSが起動しました。
覚えたコマンドでON/OFFを繰り返したい時、以下の-l
コマンドで自動でON/OFFを繰り返しします。繰り返しブート試験等に使えます。
コマンドオプションの意味は-o <秒数>
でONの時間を-f <秒数>
でOFFの時間を指定します。ONした時間とOFFした時間が出力されます。
$ ./sdcdmux -l -o 30 -f 10
POWER ON : No.1 2023/07/23 09:59:08 Sun
Tx Command on (10776)
POWER OFF: No.1 2023/07/23 09:59:38 Sun
Tx Command off (10135)
POWER ON : No.2 2023/07/23 09:59:48 Sun
Tx Command on (10776)
POWER OFF: No.2 2023/07/23 10:00:19 Sun
Tx Command off (10135)
POWER ON : No.3 2023/07/23 10:00:29 Sun
Tx Command on (10776)
:
ON/OFFできな場合があったが、送信ボーレートを調整して解決
最初は、1回の送信で電源ONしないまたはOFFしないことがありました。出力が弱いのかと思ったのですが、近づけても変わりません。赤外線LEDの送信の波形をとっても、38KHzで変調されています。パルスの長さも、受信時の通りに出力していました。全く動かないわけでもないので、コードはあっているように思えます。
行き詰っていましたが、この赤外線LEDが送信したものを、赤外線受信モジュールはどう出力しているのか見てみることにしました。つまり、自身が出した赤外線LEDの出力を、この受信モジュールはどう認識しているかを見ました。
するとこの赤外線受信モジュールは、実際の変調された部分より出力が遅れ、かつ長くなることがわかりました。つまり、実際のリモコンから受信した波形は、リモコンが送信した長さのパルスに比べて少し長く受信モジュールのLOW部分が長くなっていました。このままの長さで送信しても、受信長がさらに長くなり合わなくなってしまいます。
個々のパルスがどれくらい長くなるか計ってみましたが、バラバラで一律、何クロック削減すればよいか定まりませんが、大体でパルス長を合わせればよいと考え、送信ボーレートを約1.04倍になるよう39.6KHzで送信したところ、安定してON/OFFできるようになりました。
赤外線モジュールのデータシートはそういう記述がないので、なぜこうなるのか原因はわかっていません。
また、FT232Hのボーレートの設定は、ftdi_set_baudrate(fth, CLOCK_RATE_FOR_IFR)で設定するのですが、第2引数のCLOCK_RATE_FOR_IFRは3800です。38KHzなら38000なのですが、1/10にしないと38KHzになりません。送信は39.6KHzにしましたので、3960です。他を検索しても、BITBANGモードでは1/16だろうとありましたが、1/16では小さすぎました。オシロで確認してみると1/10のようです。非同期シリアル通信の10bit(8bitデータ+Start/Stop-bit)から来ているのかもしれませんが、BITBANGモードとset_baudrateとの関係はよくわかりません。
おまけ:赤外線コマンドがキャプチャできることで、いろんな機器が操作できた
ともあれ、安定して動作したので、いろいろな身近にある赤外線リモコンを学習させてみました。シーリングライト(天井のライト)、ディスプレイのリモコンは、ON/OFFできました。エアコンについては、LEDの出力が弱いようで認識しませんでした。近づければ動くのかもしれませんが、我が家のエアコンは古いので、そのせいかもしれません。TVは近くにないので、ためしてませんが、たぶん動くだろうと思います。
秋月電子に以下のボードが売っていますので、リモコン部分だけであれば試すことができます。赤外線LEDを複数並べて送信出力を上げて、PC操作の自作IoT家電制御リモコンにしてもよいかもしれません。(2023.07.23追記)
次回
このツールのSDカードへの書き込みに4GBで約8分ほどかかってました。これをなんとか時短にするために、フラッシュメモリ(eMMC)にしたところ、2分ちょっとで書込みができるようになりました。次回、そのeMMC版の作成の経緯を記したいと思います。ここまで読んでくださりありがとうございました。
Discussion