2012年12月24日月曜日

オルゴール&イルミネーションで飾るクリスマスリース

この時期にふさわしいテーマとして、「クリスマスリース」を作ります。
ここでは例によってAVRマイコンを使い、光と音で楽しめるものを考えます。

1 概要

クリスマスに玄関などに飾る「リース」に、光(イルミネーション)と音(オルゴール)で彩りを追加します。
オルゴール音は、SDカードに記録したWAVデータを再生することにより実現します。

「SDカードに記録したWAVデータを再生する」のは難易度が高いな… と思っていたら、CQ出版社発行「エレキジャックBASIC No.1」の記事「マイコン一つだけで作る簡易WAVEプレーヤ」(以下「簡易WAVEプレーヤ」)に全く目的通りの記事があったので、本記事を参考にしつつ製作を進めることとします。

2 各部の説明

2.1 リース

100円ショップで売っている、蔓などでできているリース台に、これも100円ショップで入手した造花を固定していきます。

造花は5・6種類ほど用意し、以下のように一節ずつ切って使います。




固定は、よく作例で見られる「黄銅線」では、私のような手芸初心者にはうまく行きませんでした。結局「タイラップ」でどんどん縛って行くのが簡単で便利です。ただし予想以上にやり直しが生じるので、多目に用意しておきます。

LEDは、バラ品を全て配線するのは大変なので、これもまた100円ショップで見つけた、以下のような10本並列になっているものを、赤・緑・黄3色を入手して利用しました。




今回は、電池ボックスは使わないので、切り取った後、3色分を揃えるようにまとめて、単にリースに巻きつけます。

2.2 回路

例によって、作画はフリーソフト「BSch3V」を使用しています。




「簡易WAVEプレーヤ」のように、マイクロSDカードアダプタに直接半田付けする自信がありませんでした(溶けそう・・・)ので、秋月電子の レギュレータ内蔵のSDカードアダプタ を使用しました。

この部品には、3.3Vの定電圧レギュレータが内蔵されています。折しも、手持ちに5VのACアダプタしか無かったので、当レギュレータを使ってSDカードとAVRに3.3Vを供給することにします。

ただし、LEDとスピーカまで供給するのは無理と思われるので、これらは5Vを直接供給します。

スピーカ駆動用のアンプは、 HT82V739というIC が、外付け部品が少なく(小さく)便利です。

LEDは、PWM駆動を考えて、Timer1/2のポートを使用します。
LEDを駆動するトランジスタは、上記定数では電流が150mA程度流れるので、余裕を見て、
Ic=800mAの2SC2120としました。

2.3 プログラム

今回も、製作のキーアイテムはプログラム部です。
先述の通り、「簡易WAVEプレーヤ」をベースにして実装しました。

2.3.1 FATライブラリ

「簡易WAVEプレーヤ」と同様に、「ぷちFatFS」ライブラリ を使用します。これにより、独自にFAT解析処理を検討することが不要となります。また、同プレーヤに倣って、バージョンは「R0.02」としました。

2.3.2 SDカード操作

2.3.2.1 SPI通信

「簡易WAVEプレーヤ」では、AVR内蔵のSPI通信モジュールを使用しています。
本件では、以下の背景から、ソフトウエア(ポートのon/off)によって実現しています。

・ISPと端子を共用している。このことが原因では無いとは思うが、以前書込みでトラブルになった経験がある。
・この時作成した、ソフトウエアISP通信のモジュールがあるので流用する。

※ただし、このことで新たな問題に遭遇してしまいました。解決策は 2.4.5項 を参照ください。

SPI通信のモードは、SDカードスロットのモジュールにおいて、各端子がプルアップされていることから、Mode3を使用しました。

・Mode3:クロックのディゼーブル時⇒H データのシフト⇒↓ データのラッチ⇒ ↑

(参考)
・Mode0:クロックのディゼーブル時⇒L データのシフト⇒↓ データのラッチ⇒ ↑

2.3.2.2 SDカード初期化

今回、手持ちにSDHC(Ver.2)カードが無く、デバッグが不能だったため、初期化はVer.1に関する手順のみにしました。Ver.1のみであれば、手順は以下の通り簡便となります。

