Epsode 30 <ベアメタルシリーズ>Raspberry Pi PicoでSPI通信やってみた

ブログ

皆さんこんにちは、グレキチです。

3月末になりましたが、皆様いかがお過ごしでしょうか?
年度末で忙しくされている方が多いかもしれません。先月、暫定税率廃止のお陰でやっと下がった筈のガソリン価格が、どこぞやのクソッタレ国家が中東でまた新たな戦争をおっ始めたために、暫定税率廃止前よりも上がってしまうという事態になっており、怒りが込み上げくるのをひしひしと感じます😤

さて、気を取り直して今回は、またベアメタルシリーズの内容になります。最近手に入れたGY-91(9軸センサー+2センサー)というデバイスを、SPI通信で操作するというのをやってみたので、その紹介になります。

SPI通信とは?

SPI(Serial Peripheral Interface)は、データ伝送路規格の一つで、1980年代半ばにアメリカ企業のモトローラによって提唱されました。複数の装置が一つの伝送路を共有するバス型の接続方式で、一方向の通信に一本の信号線を用いる全二重シリアル通信方式を用いています。クロック信号で同期するので、非同期型のシリアル通信インターフェース(UARTやCANなど)より高速に通信できる(数100kHz〜数10MHz)のが特徴です。
一般的には、SPI通信でマスターとスレーブの間で適切に動作する最大距離は1mと謳われているようで、基本的に短距離通信を対象として、大容量のデータを送受信する用途に使われます。

通信を行うのにマスター(親機)とスレーブ(子機)の間で4本の信号線(SCLK、MOSI、 MISO、CS)を使います(※派生仕様で3線式もあるが、基本はあくまで4線式)。
各信号線の意味合いは以下の通りです。

 SCLK(シリアルクロック): 通信同期のためシリアル信号を伝送する
 MOSI(マスター アウト スレーブ イン): マスターからスレーブへデータ発信する
 MISO(マスター イン スレーブ アウト): マスターがスレーブからデータ受信する
 CS(チップセレクト): マスターが複数のスレーブから通信相手を決定する
  ※スレーブが単体の場合は利用不要、SS(スレーブセレクト)と呼ぶこともある

SPI通信概略図 (出典:NXP community Blog)

以上の情報をようやくすると、SPI通信はCS信号線があることによって、以前紹介したI2Cのように、複数デバイスを並列に接続して制御することが出来て、かつ高速なデータ転送が可能だが、つ通信が保証される最大距離が1m以下と短いということになります。

使用デバイス(GY-91)について

今回使用したGY-91は、正確には、MPU9520という9軸(加速度計、ジャイロスコープ(角速度計)、磁力計の各3軸 × 3 = 9軸)センサーと、BMP280(気圧と温度)センサーを基盤にマウントしたデバイスです。一般的な意見としては優れものの様で、これだけのセンサーを搭載していて、基盤のサイズはなんと切手くらいのサイズ!😵という驚異的な小ささです。

使用用途としては、ドローン(UAV)飛行制御、ロボット駆動制御、気象観測装置、屋内ナビゲーション装置などで利用実績があるようです。一口にGY-91と言っても、実は何が正規品なのかはっきりしていないようで、センサーメーカーが制作したものから町工場で作ったようなものまで溢れかえっているようでしたので、もし購入をお考えなら、扱っているショップでの情報をくまなく調べてから、極力信頼のある(ありそうな)製造元であることを確認の上で選択することを強くお勧めします。
今回私が購入したものは、結果的には、多分中華メーカーが無理やり作ったんだろうなーとついつい邪推してしまうようなデバイスで、説明書などは添付されていませんでした。そのため、構成部品の詳細情報を得るには現物をチェックする以外にありませんでしたが、使用マニュアルなどはウェブ上に多数転がっていたので、それらを掻き集めて段々と詳細を把握していきました。
Web上で“gy-91”で検索して引っかかった情報から、デバイスの諸元を簡単にまとめると下表のようになります。

デバイスモデル名GY-91
製造元不明(※中華OEMと推測)
使用チップMPU9250 + BMP280
機能3軸加速度計、3軸ジャイロ、3軸磁力計、
気圧計、温度計
通信方式I2C、SPI
データ分解能16 bit
動作電圧3 〜 5 V
動作温度– 40 〜 85 ℃
サイズ約 20 × 14 mm


