4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ELRS受信機をUSBドングルにする(VSCode + PlatformIO版)

Last updated at Posted at 2023-05-01

以前にArduino IDEで、ELRS USBドングルを作成する記事を書きましたが、

どうやらボードやライブラリーのバージョンアップのせいで、うまくコンパイルできなくなっている様なので、新たに作り直しました。

今回の開発環境は、[ VSCode + PlatformIO ] で行います。
PlatformIOならボードやライブラリのバージョンをプロジェクトごとに指定できるので、Arduino IDEの時のようにアップデートしたら動かなくトラブルを回避できると思ったからです。

用意するもの

① マイコンボード Seeed XIAO RP2040 ( スイッチサイエンス 979円)
② ELRS受信機 2.4GHz(何でもいいです)
③ 熱収縮チューブ
④ USB Type-C アダプタ(又はケーブル)

配線

下図のように配線します。

熱収縮チューブで絶縁します。

プログラム

VSCodeとPlatformIO のインストールは既に済んでいる前提で行きます。

新規プロジェクトを作成します。下図のように設定して下さい。

platformio.iniの中身を下記のように書き換えます。

platformio.ini
[env:seeed_xiao_rp2040]
platform = raspberrypi
board = seeed_xiao_rp2040
framework = arduino
monitor_speed = 420000
platform_packages =
    framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git
lib_deps = adafruit/Adafruit TinyUSB Library@^2.1.0
build_flags = -DUSE_TINYUSB

main.cppに下記をコピペして下さい。
右上にコピーアイコンがあるのでそれをクリックすれば、全コピーできます。

main.cpp
#include <Arduino.h>
#include "Adafruit_TinyUSB.h"

//#define DEBUG			// <---受信データをシリアルモニターに表示させたい時は、コメントアウトを外してください。

// USB HID report descriptor
// ゲームパッドデータの構造を指定します  (プロポ受信機用 (16bitデータ x 8ch) + (1bitデータ x 8ch))
#define TUD_HID_REPORT_DESC_GAMEPAD_9(...) \
  HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP     ) ,\
  HID_USAGE      ( HID_USAGE_DESKTOP_GAMEPAD  ) ,\
  HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\
  /* Report ID if any */\
  __VA_ARGS__ \
  HID_USAGE_PAGE   ( HID_USAGE_PAGE_DESKTOP   ) ,\
  HID_USAGE        ( HID_USAGE_DESKTOP_X      ) ,\
  HID_USAGE        ( HID_USAGE_DESKTOP_Y      ) ,\
  HID_USAGE        ( HID_USAGE_DESKTOP_Z      ) ,\
  HID_USAGE        ( HID_USAGE_DESKTOP_RX     ) ,\
  HID_USAGE        ( HID_USAGE_DESKTOP_RY     ) ,\
  HID_USAGE        ( HID_USAGE_DESKTOP_RZ     ) ,\
  HID_USAGE        ( HID_USAGE_DESKTOP_SLIDER ) ,\
  HID_USAGE        ( HID_USAGE_DESKTOP_DIAL   ) ,\
  HID_LOGICAL_MIN  ( 0                        ) ,\
  HID_LOGICAL_MAX_N ( 0x07ff,2                ) ,\
  HID_REPORT_COUNT ( 8                        ) ,\
  HID_REPORT_SIZE  ( 16                       ) ,\
  HID_INPUT        ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
  /* 8 bit Button Map */ \
  HID_USAGE_PAGE   ( HID_USAGE_PAGE_BUTTON    ) ,\
  HID_USAGE_MIN    ( 1                        ) ,\
  HID_USAGE_MAX    ( 8                        ) ,\
  HID_LOGICAL_MIN  ( 0                        ) ,\
  HID_LOGICAL_MAX  ( 1                        ) ,\
  HID_REPORT_COUNT ( 8                        ) ,\
  HID_REPORT_SIZE  ( 1                        ) ,\
  HID_INPUT        ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
  HID_COLLECTION_END\

  // CrossFire用
#define CRSF_BAUDRATE  420000
#define CRSF_MAX_PACKET_LEN 64
#define CRSF_NUM_CHANNELS 16