・ダミークロックを送信
・「CMD0」を発行 ⇒ 「0x01」が返ったらOK
・「CMD1」を発行 ⇒ 「0x00」が返ったらOK
・念のため「CMD16」を発行し、データ長を512バイト固定にする。

※各CMDについては、「MMCの使いかた」 に詳しく説明されています。

この初期化関数を、「ぷちFatFS」ライブラリの用法に従って、disk_initialize()関数に追加します。

2.3.2.3 シングルブロックリード

「簡易WAVEプレーヤ」に準じます。
「CMD17」を発行し、 「0xfe」が返ってきたら、1ブロック(=512バイト)を読み込みます。
このうち、与えられたオフセット以降必要なバイト数だけ採用し、他は読み飛ばします。
この関数を、「ぷちFatFS」ライブラリの用法に従って、disk_readp()関数に追加します。

2.3.3 音声データ再生

「簡易WAVEプレーヤ」に準じます。

・音声データを、「モノラル / 32kHz / 8ビット」の形式でSDカードに保存しておく。
・WAV形式ファイルの仕様に従い、データの最初44バイトを読み飛ばす。

読み込んだデータは、2ページ用意したデータバッファのうち、割込み処理でリード要求のあったページに格納します。

DA変換は、AVRのPWM機能 を用いた簡易な1ビットDACで、再生可能なフォーマットはAVRの仕様より、32kHz・8ビットに一意に決まります。

サンプリング周波数(32kHz)で割込みを発生し、そこでデータバッファのPCM値をそのまま設定します。

このとき、当該ページのデータバッファの最後まで読み終わったら、ページを切り替えてデータリード要求を有効にします。

今回は4曲用意しましたので、"001.wav"~"004.wav" と決めうちでファイルを用意して、単に順に再生します。

2.3.4 LED駆動

Timer1・Timer2のPWM機能を用いて、LEDが序々に明滅(じわっと変化)するようにします。
前述の割込み処理にタイマ変数を用意して、経過時間を測定します。
経過時間ごとに、PWM値を増加/減少させることで明滅を実現します。

また、PWM機能をオフして、単にLEDが点滅するモードも用意しました。
この場合は、割込みタイマ変数を見て、経過時間ごとにポートをon/offするだけです。

何れも、設定後は関数をすぐ抜ける構造(擬似マルチタスク)にします。
前回のサイコロのように、経過時間をループ内で判断する手法は、音声データ処理の妨げとなるので、今回のようなケースでは使用できません。

2.4 プログラムリスト

2.4.1 main.c

※「ぷちFatFs」ライブラリに関する記述を、赤字で示します。

//////////////////////////////////////////////////////////////////////////////
//
// SDC MusicBox And Illumination "for Christmas"
//
// Programmed by (c)ota957
//
// Using "Petit FatFS" Library
//
// MPU =  ATmega328P
// lfuse = 0xe2(CKDIV8 disable)
// hfuse = 0xdf(default)
// efuse = 0x07(default)(avrdude format)
//
//////////////////////////////////////////////////////////////////////////////
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdio.h>
#include "sdc.h"
//#include "pff/diskio.h"
#include "pff/pff.h"

//////////////////////////////////////////////////////////////////////////////
// グローバル変数及び各定義
//////////////////////////////////////////////////////////////////////////////
#define BUF_SIZE 512

volatile uint8_t uBUF[2][BUF_SIZE];
volatile uint8_t uPAGE;
volatile uint16_t uREADSIZE[2];
volatile uint16_t uREADPTR;
volatile uint8_t uREADREQ;
volatile uint8_t uEOF;

volatile uint16_t uTIMER32US;

#define _SBI(p, q) ((p) |= _BV(q))
#define _CBI(p, q) ((p) &= ~_BV(q))

#define WAV_HEADER_SIZE 44

