はじめに
今回は『かず』さんよりコメントをいただいていた、Race Chronoで、RaspberryPi Pico Wを使う方法について解説します。
Race Chronoは、モータースポーツのラップタイム・車両の位置・速度情報が簡単に解析でき、以下のような動画も作れてしまうスマホアプリです。
Android/iOS版が用意されており、スマホ内蔵のGPSや、OBDⅡリーダーなどの専用機器、さらには『Race Chrono DIY』として、自作の機器からもBluetoothやWi-Fiでデータを取得・解析することができます。
今回は上記『Race Chrono DIY』に、RaspberryPi Pico W を使う方法について解説したいと思います。環境はArduino言語の環境を使います。
以下はかずさんがノーマルのRaspberryPiを使って作成された動画です。今回は以下をPico Wに置き換えたいとのことでコメントをいただきました。
~ この記事の内容 / Contents ~
環境
この記事で使用する環境は以下の通りです。
環境 | バージョンなど | 備考 |
開発用PCのOS | Windows11 | Windows10でもOKです |
言語 | Arduino言語 | |
開発環境 | Arduino IDE 2.2.1 | |
ボード | RaspberryPi Pico W | |
ボードサポートパッケージ | ||
アプリ | Race Chrono 無料版 | 記事ではAndroid版を使用します。 |
今回、Race Chronoとの接続には「Bluetooth COMM」(Bluetooth上でのシリアル通信(SPP) )を使用しますが、MicroPythonではまだサポートされていません。
そのため、今回は「Arduino言語」の環境を使ってRace Chronoとの接続を実現します。Arduino言語のセットアップ方法については、こちらの記事をご覧ください。
RaspberryPiとの接続
接続する機器
今回接続する機器は以下の通りです。
RaspberryPi Pico Wを経由して、Race Chronoに車の水温・油温の情報を送信します。
実際には車載の機器やメーターなどをPico Wにつなぎますが、今回は代わりにWindowsPCのPythonプログラムから、ダミーの情報を送ります。
接続方法はPico WとスマホがBluetooth、Pico Wとメーターが有線のシリアル(UART)です。
Pico Wとメーターの接続
Pico Wの「UART0」ピンをメーター(WindowsPC)につなぎます。シリアル通信ではメーター(WidnowsPC)の「Tx」と、PicoWの「Rx」を接続する必要があるので注意してください。
WindowsPC側のシリアルは、スイッチサイエンスさんの「FTDI USBシリアル変換アダプター Rev.2」を使用してUSBで接続しています。
ピン番号 | Pico W | シリアル変換アダプター Rev.2 |
21 | UART0 Tx | Rx |
22 | UART0 Rx | Tx |
23 | GND | GND |
Pico W ピンアサイン(Pin-Out)
※ Pico W公式サイトより引用
使用する部品
Pico W 本体
Raspberry Pi Pico W本体です。技適対応品かどうか心配…という方は、以下のページで出品者を「共立エレショップAセレクト」(国内の有名ショップ)にして購入してください。
ブレッドボード
国内サンハヤト製のブレッドボードです。少々堅めの指し心地ですが、海外製と違ってピン穴の番号がすべて印刷されており、品質も高いのでおすすめです。
配線
配線にはジャンパワイヤの自作にも使える「協和ハーモネット 耐熱通信機器用ビニル電線」を使っています。少し硬めの配線ですが、ジャンパワイヤとしても、配線としても使える便利な配線です。
USBシリアル変換
スイッチサイエンスさんの「FTDI USBシリアル変換アダプター Rev.2」です。チップは定番のFTDI社なので安定性は抜群。ドライバもメーカー内で共通で使えるので手間が少ないです。
通信データフォーマット
RaceChrono・メーターの通信データフォーマットは以下の通りです。
RaceChrono
RaceChronoで使える通信データフォーマットはいくつかありますが、記事では以下の「RC3」というフォーマットを使用します。
RC3は、以下のように、フォーマット形式・時間・カウントなどを、カンマで区切って並べ、末尾に「*」「チェックサム」「CRLF」を付加する『文字列形式のデータ』です。
※ RC3のフォーマットの詳細については、こちらの公式ページをご覧ください。
メーター(WindowsPC)
水温 → 油温の順に並んだデータをカンマで区切ってたデータです。
温度情報は1周期ごとに0.1度上昇した値を送信し続けます。単位は「0.1度」で、10倍して整数にした値が送信されます。
Race Chrono DIYの登録
Race Chrono でPico Wを使うには、事前にPico Wを「Race Chrono DIY」として登録しておく必要があります。以下の手順でPico Wを登録してください。
Race Chronoのメイン画面の設定アイコンをタップして、「その他デバイスを追加」をタップします。
表示された一覧の中から「Race Chrono DIY」をタップします。通信方法として「Bluetooth RFCOMM」を選択します。
Bluetoothデバイスの一覧が表示されるので「範囲内のデバイスを検索する」をタップし、検索されたPico Wをタップします。
検索の際は、Pico WでBluetoothが使える(SerialBT.begin関数が実行された)状態である必要があります。以下の操作は後述のPicoWのプログラムを実行してから実施してください。
通信に追加データフォーマットとして「RC2/RC3」をタップします。選択後設定画面に戻ったらPico Wの登録は完了です。
プログラム概要
今回のプログラムの概要は以下の通りです。
- Bluetooth(SPP)、シリアル通信を開始する。
- メーターから、水温・油温を取得する。
- 水温・油温をRace Chronoに送信する
実行方法
以下の順序で実行します。
- Pico Wのプログラムを実行
- メーター(Windows)のプログラムを実行
- Race Chronoで開始ボタンを押す
実行結果
後述するプログラムを実行すると、以下のようにRace Chronoでメーター(WindowsPC)のデータが表示されます。
全体コード( RaspberryPi W 側)
RaspberryPi Pico W側の全体コードは以下の通りです。詳細は次の「コードのポイント」で解説します。
#include "SerialBT.h"
// RaceChronoに送信するデータ(文字列)です
String frame;
// 水温データです
float waterTemp;
// 油温データです
float oilTemp;
// ループカウンタです
unsigned int count;
void setup() {
// 送信データ初期化です。$はチェックサム計算後につけます。
frame = "RC3,";
// 各種変数の初期化です。
count = 0;
waterTemp = 0.0;
oilTemp = 0.0;
// Bluetoothシリアルを開始します
SerialBT.begin();
// USBシリアル(デバッグ用)を開始します
Serial.begin(115200);
// メーターとの有線シリアル(UART0)を開始します。
// GP16,17を使う場合は指定が必要です。
Serial1.setTX(16);
Serial1.setRX(17);
Serial1.begin(115200);
// USBシリアル(デバッグ用)の準備ができるまで待機します。
while( Serial == false ){ delay(100); }
Serial.println("setup done.");
}
//
// メーターからシリアル通信で温度を取得します。
//
int readTemp()
{
waterTemp = 0.0;
oilTemp = 0.0;
if( Serial1.available() )
{
// カンマまで読み取ります
String wStr = Serial1.readStringUntil(',');
// データの最後(改行)まで読み取ります。
String oStr = Serial1.readStringUntil('\n');
// 整数→少数点に変換します。
waterTemp = wStr.toInt() / 10.0;
oilTemp = oStr.toInt() / 10.0;
//Serial.println(String(waterTemp));
//Serial.println(String(oilTemp));
}
return 0;
}
//
// チェックサムを計算します
//
int calcCheckSum( String data, byte& checkSum )
{
checkSum = 0;
// データ文字列の最初から最後までを使ってチェックサムを計算します。
// 終端のNULL文字は計算に含めません。
for(int i=0; i<data.length(); i++ )
{
checkSum = checkSum ^ (byte) data[i];
}
return 0;
}
//
// RC3形式のデータを作成します。
//
int createRC3( String& rc3Str )
{
rc3Str = "RC3,";
rc3Str = rc3Str + ","; // time
rc3Str = rc3Str + String(count) + ","; // count
rc3Str = rc3Str + ","; // xacc
rc3Str = rc3Str + ","; // yacc
rc3Str = rc3Str + ","; // zacc
rc3Str = rc3Str + ","; // gyrox
rc3Str = rc3Str + ","; // gyroy
rc3Str = rc3Str + ","; // gyroz
rc3Str = rc3Str + ","; // rpm/d1
rc3Str = rc3Str + ","; // d2
rc3Str = rc3Str + String(waterTemp) + ","; // a1
rc3Str = rc3Str + String(oilTemp) + ","; // a2
rc3Str = rc3Str + ","; // a3
rc3Str = rc3Str + ","; // a4
rc3Str = rc3Str + ","; // a5
rc3Str = rc3Str + ","; // a6
rc3Str = rc3Str + ","; // a7
rc3Str = rc3Str + ","; // a8
rc3Str = rc3Str + ","; // a9
rc3Str = rc3Str + ","; // a10
rc3Str = rc3Str + ","; // a11
rc3Str = rc3Str + ","; // a12
rc3Str = rc3Str + ","; // a13
rc3Str = rc3Str + ","; // a14
rc3Str = rc3Str ; // a15
return 0;
}
//
// メインループです
//
void loop() {
// 水温・油温を取得します。
readTemp();
// 送信文字列を初期化します。
String frame = "";
// RC3形式のデータを作成します。
createRC3(frame);
// チェックサムを計算します。
byte checkSum = 0;
calcCheckSum(frame, checkSum);
// チェックサムを16進文字列にします。
String checkSumHexStr = String(checkSum,HEX);
// 送信データの先頭に「$」、末尾に「*」とチェックサム、最後に改行CRLFを追加します。
frame = "$" + frame + "*" + checkSumHexStr + "\r\n";
// Bluetoothで送信します。
if(SerialBT)
{
// 送信文字列+NULL文字分の配列を作成します。
char charBuf[frame.length()+1];
// 文字列から配列に変換します。
frame.toCharArray(charBuf, frame.length()+1);
// 配列を送信します。
SerialBT.write(charBuf);
Serial.println("charBuf: " + String(charBuf) );
}
// カウンタの加算です。MAXまでいったら0に戻します。
count++;
if(count >= 65535 ) { count = 0; }
delay(1000);
}
コードのポイント(RaspberryPi Pico W)
各種シリアル通信の設定
RaceChronoとの通信に使う「Bluetoothシリアル」。デバッグ用の「USBシリアル」。メーターと通信するための「有線のシリアル(UART0)」を設定します。
UART0の設定では、Tx、Rxに使うピンを「GPIOの番号」で指定する必要があるので注意してください。
void setup() {
// ~~~~~ 省略 ~~~~~~
// Bluetoothシリアルを開始します
SerialBT.begin();
// USBシリアル(デバッグ用)を開始します
Serial.begin(115200);
// メーターとの有線シリアル(UART0)を開始します。
// GP16,17を使う場合は指定が必要です。
Serial1.setTX(16);
Serial1.setRX(17);
Serial1.begin(115200);
// USBシリアル(デバッグ用)の準備ができるまで待機します。
while( Serial == false ){ delay(100); }
Serial.println("setup done.");
}
水温・油温の取得
メーターから水温・油温を受信します。水温・油温の順で、カンマで区切りの文字列として送られるてくるため、readStringUntil関数で「,」と「¥n」までを区切り文字に指定して、その前の値を取得しています。
取得した温度は10倍された値なので、値を10分の1にして元の少数点付きの値に戻しています。
//
// メーターからシリアル通信で温度を取得します。
//
int readTemp()
{
waterTemp = 0.0;
oilTemp = 0.0;
if( Serial1.available() )
{
// カンマまで読み取ります
String wStr = Serial1.readStringUntil(',');
// データの最後(改行)まで読み取ります。
String oStr = Serial1.readStringUntil('\n');
// 整数→少数点に変換します。
waterTemp = wStr.toInt() / 10.0;
oilTemp = oStr.toInt() / 10.0;
Serial.println(String(waterTemp));
Serial.println(String(oilTemp));
}
return 0;
}
RC3フォーマットのデータ作成
前述したRC3形式のデータ文字列を作成します。今回はアナログ(入力)の部分に水温と油温を入力しています。
//
// RC3形式のデータを作成します。
//
int createRC3( String& rc3Str )
{
rc3Str = "RC3,";
rc3Str = rc3Str + ","; // time
rc3Str = rc3Str + String(count) + ","; // count
rc3Str = rc3Str + ","; // xacc
rc3Str = rc3Str + ","; // yacc
rc3Str = rc3Str + ","; // zacc
rc3Str = rc3Str + ","; // gyrox
rc3Str = rc3Str + ","; // gyroy
rc3Str = rc3Str + ","; // gyroz
rc3Str = rc3Str + ","; // rpm/d1
rc3Str = rc3Str + ","; // d2
rc3Str = rc3Str + String(waterTemp) + ","; // a1
rc3Str = rc3Str + String(oilTemp) + ","; // a2
rc3Str = rc3Str + ","; // a3
rc3Str = rc3Str + ","; // a4
rc3Str = rc3Str + ","; // a5
rc3Str = rc3Str + ","; // a6
rc3Str = rc3Str + ","; // a7
rc3Str = rc3Str + ","; // a8
rc3Str = rc3Str + ","; // a9
rc3Str = rc3Str + ","; // a10
rc3Str = rc3Str + ","; // a11
rc3Str = rc3Str + ","; // a12
rc3Str = rc3Str + ","; // a13
rc3Str = rc3Str + ","; // a14
rc3Str = rc3Str ; // a15
return 0;
}
チェックサムの計算
Race Chrono公式のコードをベースに、RC3のチェックサムを計算します。
チェックサムは送信するデータ文字列の、先頭の「$」、末尾の「*」を”含まない”部分で計算します。計算方法は「各文字」の排他的論理和(XOR)の足し合わせです。
先頭・末尾を含まない、全ての文字が対象になるので、数値を区切る「,」や、小数点「.」もチェックサム計算に含めます。詳細はこちら公式ページをご覧ください。
//
// チェックサムを計算します
//
int calcCheckSum( String data, byte& checkSum )
{
checkSum = 0;
// データ文字列の最初から最後までを使ってチェックサムを計算します。
// 終端のNULL文字は計算に含めません。
for(int i=0; i<data.length(); i++ )
{
checkSum = checkSum ^ (byte) data[i];
}
return 0;
}
送信データの作成
計算したチェックサムなどを、送信データに追加します。
先頭には「$」マークを、末尾には「*」に続けてチェックサムを追加します。チェックサムの後ろに送信データの終端(改行)を示す「\r\n」を付けて完成です。
// チェックサムを16進文字列にします。
String checkSumHexStr = String(checkSum,HEX);
// 送信データの先頭に「$」、末尾に「*」とチェックサム、最後に改行CRLFを追加します。
frame = "$" + frame + "*" + checkSumHexStr + "\r\n";
データの送信
作成した文字列をBluetoothシリアル(SPP)で送信します。
送信の際は、char配列の最後にNULL文字が必要なので、配列を「文字数+1」のサイズで作成し、toCharArray関数で変換することで、配列の最後にNULL文字が追加します。
// Bluetoothで送信します。
if(SerialBT)
{
// 送信文字列+NULL文字分の配列を作成します。
char charBuf[frame.length()+1];
// 文字列から配列に変換します。
frame.toCharArray(charBuf, frame.length()+1);
// 配列を送信します。
SerialBT.write(charBuf);
// Serial.println("charBuf: " + String(charBuf) );
}
全体コード WindowsPC側
メーターの代わりに使う、Windows側のプログラムは以下の通りです。
『pyserial』というライブラリを使用するので、コマンドプロンプトから以下のコマンドを入力してインストールしてください。
以下のコマンドを入力
pip install pyserial
import serial, time
# シリアルを開始します
ser = serial.Serial("COM7", 115200)
# 水温・油温用変数です
wTemp = 0;
oTemp = 0;
while(True):
# カンマ区切りの送信データを作成します
frame = str(wTemp) + "," + str(oTemp) + "\n"
# データを送信します
ser.write(frame.encode("utf-8"))
print(frame)
# 温度データを変化させます。
wTemp += 1
oTemp += 1
time.sleep(1);
まとめ
RaspberryPi Pico W をRace Chronoで使う方法について解説しました。
少々コードは長くなってしまいましたが、記事のようにすることで、メーターや車載機器の情報を、Race Chronoで表示・解析することができると思います。
Race Chronoの環境を、安価に製作されたい方、軽量・コンパクトに作成したい方の参考になればうれしいです。
質問・要望 大歓迎です
「こんな解説記事作って」「こんなことがしたいけど、〇〇で困ってる」など、コメント欄で教えてください。 質問・要望に、中の人ができる限り対応します。
使えたよ・設定できたよの一言コメントも大歓迎。気軽に足跡を残してみてください。記事を紹介したい方はブログ、SNSにバシバシ貼ってもらってOKです。