Raspberry Pi 4のGPIOの速度を測定する

概要

Raspberry Pi 4とマイコンを使って、GPIOでどのくらいの通信速度が出せるかを実験・測定しました。

使用機材・言語

データ受信側:Raspberry Pi 4 (Raspberry OS、C++(better Cとして))
データ送信側:Renesas RX231 RSK(C言語、システムクロック: 54MHz)

通信環境・シーケンス

通信シーケンスは以下としました。

マイコン側1. DATA書き込み
2. SYN: High書込み
4. SYNACK: High検出後、SYN: Low書込み
5. SYNACK: Low検出後、最初に戻り次のデータを書き込み
RasPi側2. SYN: High検出、DATA読み込み
3. DATA読み込み完了にて、SYNACK: High書込み
5. SYN: Low検出後、SYNACK: Low書込み、SYN: High待ち受けに戻る

マイコン後側ピンアサイン

SYN: PE2 – D10_RXD12 – J4.1 →OUTPUT、高駆動出力
SYNACK: PE1 – D9_TXD12 – J4.2 →INPUT
DATA: PE0 – D8_SCK12 – J4.3 →OUTPUT、高駆動出出力

Raspberry Pi 4側ピンアサイン

SYN: GPIO 17 (INPUT)
SYNACK: GPIO 27 (OUTPUT)
DATA: GPIO 22 (INPUT)

マイコン側ソース

CS+ CCを使い作成。システム設定やペリフェラルはスマート・コンフィグレータで設定。
プロジェクト名はGPIOBurstTest

UserConfig.h

#ifndef USER_CONFIG_H
#define USER_CONFIG_H

//////////////////////////////////////////////////////////////////////////
// ピンアサイン
//////////////////////////////////////////////////////////////////////////
// 入力LOW
#define UC_LOW       0
// 入力HIGH
#define UC_HIGH      1

// RSK LED
#define UC_LED0                                               PORT1.PODR.BIT.B7
#define UC_LED0_ON                                            UC_LOW
#define UC_LED0_OFF                                           UC_HIGH
#define UC_LED1                                               PORT5.PODR.BIT.B0
#define UC_LED1_ON                                            UC_LOW
#define UC_LED1_OFF                                           UC_HIGH
#define UC_LED2                                               PORT5.PODR.BIT.B1
#define UC_LED2_ON                                            UC_LOW
#define UC_LED2_OFF                                           UC_HIGH
#define UC_LED3                                               PORT5.PODR.BIT.B2
#define UC_LED3_ON                                            UC_LOW
#define UC_LED3_OFF                                           UC_HIGH

// Burst通信用

// SYN - PE2 - D10_RXD12 - J4.1
#define UC_SYN                                                PORTE.PODR.BIT.B2
#define UC_SYN_ON                                             UC_HIGH
#define UC_SYN_OFF                                            UC_LOW
// SYNACK - PE1 - D9_TXD12 - J4.2
#define UC_SYNACK                                             PORTE.PIDR.BIT.B1
#define UC_SYNACK_ON                                          UC_HIGH
#define UC_SYNACK_OFF                                         UC_LOW
// DATA - PE0 - D8_SCK12 - J4.3
#define UC_DATA                                               PORTE.PODR.BIT.B0
#define UC_DATA_ON                                            UC_HIGH
#define UC_DATA_OFF                                           UC_LOW


#endif
GPIOBurstTest.c

#ifdef __cplusplus
//#include <ios>                        // Remove the comment when you use ios
//_SINT ios_base::Init::init_cnt;       // Remove the comment when you use ios
#endif
// Use Common Fit
#include "platform.h"
#include "r_cg_macrodriver.h"

// Use Config
#include "UserConfig.h"


void main(void);
#ifdef __cplusplus
extern "C" {
void abort(void);
}
#endif

void main(void)
{
    UC_LED0 = UC_LED0_OFF;
    UC_LED1 = UC_LED1_OFF;
    UC_LED2 = UC_LED2_OFF;
    UC_LED3 = UC_LED3_OFF;

    UC_DATA = UC_DATA_ON;

    unsigned char data1 = 1;

    while(1)
    {
        // データ設定 SYN
        UC_DATA = data1 ^= 1;
        UC_SYN = UC_SYN_ON;
        UC_LED0 = UC_LED0_ON;
        // SYNACK待ち受け
        while (1)
        {
            if (UC_SYNACK == UC_SYNACK_ON)
            {
//                UC_LED1 = UC_LED1_ON;
                break;
            }
            nop();
        }
        // ACK処理
        UC_SYN = UC_SYN_OFF;
        // データ送信OK待ち受け
        while (1)
        {
            if (UC_SYNACK == UC_SYNACK_OFF)
            {
//                UC_LED2 = UC_LED2_ON;
                break;
            }
            nop();
        }
//        UC_LED0 = UC_LED0_OFF;
//        UC_LED1 = UC_LED2_OFF;
//        UC_LED2 = UC_LED2_OFF;
    }
  
    
   while(1)
    {
        nop();
    }
}