//////////////////////////////////////////////////////////////////////////////
// 各割込み処理
//////////////////////////////////////////////////////////////////////////////
// ---------------------------------------------------------------------------
// カウンタ/タイマ0オーバーフロー
// ---------------------------------------------------------------------------
ISR(TIMER0_OVF_vect)
{
    OCR0A = OCR0B = uBUF[uPAGE][uREADPTR++];

    uTIMER32US++;

    // バッファを最後まで読んだら、ページを切り替える
    if(uREADPTR == uREADSIZE[uPAGE]){
        uPAGE ^= 1;
        uREADPTR = 0;
        uREADREQ = 1;
        // ファイルを最後まで読んだら、PWM出力を停止する
        if(uREADSIZE[uPAGE] != BUF_SIZE){
            OCR0A = OCR0B = 128;
            _CBI(TIMSK0, TOIE0);
            uEOF = 1;
        }
    }
}

//////////////////////////////////////////////////////////////////////////////
// 各サブルーチン
//////////////////////////////////////////////////////////////////////////////
// ---------------------------------------------------------------------------
// LED表示部初期化(Aパターン用)
// ---------------------------------------------------------------------------
void LEDDispInitA(void)
{
    _SBI(DDRB, DDB1);       // PB1 outut
    _SBI(DDRB, DDB2);       // PB2 outut
    _SBI(DDRD, DDD3);       // PD3 outut

    TCCR1A &= ~_BV(COM1A1); // COM1A disable
    TCCR1A &= ~_BV(COM1B1); // COM1B disable
    TCCR2A &= ~_BV(COM2B1); // COM2B disable

    _SBI(PORTB, PB1);
    _SBI(PORTB, PB2);
    _SBI(PORTD, PD3);
}

// ---------------------------------------------------------------------------
// LED表示部初期化(Bパターン用)
// ---------------------------------------------------------------------------
void LEDDispInitB(void)
{
    _SBI(DDRB, DDB1);       // PB1 outut
    _SBI(DDRB, DDB2);       // PB2 outut
    _SBI(DDRD, DDD3);       // PD3 outut

    TCCR1A |= _BV(COM1A1);  // COM1A 非反転動作
    TCCR1A |= _BV(COM1B1);  // COM1B 非反転動作
    TCCR2A |= _BV(COM2B1);  // COM2B 非反転動作

    OCR1A = 0;
    OCR1B = 0;
    OCR2B = 0;
}

// ---------------------------------------------------------------------------
// LED表示部初期化 分岐
// ---------------------------------------------------------------------------
void LEDDispInit(uint8_t uPattern)
{
    if(uPattern == 1)
        LEDDispInitA();
    else if(uPattern == 2)
        LEDDispInitB();
    else if(uPattern == 3)
        LEDDispInitA();
    else
        LEDDispInitB();
}

// ---------------------------------------------------------------------------
// 表示パターンA
// ---------------------------------------------------------------------------
void LEDDispProcA(uint16_t uTime, uint8_t uCountA, uint8_t uCountB, uint8_t uCountC)
{
    static uint8_t uDispA = 0, uDispB = 0, uDispC = 0;

    // 32us * uTime * Ucountnの間隔で点滅する
    if(uTIMER32US > uTime){
        uTIMER32US = 0;

        if(++uDispA > uCountA){
            uDispA = 0;
            PORTB ^= _BV(PB1);
        }
        if(++uDispB > uCountB){
            uDispB = 0;
            PORTB ^= _BV(PB2);
        }
        if(++uDispC > uCountC){
            uDispC = 0;
            PORTD ^= _BV(PD3);
        }
    }
}