GY-91の概要は以上です。それでは、各センサー毎に中身をもう少し詳しくみていきましょう。

MPU9250(9軸センサー)

私が購入した現物の表面には、 “MP92 623LB1”と刻印されており、これをネットで調べたところ、モーションセンサーを手掛けているアメリカのInvenSense社(現在はTDK傘下)の製品であるということがわかりました。刻印が偽造されていないとすれば、9軸センサーとしては信頼と実績のある製品のようでした😆
InvenSense社が提供しているマニュアルによると、『MPU9250は、2つのダイを1つのQFNパッケージに統合したマルチチップモジュール(MCM)です。一方のダイには3軸ジャイロスコープと3軸加速度計が搭載され、もう一方のダイには旭化成マイクロデバイス株式会社製のAK8963ー3軸磁力計が搭載されています。』と記載されており、9軸と言っても、ジャイロおよび加速度計で1つのチップ、磁力計はまた別のチップということで、内部でが2つのチップ構成になっているとのことでした。このチップ構成のために各々で制御方法が異なりプログラム内容に影響してくるので、覚えておきたいポイントです。
また、加速度、ジャイロ、磁力を計測できるこのようなセンサーは、一般的には慣性計測装置(IMU:Inertial Measurement Unit)と呼称されているようでした。

センサーの主要諸元は下表の通りです。

センサーモデル名MPU-9250
製造元加速度計及びジャイロ:InvenSense
磁力計:旭化成マイクロデバイス
3軸加速度計 測定範囲±2、±4、±8、±16 g
3軸角速度計(ジャイロ) 測定範囲±250、±500、±1000、±2000 dps(° /sec)
3軸磁力計 測定範囲±4800 μT
通信方式I2C、SPI
FIFOバッファサイズ512 バイト
I2Cクロック数(最大)400 kHz
SPI Read/Write クロック数(最大)1 MHz
SPI Read only クロック数(最大)20 MHz
動作電圧2.4 〜 3.6 V
消費電力約 3.7 mA  (全センサー稼働時)
BMP280(気圧、温度センサー)

次は、BMP280の詳細です。こちらも正規品がきっちり基盤にマウントされていればの話ではあるのですが、公式な主要諸元は下表の通りです。

センサーモデル名BMP280
製造元Bosch Sensortec
気圧計 測定範囲300 〜 1100 hPa
温度計 測定範囲– 40 〜 85 ℃
通信方式I2C、SPI
圧力分解能0.16 Pa
温度分解能0.01 ℃
動作電圧1.71 〜 3.6 V

回路図と配線図

回路図と配線図を作成するにあたり、時間の都合で該当デバイスのGY-91のデータ作成が出来なかったため、代替としてMPU9250を使用しています。そのため、ピン位置や外観が違っていますが、それについてはご容赦下さい🙇(後々時間を見つけて更新したいと思っています)

それでは、まずは回路図です。作成したものは下図の通りです。

計測結果は今回、UART通信を使ってPC上に表示させるため、以前紹介したPicoProbeを使っています。また、今回のメインで使っているマイコンはPicoWに変更してます。理由は、この実装トライをやっている最中に、これまで使っていたPicoが過電圧のために壊れてしまったためです😭 過電圧の原因は、PicoProbeの電源はPCからUSBで供給していて、メインマイコンはそのPicoProbeとVSYS同士で繋いで電源供給を確保していましたが、それが不味かったようです。調べたところ、基本的にはPico同士をVSYS経由で繋いでの電源供給はぜずに、各々PCからUSB経由で直接電源供給したほうが良いということでした。また一つ、今後の良い教訓になりました😅 ということなので、今回は、VSYS同士が結線されていないことが図から見て取れると思います。

それと、今回代替でMPU9250のデバイスになっていますが、ピンの種類は電源ピンとINTを除いてほぼ同じだったため活用させてもらいました。しかし、GY -91とは違うピンを使っているので、気を付けるポイントがあります。それは、MPU9250を純粋に使う場合、電源供給はVDDとVDDIO両方に行う必要があるので上の回路図でもそうしていますが、本来使用するGY-91デバイスの場合は、電源ピンにはVINと3V3の2つがあって、基本的には3V3の方しか結線しません。VINは5V電源でも使えるようにと設定されているピンで、内部に降圧レギュレータが搭載されている構造になっています。間違ってVINの方にも繋いでしまうと、二重に電源供給することになり、最悪先ほどお伝えしたばかりの電圧過多で壊れる原因になるため、気をつけて下さい☝️