#ifdef __cplusplus
void abort(void)
{

}

Raspberry Pi 4側ソース

Visual Studio 2019を使用し、Windowsからリモートビルドで使用。
GPIOにはWiringPiを使用。
通信時間の測定として、1,000,000万回ごとに時間を計測してコンソールに出力する。Printfが反応しなかったので、std::coutを使用。
待ち受けにusleep(0)をいれてみたが、速度が異常に遅くなったので、コメントアウト。

#include <wiringPi.h>
#include <iostream>
#include <unistd.h>
#include <time.h>

#define SYN    17
#define SYNACK 27
#define DATA 22

int main(void)
{
    std::cout << "APP Start" << std::endl;

    //wiringPiSetupSys();
    wiringPiSetupGpio();

    pinMode(SYN, INPUT);
    pinMode(SYNACK, OUTPUT);
    pinMode(DATA, INPUT);

    digitalWrite(SYNACK, 0);


    // 通信して取得したデータ
    int data = 0;

    // 時間計測関連
    unsigned int sec;
    int nsec;
    double d_sec;
    struct timespec start_time, end_time;
    int timeCounter = 0;
    const int timeCounterConst = 1000000;
    while (true)
    {
        // 開始時間測定
        if (timeCounter == 0)
        {
            timespec_get(&start_time, TIME_UTC);
        }
        //std::cout << "Wait SYN" << std::endl;

        // SYNまち
        while (true)
        {
            if (digitalRead(SYN) == 1)
            {
                //std::cout << "SYN OK" << std::endl;
                break;
            }
            // NOP
        }

        // Data読み取り
        data = digitalRead(DATA);
        //std::cout << "SYN OK" << std::endl;
        // SYNACK
        digitalWrite(SYNACK, 1);
        //std::cout << "Wait ACK" << std::endl;

        // ACKまち
        while (true)
        {
            if (digitalRead(SYN) == 0)
            {
                //std::cout << "ACK OK" << std::endl;
                break;
            }
            // NOP
        }

        // SYNACK取り下げ
        digitalWrite(SYNACK, 0);

        timeCounter++;
        // 終了時間測定
        if (timeCounter >= timeCounterConst - 1)
        {
            // カウンタの初期化
            timeCounter = 0;

            timespec_get(&end_time, TIME_UTC);
            sec = end_time.tv_sec - start_time.tv_sec;
            nsec = end_time.tv_nsec - start_time.tv_nsec;
            d_sec = (double)sec
                + (double)nsec / (1000 * 1000 * 1000);
            std::cout << "time:" << d_sec << std::endl;

        }

    }
    return 0;
}

実行結果

1,000,000回通信の実行時間は、平均で概ね1.66秒。通信速度にして、602,409 bpsとなりました。
ちなみにRaspberry側の待ち受けにusleep(0)をいれると、平均134.8秒で7,418 bpsとなります。この場合はCPU使用率はほぼ0になります。
usleep(0)の待ち時間は額面どおりではない模様です。

このソースは1bitの通信ですが、Dataとして4bitのピン書込みをすると、2.41秒まで落ちます。この時の通信速度は、1,659,751 bpsです。
8bitで書込みすると、3.19秒で、この時の通信速度は、2,507,836 bpsとなります。
この時のデータ幅の増加はマイコンのみで、Raspberry Pi 4側は1bit読み込みです。さらに、データもbit反転しているだけですので、実際の速度はもう少し遅くなると思います。

補足

Raspberry Pi 4、RX231単体でのGPIOの最大速度をオシロスコープで測定してみました。
プログラムはどちらもHigh/Lowべた書きです。

Raspberry Pi 4: 約26 MHz

RX231: 約2.7 MHz

この測定結果から、Raspberry Pi 4のGPIOはCPUのクロック周波数の1.5 GHzに比べて多少遅いとはいえ、そこそこ十分な速度が出ています。
対して、RX231の方はクロック周波数の54 MHzに対して、GPIOを一回動作させるのに10クロックかかっていますので、マイコンのクロック周波数的には妥当な値です。
通信速度をGPIOの通信速度を速めるには、RX231よりもクロック周波数の高いMCUを使うのが良いように推測できます。


作成:松林雄一