// ---------------------------------------------------------------------------
// 表示パターンB
// ---------------------------------------------------------------------------
void LEDDispProcB(uint16_t uTime, uint8_t uCountA, uint8_t uCountB, uint8_t uCountC)
{
    static uint8_t uDimmerA = 0, uIncDecA = 0;
    static uint8_t uDimmerB = 0, uIncDecB = 0;
    static uint8_t uDimmerC = 0, uIncDecC = 0;

    // 32us * uTime の間隔で明度を増減する
    if(uTIMER32US >= uTime){
        uTIMER32US = 0;

        if(uIncDecA == 0 && uDimmerA == 255) uIncDecA = 1;
        if(uIncDecA == 1 && uDimmerA == 0) uIncDecA = 0;
        if(uIncDecB == 0 && uDimmerB == 255) uIncDecB = 1;
        if(uIncDecB == 1 && uDimmerB == 0) uIncDecB = 0;
        if(uIncDecC == 0 && uDimmerC == 255) uIncDecC = 1;
        if(uIncDecC == 1 && uDimmerC == 0) uIncDecC = 0;

        if(uIncDecA == 0){
            if(uDimmerA > (255 - uCountA)) uDimmerA = 255;
            else uDimmerA += uCountA;
        }else{
            if(uDimmerA < uCountA) uDimmerA = 0;
            else uDimmerA -= uCountA;
        }
        OCR1A = uDimmerA;

        if(uIncDecB == 0){
            if(uDimmerB > (255 - uCountB)) uDimmerB = 255;
            else uDimmerB += uCountB;
        }else{
            if(uDimmerB < uCountB) uDimmerB = 0;
            else uDimmerB -= uCountB;
        }
        OCR1B = uDimmerB;

        if(uIncDecC == 0){
            if(uDimmerC > (255 - uCountC)) uDimmerC = 255;
            else uDimmerC += uCountC;
        }else{
            if(uDimmerC < uCountC) uDimmerC = 0;
            else uDimmerC -= uCountC;
        }
        OCR2B = uDimmerC;
    }
}

// ---------------------------------------------------------------------------
// 表示パターン 分岐
// ---------------------------------------------------------------------------
void LEDDispProc(uint8_t uPattern)
{
     if(uPattern == 1)
        LEDDispProcA(3000, 20, 19, 18);
    else if(uPattern == 2)
        LEDDispProcB(2500, 7, 8, 10);
    else if(uPattern == 3)
        LEDDispProcA(5000, 10, 11, 12);
    else
        LEDDispProcB(1500, 4, 5, 6);
}

//////////////////////////////////////////////////////////////////////////////
// メインループ
//////////////////////////////////////////////////////////////////////////////
int main(void)
{
//  USART_Init(38400);

    // SDC I/O 設定
    _SBI(DDRC, DDC0);   // SDI outut
    _CBI(DDRC, DDC1);   // SDO input
    _SBI(DDRC, DDC2);   // CLK output
    _SBI(DDRC, DDC3);   // CS output

    _SBI(PORTC, PC0);   // SDI→H(Disable)
    _SBI(PORTC, PC2);   // CLK→H(Disable)
    _SBI(PORTC, PC3);   // CS→H(Disable)

    // TIMER0(音声出力)設定
    _SBI(DDRD, DDD5);                       // PD5 outut
    _SBI(DDRD, DDD6);                       // PD6 outut
    TCCR0A |= _BV(COM0A1);                  // COM0A 非反転動作
    TCCR0A |= _BV(COM0B1) | _BV(COM0B0);    // COM0B 反転動作
    TCCR0A |= _BV(WGM01) | _BV(WGM00);      // 8bit HighSpeed PWM
    TCCR0B |= _BV(CS00);                    // ck0 = 1/1
    OCR0A = OCR0B = 128;

    // TIMER1(LED制御)設定
    TCCR1B |= _BV(WGM12);   // 8bit HighSpeed PWM
    TCCR1A |= _BV(WGM10);   // ↑
    TCCR1B |= _BV(CS12);    // ck1 = 1/256

    // TIMER2(LED制御)設定
    TCCR2A |= _BV(WGM21) | _BV(WGM20);  // 8bit HighSpeed PWM
    TCCR2B |= _BV(CS22) | _BV(CS21);    // ck0 = 1/256


    // 空き端子のプルアップ
    PORTB |= _BV(PB0) | _BV(PB3) | _BV(PB4) | _BV(PB5) | _BV(PB6) | _BV(PB7);
    PORTC |= _BV(PC4) | _BV(PC5);
    PORTD |= _BV(PD0) | _BV(PD1) | _BV(PD2) | _BV(PD4) | _BV(PD7);

    // 初期表示
    _SBI(DDRD, DDD3);
    _SBI(PORTD, PD3);
    _delay_ms(500);
    _CBI(PORTD, PD3);
    _delay_ms(500);
    _SBI(PORTD, PD3);
    _delay_ms(500);
    _CBI(PORTD, PD3);
    _delay_ms(500);


    FATFS fs;
    FRESULT fRes;

    while(1){    // 初回エラーになることがあり、リトライする

        fRes = pf_mount(&fs);
//      USART_TxChar(fRes, 'm');
        if(fRes != FR_OK){
            _SBI(PORTD, PD3);
            _delay_ms(500);
            _CBI(PORTD, PD3);
            _delay_ms(500);
        }else
            break;
    }

    sei();

    char cFileName[8+1+3+1];
    uint8_t uSeqNo = 1;

    while(1){

        sprintf(cFileName, "%03d.wav", uSeqNo);

        fRes = pf_open(cFileName);
//      USART_TxChar(fRes, 'p');
        if(fRes != FR_OK)
            return 0;

        //break;

        uPAGE = 0;
        fRes = pf_read((void*)uBUF[uPAGE], BUF_SIZE, (WORD*)&uREADSIZE[uPAGE]);

        uREADPTR = WAV_HEADER_SIZE;
        uREADREQ = 1;
        uEOF = 0;
        _SBI(TIMSK0, TOIE0);

        LEDDispInit(uSeqNo);

        while(1){

            if(uREADREQ){
                fRes = pf_read((void*)uBUF[uPAGE^1], BUF_SIZE, (WORD*)&uREADSIZE[uPAGE^1]);
                uREADREQ = 0;
            }

            if(uEOF) break;

            LEDDispProc(uSeqNo);
        }

        uSeqNo = (uSeqNo == 4) ? 1 : (uSeqNo + 1);
    }
}
// main.c ここまで ///////////////////////////////////////////////////////////////


