理系的な戯れ

理工学系とくにロボットやドローンに関する計算・プログラミング等の話題を扱って、そのようなことに興味がある人たちのお役に立てればと思っております。

Raspberry Pi PicoのUARTでラジコン受信機の信号を読む

PicoのUARTでラジコン受信機の信号を読む

はじめに

Raspberry Pi Pico(以下Pico)でマルチコプタのドローンを制御しようと考えています。 そこでFutabaさんのS.BUS対応のラジコンの受信機をPicoに接続して、情報を読み取ってみたいと思います。

S.BUS信号はシリアル通信の規格なので、UARTで読み取ります。 接続するときはNOT回路を介して反転する必要があるので注意してください。 S.BUSについては以前に記事にしているのでそちらも読んでみてください。

blog.rikei-tawamure.com

UARTの設定

基本的にはPicoのプログラムはpico-sdkを使用して行います。UARTについて、どのような関数を使って やりたいことができるのか確認すれば良いことになります。

github.com

また、以下の内容は全面的にpico-examplesのサンプルプログラムの uart/uart_adabancedを元にしております。

github.com

UARTの初期化

UARTの初期化は以下のように行います。

uart_init(UART_ID, 2400);
  • UART_ID はuart0 又はuart1、PicoのUARTは二つなのでどちらかに適宜変更
  • 2400は通信速度、適宜変更

主な通信設定項目

UARTを使えるようにするには、通信設定を送受信する相手との間で合わせる必要があります。 主な設定項目は以下のようなものがあります。

  • GPIOとその使用ピンの設定
  • 通信速度
  • データ長
  • ストップビット長
  • パリティチェックの設定
  • フロー制御
  • FIFOの許可不許可
  • 割り込みの設定

以下のコードでそれぞれを行います。

GPIOとその使用ピンの設定
gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);

ここで、UART_TX_PINやUART_RX_PINは#defineでマクロ定義されていますが、直接数値で指定しても構いません。

  • TXに使用するピンはuart0は0番、uart1は3番
  • RXに使用するピンはuart0は1番、uart1は4番
通信速度
int actual = uart_set_baudrate(UART_ID, BAUD_RATE);

BAUD_RATEに任意の通信速度を設定します。

S.BUSだと100000です。

actualには通信速度が戻される様です。

フロー制御
uart_set_hw_flow(UART_ID, false, false);

TX,DX信号しか使わない(CTS/RTS使わない)のでハードウェアフロー制御しないので、上記の様に引数はfales,falseで大丈夫。

一応、cts、rtsの順に使うか使わないかをtrue,falseで設定。

データ長・ストップビット長・パリティチェックの設定
uart_set_format(UART_ID, DATA_BITS, STOP_BITS, PARITY);

それぞれ

  • DATA_BITS:データビット、今回は8
  • STOP_BITS:ストップビット1か2、今回は2
  • PARITY:パリティチェックが偶数( UART_PARITY_EVEN)奇数(UART_PARITY_ODD)・無(UART_PARITY_NONE)

な感じに適宜設定する。

FIFOの許可不許可
uart_set_fifo_enabled(UART_ID, false);

falseになってるのはFIFOバッファを使わないという意味。

確認不足で申し訳ないですが、バッファを使わないと、受信なら 1文字受信するたびに割り込みがかかるということだと思います。

通信速度が早いと、受信割り込み処理を素早く終わらせないと、 全体のパフォーマンスに多大な影響を与えます。

割り込みの設定

今回は、受信データが発生すると割り込みをかけて、受信データ処理をします。

受信データを処理する関数のことをハンドラ関数と言ったりして、そう言った関数を あらかじめ用意する必要があります。

サンプルのソースをコメントを含めてそのまま引用します。

// Set up a RX interrupt
// We need to set up the handler first
// Select correct interrupt for the UART we are using
int UART_IRQ = UART_ID == uart0 ? UART0_IRQ : UART1_IRQ;

UART_IRQは割り込みを発生するUARTがどちらかなのかを設定します。 私なんかは全く使わない三項演算子が使われています。

// And set up and enable the interrupt handlers
irq_set_exclusive_handler(UART_IRQ, on_uart_rx);
irq_set_enabled(UART_IRQ, true);

ハンドラ関数にon_uart_rxを使うよと書かれています。 UARTとしては割り込み使いますよと言うことですね。

// Now enable the UART to send interrupts - RX only
uart_set_irq_enables(UART_ID, true, false);

最後にシステムとしてUARTの割り込みを許可して、割り込みがかかる様になります。

標準出力をUSBに設定してシリアルコンソールにprintfの結果を表示させる

微妙にこのタイトルの表現あっているのか悩むところです。

サンプルプログラムのhallo_worldに書かれていて ”Getthing Started with Pico”のドキュメントにも書かれているのですが UARTピンとPCを接続しなくても、USB端子を使ってPCと接続することで、 シリアル通信アプリをPCで立ち上げると プログラムでprintfでの表示が確認できます。

UARTを使ってもprintfの標準出力をUARTにして同じことはできますが、 USBシリアル変換のためのデバイスとかケーブルを用意しないとならないのがほとんどで、 結局はUSB使います。例外はホストにRaspberry Piを使う場合です。この時はUARTがPi側にも存在しているので、直接接続可能です。

