Pico W PR

【質問回答】RaspberryPi Pico WをRace Chronoで使う方法【Bluetooth】

記事内に商品プロモーションを含む場合があります

はじめに

今回は『かず』さんよりコメントをいただいていた、Race Chronoで、RaspberryPi Pico Wを使う方法について解説します。

Race Chronoは、モータースポーツのラップタイム・車両の位置・速度情報が簡単に解析でき、以下のような動画も作れてしまうスマホアプリです。

Android/iOS版が用意されており、スマホ内蔵のGPSや、OBDⅡリーダーなどの専用機器、さらには『Race Chrono DIY』として、自作の機器からもBluetoothやWi-Fiでデータを取得・解析することができます。

Race Chrono公式:https://racechrono.com

今回は上記『Race Chrono DIY』に、RaspberryPi Pico W を使う方法について解説したいと思います。環境はArduino言語の環境を使います。

以下はかずさんがノーマルのRaspberryPiを使って作成された動画です。今回は以下をPico Wに置き換えたいとのことでコメントをいただきました。

環境

この記事で使用する環境は以下の通りです。

環境 バージョンなど 備考
開発用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とメーターをシリアル通信で接続する方法を説明する画像
Pico Wとメーター(WindowsPC)の接続

ピン番号 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セレクト」(国内の有名ショップ)にして購入してください。

¥1,780 (2024/10/07 17:11時点 | Amazon調べ)

ブレッドボード

国内サンハヤト製のブレッドボードです。少々堅めの指し心地ですが、海外製と違ってピン穴の番号がすべて印刷されており、品質も高いのでおすすめです。

配線

配線にはジャンパワイヤの自作にも使える「協和ハーモネット 耐熱通信機器用ビニル電線」を使っています。少し硬めの配線ですが、ジャンパワイヤとしても、配線としても使える便利な配線です。

USBシリアル変換

スイッチサイエンスさんの「FTDI USBシリアル変換アダプター Rev.2」です。チップは定番のFTDI社なので安定性は抜群。ドライバもメーカー内で共通で使えるので手間が少ないです。

通信データフォーマット

RaceChrono・メーターの通信データフォーマットは以下の通りです。

RaceChrono

RaceChronoで使える通信データフォーマットはいくつかありますが、記事では以下の「RC3」というフォーマットを使用します。

RC3は、以下のように、フォーマット形式・時間・カウントなどを、カンマで区切って並べ、末尾に「*」「チェックサム」「CRLF」を付加する『文字列形式のデータ』です。

RC3の送信データフォーマットを説明する画像
RC3のデータフォーマット

※ 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を登録する手順を説明する画像1
Race Chrono DIYを登録する手順を説明する画像2

表示された一覧の中から「Race Chrono DIY」をタップします。通信方法として「Bluetooth RFCOMM」を選択します。

Race Chrono DIYを登録する手順を説明する画像3
Race Chrono DIYを登録する手順を説明する画像4

Bluetoothデバイスの一覧が表示されるので「範囲内のデバイスを検索する」をタップし、検索されたPico Wをタップします。

検索の際は、Pico WでBluetoothが使える(SerialBT.begin関数が実行された)状態である必要があります。以下の操作は後述のPicoWのプログラムを実行してから実施してください。

Race Chrono DIYを登録する手順を説明する画像5
Race Chrono DIYを登録する手順を説明する画像6

通信に追加データフォーマットとして「RC2/RC3」をタップします。選択後設定画面に戻ったらPico Wの登録は完了です。

Race Chrono DIYを登録する手順を説明する画像6
Race Chrono DIYを登録する手順を説明する画像7

プログラム概要

今回のプログラムの概要は以下の通りです。

  • Bluetooth(SPP)、シリアル通信を開始する。
  • メーターから、水温・油温を取得する。
  • 水温・油温をRace Chronoに送信する

実行方法

以下の順序で実行します。

  1. Pico Wのプログラムを実行
  2. メーター(Windows)のプログラムを実行
  3. Race Chronoで開始ボタンを押す

実行結果

後述するプログラムを実行すると、以下のようにRace Chronoでメーター(WindowsPC)のデータが表示されます。

Race Chronoの実行画面
RaceChronoの実行画面
Pico WとWindowPCの実行結果の画面
メーターとPicoWの実行画面

全体コード( 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の送信データフォーマットを説明する画像
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です。

ABOUT ME
えす
現役のソフトウェアエンジニアです。 C++ C# Python を使ってます。10年ちょい設計/開発部門にいましたが、今はQAエンジニアっぽいことをしています。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

Index