2.4.2 sdc.h

//////////////////////////////////////////////////////////////////////////////
//
// Header File of sdc.c 
//
// Programmed by (c)ota957
//
//
//////////////////////////////////////////////////////////////////////////////
#ifndef _SDC_H

#include <avr/io.h>

void USART_Init(uint32_t uBaudRate);
void USART_TxChar(uint8_t uData, uint8_t uSpace);
uint8_t SDC_Init(void);
uint8_t SDC_Read(uint32_t uAddress, uint8_t uData[], uint16_t uOffset, uint16_t uBytes);

#define _SDC_H
#endif

// sdc.h ここまで ////////////////////////////////////////////////////////////////

2.4.3 sdc.c

//////////////////////////////////////////////////////////////////////////////
//
// SDカード通信モジュール 
//
// Programmed by (c)ota957
//
//
//////////////////////////////////////////////////////////////////////////////
//#define _DEBUG_COMMAND
//#define _DEBUG_READDATA

#include <avr/io.h>

//----------------------------------------------------------------------------
// ポート定義(ポートを変更したら必ず見直すこと!)
//----------------------------------------------------------------------------
#define SDC_PORT_OUT PORTC

#define SDC_PORT_IN PINC
#define SDI_PIN PC0
#define SDO_PIN PINC1
#define CK_PIN PC2
#define CS_PIN PC3

//----------------------------------------------------------------------------
// コマンド値定義
//----------------------------------------------------------------------------
#define CMD0 0x40
#define CMD1 0x41
#define CMD16 0x50
#define CMD17 0x51
#define CRC_CMD0 0x95
#define CRC_CMD1 0xf9
#define CRC_DUMMY 0x00
#define RES_IDLE 0x01
#define RES_NOERR 0x00
#define TOKEN_CMD17 0xfe

//----------------------------------------------------------------------------
// USART初期化(デバッグ用)
//----------------------------------------------------------------------------
void USART_Init(uint32_t uBaudRate)
{
    uint16_t uUBrr = F_CPU / (16 * uBaudRate) - 1;
    UBRR0 = uUBrr;

    UCSR0B |= (1 << TXEN0) | (1 << RXEN0);
}

