yk-hiro’s handicrafts / ひろ’s ジャンク箱

電子回路やマイコンプログラムについて チラシの裏に書いていく感じのブログ

ラズパイPico MIDI I/F テストプログラムのソースコード

こんばんは!!

今日も暑い日でした

それでは本題です

今回の目的

MIDI I/F のテスト用にコーディングしたプログラムについて ソースコードと関連ファイルを示す

テストプログラムの動作

  • MIDI I/Fは UART1 を使用する
  • デバッグ用に標準入出力 1 を用いて キャラクターコンソール 2 で画面表示・キー入力する

キャラクターコンソールの表示

MIDIデータ表示行 先頭の8桁の数字は RP2040の64bitフリーランカウンター 3 の下位32bitを読み出して 100us の桁に切り上げて表示している

時間表示は常時更新して プログラムが継続動作しているか確認する

上は 音名C(ノート番号 0x30) D(0x32) E(0x34) F(0x35) G(0x37) のノートオン・ノートオフを受信して表示している例である

疑似的なMIDIデータの送信は 音名を割り付けたキーボードのキー(z x d c f v b h n j m , )を押すとノートオンが送出される

スペースキー を押すと 最後の音のノートオフが送出される

プロジェクトフォルダの作り方は別の記事を参照

yk-hiro.hatenablog.com

次項以降にプログラムのソースコードと関連ファイルを示す 4

ソースコード uart_midi_test.c に 以下の関数を記述した

main()関数

  • 標準入出力の初期化
  • 受信MIDIデータをキャラクターコンソール に表示
  • Pico内部カウンターの時間も併せて表示

UART_Init()関数

  • UART、GPIO の初期化

_debug_midi_tx()関数

  • 疑似的なMIDIデータを送信
  • キャラクターコンソールのキー入力で 発音するノート番号を指示する

uart_midi.h

/**
 * UART MIDI I/F test programe  (10.Jul.2024) 
 * Copyrught (c) 2024 id:yk-hiro(on Hatena blog) 
 * 
 * SPDX-License-Identifier: BSD-3-Clause
 */

#define MIDI_UART_ID uart1
#define MIDI_BAUD_RATE 31250
#define MIDI_UART_TX_PIN 4
#define MIDI_UART_RX_PIN 5

void UART_Init(void);
void _debug_midi_tx( void );

uart_midi_test.c