typedef enum
{
	CRSF_ADDRESS_BROADCAST          = 0x00,
	CRSF_ADDRESS_USB                = 0x10,
	CRSF_ADDRESS_TBS_CORE_PNP_PRO   = 0x80,
	CRSF_ADDRESS_RESERVED1          = 0x8A,
	CRSF_ADDRESS_CURRENT_SENSOR     = 0xC0,
	CRSF_ADDRESS_GPS                = 0xC2,
	CRSF_ADDRESS_TBS_BLACKBOX       = 0xC4,
	CRSF_ADDRESS_FLIGHT_CONTROLLER  = 0xC8,   // 受信データはこれで来る
	CRSF_ADDRESS_RESERVED2          = 0xCA,
	CRSF_ADDRESS_RACE_TAG           = 0xCC,
	CRSF_ADDRESS_RADIO_TRANSMITTER  = 0xEA,
	CRSF_ADDRESS_CRSF_RECEIVER      = 0xEC,
	CRSF_ADDRESS_CRSF_TRANSMITTER   = 0xEE,
} crsf_addr_e;

typedef enum
{
	CRSF_FRAMETYPE_GPS                = 0x02,
	CRSF_FRAMETYPE_BATTERY_SENSOR     = 0x08,
	CRSF_FRAMETYPE_LINK_STATISTICS    = 0x14,
	CRSF_FRAMETYPE_OPENTX_SYNC        = 0x10,
	CRSF_FRAMETYPE_RADIO_ID           = 0x3A,
	CRSF_FRAMETYPE_RC_CHANNELS_PACKED = 0x16,   // チャンネルパックフレーム
	CRSF_FRAMETYPE_ATTITUDE           = 0x1E,
	CRSF_FRAMETYPE_FLIGHT_MODE        = 0x21,
	// Extended Header Frames, range: 0x28 to 0x96
	CRSF_FRAMETYPE_DEVICE_PING        = 0x28,
	CRSF_FRAMETYPE_DEVICE_INFO        = 0x29,
	CRSF_FRAMETYPE_PARAMETER_SETTINGS_ENTRY = 0x2B,
	CRSF_FRAMETYPE_PARAMETER_READ     = 0x2C,
	CRSF_FRAMETYPE_PARAMETER_WRITE    = 0x2D,
	CRSF_FRAMETYPE_COMMAND            = 0x32,
	// MSP commands
	CRSF_FRAMETYPE_MSP_REQ            = 0x7A,   // response request using msp sequence as command
	CRSF_FRAMETYPE_MSP_RESP           = 0x7B,   // reply with 58 byte chunked binary
	CRSF_FRAMETYPE_MSP_WRITE          = 0x7C,   // write with 8 byte chunked binary (OpenTX outbound telemetry buffer limit)
} crsf_frame_type_e;

#pragma pack(push,1)	// データを1バイト単位に詰めて配置
//----------- #pragma ここから ------------------------
typedef struct{
    unsigned ch0 : 11;	// 11bit チャンネルデータ
    unsigned ch1 : 11;
    unsigned ch2 : 11;
    unsigned ch3 : 11;
    unsigned ch4 : 11;
    unsigned ch5 : 11;
    unsigned ch6 : 11;
    unsigned ch7 : 11;
    unsigned ch8 : 11;
  unsigned ch9 : 11;
    unsigned ch10 : 11;
    unsigned ch11 : 11;
    unsigned ch12 : 11;
    unsigned ch13 : 11;
    unsigned ch14 : 11;
    unsigned ch15 : 11;
} crsf_channels;

typedef union{				// チャンネルデータ共用体
	uint8_t byte[22];
	crsf_channels b11;
} crsf_data;

typedef struct{				// CRSFフレーム
	uint8_t device_addr;	// アドレス
	uint8_t frame_size;		// この後からのバイト数
	uint8_t type;			// タイプ
	crsf_data data;			// チャンネルデータ
	uint8_t crc;			// CRC
} crsf_frame;

typedef struct {			// ゲームパッドデータ
	uint16_t ch[8];			// 16bit 8ch
	uint8_t sw;				// 1bit  8ch
} gamepad_data;

//------------#pragma ここまで-------------------------
#pragma pack(pop)


uint8_t const desc_hid_report[] =
{
	TUD_HID_REPORT_DESC_GAMEPAD_9()				// USB GamePad のデータ構造を指定
};

Adafruit_USBD_HID usb_hid;						// USB HID object