//----------------------------------------------------------------------------
// USART 1バイト送信(デバッグ用)
//----------------------------------------------------------------------------
static void USART_Tx(uint8_t uData)
{
    while(bit_is_clear(UCSR0A, UDRE0));
    UDR0 = uData;
}

//----------------------------------------------------------------------------
// USART 2文字+区切り文字送信(デバッグ用)
//----------------------------------------------------------------------------
void USART_TxChar(uint8_t uData, uint8_t uSpace)
{
    uint8_t uDataH = uData >> 4;
    uint8_t uDataL = uData & 0x0f;

    uint8_t uCh = uDataH < 0x0a ? '0' + uDataH : 'W' + uDataH;
    USART_Tx(uCh);
    uCh = uDataL < 0x0a ? '0' + uDataL : 'W' + uDataL;
    USART_Tx(uCh);
    USART_Tx(uSpace);
}

//----------------------------------------------------------------------------
// SDカード ポート操作マクロ
//----------------------------------------------------------------------------
#define SDC_TX_H (1 << SDI_PIN)
#define SDC_TX_L ~(1 << SDI_PIN)
#define SDC_CK_H (1 << CK_PIN)
#define SDC_CK_L ~(1 << CK_PIN)

#define SDC_CS_ON() SDC_PORT_OUT &= ~(1 << CS_PIN)
#define SDC_CS_OFF() SDC_PORT_OUT |= (1 << CS_PIN)

#define SDC_RX() bit_is_set(SDC_PORT_IN, SDO_PIN)

//----------------------------------------------------------------------------
// SDカード 1バイト送受信(SPIモード0用)
//----------------------------------------------------------------------------
/*
static uint8_t SDC_TxRx0(uint8_t uTxData)
{
    uint8_t uRxData = 0, uPort = 0, uMask = 0b10000000;

    while(1){

        // TxData Send & Clock→L
        if(uTxData & uMask) uPort |= SDC_TX_H;
        else if(uMask == 0) uPort |= SDC_TX_H;
        else uPort &= SDC_TX_L;

        uPort &= SDC_CK_L;

        SDC_PORT_OUT = uPort;

        // CK=Lの時間を稼ぐために、他に必要な処理(含受信)はここで行う
        if(uMask == 0) break;

        if(SDC_RX()) uRxData |= uMask; // RxData Capture

        uMask >>= 1;
        uPort = SDC_PORT_OUT;

        // Clock→H
        SDC_PORT_OUT |= SDC_CK_H;
    }

    return uRxData;
}
*/
//----------------------------------------------------------------------------
// SDカード 1バイト送受信(SPIモード3用)
//----------------------------------------------------------------------------
uint8_t SDC_TxRx(uint8_t uTxData)
{
    register uint8_t uRxData = 0, uPort = 0, uMask = 0b10000000;

    while(1){

        // TxData Send & Clock→L
        if(uTxData & uMask){
            uPort |= SDC_TX_H;
            uPort &= SDC_CK_L;
        }else if(uMask == 0)
            uPort |= SDC_TX_H | SDC_CK_H;
        else
            uPort &= SDC_TX_L & SDC_CK_L;

        SDC_PORT_OUT = uPort;

        // CK=Lの時間を稼ぐために、他に必要な処理(含受信)はここで行う
        if(uMask == 0) break;

        if(SDC_RX()) uRxData |= uMask; // RxData Capture

        uMask >>= 1;
        uPort = SDC_PORT_OUT;

        // Clock→H
        SDC_PORT_OUT |= SDC_CK_H;

    }

    return uRxData;
}