そして最後に、計測結果はPC上に表示するなら何故LCDを繋いでいるのか?ですが、センサーできちんと計測できているか、簡易的に加速度の結果のみを即座に確認できるようにしかったからで、デバッグ用途のために使いました。そのLCDはI2C通信用のプルアップ抵抗を外付けしてます。(これは前回までの教訓の一つです😁)

それでは次に配線図です。

ラズパイPicoでのSPI通信は、SPI0とSPI1の2系統使えるのですが、上記の通り、各デバイス配置の関係で今回はSPI1(GPIO12〜15)を使うことにしてます。コード内容(使用レジスタ)に影響してくるので注意して下さい。

プログラム内容

ここでは、SPI通信の制御に関する内容について掻い摘んで説明します。具体的なプログラム内容の説明の前に、ラズパイPicoでのSPI通信に関する概要を少し説明しておきます。(以下括弧内は、RP2040データシートからの引用です)

ラズパイPicoでのSPI通信では、ARM社のPrimeCell同期シリアルポート(SSP)というインターフェースをベースにしたコントローラが2つ搭載されています。

各コントローラは以下の機能をサポートしています。
 ⚫︎マスターおよびスレーブ インターフェース
  ・Motorola社 SPI互換インターフェース
  ・Texas Instruments社 同期シリアルインターフェース
  ・National Semiconductor社 マイクロワイヤインターフェース
 ⚫︎8つのディープ Tx および Rx FIFO
 ⚫︎FIFOの処理またはエラー状態の通知のための割り込み生成機能
 ⚫︎DMAによる駆動が可能
 ⚫︎プログラム可能なクロック周波数
 ⚫︎プログラム可能なデータサイズ(4~16ビット)

SPI通信では、SPIタイミングの基準クロックとしてclk_periを使用し、バスクロックとしてclk_sysを使用します。
PrimeCell SSPは、周辺機器から受信したデータをシリアル-パラレル変換します。CPUは、AMBA APBインターフェースを介してデータ、制御、およびステータス情報にアクセスします。送受信パスは内部FIFOメモリでバッファリングされており、送信モードと受信モードの両方で最大8つの16ビット値を独立して格納できます。シリアルデータはSSPTXDで送信され、SSPRXDで受信されます。
PrimeCell SSPは、入力クロックSSPCLKからシリアル出力クロックSSPCLKOUTを生成するための、プログラム可能なビットレートクロック分周器とプリスケーラを備えています。ビットレートは、SSPCLKの周波数選択に応じて2MHzとそれ以上がサポートされ、最大ビットレートは周辺機器によって決定されます。 』

以上、概要の中で注目しておきたい情報を掻い摘んで記載しました。全体的に詳しく知りたい方は、RP2040データシートp.501以降を読んでみて下さい。

それでは次に、使用する主なレジスタについて説明しておきます。ラズパイPicoではSPI関連のレジスタは18個ありますが、今回の実装では以下の5つのみを使用しています。

 SSPCR0(コントロールレジスタ0): モード選択や送信データサイズの設定
 SSPCR1(コントロールレジスタ1): 同期シリアルポートの効果切替 など
 SSPDR(データレジスタ): 送受信データの格納、サイズ16bit未満は右寄せ要
 SSPSR(ステータスレジスタ): ステータス確認
 SSPCPSR(クロックプレスケールレジスタ): クロックプリスケール分周器の設定

たったこれだけ?と思うかもしれませんが、Pico側のコントロールで使用するのはこの5つだけということです。実際には、使用するデバイスに割り当てられている多くのレジスタをコントロールしないといけない構成になっています😅

それでは、いよいよ具体的なコード内容の説明に移ります。

まずば、データ送受信を扱う関数についてです。下記の通りspi_xferと命名しました。
(※ブレッドボードへのマウント位置の関係で、SPI1の方を使用しています)

static inline uint8_t spi_xfer(uint8_t data)
{
    // 送信処理
    while (!(GET32(SPI1_SSPSR_RW) & (1 << 1))); // TNF (送信バッファ空き待ち)
    PUT32(SPI1_SSPDR_RW, data); // 送信FIFOの書き込み
    
    // 受信待ち
    while (!(GET32(SPI1_SSPSR_RW) & (1 << 2))); // RNE (受信バッファ入り待ち)
    
    return (uint8_t)GET32(SPI1_SSPDR_RW); // 受信FIFOの読み込み
}