uint8_t rxbuf[CRSF_MAX_PACKET_LEN + 3];	// 受信した生データ
uint8_t rxPos = 0;
uint8_t frameSize = 0;
bool datardyf = false;							// USBに送るデータが揃った。
uint32_t gaptime;								// フレーム区切り測定用
uint32_t time_m;								// インターバル時間(debug用)
static gamepad_data gp;							// CH毎に並び替えたデータ

// CRSFから受信した11bitシリアルデータを16bitデータにデコード
void crsfdecode () {
	crsf_frame *crsf =(crsf_frame *)rxbuf;

	if (crsf->device_addr == CRSF_ADDRESS_FLIGHT_CONTROLLER) {	// ヘッダチェック
		if (crsf->type == CRSF_FRAMETYPE_RC_CHANNELS_PACKED) {	// CHデータならデコード
			gp.sw = 0;
			gp.ch[0] = (uint16_t)crsf->data.b11.ch0;
			gp.ch[1] = (uint16_t)crsf->data.b11.ch1;
			gp.ch[2] = (uint16_t)crsf->data.b11.ch2;
			gp.ch[3] = (uint16_t)crsf->data.b11.ch3;
			if ( (uint16_t)crsf->data.b11.ch4 > 0x3ff )  gp.sw |= 0x01; // AUX1は2値データ
			gp.ch[4] = (uint16_t)crsf->data.b11.ch5;
			gp.ch[5] = (uint16_t)crsf->data.b11.ch6;
			gp.ch[6] = (uint16_t)crsf->data.b11.ch7;
			gp.ch[7] = (uint16_t)crsf->data.b11.ch8;
			if ( (uint16_t)crsf->data.b11.ch9 > 0x3ff )		gp.sw |= 0x02;
			if ( (uint16_t)crsf->data.b11.ch10 > 0x3ff )	gp.sw |= 0x04;
			if ( (uint16_t)crsf->data.b11.ch11 > 0x3ff )	gp.sw |= 0x08;
			if ( (uint16_t)crsf->data.b11.ch12 > 0x3ff )	gp.sw |= 0x10;
			if ( (uint16_t)crsf->data.b11.ch13 > 0x3ff )	gp.sw |= 0x20;
			if ( (uint16_t)crsf->data.b11.ch14 > 0x3ff )	gp.sw |= 0x40;
			if ( (uint16_t)crsf->data.b11.ch15 > 0x3ff )	gp.sw |= 0x80;
			datardyf = true; // データ揃ったよフラグ
		}
	}
}	

// CRSF受信処理
void crsf(void) {
	uint8_t data;

	// CRSFから1バイト受信
	if (Serial1.available()) {	    // Serial1に受信データがあるなら
		data  = Serial1.read();		// 8ビットデータ読込
		gaptime = micros();
		if (rxPos == 1) {
			frameSize = data;		// 2byte目はフレームサイズ
		}
		if(rxPos < CRSF_MAX_PACKET_LEN){
		    rxbuf[rxPos++] = data;		// 受信データをバッファに格納
        }
		if (rxPos > 1 && rxPos == frameSize + 2) {
			crsfdecode();			// 1フレーム受信し終わったらデーコードする
			rxPos = 0;
		}
	}
	else {
		if (rxPos > 0 && micros() - gaptime > 300) { // 300us以上データが来なかったら区切りと判定
			rxPos = 0;
		}
	}
}

// UART通信処理( Firmware書き換え用 )
// ExpressLRS Configurator の Flashing Method は [UART]ではなく [BetaflightPassthough] にすること。
void uart(void) {
	uint32_t t;

	if (Serial.available()) { // PCからデータが来たら、強制的に書き換えモードだと判断
		t = millis();
		do {
			while (Serial.available()) {      // PCからデータが来たら
				Serial1.write(Serial.read()); // PCからのデータを受信機に送る
				t = millis();
			}
			while (Serial1.available()) {     // 受信機からデータが来たら
				Serial.write(Serial1.read()); // 受信機のデータをPCに送る
				t = millis();
			}
		} while (millis() - t < 2000);        // データが来なくなったら終了
	}
}