//----------------------------------------------------------------------------
// SDカード 1バイト受信(高速 受信専用)(SPIモード3用)
//----------------------------------------------------------------------------
uint8_t SDC_Rx(void)
{
    register uint8_t uRxData = 0;

    SDC_PORT_OUT &= SDC_CK_L; SDC_PORT_OUT |= SDC_CK_H; if(SDC_RX()) uRxData |= 0b10000000;
    SDC_PORT_OUT &= SDC_CK_L; SDC_PORT_OUT |= SDC_CK_H; if(SDC_RX()) uRxData |= 0b01000000;
    SDC_PORT_OUT &= SDC_CK_L; SDC_PORT_OUT |= SDC_CK_H; if(SDC_RX()) uRxData |= 0b00100000;
    SDC_PORT_OUT &= SDC_CK_L; SDC_PORT_OUT |= SDC_CK_H; if(SDC_RX()) uRxData |= 0b00010000;
    SDC_PORT_OUT &= SDC_CK_L; SDC_PORT_OUT |= SDC_CK_H; if(SDC_RX()) uRxData |= 0b00001000;
    SDC_PORT_OUT &= SDC_CK_L; SDC_PORT_OUT |= SDC_CK_H; if(SDC_RX()) uRxData |= 0b00000100;
    SDC_PORT_OUT &= SDC_CK_L; SDC_PORT_OUT |= SDC_CK_H; if(SDC_RX()) uRxData |= 0b00000010;
    SDC_PORT_OUT &= SDC_CK_L; SDC_PORT_OUT |= SDC_CK_H; if(SDC_RX()) uRxData |= 0b00000001;

    return uRxData;
}

//----------------------------------------------------------------------------
// SDカード コマンド発行
//----------------------------------------------------------------------------
static uint8_t SDC_Command(uint8_t uCommand, uint32_t uData, uint8_t uCrc)
{
    register uint8_t uRes, i;

    // ビジーチェック
    for(i = 255; i > 0; i--){
        uint8_t uRes = SDC_TxRx(0xff);
        if(uRes == 0xff) break;
    }

    // コマンド + データ + CRCを送信
    SDC_TxRx(uCommand);

    SDC_TxRx((uint8_t)(uData >> 24));
    SDC_TxRx((uint8_t)(uData >> 16));
    SDC_TxRx((uint8_t)(uData >> 8));
    SDC_TxRx((uint8_t)uData);

    SDC_TxRx(uCrc);
#ifdef _DEBUG_COMMAND
    USART_TxChar(uCommand, 'm');
#endif
    // レスポンス
    for(i = 255; i > 0; i--){
        uRes = SDC_TxRx(0xff);
#ifdef _DEBUG_COMMAND
        USART_TxChar(uRes, 'p');
#endif
        if(uRes != 0xff) break;
    }

    // ダミークロックを送信
    SDC_TxRx(0xff);

    return uRes;
}

//----------------------------------------------------------------------------
// SDカード 初期化
//----------------------------------------------------------------------------
uint8_t SDC_Init(void)
{
    uint8_t i, uRes;

    // ダミークロック送信
    for(i = 10; i; i--) SDC_TxRx(0xff);

    SDC_CS_ON();

    for(i = 10; i; i--){
        uRes = SDC_Command(CMD0, 0, CRC_CMD0);
        if(uRes == RES_IDLE) break;
    }
    if(uRes != RES_IDLE){
        SDC_CS_OFF();
        return uRes;
    }

    for(i = 10; i; i--){
        uRes = SDC_Command(CMD1, 0, CRC_CMD1);
        if(uRes == RES_NOERR) break;
    }
    if(uRes != RES_NOERR){
        SDC_CS_OFF();
        return uRes;
    }

    uRes = SDC_Command(CMD16, 512, CRC_DUMMY);

    SDC_CS_OFF();
    return uRes;
}

//----------------------------------------------------------------------------
// SDカード データ読み込み
//----------------------------------------------------------------------------
uint8_t SDC_Read(uint32_t uAddress, uint8_t uData[], uint16_t uOffset, uint16_t uBytes)
{
    uint8_t uRes;
    register uint16_t i;

    SDC_CS_ON();

    // コマンド17発行
    uRes = SDC_Command(CMD17, uAddress, CRC_DUMMY);
    if(uRes != RES_NOERR){
        SDC_CS_OFF();
        return uRes;
    }

    // データトークンを検出
    for(i = 10000; i; i--){
        uRes = SDC_Rx();
        if(uRes != 0xff) break;
    }
    if(uRes != TOKEN_CMD17){
        SDC_CS_OFF();
        return uRes;
    }

    for(i = uOffset; i; i--) SDC_Rx(); // オフセットを読み飛ばす

    for(; i < uBytes; i++){
        uData[i] = SDC_Rx();           // 指定バイトを読み込み
#ifdef _DEBUG_READDATA
        USART_TxChar(uData[i], ' ');
#endif
    }
    for(i = 512 - uOffset - uBytes; i; i--) SDC_Rx(); // 残りを読み飛ばす
    SDC_Rx(); SDC_Rx(); // CRCを読み飛ばす

    SDC_CS_OFF();
    return 0x00;
}

