ここでは例によって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 ここまで ////////////////////////////////////////////////////////////////
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アダプタを繋ぐと完成です。