// // EasyMP3(VS1001K) + MMC/SD MP3 Player(prototype) // // MCU: ATmega8 // FUSE bit: D9EFh (external clock) // Compiler: AVR-GCC // // 2003.6.3 KAWAKAMI Yukio // #include #include #include #include #define LF 10 #define ON 1 #define OFF 0 #define TRUE 1 #define FALSE 0 #define SPI_CS 2 // PORTB #define SPI_DIN 3 #define SPI_CLK 5 #define SPI_DOUT 4 //#define MP3_SO 0 // PORTC 未使用 #define MP3_SCLK 7 // PORTD #define MP3_CS 6 #define MP3_SI 5 #define MP3_BSYNC 4 #define MP3_DCLK 3 #define MP3_DREQ 2 // スイッチフラグの論理ビット #define SW_START 0 #define SW_STOP 0 #define SW_REW 1 #define SW_FF 2 #define SW_VOLDOWN 3 #define SW_VOLUP 4 // スイッチの物理ビット #define SW_PORT_START 2 // PINC #define SW_PORT_STOP 2 // == SW_PORT_START #define SW_PORT_REW 1 // PINB #define SW_PORT_FF 0 // PINB #define SW_PORT_VOLDOWN 1 // PINC #define SW_PORT_VOLUP 0 // PIND RxDと共用 #define POWER 3 // PORTC #define CHARGE 4 // 充電ON/OFF #define VOLTAGE 5 // 電池電圧測定 #define ACIN 1 // PORTD ACアダプターの検出 TxDと共用 #define CARD_POWER 0 // PORTC MMC/SDカードの電源(HでOFF, LでON) #define BEEP_HIGH 0 #define BEEP_LOW 1 #define NOP asm volatile("nop\n"::) #define WDR asm volatile("wdr\n"::) #define SLEEP asm volatile("sleep\n"::) // EEPROM アドレス #define EEPROM_MUSIC 1 // 再生中の曲番号 #define EEPROM_CLUSTER_L 2 // 再生中のクラスタ(LOW) #define EEPROM_CLUSTER_H 3 // 〃 (HIGH) #define EEPROM_REMAIN1 4 // 再生中の曲の残りセクタ数 #define EEPROM_REMAIN2 5 #define EEPROM_REMAIN3 6 #define EEPROM_SIZE1 8 // 再生中の曲データの長さ #define EEPROM_SIZE2 9 // SDカードが交換されたかどうか #define EEPROM_SIZE3 10 // 判別するのに使う #define EEPROM_SIZE4 11 #define EEPROM_VOLUME 12 // ボリューム typedef unsigned char uchar; typedef unsigned int uint; typedef unsigned long ulong; // バッテリー電圧 V/7.5*1024 #define BATT_START 420 // 起動電圧 約3.1V 起動しないと充電できない #define BATT_SHUTDOWN 410 // シャットダウン電圧 約3.0V #define BATT_42 573 // 4.2V #define BATT_40 546 // バッテリー電圧 4.0V #define BATT_39 532 // 3.9V #define BATT_38 518 // 3.8V #define BATT_37 505 // 3.7V #define BATT_36 491 // 3.6V #define BATT_35 477 // 3.5V #define BATT_34 462 // 3.4V #define BATT_33 450 // 3.3V #define BATT_32 436 // 3.2V #define BATT_HIGH 570 // トリクル充電に切り替える電圧 約4.2V #define BATT_OVER 586 // 充電での最大電圧 約4.3V #define CHARGE_TIMEOVER 3600*15 // 充電時間の最大長(秒) #define CHARGE_DUTY 98 // 充電時のデューティサイクル(%) #define TRICKLE_DUTY 40 // トリクル充電時のデューティ(%) #define PLAY_TRICKLE_DUTY 90 // 再生中のトリクル充電デューティ uchar InitCard(void); uchar SPI_read_open(ulong adrs); void SPI_read_close(void); uchar SPI_command(uchar com, ulong arg); uchar SPI_in(void); void SPI_out(uchar data); void SPI_CS_ON(void); void SPI_CS_OFF(void); void SPI_dummy_clock(void); void SPI_wait(void); void InitWait(void); void read_32(ulong addr); uint read_word(ulong addr); uchar read_VFAT_info(void); uint search_MP3(uint num); char play_music(uint n, uint clst, ulong remainbyte); uint next_cluster(uint c); void beep(uchar n, char err); void MP3_sin(PGM_VOID_P data); void MP3_init(void); void MP3_command(uchar addr, uint arg); void MP3_com_write(uchar data); uint MP3_command_read(uchar addr); void MP3_init(); void shutdown(char flag); uint check_batt(void); void write_eeprom(uchar adrs, uchar data); uchar read_eeprom(uchar adrs); void charge_job(void); void switch_wait(void); // テスト用連続正弦波出力データ Beepとして使う PROGMEM uchar BeepData[] = { 0x53, 0xEF, 0x6E, 0x31, 0, 0, 0, 0 }; // 1500Hz PROGMEM uchar ErrorBeep[] = { 0x53, 0xEF, 0x6E, 0x35, 0, 0, 0, 0 }; // 500Hz PROGMEM uchar BeepStop[] = { 0x45, 0x78, 0x69, 0x74, 0, 0, 0, 0 }; // 止める uchar Buff[32]; uint Buff_p; uint Cluster; // 現在再生中のクラスタ uint NextCluster; // 次回再生するクラスタ uint PlayMusic; // 再生中の曲番号 uint MusicCount; // 現在のカードに入っている曲数 ulong MusicSize; // 再生中の曲のサイズ ulong RemainSec; // 再生中の曲の残りセクタ数 uchar FATtype; // 0:FAT12 1:FAT16 uchar SectorsPerCluster; // 1クラスタのセクタ数 uint RootDirEntriesCount; // ルートディレクトリエントリー数 uint FATstart; // FAT開始セクタ uint DIRstart; // ルートディレクトリ開始セクタ ulong DataStart; // データ領域開始セクタ volatile uchar Volume; // 音量 volatile uint BattVolt; // タイマー割り込みで取得した電源電圧 volatile uint BattVoltOld; // 平均値計算用 volatile uchar SwitchFlag; // タイマー割り込みで取得したスイッチの情報 volatile uchar StopSw; // START/STOPスイッチ入力フラグ volatile uchar RewSw; // REWスイッチ入力フラグ volatile uchar FfSw; // FFスイッチ入力フラグ volatile uchar VolUpSw; // Vol upスイッチ入力フラグ volatile uchar VolDownSw; // Vol downスイッチ入力フラグ volatile uchar ChargeFlag; // 充電フラグ volatile uchar ChargeTimer1; // 充電タイマー1 volatile uint ChargeTimer2; // 充電タイマー2(秒単位) uchar FATCache[512]; // FATキャッシュ ulong FATsec; // FATキャッシュしているセクタ char Debug; // デバッグフラグ //********************************* // タイマー0オーバーフロー割り込み // 8MHz/1024/78=100.16 約100Hz //********************************* SIGNAL(SIG_OVERFLOW0) { //SIGNAL(SIG_OUTPUT_COMPARE2) { uchar old_sw; // 次の割り込み設定 outp((uchar)(256-(78-1)), TCNT0); // 充電処理 charge_job(); // スイッチチェック old_sw = SwitchFlag; SwitchFlag = 0; if (!(inp(PINC) & (1<= 20){ Volume += 8; if (Volume > 0xCF) Volume = 0xCF; MP3_command(11, ((uint)Volume << 8)|((uint)Volume)); // 音量設定 VolDownSw = 1; } SwitchFlag |= (1<= 20){ Volume -= 8; if (Volume < 0x1F) Volume = 0x1F; MP3_command(11, ((uint)Volume << 8)|((uint)Volume)); // 音量設定 VolUpSw = 1; } SwitchFlag |= (1< MusicCount) PlayMusic = 1; switch_wait(); // スイッチが放されるまで待つ // 再生 r = play_music(PlayMusic, Cluster, RemainSec); switch_wait(); // スイッチが放されるまで待つ if (r < 0){ beep(-r, BEEP_LOW); // エラー発生 shutdown(0); } else if (r == 1){ // 途中でSTOPが押された if (StopSw > 100){ // 1秒以上の長押しはバッテリー電圧チェック if (BattVolt > BATT_40){ beep(3, BEEP_HIGH); // 4.0V以上 高音3回 } else if (BattVolt > BATT_38){ beep(2, BEEP_HIGH); // 3.8V以上 高音2回 } else if (BattVolt > BATT_36){ beep(1, BEEP_HIGH); // 3.6V以上 高音1回 } else if (BattVolt > BATT_34){ beep(1, BEEP_LOW); // 3.4V以上 低音1回 } else if (BattVolt > BATT_32){ beep(2, BEEP_LOW); // 3.2V以上 低音2回 } else { beep(3, BEEP_LOW); // それ以下 低音3回 } continue; // 続きから演奏 } else { if (ChargeFlag == 0){ shutdown(0); // 電源を切る } else { // 充電中なので待機状態へ sbi(PORTC, CARD_POWER); // MMC/SDカード電源 OFF MP3_command(0, 0x0010); // VS1001k powerdown StopSw = 0; while (StopSw == 0){ WDR; SLEEP; if (ChargeFlag == 0){ shutdown(0); // 充電中断されたのでシャットダウン } } break; // 再生再開 } } } else if (r == 3){ // REW if (RewSw > 100){ // 1秒以上の長押しは最初の曲 PlayMusic = 1; } else if (RewSw > 25){ // 250ms以上の長押しは前の曲 if (--PlayMusic == 0){ PlayMusic = MusicCount; } } Cluster = 0; // 曲の最初から } else { // FF or その曲の再生終了 PlayMusic++; // 次の曲 Cluster = 0; } if (BattVolt < BATT_START){ beep(1, BEEP_LOW); // 起動電圧以下になったら曲間に警告音 } } } } //********************************************************* // スイッチが確実に放されるまで待つ //********************************************************* void switch_wait(void){ sei(); for (;;) { WDR; SLEEP; SLEEP; if (!(SwitchFlag & ((1<= 100){ ChargeTimer1 = 0; if (inp(PORTC) & (1<= (uint)CHARGE_TIMEOVER)|| (BattVolt > BATT_HIGH)){ ChargeFlag = 2; // タイムアウトか高電圧ならトリクル充電 } else { sbi(PORTC, CHARGE); // ON } } } else if (ChargeFlag == 2){ // トリクル充電モード ChargeTimer1++; if (ChargeTimer1 >= 100){ ChargeTimer1 = 0; if (BattVolt > BATT_OVER){ cbi(PORTC, CHARGE); // 電圧が上がりすぎたので止める ChargeFlag = 3; // 強制停止モード } else { sbi(PORTC, CHARGE); // ON } } else { if (inp(PORTC) & (1<= 0x1000){ FATtype = 1; // FAT16 } else { FATtype = 0; // FAT12 } return 0; } //********************************************************* // MP3ファイルを捜す // 引数: // num この数だけ見つけたら戻る // この数以下であってもディレクトリエントリを全て調べ終わったら戻る // 戻り値: // 見つけた数 // Buff[] 最後に見つかった MP3のディレクトリ情報 //********************************************************* uint search_MP3(uint num){ uint ent; ulong sec; uchar i, j; uint n; n = 0; sec = (ulong)DIRstart * 512; for (ent=0; ent < RootDirEntriesCount; ){ if (SPI_read_open(sec)) return n; for (i=0; i < 16; i++){ for (j = 0; j < 32; j++){ Buff[j] = SPI_in(); } if (Buff[0] == 0) break; if (((Buff[26] != 0)||(Buff[27] != 0))&& // クラスタ番号 (Buff[0] != 0xE5)&& // 削除マーク (!(Buff[11] & 0x18))&& // 通常ファイル (Buff[8]=='M')&&(Buff[9]=='P')&&(Buff[10]=='3')){ // 拡張子MP3 n++; if (--num == 0) break; } ent++; } SPI_read_close(); if (Buff[0] == 0) break; if (num == 0) break; sec += 512; // 次のセクタ } return n; } //**************************************** // MP3ファイルを再生 // fn: 曲番号 // clst: 演奏開始クラスタ。==0 の場合は最初から // remainsec: 再生データ残りセクタ数 //**************************************** char play_music(uint fn, uint clst, ulong remainsec){ uint i, j; ulong sec; uint remain; uint cn; cli(); // 割込み禁止 if (fn != search_MP3(fn)){ sei(); // 割り込み許可 return -1; // 曲が無い } MusicSize = (ulong)Buff[28] + ((ulong)Buff[29]<<8) + ((ulong)Buff[30]<<16) + ((ulong)Buff[31]<<24); if (clst){ // 途中から再生 Cluster = clst; RemainSec = remainsec; remain = MusicSize - (remainsec * 512); } else { // 最初から再生 Cluster = (uint)Buff[26] + ((uint)Buff[27] << 8); RemainSec = MusicSize / 512; remain = MusicSize % 512; } if ((Cluster == 0xFFFF)||(Cluster < 2)){ sei(); // 割り込み許可 return -2; // クラスタ異常 } // 次のクラスタを調べておく NextCluster = next_cluster(Cluster); StopSw = 0; RewSw = 0; FfSw = 0; cn = SectorsPerCluster; sec = (((ulong)Cluster - 2) * (ulong)SectorsPerCluster + DataStart) * 512; MP3_init(); while(RemainSec > 0){ uchar data; char bit; if (SPI_read_open(sec)) return -2; SPI_out(0xFF); // 1byte目のクロック送出 & WAIT for (i=0; i < 512; i++){ // 転送待ちチェック if (!(inp(PIND)&(1< 2)||(FfSw > 2)||(RewSw > 2)){ // STOP or FF or REWボタンが押された for (; i < 512; i++){ SPI_out(0xFF); // クロック送出&WAIT } break; } while (!(inp(PIND)&(1< 2){ while (!(inp(PIND)&(1< 2){ while (!(inp(PIND)&(1< 2){ while (!(inp(PIND)&(1< 0){ sec += 512; // 次のセクタ } else { // 次のクラスタ if (NextCluster == 0){ Cluster = next_cluster(Cluster); } else { Cluster = NextCluster; NextCluster = 0; } if ((Cluster == 0xFFFF)||(Cluster < 2)){ // 異常クラスタが検出されたので強制終了 remain = 0; break; } sec = (((ulong)Cluster - 2) * (ulong)SectorsPerCluster + DataStart) * 512; cn = SectorsPerCluster; } } // 1セクタ(512bytes)未満の分を再生 if (remain > 0){ uchar data; char bit; if (SPI_read_open(sec)) return -2; SPI_out(0xFF); // 1byte目のクロック送出&WAIT for (i=0; i < remain; i++){ while(!(inp(PIND)&(1<> 1); if ((sec & 511) == 511){ // セクタをまたぐ data = read_word(sec) & 0x00FF; data += ((read_word(sec+1) & 0x00FF) << 8); } else { data = read_word(sec); } if (c & 1){ c = data >> 4; } else { c = data & 0x0fff; } if (c >= 0x0ff8) c = 0xFFFF; } else { // FAT16 sec = (ulong)FATstart * 512 + (ulong)c * 2; c = read_word(sec); if (c >= 0xfff8) c = 0xFFFF; } return c; } //******************************************************* // バッテリー電圧を調べ、低すぎればシャットダウンする //******************************************************* uint check_batt(void){ if (BattVolt < BATT_SHUTDOWN){ SPI_CS_OFF(); sbi(PORTC, CARD_POWER); // MMC/SDカード電源 OFF beep(4, BEEP_LOW); shutdown(0); // シャットダウン } return BattVolt; } //********************************************************* // シャットダウン // flag != 0 の場合は EEPROM書き込みを行わずに電源を切る //********************************************************* void shutdown(char flag){ SPI_CS_OFF(); sbi(PORTC, CARD_POWER); // MMC/SDカード電源 OFF MP3_command(0, 0x0010); // VS1001k powerdown if (!flag){ write_eeprom(EEPROM_VOLUME, Volume); write_eeprom(EEPROM_MUSIC, PlayMusic); write_eeprom(EEPROM_CLUSTER_L, Cluster); write_eeprom(EEPROM_CLUSTER_H, Cluster>>8); write_eeprom(EEPROM_REMAIN1, RemainSec); write_eeprom(EEPROM_REMAIN2, RemainSec>>8); write_eeprom(EEPROM_REMAIN3, RemainSec>>16); write_eeprom(EEPROM_SIZE1, MusicSize); write_eeprom(EEPROM_SIZE2, MusicSize>>8); write_eeprom(EEPROM_SIZE3, MusicSize>>16); write_eeprom(EEPROM_SIZE4, MusicSize>>24); } for(;;){ // 無限ループ cbi(PORTC, POWER); // Power down SLEEP; } } //******************************************************* // EEPROM 書き込み //******************************************************* void write_eeprom(uchar adrs, uchar data){ // 同じ内容なら書き込み動作を行わない if (read_eeprom(adrs) != data){ while(inp(EECR) & (1<> 8, EEARH); outp(adrs, EEARL); outp(data, EEDR); sbi(EECR, EEMWE); sbi(EECR, EEWE); } } //******************************************************* // EEPROM 読み込み //******************************************************* uchar read_eeprom(uchar adrs){ while(inp(EECR) & (1<> 8, EEARH); outp(adrs, EEARL); sbi(EECR, EERE); return inp(EEDR); } //**************************************** // BEEP // 引数: n 鳴動回数 // err != 0 ならエラー音(低い音) // 割り込み許可にすることに注意 //**************************************** void beep(uchar n, char err){ char i; cli(); // MP3_init()処理中に音量調節されたら困るので割込み禁止 MP3_init(); sei(); // 割り込み許可 for (; n > 0; --n){ if (err){ MP3_sin(ErrorBeep); } else { MP3_sin(BeepData); } // 時間待ち。この間が BEEPの音の長さになる for (i=0; i < 10*2; i++){ // 約100ms SLEEP; } MP3_sin(BeepStop); // BEEPの音間 for (i=0; i < 3*2; i++){ // 約30ms SLEEP; } } MP3_sin(BeepStop); // 時々止まらないことがあるのでもう一回 } //**************************************** // EasyMP3 初期化 //**************************************** void MP3_init(void){ MP3_command(0, 0x0004); // Soft Reset InitWait(); MP3_command(0, 0x0000); while(!(inp(PIND)&(1<> 8); MP3_com_write(arg); sbi(PORTD, MP3_CS); } //**************************************** // EasyMP3 コマンド入力 未使用 //**************************************** /* uint MP3_command_read(uchar addr){ char i; uint r = 0; cbi(PORTD, MP3_CS); MP3_com_write(3); MP3_com_write(addr); for(i=0; i<16; i++){ sbi(PORTD, MP3_SCLK); r <<= 1; if (inp(PINC) & (1<> 24); SPI_out(arg >> 16); SPI_out(arg >> 8); SPI_out(arg); SPI_out(0x95); // 初期化コマンドのCRC for(i = 0; i < 2048; i++){ r = SPI_in(); if (r == 0xFE) break; if (!(r & 0x80)) break; } return r; } //**************************************** // MMC/SD SPIデータ出力 //**************************************** void SPI_out(uchar data){ outp(data, SPDR); while(!(inp(SPSR) & 0x80)); } //**************************************** // MMC/SD SPIデータ入力 //**************************************** uchar SPI_in(void){ outp(0xFF, SPDR); while(!(inp(SPSR) & 0x80)); return inp(SPDR); } //**************************************** // MMC/SD SPIクロック終了待ち // SPDRをアクセスしてない場合は // SPIF=0になって無限ループする場合があることに注意 //**************************************** void SPI_wait(void){ while(!(inp(SPSR) & 0x80)); } //**************************************** // MMC/SD SPI CS = ON //**************************************** void SPI_CS_ON(void){ outp(0x50, SPCR); // SPIマスタ, CPOL=0, クロック 8MHz/4=2MHz sbi(SPSR, SPI2X); // クロックを2倍 4MHz cbi(PORTB, SPI_CS); outp(0x2C, DDRB); // PB5,3,2 out PB4,1,0 in } //************************************************************ // MMC/SD SPI CS = OFF // カード未使用時はポートをハイインピーダンスにしておく //************************************************************ void SPI_CS_OFF(void){ sbi(PORTB, SPI_CS); outp(0x00, DDRB); // PB5〜0 in outp(0x00, SPCR); // SPI禁止 cbi(PORTB, SPI_CS); }