これは便利です。原始的なprintfデバグで十分ならば、これだけで生産性100倍アップです。

以上を実現するには、プログラムのソースがある同じディレクトリにあるCmakeLists.txtの中身を以下の様にします。 (以下はサンプルのuart_advancedを目的に合わせて修正しています。)

add_executable(uart_advanced
        uart_advanced.c
        )

# Pull in our pico_stdlib which pulls in commonly used features
target_link_libraries(uart_advanced pico_stdlib hardware_uart)

# enable usb output, disable uart output
pico_enable_stdio_usb(uart_advanced 1)
pico_enable_stdio_uart(uart_advanced 0)

# create map/bin/hex file etc.
pico_add_extra_outputs(uart_advanced)

# add url via pico_set_program_url
example_auto_set_url(uart_advanced)

上記で"uart_advanced"はソースの名前です。

# enable usb output, disable uart output
pico_enable_stdio_usb(uart_advanced 1)
pico_enable_stdio_uart(uart_advanced 0)

の部分で、USB及びuartを標準出力(入力)として許可するかしないかを設定しています。 1は許可、0は不許可です。

サンプルはUSBを使用しUARTは使用しないと言う設定ですね。

ソースの中では

stdio_init_all();

の記述が必要です。

デフォルトで通信速度は115200のようです。

通信ソフトは、 WIndowsならTeraterm、Linuxならscreenでしょうか。 Picoのドキュメントにはminicomが進められていてMacの場合

インストール

brew install minicom

実行

 minicom -b 115200 -o -D /dev/ttyACM0

"/dev/ttyACM0"はUARTのパスを含めたファイル名なのですが、環境によって違います。

Picoを挿した場合と挿さない場合で

ls /dev

を実行して、増えたり減ったりしたファイルを特定して調べてみてください。

ラジコン受信機からの信号の読み取り

ということで今回は6チャンネル送信機からの情報を受信してそれぞれのチャンネルの情報に直してコンソールに送って表示することをします。

S.BUSの詳細は以前の投稿に譲りますが、各チャンネル11ビットで情報を構成しているので、もらった8ビット区切りのデータから 読み解いていくのに、ビットシフトを駆使します。

以下にサンプルプログラムを参考にした作ったプログラムを掲載します。

#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/uart.h"
#include "hardware/irq.h"

#define UART_ID uart0
#define BAUD_RATE 100000//115200
#define DATA_BITS 8
#define STOP_BITS 2//1
#define PARITY    UART_PARITY_EVEN

#define UART_TX_PIN 0
#define UART_RX_PIN 1

static int chars_rxed = 0;
static int data_num=0;
uint8_t sbus_data[25];

uint8_t ch;

// RX 受信割り込みハンドラ関数
void on_uart_rx() {
    short data;
    while (uart_is_readable(UART_ID)) {
        ch = uart_getc(UART_ID);
        if(ch==0x0f&&chars_rxed==0){
            sbus_data[chars_rxed]=ch;
            //printf("%02X ",ch);
            chars_rxed++;
        }
        else if(chars_rxed>0){
            sbus_data[chars_rxed]=ch;
            //printf("%02X ",ch);
            chars_rxed++;            
        }

        switch(chars_rxed){
            case 3:
                data=(sbus_data[1]|(sbus_data[2]<<8)&0x07ff);
                printf("%04d ",data);
                break;
            case 4:
                data=(sbus_data[3]<<5|sbus_data[2]>>3)&0x07ff;
                printf("%04d ",data);
                break;
            case 6:
                data=(sbus_data[3]>>6|sbus_data[4]<<2|sbus_data[5]<<10)&0x07ff;
                printf("%04d ",data);
                break;
            case 7:
                data=(sbus_data[6]<<7|sbus_data[5]>>1)&0x07ff;
                printf("%04d ",data);
                break;
            case 8:
                data=(sbus_data[7]<<4|sbus_data[6]>>4)&0x07ff;
                printf("%04d ",data);
                break;
            case 10:
                data=(sbus_data[7]>>7|sbus_data[8]<<1|sbus_data[9]<<9)&0x07ff;
                printf("%04d ",data);
                break;
                
        }

        if(chars_rxed==25){
            printf("\n");
            chars_rxed=0;
        }
        
    }
}

int main() {
    stdio_init_all();
    uart_init(UART_ID, 2400);

    gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
    gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);

    int actual = uart_set_baudrate(UART_ID, BAUD_RATE);

    uart_set_hw_flow(UART_ID, false, false);

    uart_set_format(UART_ID, DATA_BITS, STOP_BITS, PARITY);

    uart_set_fifo_enabled(UART_ID, false);

    int UART_IRQ = UART_ID == uart0 ? UART0_IRQ : UART1_IRQ;

    irq_set_exclusive_handler(UART_IRQ, on_uart_rx);
    irq_set_enabled(UART_IRQ, true);

    uart_set_irq_enables(UART_ID, true, false);

    while (1){
        tight_loop_contents();
    }
}

終わりに

ということでUARTを設定してラジコン受信機の情報を受け取ることができました。

受信したデータは割り込み関数を設定して、そちらで処理しています。

プロポの信号を受信して読み解いているところの動画です。


Raspberry PI PicoのUARTでラジコン受信機の信号を読む

では、また!