ラズピコ PIO UARTで送信してみる

  • 2023.03.17
  • PIO
ラズピコ PIO UARTで送信してみる

皆さん こんにちは。

今回は PIO を使って UART で送信してみます。
PIOについて詳しく知りたい方は PIO~解説編~ をご覧になってください。

UARTのフォーマット

UARTで送受信する電文のフォーマットがわかっていないとPIOで送信するプログラムを書くことができません。
まずはフォーマットを確認しておきましょう。

このフォーマットはマイコン端子で見た論理レベルになります。
当然、送受信どちらも同じです。

まずスタートビットが1ビットあります。このレベルは”low”です。
その後データビットが続きます。LSBから送り始めます。
データ長は7か8ビットが一般的ですが、今回はデータ長を8ビットに決めました。
その後パリティビットが1ビットあります。
パリティビットは”なし”にすることも可能です。
パリティビットをつける場合には偶数(even)か奇数(odd)のどちらかに決めます。
今回は偶数にしました。
最後にストップビットが1ビットです。

(今回はスタートビット+8ビット+偶数パリティ+ストップビット1の固定フォーマットで送信するコードを書きました)

ラズピコのマイコン RP2040 の UART ではデータ長8+パリティの構成をつくることができません。

データ長8+パリティの構成ができることを確認しました。失礼しました。

そこで今回はそれを実現してみることにします。

今回のフォーマットを図に示すと、以下の通りになります。

これは1バイトあたりの図になり、11ビットで構成されます。
nバイトを送信(または受信)する場合にはこれをn回繰り返すことになります。
左から右に向かって時が流れていきます。

パリティが偶数の場合、各ビットの1の数が偶数になるようにパリティを 0 または 1 にします。

例えば小文字の ‘P’ は2進数で 01110000b (0x70) ですから偶数パリティは 1 になります。
奇数の場合、パリティは 0 です。

通信速度が115200bpsの場合、スタートビット(1ビット)分の幅が1秒間に115200個存在することになります。
bpsは bit per second の略で、1秒間あたりのビット数です。

送信する場合、PIOアセンブラはC言語システム側から受け取ったデータをこのフォーマットに従って出力します。

プロジェクトの作成

以下の設定でプロジェクトを作成します。

Project Name: picoPioUartTx

Library Options の PIO interface をチェックする
Console Options の Console over UART のチェックをはずす
IDE Options の Create VSCode project をチェックする
Debugger: は PicoProbe を選択する

起動後、何かファイルが開いてたら File – Close Folder で閉じた後、File – Open Folder から picoPioUartTx のフォルダーを辿って選択します。

こちらの環境では C:\Users\m3925\Documents\Pico\pico-project-generator\picoPioUartTx です。

m3925の部分は皆さんのユーザー名に置き換えてください。

vscode関連の json ファイルは前回作成済のプロジェクトのものをドラッグ&ドロップでコピー&ペーストすると楽です。

コードの編集

CMakeLists.txtを編集する

PIOでプログラムする場合、アセンブラを書くファイルが必要になり、その拡張子は pio になります。

今回のプロジェクト名は picoPioUartTx ですから後で picoPioUartTx.pio というファイルを作成します。

C/C++ SDK の環境では コンパイルする際にこのファイルをヘッダーファイルとして取り込むことになります。

picoPioUartTx.c では冒頭に

#incldue "picoPioUartTx.pio.h"

と記述することで、アセンブラのファイルをCのヘッダーファイルとして取り込むことができます。

CMakeLists.txtでもこのファイルの存在を知らせてあげる必要があり、その部分は編集して追加します。

(この部分はコードジェネレーターが対応してくれないので不足分を追加する必要があります)

CMakeLists.txtの add_executable()の後あたりに以下の1行を追加します。

pico_generate_pio_header(picoPioUartTx ${CMAKE_CURRENT_LIST_DIR}/picoPioUartTx.pio)

picoPioUartTx.pioを編集する

以下のコードを書いて、picoPioUartTx.pio で保存し、picoPioUartTx.c と同じフォルダにコピーしておきます。

; 以降はアセンブラにコメント解説を入れておきました。
// 以降はCのコメント解説です。

.program picoPioUartTx
.side_set 1 opt ; サイドセットピンを指定します

; An 8n1 UART transmit program.
; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin.

; TX FIFO からOSRに取り込み、サイドセットピンを"high"にして、7クロック遅延します
    pull       side 1 [7]
; Xに8をセットし、サイドセットピンを"low"にして、7クロック遅延します(スタートビット)
    set x, 8   side 0 [7]
bitloop:
; OSRから1ビットpinsに出力します
    out pins, 1
; Xがゼロになるまでループします
    jmp x-- bitloop   [6]
; ゼロになったら pull side 1 [7] の命令に移動します