コード内容を見ると、ちょっと面白いと思いませんか?
送信データを書き込んだ直後に、受信データを読み込んでいます。これは何故かと言うと、SPI通信のデータ送受信の特徴として、データを送信すると同時にデータが返ってくるという特徴があります。これが全二重通信ならではの技、ですかね。送ると同時に答えてくれる。それではもしかして、SPIは未来を予測できるエスパーなんでしょうか?🤔(この答えは後々わかります)
そして、この“同時に”というところが、コード作成時に気を付けるべきポイントになってきます。何を言っているかというと、送ると同時に答えてくれるということは、答えてくれる内容は今回送った内容に対してではなく、その直前に送った内容に対して返答しているから成立しているだけのようでした。
それはそうですよね。出前注文したら、オーダーが料理人に伝わらないと、客に何を届けたらいいかわからないですからね☹️ と言うことで、残念ながらSPIはエスパーでは無かったようです😆
さらに言うと、その返ってくる内容はFIFOで順番にしか返って来ないので、途中のデータを拾いたいとかは出来ないわけです。これらのルールを把握した上で、コードを作成する必要があります☝️

このルールで特に気を付けないといけないのは、データを読み出したい時です。データ書き込みの際は、送信FIFOのサイズさえ気を付けていれば、書き込むだけでデータを受け取る必要が無いので、何が返ってこようがお構いなしでOKですが、読み出しの方はそうはいきません。確実なデータを受け取る必要があるので、以前の処理の残りカスなんかをつかまされたりしたら、エライこっちゃ!😱ですからね。
従って、必要のないゴミデータをうまく処理する清掃員的なものが必要になってきます。(町のゴミ清掃員の方々、いつもありがとうございます!)
そしてここでも、ちょっとしたコツが必要になってきます。SSPDR(データレジスタ)を直接リセットするということが出来ないので、溜まったゴミデータは一つ一つ読み出さないといけません。その処理が下記になります。

// ゴミを読み捨てる処理
while ((GET32(SPI1_SSPSR_RW) & (1 << 2))) // RX FIFO Not Empty を確認
{
    (void)GET32(SPI1_SSPDR_RW);  // ゴミを読んで捨てる
}

受信(RX)側のFIFOが空になるまで、データを読み出すという処理ですね。この処理は、読み取り時ばかりでなく、書き込み時に以前の書き込み処理が残っていて、何度も意図しない処理が実施されるというつまらない失敗(経験談です)を防ぐ意味でも、書き込み用の関数などにも適用しておくと安心です☝️
以上の内容を踏まえて、今回私は、以下の書き込み関数と読み出し関数を作成しました。

まずは、書き込み関数です。

// 1バイト書き込み用
static inline void write_spi_reg(uint32_t cs_bit, uint8_t reg, uint8_t val)
{
    PUT32(SIO_GPIO_OUT_SET, cs_bit); // CS High
    delay_us(10);

    // ゴミを読み捨てる
    while ((GET32(SPI1_SSPSR_RW) & (1 << 2))) // RX FIFO Not Empty を確認
    {
        (void)GET32(SPI1_SSPDR_RW);  // ゴミを読んで捨てる
    }

    PUT32(SIO_GPIO_OUT_CLR, cs_bit); // CS Low
    delay_us(2);
    
    spi_xfer(reg & 0x7F);  // 書き込み時は最上位bit 0
    spi_xfer(val);
    
    // SPIの物理的な転送が完全に終わるのを待つ
    while (GET32(SPI1_SSPSR_RW) & (1 << 4)); // BSY (Bit 4)
    PUT32(SIO_GPIO_OUT_SET, cs_bit); // CS High
}

