目次
BananaPI-M1を使って、オーディオプレイヤーを製作する
製作のきっかけ
今となってはスペック不足となって、使わなくなったBananaPI M1。
せっかくなので、就寝時のオーディオプレイヤーとして、色々と機能を持たせて製作したい。
そもそも著者は、就寝時に音楽をかけながら眠りにつくので、少し特殊な使い方(要件)となるので、
備忘として残しておく
製作にあたり
今回は色々と弄ったり、PICを使って利便性を上げたりなどしていて、資料が多くなる事が予想されるので、
章立てで進めて行くことにする。
- BananaPI M1 (BPI) をオーディオプレイヤーに組み込む
- BPI(オーディオプレイヤーとして)の利便性UPと節電対策
- PC接続のスピーカーをBPIと共用して、1台のスピーカーで利用
1. BananaPI M1 (BPI) をオーディオプレイヤーにする
基本要件
- OSは、armbian Minimal (X11は入れない)
- OS領域(SDカード)で、/varはzram上に展開。shutdown時は廃棄。他の領域はROでマウント。
アップデート時は、RWに変更する。
構想/テスト
とりあえず、音が出る事を確認。(mp3 or wav ファイル)
2. BPI(オーディオプレイヤーとして)の利便性UPと節電対策
基本要件
- BPI電源ONで自動再生開始
- 再生開始から、XX分経過すると自動shutdown実行
- 音楽ファイルは指定されたディレクトリに入れて置き、曲順はランダム再生させる。
拡張要件(可能であれば)
ヘッドレス状態で
- 音量調整
- 曲スキップ
- ボタン押下で、shutdown実行
構想/テスト
- ユーザーはメンテナンスユーザー(user099)と音楽再生専用ユーザー(music)を作成。
OS起動後、musicユーザーで自動ログイン。ログインシェルにスクリプトを指定。- atコマンドで、xx分後にshutdown実行するようにスケジュール
- 再生ソフトmplayer (CUIモード)で再生。引数でランダム再生可能
再生時にキーボード操作(10キー部分)で、Pause/再生/音量調整/スキップ等がコントロール可能
- 音楽ファイル保存場所(USBメモリー)は、基本ROでマウントさせる。曲ファイル更新時は、RWに変更、Remountを行って書込む。
- shutdownは、GPIO Pinから、押しボタンスイッチ接続。RasberryPi(raspios)は、デフォで設定済みで、スイッチ付けるだけで良いらしいが、BananaPi M1 (armbian)では要調査
3. PC接続のスピーカーをBPIと共用して、1台のスピーカーで利用
PC用スピーカーとBPI用スピーカーの2台を置く事は、置き場所の問題もさることながら、
アンプ付きスピーカーである為、USB電源ではあるけど、配線周りもごちゃごちゃする。
また、節電対策としても(チューニングすれば可能かもしれないが)、PCやBPIのシャットダウン状態では、
USB電源は供給されたままであり、スピーカー電源が入りっぱなしだ。
PICやリレーを使って、スピーカー1台を共用する事にする。
今回の要件では、PCとBPIを同時に音を鳴らす必要は無い。
3.1 スピーカーへの電源供給コントロールと音声自動切替
基本要件
・ スピーカーの電源は、
- PC ON時は、PC のUSBからスピーカーへ供給
- BPI ON時は、BPIのUSBからスピーカーへ供給
- 2台同時 ON状態の場合は不定
- 2台ともOFF状態の場合はスピーカーへ供給しない
・ スピーカーへの音声出力は、
- PC ON時は、PC のAudio出力端子をスピーカー入力端子へ接続
- BPI ON時は、BPIのAudio出力端子をスピーカー入力端子へ接続
- 2台同時 ON状態の場合はPC優先
構想/テスト
PCのシャットダウン状態のUSB電源供給OFFは、BIOS設定で可能となったが、
BPI側の設定が不明 (そもそも設定可能かも不明)
BPI側のUSB電源供給ストップについては、後述する「3.2 BananaPIの電源供給コントロールの仕組み」で実装する。
ブレッドボード上のテストでは、BPI側の電源供給はストップ可能であると仮定して、単体テストを行う。
回路図
プログラム
/* * File: main.c * Author: kampari * PIC: 12F509 * MPLAB X IDE V6.20 * XC8 V2.46 * Created on 2024/09/15, 19:57 * * PCとBananaPiをスピーカー1台で利用する オーディオセレクタ * * 接続環境/仕様 * PC、BPIからそれぞれUSB(5V受電用)と音声出力(3.5φ) * スピーカーは、電源供給用USB(5V) と音声入力(3.5φ) * スピーカーの電源供給は、PC、BPIから供給し、PC、BPIの電源ONにて供給 * PC、BPIが2台同時ON状態時は、BPIからの音声を優先とし、 * スライドSWでPC優先に変更可能とする * PC、BPI両方の電源OFF時は、スピーカーの電源供給OFFとする * BPIはshutdownで電源OFFとなるが、未来RPI置き換え時(shutdownでhalt)を考慮しておく * * 配線仕様 * VDD VSS * PC 5V(IN) GP5 GP0 スピーカー電源供給flag (ON=1 OFF=0) * BPI 5V(IN) GP4 GP1 音声セレクタ(リレー電源) (BPI ON時 1 OFF時0) * GP3(MCR) GP2 PC優先flag (優先時=1 Default=0) */ #include <stdio.h> #include <stdlib.h> #include <xc.h> // PIC12F509 #pragma config OSC = IntRC // Oscillator Selection bits (internal RC oscillator) #pragma config WDT = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config CP = OFF // Code Protection bit (Code protection off) #pragma config MCLRE = ON // GP3/MCLR Pin Function Select bit (GP3/MCLR pinfunction is digital input, MCLR internally tied to VDD) #ifndef _XTAL_FREQ #define _XTAL_FREQ 4000000 #endif #define ON 1 #define OFF 0 void main(void) { // 12F509 Settiing OSCCAL = 0b01111110; // 4MHz TRISGPIO = 0b00111100; // GPIO Setting GP2-5:InPut GP0-1:OutPut CLRWDT(); OPTION = 0b11000111; // BIT0-2:TMR0 Prescaler 256 GP0 = OFF; GP1 = OFF; register unsigned char i = 0; register unsigned char j = 0; register unsigned char k = 0; while(1){ for( i = 0 ; i < 6; i++ ) { if ( ( GP4 == ON ) || ( GP5 == ON ) ){ j++; } if ( ( GP4 == OFF ) && ( GP5 == OFF ) ){ k++; } __delay_ms(500); } if ( j == 6 ){ if ( GP0 == OFF ) { GP0 = ON; } if ( ( GP4 == ON ) && ( GP2 == OFF ) && ( GP1 == OFF ) ){ GP1 = ON; } else if ( ( GP4 == ON ) && ( GP2 == ON ) && ( GP1 == ON ) ){ GP1 = OFF; } else if ( ( GP4 == OFF ) && ( GP1 == ON ) ){ GP1 = OFF; } } if ( ( k == 6) && ( GP0 == ON) ) { GP0 = OFF; GP1 = OFF; } j = 0; k = 0; } }
3.2 BananaPIの電源供給コントロールの仕組み
BPI shutdown時にUSB供給をカットする為には、BPIへの電源供給をカットする為に、PICとリレーを使い、電源コントロールを行う。
基本要件
- 外部スイッチとして電源ONボタンを用意する
- (自動)shutdown後は、電源供給をカットする
- 音楽再生開始後は、指定時間経過しないと自動シャットダウンとならない為、強制電源カットボタンを用意する
なお強制電源カットは、SDカードのデータ破損を招くため、別途、BPI側でshutdown実行ボタンを準備を検討する。
構想/テスト
BPIのUSB電源供給及び、GPIOピン(5V,3.3V,信号ピンなど)は、以下の3パターンである事が分かった。
A. BPI電源未接続状態
→ USB電源供給及び、GPIOピンはLow (当たり前)
B. BPI電源ON状態
→ USB電源供給は5V。 GPIOピン(5V,3.3V)は出力。他の信号PinのHigh/Lowは設定による。
C. BPI電源ON後にshutdown実施 (電源接続状態)
→ LEDランプが消灯するので、USBやGPIOピン電圧も供給停止となると思いきや、
上記Bの状態を維持。
著者はケースファンは未装着なのだが、装着しているとshutdown後も回りっぱなしとなるだろう。
上記の状態からGPIOピンの状態を調査。BPI電源ON状態でデフォルトLow状態。OSからコマンドでHighへ変更可能であること。
もしくは、OS起動状態でHigh状態。上記Cの状態でLow状態である事。
この条件に一致するのは、GPIO J12 Pin5番であり、Pin5のLow/Highにて判定する。
回路図
プログラム
/* * File: main.c * Author: kampari * PIC: 12F509 * MPLAB X IDE V6.20 * XC8 V2.46 * Created on 2024/09/15, 19:57 * * BananaPiの電源を外部リレーを使ってOFF/ONする * * 接続環境/仕様 * Switch押下されるまで待ち状態(何もしない) * GP5 Switch ONにすると、GP0はHighを出力 (リレー動作 BPIへ電源供給開始) * GP4 がHigh入力後に、一定時間 Low状態となった場合、GP0はLowを出力 (リレー復帰 BPIへ電源供給停止) * GP3 Switch ONにすると、MCLR (リレー復帰 BPIへ電源供給停止) * * 配線仕様 * VDD VSS * ON Switch GP5 GP0 BPI電源供給(リレー電源) (BPI ON時 1 OFF時0) * BPI halt状態監視 GP4 GP1 動作確認用LED * Reset Switch GP3 GP2 */ #include <stdio.h> #include <stdlib.h> #include <xc.h> // PIC12F509 #pragma config OSC = IntRC // Oscillator Selection bits (internal RC oscillator) #pragma config WDT = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config CP = OFF // Code Protection bit (Code protection off) #pragma config MCLRE = ON // GP3/MCLR Pin Function Select bit (GP3/MCLR pinfunction is digital input, MCLR internally tied to VDD) #ifndef _XTAL_FREQ #define _XTAL_FREQ 4000000 #endif #define ON 1 #define OFF 0 #define STEP 195 #define TMR0SET 256 - STEP // TMR0はカウントアップして、256でオーバーフローする。 // TMR0の初期値を 256 - 195 = 61に設定すれば、 // 61からカウントアップして、195回目に256 オーバーフローする。 static unsigned char CountTMR0 = 0; void Wait_50ms(void) { // TMR0のプリスケーラ最大256を設定しても、約63msでオーバーフローする。 // まず、基準となる1秒を作りたいので、50msでオーバーフローさせ、 // オーバーフローを20回カウントしたら、1秒(1000ms)とする。 TMR0 = TMR0SET; // Timer Clear while( TMR0 ); // Wait Counter Over Flow CountTMR0++; } void main(void) { // 12F509 Settiing OSCCAL = 0b01111110; // 12F509 IntRC = 4MHz動作 TRISGPIO = 0b00111000; // GPIO Setting GP3-5:InPut GP0-2:OutPut GPIO = 0b00000000; // GPIO ALL OFF; OPTION = 0b11000111; // BIT0-2:TMR0 Prescaler 256 // T0CS: Timer0 Clock Source Select bit 0: FOSC /4 // PICは、基本的に1命令サイクル(=4クロック)で実行する // ※ 1命令サイクル周期:使用クロックが4MHzなら(1/4000000) * 4 = 1 (μs) // TMR0の初期値 = 256 - 任意の割り込み発生間隔 (s) /( 1命令サイクル周期 (s) × プリスケーラ比 ) // 12F509 は、TMR0割り込みやオーバーフロー時のフラグ保存(レジスタ)は無いらしいので、 // TMR0の値を常にチェックして、プログラム上で条件処理を行う必要がある。 static unsigned char i = 0; static unsigned char j = 0; static unsigned char Count_GP4OFF = 0; while(1){ j = 0; for ( i = 1 ; i <= 5; i++ ) { if ( ( GP0 == OFF) && ( GP5 == ON ) ) { j++; } } if ( ( GP0 == OFF) && ( j == 5 ) ){ GP0 = ON; for ( i = 1; i <= 30; i++ ){ GP1 = ON; __delay_ms(500); GP1 = OFF; __delay_ms(500); } } if ( ( GP0 == ON) && ( GP4 == OFF )){ Wait_50ms(); if (CountTMR0 == 20 ){ // 50ms X 20 = 1秒間カウントしたら CountTMR0 = 0; Count_GP4OFF++; if ( Count_GP4OFF == 5 ){ // GP4が5秒間 OFFの場合、Haltと確定 Count_GP4OFF = 0; for ( i = 1; i <= 10; i++ ){ GP1 = ON; __delay_ms(500); GP1 = OFF; __delay_ms(500); } GP0 = OFF; for ( i = 1; i <= 10; i++ ){ GP1 = ON; __delay_ms(500); GP1 = OFF; __delay_ms(500); } } } } else if ( GP0 == OFF) { CountTMR0 = 0; Count_GP4OFF = 0; } } }