// // VS1001K + MMC/SD MP3 Player // // COUGAR 2200 Figure Version // // MCU: ATmega8 FUSE bit: D1A3h (BOD=2.7V, internal 4MHz) // Compiler: AVR-GCC // // 2004.4.25 KAWAKAMI Yukio // 2004.7.3 低電圧リセット検出追加 // 2004.7.4 スイッチ論理反転 // 2004.7.8 連続再生モード追加 // 2004.12.18 連続再生モード時に曲の最後が再生されないのを修整 // #include #include #include #include #define VOLUME 0x06 // 音量 小さいほど音は大きい #define LF 10 #define ON 1 #define OFF 0 #define TRUE 1 #define FALSE 0 #define NOP asm volatile("nop\n"::) #define WDR asm volatile("wdr\n"::) #define SLEEP asm volatile("sleep\n"::) typedef unsigned char uchar; typedef unsigned int uint; typedef unsigned long ulong; //#define VS1001_CLOCK 14318180 // EasyMP3デフォルト #define VS1001_CLOCK 12000000 // VS1001のクロック(Hz) #define SPI_CS 2 // PORTB #define SPI_DIN 3 #define SPI_CLK 5 #define SPI_DOUT 4 //#define MP3_SO 1 // PORTB #define MP3_SI 7 // PORTD #define MP3_SCLK 6 #define MP3_CS 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 3 // PINC #define SW_PORT_STOP 3 // == SW_PORT_START #define SW_PORT_REW 2 // PINC #define SW_PORT_FF 1 // PINC #define SW_PORT_VOLDOWN 0 // PINC #define SW_PORT_VOLUP 0 // PINB #define POWER 4 // PORTC 電源ロック #define VOLTAGE 5 // 電池電圧測定 #define MUTE 1 // PORTB 音声出力ミュート #define LED 1 // PORTD == TxD POWER LED // RF-2200 MP3 Player #define SW_PORT 7 // PINB #define POWER_LOCK 6 // PORTB #define BEEP_HIGH 0 #define BEEP_LOW 1 // 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 // ボリューム #define EEPROM_RUNNING_TIME_H 13 // 現在の電池の稼働時間(時) #define EEPROM_RUNNING_TIME_M 14 //      〃 (分) #define EEPROM_RUNNING_TIME_OLD_H 15 // 前の電池の稼働時間(時) #define EEPROM_RUNNING_TIME_OLD_M 16 // 〃 (分) #define EEPROM_BATTLOG 17 // 電圧ログ開始 // バッテリー電圧 V * 200 - 12 (実測による) #define BATT_START 410 // 起動電圧 約2.1V #define BATT_SHUTDOWN 380 // シャットダウン電圧 約2.0V #define BATT_LOW 428 // ローバッテリー検出 約2.2V #define BATT_26 508 // 2.6V #define BATT_25 488 // 2.5V #define BATT_24 468 // 2.4V #define BATT_23 448 // 2.3V #define BATT_22 428 // 2.2V 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_dummy_clock(void); void SPI_wait(void); void InitWait(void); void read_32(ulong addr); uchar read_VFAT_info(void); uint search_MP3(uint num); char play_music(uint n, uint clst, long remainbyte); uint next_cluster(uint c); uint check_cont_cluster(uint c); uint read_word(ulong addr); 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 shutdown(char flag); int check_batt(void); void write_eeprom(uchar adrs, uchar data); uchar read_eeprom(uchar adrs); void switch_wait(void); uchar ReadCom(void); void WriteCom(uchar data); void WriteComMsg(PGM_VOID_P msg); void WriteComStr(uchar *msg); void WriteHex(uchar data); void print_sector(ulong sec); // テスト用連続正弦波出力データ 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 }; // 止める uint Cluster; // 現在再生中のクラスタ uint NextCluster; // 次回再生するクラスタ uint PlayMusic; // 再生中の曲番号 uint MusicCount; // 現在のカードに入っている曲数 ulong MusicSize; // 再生中の曲のサイズ long RemainSec; // 再生中の曲の残りセクタ数 char NoBusyRetry; // BUSYにならないのでリトライしたことを示すフラグ uchar FATtype; // 0:FAT12 1:FAT16 uchar SectorsPerCluster; // 1クラスタのセクタ数 uint RootDirEntriesCount; // ルートディレクトリエントリー数 uint FATstart; // FAT開始セクタ uint DIRstart; // ルートディレクトリ開始セクタ ulong DataStart; // データ領域開始セクタ volatile uchar Volume; // 音量 volatile int BattVolt; // タイマー割り込みで取得した電源電圧 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 FlushTimer; // LED点滅タイマ volatile uchar Timer; // 汎用タイマ volatile char DiskReadFlag; volatile int DiskP; ulong DiskCacheAddr; // キャッシュしているアドレス uchar DiskCache[512]; // FATキャッシュ uchar DataBuff[256]; // データバッファ char ContFlag; // 連続再生フラグ char Debug; // デバッグフラグ //********************************* // タイマー2比較一致割り込み // 4MHz/1024/39=100.16 約100Hz //********************************* SIGNAL(SIG_OUTPUT_COMPARE2) { uchar old_sw; // 汎用タイマ if (Timer){ --Timer; } // スイッチチェック スイッチは FFとみなす old_sw = SwitchFlag; SwitchFlag = 0; if (!(inp(PINB) & (1<= 512){ cbi(SPCR, SPIE); // SPI割り込み禁止 } else { outp(0xFF, SPDR); // SD/MMCカードへ次のデータリクエスト } } //********************************* // メインルーチン //********************************* int main (void) { char r; if (inp(MCUCSR) & (1<= 100){ ContFlag = 1; // 起動時の長押しは連続再生モード } MP3_init(); for(;;){ if (PlayMusic > MusicCount) PlayMusic = 1; switch_wait(); // スイッチが放されるまで待つ // 再生 r = play_music(PlayMusic, Cluster, RemainSec); PlayMusic++; // 次の曲 Cluster = 0; switch_wait(); // スイッチが放されるまで待つ if (r == 2){ if (FfSw > 25){ shutdown(0); // 長押しは停止 } // 他は次の曲 } else if (!ContFlag){ Timer = 100; while(Timer){ SLEEP; // 再生が終わるまで待つ } shutdown(0); // 1曲再生したらシャットダウン } } } //********************************************************* // スイッチが確実に放されるまで待つ //********************************************************* void switch_wait(void){ sei(); for (;;) { WDR; SLEEP; SLEEP; if (!(SwitchFlag & ((1<= 0x1000){ FATtype = 1; // FAT16 } else { FATtype = 0; // FAT12 } return 0; } //********************************************************* // MP3ファイルを捜す // 引数: // num この数だけ見つけたら戻る // この数以下であってもディレクトリエントリを全て調べ終わったら戻る // 戻り値: // 見つけた数 // DataBuff[0〜31] 最後に見つかった 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++){ DataBuff[j] = SPI_in(); } if (DataBuff[0] == 0) break; // 未使用領域検出 if (((DataBuff[26] != 0)||(DataBuff[27] != 0))&& // クラスタ番号 (DataBuff[0] != 0xE5)&& // 削除マーク (!(DataBuff[11] & 0x18))&& // 通常ファイル (DataBuff[8]=='M')&&(DataBuff[9]=='P')&&(DataBuff[10]=='3')){ // 拡張子MP3 n++; if (--num == 0) break; } ent++; } for (++i; i < 16; i++){ for (j = 0; j < 32; j++){ SPI_in(); // セクタ分の残りを読み捨て } } SPI_read_close(); if (DataBuff[0] == 0) break; // 未使用領域検出 if (num == 0) break; sec += 512; // 次のセクタ } return n; } //**************************************** // MP3ファイルを再生 // fn: 曲番号 // clst: 演奏開始クラスタ。==0 の場合は最初から // remainsec: 再生データ残りセクタ数 //**************************************** char play_music(uint fn, uint clst, long remainsec){ ulong sec; uint remain; uint cn; uint i; uint last_cluster; uint *clust_buff; uint c; char close_flag; if (fn != search_MP3(fn)){ return -1; // 曲が無い } MusicSize = (ulong)DataBuff[28] + ((ulong)DataBuff[29]<<8) + ((ulong)DataBuff[30]<<16) + ((ulong)DataBuff[31]<<24); if (clst){ // 途中から再生 Cluster = clst; RemainSec = remainsec; remain = MusicSize - (remainsec * 512); } else { // 最初から再生 Cluster = (uint)DataBuff[26] + ((uint)DataBuff[27] << 8); RemainSec = MusicSize / 512; remain = MusicSize % 512; } if ((Cluster == 0xFFFF)||(Cluster < 2)){ return -2; // クラスタ異常 } // 連続したクラスタごとにバッファに格納 clust_buff = (uint *)DataBuff; // DataBuffをクラスタブロックバッファに c = Cluster; for (i = 0; i < sizeof(DataBuff)/4; i++){ *clust_buff++ = c; *clust_buff++ = c = check_cont_cluster(c); c = next_cluster(c); if (c == 0xFFFF){ *clust_buff++ = c; *clust_buff = c; break; } } // 最初のクラスタブロック clust_buff = (uint *)DataBuff; Cluster = *clust_buff++; last_cluster = *clust_buff++; StopSw = 0; RewSw = 0; FfSw = 0; cn = SectorsPerCluster; sec = (((ulong)Cluster - 2) * (ulong)SectorsPerCluster + DataStart) * 512; // MP3_init(); Timer = 100; // READYタイマーセット while (RemainSec > 0){ uchar data; char bit; DiskCacheAddr = 0; // Diskキャッシュを使うのでクリア if (SPI_read_open(sec)) return -2; // SPI割り込み準備 DiskP = 0; outp(0xFF, SPDR); // SD/MMCへ次のデータリクエスト sbi(SPCR, SPIE); // SPI割り込み許可 close_flag = 0; for (i = 0; i < 512; i++){ while (DiskP <= i){ ; } while (!(inp(PIND)&(1<= 512){ SPI_read_close(); close_flag = 1; } else { SLEEP; } } } data = DiskCache[i]; if (data & 0x80){ sbi(PORTD, MP3_SI); } else { cbi(PORTD, MP3_SI); } sbi(PORTD, MP3_BSYNC); // BSYNC = H sbi(PORTD, MP3_DCLK); data <<= 1; cbi(PORTD, MP3_DCLK); cbi(PORTD, MP3_BSYNC); // BSYNC = L for (bit = 0; bit < 7; bit++){ if (data & 0x80){ sbi(PORTD, MP3_SI); } else { cbi(PORTD, MP3_SI); } sbi(PORTD, MP3_DCLK); data <<= 1; cbi(PORTD, MP3_DCLK); } } if (!close_flag){ SPI_read_close(); } --RemainSec; if (--cn > 0){ sec += 512; // 次のセクタ if (Debug && (cn == 1)){ uint n; n = BattVolt; WriteHex(n>>8); WriteHex(n); WriteCom(' '); WriteHex(Cluster>>8); WriteHex(Cluster); WriteCom('\n'); } } else { // 次のクラスタ if (++Cluster > last_cluster){ // クラスタが不連続だったので読み直し if ((void *)clust_buff < (void *)DataBuff + sizeof(DataBuff)){ // クラスタブロックバッファから読み込み Cluster = *clust_buff++; last_cluster = *clust_buff++; } else { // バッファに無いので毎回読み直し Cluster = next_cluster(Cluster); last_cluster = 0; } if ((Cluster < 2)||(Cluster == 0xFFFF)){ // 異常クラスタが検出されたので強制終了 remain = 0; break; } } sec = (((ulong)Cluster - 2) * (ulong)SectorsPerCluster + DataStart) * 512; cn = SectorsPerCluster; } check_batt(); // バッテリーチェック if (Timer == 0){ // READYタイムアウトしたら if (NoBusyRetry > 0){ // リトライしてもダメなら beep(1, BEEP_LOW); // 低音ビープ1回 NoBusyRetry = 0; FfSw = 0; return 2; // FF扱いで終了 → 次の曲へ } else { NoBusyRetry = 1; RewSw = 0; return 3; // REW扱いで終了 → その曲の最初からリトライ } } // STOP, FFボタンチェック if (StopSw > 2){ beep(1, BEEP_HIGH); NoBusyRetry = 0; return 1; } else if (FfSw > 2){ beep(1, BEEP_HIGH); NoBusyRetry = 0; return 2; } else if (RewSw > 2){ beep(1, BEEP_HIGH); NoBusyRetry = 0; return 3; } } NoBusyRetry = 0; // 1セクタ(512bytes)未満の分を再生 if ((remain > 0)&&(remain < 512)){ uchar data; char bit; if (SPI_read_open(sec)) return -2; cli(); // 割込み禁止 SPI_out(0xFF); // 1byte目のクロック送出&WAIT for (i = 0; i < 512; i++){ data = inp(SPDR); outp(0xFF, SPDR); if (remain-- > 0){ while (!(inp(PIND)&(1<> 1); if ((addr & 511) == 511){ // セクタをまたぐ data = read_word(addr) & 0x00FF; data |= ((read_word(addr+1) & 0x00FF) << 8); } else { fatadr = addr & 0xFFFFFE00; data = read_word(addr); } if (c & 1){ c = data >> 4; } else { c = data & 0x0fff; } if (c >= 0x0ff8) c = 0xFFFF; } else { // FAT16 addr = (ulong)FATstart * 512 + (ulong)c * 2; fatadr = addr & 0xFFFFFE00; c = read_word(addr); if (c >= 0xFFF8) c = 0xFFFF; } return c; } //**************************************** // 連続したクラスタ検出 // 戻り値:連続した最後のクラスタ //**************************************** uint check_cont_cluster(uint c){ uint next; for(;;) { next = next_cluster(c); if (++c != next){ --c; break; } } return c; } //**************************************** // MMC/SDカードから 2byteを読む // セクタ境界のアクセスはできないので // 上位工程で考慮すること //**************************************** uint read_word(ulong addr){ uint w; uint b; uchar *p; b = addr & 511; addr &= 0xFFFFFE00; if (addr != DiskCacheAddr){ if (SPI_read_open(addr)) return 0; for (p = DiskCache; p < (DiskCache + 512); ){ *p++ = SPI_in(); } SPI_read_close(); DiskCacheAddr = addr; } w = (uint)DiskCache[b++]; w |= ((uint)DiskCache[b] << 8); return w; } //******************************************************* // バッテリー電圧を調べ、低すぎればシャットダウンする //******************************************************* int check_batt(void){ return BattVolt; } //********************************************************* // シャットダウン // flag != 0 の場合は EEPROM書き込みを行わずに電源を切る //********************************************************* void shutdown(char flag){ cli(); // 割込み禁止 sbi(PORTB, SPI_CS); // SPI CS=H cbi(PORTB, MUTE); // 音声ミュート MP3_command(0, 0x0010); // VS1001k powerdown if (!flag){ write_eeprom(EEPROM_MUSIC, PlayMusic); } for(;;){ // 無限ループ cbi(PORTB, POWER_LOCK); // POWER OFF WDR; 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 < 5; i++){ // SLEEP; } MP3_sin(BeepStop); // BEEPの音間 for (i=0; i < 3; i++){ // 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(PINB) & (1<> 24); SPI_out(arg >> 16); SPI_out(arg >> 8); SPI_out(arg); SPI_out(0x95); // 初期化コマンドのCRC for(i = 0; i < 1000; 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)); } //**************************************** // 1セクター読み込んで表示する //**************************************** void print_sector(ulong sec){ uchar r; int i; sec *= 512; r = SPI_read_open(sec); WriteHex(r); WriteCom('\n'); for (i=0; i<512; i++){ r = SPI_in(); WriteHex(r); if ((i & 31) == 31){ WriteCom('\n'); } else if (i & 1){ WriteCom(' '); } } SPI_read_close(); } //**************************************** // シリアル入力。入力があるまで待つ //**************************************** uchar ReadCom(void){ do { WDR; } while(!(inp(UCSRA)&(1<>4) + '0'; if (c > '9'){ c += 7; } WriteCom(c); c = (data & 0x0f) + '0'; if (c > '9'){ c += 7; } WriteCom(c); } //**************************************** // intを 10進文字列変換 //**************************************** /* void ItoStr(uint i, uchar str[]){ char *p; if (i >= 10000){ p = &str[5]; } else if (i >= 1000){ p = &str[4]; } else if (i >= 100){ p = &str[3]; } else if (i >= 10){ p = &str[2]; } else { p = &str[1]; } *p-- = 0; do { *p-- = (i % 10) + '0'; i /= 10; } while(i); } */ //**************************************** // 固定文字列出力 // 使用例: // char *s; // s = PSTR("Message OK.\n"); // WriteComMsg(s); //**************************************** void WriteComMsg(PGM_VOID_P msg){ char i; uchar c; i = 0; while((c = (uchar)PRG_RDB(msg++)) != 0){ WriteCom(c); i++; } } //**************************************** // バッファ文字列出力 //**************************************** void WriteComStr(uchar *msg){ char i; uchar c; i = 0; while((c = (uchar)(*msg++)) != 0){ WriteCom(c); i++; } }