書き込み関数では、データを2回連続して書き込む必要があります。それが、spi_xfer関数を2回使っている部分です。最初のspi_xfer関数で、入れている引数を0x7Fと論理積しているのは、右のコメントにも書いているように、書き込み指示の場合は最上位ビット(第7bit = 右から8ビット目)を“0”にしないといけないルールになっているからです。
それと、上記コード内容でこれまでに説明していない、CSピンをLow-High切替する処理があります。これは何かと言うと、SPI通信における所謂スタートボタンになります。SPIでは、CS(又はSS)ピンがHighの時に通信がストップしている状態で、これがLowになったら通信がスタートする仕組みになっています。複数のSPI通信デバイスを並列に接続している際も、どのCSピンがLowになったかで、マスターデバイスとどのデバイスが通信するかを決定するようにもなっています。そのため、通信が完了したら必ずHighに戻しておかないと、単一のデバイスだけがずーっと通信している状態を維持してしまうので要注意です。
加えて、コード内容をもう一度確認すると、最初にCSをHighにする処理が入っていますが、これは、万が一、前の処理が失敗していてCSがHighに戻っていない場合を危惧して追加しているものになります。デバイスの品質に絶対の信頼が持てる方は、潔くこの部分は省いてもいいかもしれません。(私は絶対省きませんが😁)
ちなみにこの処理は、読み出しの場合も同じです。

と言うことで、続いて読み出し関数です。

// 1バイト読み出し用
static inline uint8_t read_spi_reg(uint32_t cs_bit, uint8_t reg)
{
    uint8_t val = 0;
    PUT32(SIO_GPIO_OUT_SET, cs_bit); // CS High
    delay_us(10);

    // ゴミを読み捨てる
    while ((GET32(SPI1_SSPSR_RW) & (1 << 2))) // RX FIFO Not Empty を確認
    {
        (void)GET32(SPI1_SSPDR_RW);  // ゴミを読んで捨てる
    }

    PUT32(SIO_GPIO_OUT_CLR, cs_bit); // CS Low
    delay_us(2);
    
    spi_xfer(reg | 0x80); // Readフラグ(MSB 1)
    val = spi_xfer(0x00); // ダミー送信

    // SPIの物理的な転送が完全に終わるのを待つ
    while (GET32(SPI1_SSPSR_RW) & (1 << 4)); // BSY (Bit 4)
    PUT32(SIO_GPIO_OUT_SET, cs_bit); // CS High
    
    return val;
}

読み出し関数の方も概ね書き込み関数と同じような構成になっていますが、異なる部分が2箇所あります。一つ目は、最初のspi_xfer関数での引数の処理です。書き込み時とは違って、こちらでは0x80との論理和になっていますが、これは、読み出し処理の場合は、指示データの最上位ビットを“1”にしないといけないルールがあるからです。二つ目は、返り値があると言うことですね。データを読み出すということは、データを取得することが目的なので、当然と言えば当然ですね。

それでは最後に、SPIの初期化関数であるspi_initについてです。
以下のコード構成にしました。

static void spi_init(void)
{
    // SPI1(=17) IO_BANK0のリセット解除
    PUT32(RESETS_RESET_CLR, (1 << 17));
    while (!(GET32(RESETS_RESET_DONE_RW) & (1 << 17)));

    // GPIOピンの入出力設定
    PUT32(PADS_BANK0_GPIO12_SET, ((1 << 7) | (1 << 6) | (1 << 3))); // OD -> 1, IE -> 1
    
    // SPI1ピン設定 (GPIO 12:MISO, 14:SCK, 15:MOSI)
    // FUNCSEL=1 (=SPI)
    PUT32(IO_BANK0_GPIO12_CTRL_RW, (0x2 << 12 | 1));
    PUT32(IO_BANK0_GPIO14_CTRL_RW, 1);
    PUT32(IO_BANK0_GPIO15_CTRL_RW, 1);

    // GPIO13(NCS)はSIO(=5)として初期化(MPU9250用)
    PUT32(IO_BANK0_GPIO13_CTRL_RW, 5);
    PUT32(SIO_GPIO_OUT_SET, BIT_CS_MPU); // GPIO13の値をHighにセット
    PUT32(SIO_GPIO_OE_SET, BIT_CS_MPU);  // GPIO13の出力を有効化

    // SPI1一旦無効化
    PUT32(SPI1_SSPCR1_RW, 0x00); // SSE -> disable
    // ボーレート設定(1MHz程度)
    // Fclk=125MHz / (CPSR=124 * (1 + SCR=0)) ≒ 1MHz
    PUT32(SPI1_SSPCPSR_RW, 124); // 2-254の偶数でなければいけない
    PUT32(SPI1_SSPCR0_RW, 0x07); // (SCR=0,) SPH=0, SPO=0, FRF=00(Motorola mode), 8bit mode(0111)
    // SPI1有効化
    PUT32(SPI1_SSPCR1_RW, 0x02); // SSE -> enabled

    delay_ms(10); // 電源安定化のための待ち時間
}