// シリアルモニターに受信データを表示する(デバック用)
void debug_out() {
	int i;
	Serial.print(rxbuf[0], HEX);  		// device addr
	Serial.print(" ");
	Serial.print(rxbuf[1]);			 	// data size +1
	Serial.print(" ");
	Serial.print(rxbuf[2], HEX);  		// type
	Serial.print(" ");
	for (i = 0; i < 8; i++) {
		Serial.print(gp.ch[i]);
		Serial.print(" ");
	}
	Serial.print(gp.sw, BIN);
	Serial.print(" ");
	Serial.print(micros() - time_m); 	// インターバル時間(us)を表示
	Serial.println("us");
	time_m = micros();
}

void setup(){
	// USB HID デバイス設定
	usb_hid.setPollInterval(1);						// 1ms ポーリング
	usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report));	// リポートの記述形式を指定
	usb_hid.begin();
	while ( !TinyUSBDevice.mounted() ) delay(1);	// wait until device mounted

	datardyf = false;
	gaptime = 0;
	rxPos = 0;
	Serial.begin(CRSF_BAUDRATE);              // PCシリアル通信用 (受信機の速度に合わせる)
	Serial1.begin(CRSF_BAUDRATE, SERIAL_8N1); // CRSF通信用 (420kbps, 8bitdata, nonParity, 1stopbit)
	time_m = micros();						  // インターバル測定用
}

void loop(){
//	if ( TinyUSBDevice.suspended() ){
//		TinyUSBDevice.remoteWakeup();
//	}

	crsf();					// CRSF受信処理
	uart();					// UART通信処理(Firmware書き換え用)

	if (datardyf) {         // データが揃ったらUSB送信
		if ( usb_hid.ready() ) {
			usb_hid.sendReport(0, &gp, sizeof(gp));
#ifdef DEBUG
			debug_out();    // デバッグ用 (シリアルモニターで数値を確認)
#endif
		}
		datardyf = false;   // データ揃ったよフラグをクリア
	}
}

これをXIAO RP2040に焼いて下さい。
ポートを指定して、下の[→]アイコンをクリックすれば焼けます。

ExpressLRS Configuratorでファームウェア書き換え

ExpressLRS Configuratorを起動して、受信機の種類とバージョンを設定し、
Flashing Method は [UART]ではなく [BetaflightPassthough] にします。
WIFIをオフにして、BINDING_PHRASEを送信機と同じにします。
ポートを確認し、[BUILD&FLASH]を押せば、書き換えられるはずです。
もしエラーが出る場合は、受信機を書き換えモードにする必要があるかもしれません。
(ちなみに私のHappyModel EP 2400 RXは、何もせずに書き込みできました。
ExpressLRS Configurator v1.5.9、firmware v3.2.1でした。)

バインド方法

ExpressLRS ConfiguratorでBINDING_PHRASEを指定した場合は、自動でバインドするので、以下の作業は必要ありません。

① USBドングルのUSBを3回抜き差しするとバインドモードに入ります。(LED2回点滅の繰り返しになります)
② プロポをONにしてELRS送信モジュールのバインドボタンを短く押すか、 
  EdgeTXのExpressLRS Luaスクリプトの[BIND]を押します。
③ 受信機のLEDが点灯に変わればバインド完了です。

キャリブレーションをします

Windows11で説明します。
画面下の[スタート]を右クリック
[設定]→左側の[Bluethoothとデバイス]→[デバイス]→ずっと下にスクロールして[その他のデバイスとプリンタの設定]→[XIAO RP2040]を右クリック
[ゲームコントローラの設定]→[プロパティ]→[設定]→[調整]
プロポのスティックを中央にセットしてから、あとは指示に従いグルグルすれば完成です。

デバッグ方法

シリアルモニターで受信データの確認をするには、
プログラムの最初の方にある #define DEBUG の //コメントアウトを外して書き込んでください。

main.cpp
#define DEBUG	// <---受信データをシリアルモニターに表示させたい時は、コメントアウトを外してください。

PlatformIO画面の下のコンセントアイコンを押せば、シリアルモニターが起動します。
CRSFフレームの受信間隔時間も確認できます。
 どんどんスクロールして確認しづらいときは、プロポの電源をオフれば表示が止まるので、それまでのデータを確認できます。
 あなたのプロポがELRSの速度について行ってない時は、同じデータが続けて送られてきます。
こちらに以前書きましたので参考までに。

以上です。

4
3
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?