% c-sdk {
#include "hardware/clocks.h"

static inline void picoPioUartTx_program_init(PIO pio, uint sm, uint offset, uint pin_tx, uint baud) {
    // マスクしてピンを設定します
    pio_sm_set_pins_with_mask(pio, sm, 1u << pin_tx, 1u << pin_tx);

    // マスクしてピン方向を設定します
    pio_sm_set_pindirs_with_mask(pio, sm, 1u << pin_tx, 1u << pin_tx);

    // GPIO初期化します
    pio_gpio_init(pio, pin_tx);

    // デフォルト設定を得ます
    pio_sm_config c = picoPioUartTx_program_get_default_config(offset);

    // 右シフト、自動pullしない、スレッショルドを9に設定します
    // LSBファーストなので右シフトする
    // アセンブラで pull命令を書いているので自動pullは不要
    // データ8ビット+パリティ付きなのでスレッショルドは9
    sm_config_set_out_shift(&c, true, false, 9);

    // 出力に設定します(pin_tx = GPIO0)
    sm_config_set_out_pins(&c, pin_tx, 1);
    // サイドセットピンを設定します(pin_tx = GPIO0)
    // (出力ピンとサイドセットピンに同じものを指定することも可能)
    sm_config_set_sideset_pins(&c, pin_tx);

    // FIFOを送信専用にします
    sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);

    // 1クロックを通信速度の8倍に設定します
    // (クロック8個で1ビット分の幅になる仕様)
    float div = (float)clock_get_hz(clk_sys) / (8 * baud);
    sm_config_set_clkdiv(&c, div);

    // SMを初期化します
    pio_sm_init(pio, sm, offset, &c);
    // SMを有効にします
    pio_sm_set_enabled(pio, sm, true);
}

%}

前半にアセンブラのコードを書きます。

.side_set 1 opt と記述することでサイドセットピンを指定し、 "opt" の記述により命令する度にサイドセットピンの指定をしなくて済むことになります。
省略した時にはセットした状態を保持します。

% c-sdk { (1) %} の(1)の部分はC言語のヘッダーファイルとして出力されます。
初期化処理等をここに書いておきます。
ヘッダファイルになるので、関数の実態を記述する場合には static inline を指定します。

picoPioUartTx.cを編集する

#include "hardware/pio.h"
#include "pico/stdlib.h"
#include "picoPioUartTx.pio.h"

// パリティ付きで送信します
void picoPioUartTx_program_putc(PIO pio, uint sm, char c, bool even_parity) {
    uint32_t byte = (uint32_t)c;
    uint8_t parity = 0;
    for (int i = 0; i < 8; i++) {
        parity ^= byte & 0x1;
        byte >>= 1;
    }
    byte = (uint32_t)c;
    if (parity) {
        if (even_parity) {
            byte |= 0x100;  // 偶数になるようにパリティを付加します
        }
    } else {
        if (!even_parity) {
            byte |= 0x100;  // 奇数になるようにパリティを付加します
        }
    }
    pio_sm_put_blocking(pio, sm, (uint32_t)byte);  // TX FIFOへputします
}

void picoPioUartTx_program_puts(PIO pio, uint sm, const char *s) {
    while (*s) {
        picoPioUartTx_program_putc(pio, sm, *s++, true);  // trueで偶数パリティ
    }
}

int main() {
    const uint PIN_TX = 0;  // GPIO0
    const uint SERIAL_BAUD = 115200;

    // 使うPIOを指定します
    PIO pio = pio0;

    // 使うSMを指定します
    uint sm = 0;

    // 命令用のメモリーにアセンブラのプログラムを登録します
    // 第2引数には picoPioUartTx.pioファイルで書いたアセンブラコードの ".program
    // の後の名前 + _program に & をつけます"
    uint offset = pio_add_program(pio, &picoPioUartTx_program);

    // 初期化関数を呼びます。picoPioUartTx.pio に記述している関数です
    picoPioUartTx_program_init(pio, sm, offset, PIN_TX, SERIAL_BAUD);

    while (true) {
        // 文字列をUARTで送信します
        picoPioUartTx_program_puts(pio, sm, "pico\r\n");
        // 1秒待ちます
        sleep_ms(1000);
    }
}

コードの解説

コードに適時コメントを入れましたので参考になさってください。

最初に説明したフォーマットの図を眺めておくとコードも理解できると思います。

アセンブラの注意事項:

1データの幅が8クロックで設計されています。
あとはコツコツと命令を理解していけば、全体が理解できると思います。

PC側の準備

動作確認用として Tera Term を使います。

Tera Termをお持ちでない方は こちら からダウンロードしてお使いください。

インストール後、 Tera Term を起動し、シリアルポートでPicoprobeのCOMポートを選択します。

もしCOMポートが複数ある場合には、Picoprobe以外の通信ケーブルをはずしてデバイスマネージャーでCOMポート番号を確認しておきます。

起動後、設定メニューのシリアルポートから以下の設定を行います。
パリティは even に設定します。

スピート : 115200
データ : 8 bit
パリティ : even
ストップビット : 1
フロー制御 : none

動作させてみる

それでは F5キーを押してプログラムを動作させてみます。

1秒毎に Tera Term に "pico" と表示されれば成功です。

波形で確認してみた

思惑通りのフォーマットになっているか波形を観測してみました。
パリティの異なる先頭の2バイトに着目してみました。

以下のように色をつけてみました。

赤:スタートビット
緑:データビット(8)
青:パリティ
白:ストップビット

先頭の文字 'p' はバイナリーで 01110000 です。
LSBファーストなので順番を入れ替えると 00001110 となります。
波形もこのパターンと一致していることがわかります。
そしてこの値の場合偶数パリティは 1 です。
波形でも 1 が確認できました。

2番目の文字 'i' はバイナリーで 01101001 です。
LSBファーストなので順番を入れ替えると 10010110 となります。
波形もこのパターンと一致していることがわかります。
そしてこの値の場合偶数パリティは 0 です。
波形でも 0 が確認できました。

1バイトの時間は 11 * 1/115200 = 約 95μsec

で画像のΔtと一致しています。

いかがでしたか?
皆さんはうまくPIOでUARTの送信ができましたか?

GitHub に picoPioUartTx のコードをアップしましたのでご自由にお使いください。
(ただし当方では動作の保証は行っていません)

お疲れさまでした。

PIOカテゴリの最新記事