この関数で説明しておきたい箇所は、最後の段のSPI関連レジスタを操作しているところですね。
ボーレートや通信モードなどを設定するには、コントロールレジスタ1のSSPCR1SSE(同期シリアルポート)を一旦無効化しないといけないです。無効化出来たら、続けてボーレート設定、モード設定、通信データサイズを設定します。
ボーレートの設定値は、コメントで記載している計算式を使って算出するので、狙った値を木ロックプリスケールレジスタのSSPCPSRに書き込みます。今回は、ベースクロック125MHzに対して、ボーレートを1MHzに設定したかったので、書き込み値は124となっています。ここで、この書き込みの数字は、2〜254間の偶数を設定しないといけない決まりです。
その後、通信モードと通信データサイズを設定します。モードは一般的に、モトローラ方式の内で、データ読み出しが安定しているモード0かモード3のいずれかを選択します。今回はモード0で設定しました。また、通信データサイズは扱いやすい8bitとしています。
一連の設定が終わったら、SSPCR1レジスタで再びSSEを有効化することで、設定値が反映されます。
以上、spi_init関数の説明でした。

これで、SPI通信での基本的なレジスタ操作のコード内容についての説明は終了です。
しかし、今回題材にしているGY-91からのデータ取得においては、これ以降のコードでGY-91特有のレジスタの操作が溢れるほどあって、これからがようやく本番では!?と言っても過言ではないのですが、全てをここで説明するのはとても骨が折れるので、すみませんがここでは割愛させて頂きます。もし詳しい内容を知りたい方は、こちらの私のGitHubページをご確認頂ければと思います。

実施結果

今回もブレッドボードに各部品をマウントして回路を製作しました。下図の通りですね。


今回のセンサー計測では、GY-91の加速度、ジャイロ、磁力、基盤温度の4要素の値を読み出しています。この4つ全てを一度に表示できるようなディスプレイデバイスは持っていなかったので、UART経由でminicomを使ってPC上に表示しています。(写真のLCDはデバッグ用です)

リアルタイムでの数値計測状況は、以下の動画の通りです。

読み出し要素はループコード内に設定しているので、リアルタイムでデータが変わっているのが分かるかと思います。
(ようやくこのセンサーを動かすことが出来たので、次はいよいよアレの実装に移れるかな😏)

まとめ

今回の記事ではGY-91自体の実装コードは説明していないので、コード全文を見られていない方はわからないと思いますが、前半のデバイス紹介のところで説明した、BMP280の実装内容はありません😢 これは何故かというと、たまたま購入したものがどうやら、はんだ付けの状態が良く無かったらしく、どうやってもBMP280を起動することが出来なかったからでしたー😵
初めて買ったデバイスでまさかハズレを引いてしまうとは、逆の意味で私もなかなか引きが強いなーと思った次第です🤣
とは言うものの、アタイこのままでは終われない!っと思ったので、これを書いている最中に同じものをもう一つ発注しておきました。次こそは、全てのセンサーがちゃんと動作するものが手に入ることを強く祈るばかりです🙏

今回のSPI通信で、ラズパイPicoで実装できる一連の通信方法に関するものは終了になります。SPI通信の実装にあたって思ったことは、簡単そうで、ちょっとややこしかったですかね。でも、“簡単そうで”と感じたのは、先にI2C通信に取り組んでいた影響が大きいかもしれません。プルアップ抵抗に散々やられましたからね😆
あと、通信距離がもっと長く出来たら良いのになーと強く思いました。最大1mってアンタ!もうちょっと頑張ろうよー。

これまで幾つかの通信方法のベアメタル実装に取り組んできましたが、どの方法にも一長一短があり、デバイスの癖が出やすかったり無かったりと様々あるなということが、非常に良く理解出来てとても良かったと感じています。
これでやっと私も、組み込み開発者としてのスタートラインに立つことが出来たでしょうかね?🙂

主だった通信方法を紹介する内容が終わってしまったので、次回から何をブログネタとして持ってくるか悩むところですが・・・🧐

それではまた、次回まで!🖐️