/**
 * UART MIDI I/F test programe  (10.Jul.2024) 
 * Copyrught (c) 2024 id:yk-hiro(on Hatena) 
 * 
 * portion of this program by Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

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

#include "uart_midi.h"

#define mPrint_time  printf( "%08d: " , time_us_32() / 100 )


int main(void) {

    stdio_init_all();
    // In a default system, printf will also output via the default UART
    sleep_ms( 2000 );   // Picoリセット後にターミナルコンソールのCOM再接続時間を待つため(ざっくり2秒を設定)

    printf( "\x1b[2J" );  printf( "\x1b[1;1H" );  printf( "\x1b[?25l" );   // 画面クリア,カーソルを1行目1桁目に移動,カーソルoff

    UART_Init();

    printf( "\nStart UART MIDI Test\n" );
    mPrint_time;

    while (true) {      // 無限ループ : 時間表示しながら受信データを待つ

        wchar_t  midi_message_byte;     // 受信したMIDIデータを記録する変数

        _debug_midi_tx();   // デバッグ用にダミーのMIDI送信を行う

        if ( uart_is_readable_within_us( MIDI_UART_ID , 0 ) ) {
            // 受信データがあるとき
            do {

                midi_message_byte = uart_getc(MIDI_UART_ID);    // 1byte 受信FIFOから読み出し
                //This function will block until the character has been read

                // uart_putc(MIDI_UART_ID ,midi_message_byte);  // ローカルループバックテスト時はコメントにしておく     
                // MIDIの送信FIFOに書き込み
                // 送信FIFOの空きチェックは割愛、受信と送信は同じbit rateなので...

                if ( (midi_message_byte & 0x80) == 0x80 ) {     // bit8 = 1 -> MIDI Status Byteの場合
                    printf( "\n" );
                    mPrint_time;
                }

                printf( "%02X " , midi_message_byte );  // CONSOLE(stdout)にMIDIデータを出力

            } while ( uart_is_readable_within_us( MIDI_UART_ID , 0 ) );     // 受信データがなくなるまでループする

            printf( "\n" );
        }  

        printf( "\r" );
        mPrint_time;  // 1ループごとに時間表示

    }

    uart_deinit(MIDI_UART_ID);      // 一回も通らないが忘れないように入れておく

}


void UART_Init(void) {
    // Raspberry Pi Pico C/C++ SDKマニュアル P.180 の 4.1.23. hardware_uart 参照
    uint baudrate;
    uint32_t *pad_cntl_reg_adrs = 0;

    // Set up our UART with the required speed.
    // Set the TX and RX pins by using the function select on the GPIO

    baudrate = uart_init(MIDI_UART_ID, MIDI_BAUD_RATE);
    uart_set_format( MIDI_UART_ID , 8 , 1 , UART_PARITY_NONE );

    gpio_set_function(MIDI_UART_TX_PIN, GPIO_FUNC_UART);
    gpio_set_function(MIDI_UART_RX_PIN, GPIO_FUNC_UART);
    // 受信入力ピンのプルアップ・ダウンは off にする(外部回路で対応している)
    gpio_set_pulls( MIDI_UART_RX_PIN , false , false );

    printf( "MIDI baudrate : %d\n",baudrate);

    // 出力バッファの電流制限値を変更する
    printf( "MIDI TX Pin(%02d) Pad Control register address %08x\n" , 
        MIDI_UART_TX_PIN , 
        pad_cntl_reg_adrs = (uint32_t *) (PADS_BANK0_BASE + 0x04 + MIDI_UART_TX_PIN * 0x04 ) );
    printf( " Current Pad control register value : %08x \n" , *pad_cntl_reg_adrs );
    *pad_cntl_reg_adrs = 0x00000064;
    printf( " New pad control register value     : %08x \n" , *pad_cntl_reg_adrs );

    //RP2040 Datasheet  2.19.6.3. Pad Control - User Bank  (p.299)
    //
    //The User Bank Pad Control registers start at a base address of 0x4001c000 (defined as PADS_BANK0_BASE in SDK).
    // PADS_BANK0: GPIO0, GPIO1, …, GPIO28, GPIO29 Registers
    // Offsets:    0x04,  0x08, …,  0x74,   0x78
    //
    //[Pad control register]
    // |Bits| Name     | Description                                                            |Type|Reset|
    // |----|----------|------------------------------------------------------------------------|----|-----| 
    // |31:8|(Reserved)|    -                                                                   | -  |  -  |
    // |7   | OD       |Output disable.                                                         | RW | 0x0 |
    // |    |          | Has priority over output enable from peripherals                       |    |     |
    // |6   | IE       |Input enable                                                            | RW | 0x1 |
    // |5:4 | DRIVE    |Drive strength. 0x0(b00):2mA  0x1(b01):4mA  0x2(b10):8mA  0x3(b11):12mA | RW | 0x1 |
    // |3   | PUE      |Pull up enable                                                          | RW | 0x0 |
    // |2   | PDE      |Pull down enable                                                        | RW | 0x1 |
    // |1   | SCHMITT  |Enable schmitt trigger                                                  | RW | 0x1 |
    // |0   | SLEWFAST |Slew rate control. 1 = Fast, 0 = Slow                                   | RW | 0x0 |
    //
    // default 0x00 00 00 56
    // 0x56 = 0101 0110
    //        |||| ||||-Slew rate control [slow]
    //        |||| |||-Enable schmitt trigger [enable]
    //        |||| ||-Pull down enable [pull-down on]
    //        |||| |-Pull up enable [pull-up off]
    //        ||||-Drive strength [01:4mA]
    //        ||-Input enable [on]
    //        |-Output disable [off]
    //
    // 変更:1) Drive strength [01:4mA] -> [10:8mA] に変更する
    //       2) pull-down->off
    // 0x64 = 0110 0100
    //
}


void _debug_midi_tx( void ) {   // debugのために MIDIの送信FIFOにダミーデータを書き込み

    static char midi_note_prev = 255;    // static宣言で 送信したノート番号を保存できる様にする

    char midi_note = 255;
    char in_key = ' ';

    if ( PICO_ERROR_TIMEOUT == ( in_key = getchar_timeout_us( 0 ) ) ) {
        // キーイン タイムアウト(= キーインなし)
        return;
   }

    // キー入力あり → キー判定 ノート番号のセット
    switch ( in_key  ) {
        case 'z' :  // B
            midi_note = 47;      // note number
            break;
        case 'x' :  // C
            midi_note = 48;
            break;
        case 'd' :  // C#
            midi_note = 49;
            break;
        case 'c' :  // D  
            midi_note = 50;
            break;
        case 'f' :  // D#
            midi_note = 51;
            break;
        case 'v' :  // E
            midi_note = 52;
            break;
        case 'b' :  // F
            midi_note = 53;
            break;
        case 'h' :  // F#
            midi_note = 54;
            break;
        case 'n' :  // G
            midi_note = 55;
            break;
        case 'j' :  // G#
            midi_note = 56;
            break;
        case 'm' :  // A
            midi_note = 57;
            break;
        case 'k' :  // A#
            midi_note = 58;
            break;
        case ',' :  // B
            midi_note = 59;
            break;
        case '.' :  // C
            midi_note = 60;
            break;
        case ' ' :  // スペースキー入力で(前回のノート番号があれば)ノートオフ送信 -> returnする
            if ( midi_note_prev != 255 ) {
                uart_putc(MIDI_UART_ID , 0x80 );            // note off
                uart_putc(MIDI_UART_ID , midi_note_prev );  // note number
                uart_putc(MIDI_UART_ID , 90 );              // off velocity
            }
            return;

        default :   // 該当するキーがないときは なにも送信せずreturnする

            return;

    }   // end switch

    // 次のノート発音の前に 前回のノート番号(があれば)ノートオフする
    if ( midi_note_prev != 255 ) {
        uart_putc(MIDI_UART_ID , 0x80 );            // note off
        uart_putc(MIDI_UART_ID , midi_note_prev );  // note number
        uart_putc(MIDI_UART_ID , 90 );              // off velocity
    }

    // ノートオン送信
    uart_putc(MIDI_UART_ID , 0x90 );        // note on
    uart_putc(MIDI_UART_ID , midi_note );   // note number
    uart_putc(MIDI_UART_ID , 90 );          // on velocity

    midi_note_prev = midi_note;     //発音したノート番号を保存しておく

    return;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.13)

# initialize the SDK based on PICO_SDK_PATH
# note: this must happen before project()
include(pico_sdk_import.cmake)

project(uart-midi)

# initialize the Raspberry Pi Pico SDK
pico_sdk_init()

# rest of your project
add_executable(uart-midi
    uart_midi_test.c
)

# Add pico_stdlib library which aggregates commonly used features
target_link_libraries(uart-midi pico_stdlib)

# enable output = 1, disable output = 0
pico_enable_stdio_usb(uart-midi 1)
pico_enable_stdio_uart(uart-midi 1)

# create map/bin/hex/uf2 file in addition to ELF.
pico_add_extra_outputs(uart-midi)

次回はソースコードの説明

ソースコードおよび関連ファイルの内容を説明する


  1. Cの標準入出力ライブラリを使用
  2. MIDI I/F テスト時には Tera Term を使用した↩
  3. RP2040 Datasheet (p.537) 4.6. Timer
  4. プロジェクトフォルダ作成後に作成・修正したファイルは uart_midi_test.c , uart_midi.h , CMakeLists.txt の3つ