// sdc.c ここまで ////////////////////////////////////////////////////////////////

2.4.4 「ぷちFatFS」の変更箇所

diskio.c において、赤字が、変更箇所です。

(diskio.c)
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for Petit FatFs (C)ChaN, 2009      */
/*-----------------------------------------------------------------------*/

#include "diskio.h"
// Added by ota957
#include "../sdc.h"


/*-----------------------------------------------------------------------*/
/* Initialize Disk Drive                                                 */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize (void)
{
DSTATUS stat;

// Put your code here
// Added by ota957
uint8_t uRes = SDC_Init();
stat = (uRes != 0x00) ? STA_NOINIT : 0;

return stat;
}



/*-----------------------------------------------------------------------*/
/* Read Partial Sector                                                   */
/*-----------------------------------------------------------------------*/

DRESULT disk_readp (
BYTE* dest, /* Pointer to the destination object */
DWORD sector, /* Sector number (LBA) */
WORD sofs, /* Offset in the sector */
WORD count /* Byte count (bit15:destination) */
)
{
DRESULT res;

// Put your code here
// Added by ota957
uint8_t uRes = SDC_Read(sector * 512, dest, sofs, count);
res = (uRes != 0x00) ? RES_ERROR : RES_OK;

return res;
}



/*-----------------------------------------------------------------------*/
/* Write Partial Sector                                                  */
/*-----------------------------------------------------------------------*/

DRESULT disk_writep (
// BYTE* buff, /* Pointer to the data to be written, NULL:Initiate/Finalize write operation */
// Added by ota957
const BYTE* buff,
DWORD sc /* Sector number (LBA) or Number of bytes to send */
)
{
DRESULT res;


if (!buff) {
if (sc) {

// Initiate write process

} else {

// Finalize write process

}
} else {

// Send data to the disk

}
// Added by ota957
res = RES_OK;
return res;
}
// diskio.c ここまで //////////////////////////////////////////////////////////////

※ diskio.h・integer.h・pff.c・pff.hは、追加/変更はありません。

2.4.5 問題点と修正

過去自作物を流用したSPI通信関数(SDC_TxRx() in sdc.c)は、送受信を同時に行う構造でしたが、これではデータの読み込みが遅すぎて再生に間に合いませんでした。

結局、受信部を独立させて、ループ構造も廃し、さらに変数をレジスタ指定した関数(SDC_Rx())を用意して解決しました。

また、パワーオン時に初期化がうまく行かないSDカードが存在し、今回は pf_mount()関数(in main.c)を複数回ループすることで回避しました。

3 組み立て

3.1 基板

パターンは以下の通りです。
例によって、作画はフリーソフト「PCBE」を使用しています。
※部品面です。



基板は、配線の手間が省けることから、サンハヤト製「ICB-86基板」を使っています。
ただし、以下の赤色箇所のパターンをカットします。
※こちらは半田面です。



以下のように、ICB-86基板に各部品を実装します。




3.2 WAVデータ準備

好みのオルゴール楽曲4曲を、CDなどからリッピングして、本作品で使えるようにフォーマット変換します。

フォーマットは、「簡易WAVEプレーヤ」と同様に、「モノラル / 32kHz / 8ビット」 に変換します。

フォーマット変換は、フリーソフト「Soundengine Free」が、操作が容易で便利です。

3.3 リース部との結合

以下のように、リース中央にプラ板などで実装し、結合します。




プラ板の寸法は、リース台の内径より一回り大きくして、リース台と造花の隙間に挟み込むと、造花が緩衝材となって以外にしっかりと納まりました。


4 完成

最後に、リースの造花・配線を整えて、ACアダプタを繋ぐと完成です。