目次
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;
}
}
}


