// // VS1011 + MMC/SD MP3 Player // // MCU: ATmega8 FUSE bit: D1E4h CR osc. 8MHz // Compiler: AVR-GCC // // 2005.11.12 KAWAKAMI Yukio // #include #include #include #include // バッテリー電圧 #define BATT_LOW 220 // LED点滅 約2.2V #define BATT_START 220 // 起動電圧 約2.2V #define BATT_SHUTDOWN 205 // シャットダウン 約2.05V @単四 #define BEEP_VOL_DIFF 0x10 // BEEP音のボリュームを通常より下げる #define VOLUME_DEFAULT 0x40 // デフォルトボリューム // バッテリーの A/D変換値を電圧値に変換する際のパラメータ // [A/D変換値(0〜1023)] * BATT_MUL / BATT_DIV // 電圧値は 1V=100 のゲタ履き表現 #define BATT_MUL 106 #define BATT_DIV 200 // 実測による補正値 #define LF 10 #define ON 1 #define OFF 0 #define TRUE 1 #define FALSE 0 #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"::) #ifndef sbi // AVR-LibC 1.2.3以降、使えなくなったマクロの定義 #define BV(bit) (1<<(bit)) #define cbi(addr,bit) addr &= ~_BV(bit) #define sbi(addr,bit) addr |= _BV(bit) #define outp(data,addr) addr = (data) #define inp(addr) (addr) #define outb(addr,data) addr = (data) #define inb(addr) (addr) #define PRG_RDB(addr) (pgm_read_byte(addr)) #endif typedef unsigned char uchar; typedef unsigned int uint; typedef unsigned long ulong; #define VS1011_CLOCK 12288000 // VS1011のクロック(Hz) #define SPI_CS 2 // PORTB #define SPI_DIN 3 #define SPI_CLK 5 #define SPI_DOUT 4 #define MP3_DATA_CS 0 // PORTB #define MP3_CTRL_CS 1 #define MP3_DREQ 2 #define MP3_RESET 3 // PORTD #define LED 0 // PORTC #define POWER 1 #define BATT_VOLT 7 // PORTC 電池電圧測定 // スイッチフラグの論理ビット #define SW_START 7 #define SW_STOP 7 #define SW_FF 7 // START/STOP/FF 兼用 #define SW_REW 6 #define SW_VOLDOWN 5 #define SW_VOLUP 4 // スイッチの物理ビット #define SW_PORT_START 7 // PIND #define SW_PORT_STOP 7 // == SW_PORT_START //#define SW_PORT_REW 2 // PINC #define SW_PORT_REW 6 // PIND #define SW_PORT_VOLUP 5 // PIND #define SW_PORT_VOLDOWN 4 // PIND // 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 // 〃 (分) uchar InitCard(char pol); 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); uint ADCin(char ch); uint CalcBatt(uint val); void read_32(ulong addr); uchar read_VFAT_info(void); int read_card(ulong addr); 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 VS1011_reset(void); void set_volume(uchar vol); void shutdown(char flag); int check_batt(void); void write_eeprom(uchar adrs, uchar data); uchar read_eeprom(uchar adrs); void switch_wait(void); // テスト用連続正弦波出力データ Beepとして使う PROGMEM uchar BeepData[] = { 0x53, 0xEF, 0x6E, 0xF0, 0, 0, 0, 0 }; // 1500Hz PROGMEM uchar ErrorBeep[] = { 0x53, 0xEF, 0x6E, 0xA4, 0, 0, 0, 0 }; // 500Hz PROGMEM uchar BeepStop[] = { 0x45, 0x78, 0x69, 0x74, 0, 0, 0, 0 }; // 止める uchar DiskCache[512]; // ディスクキャッシュ uchar DataBuff[32]; // データバッファ ulong DiskCacheAddr; // キャッシュしているアドレス uint Cluster; // 現在再生中のクラスタ 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 uchar VolumeReg; // 現在の実際の音量設定値 volatile int BattVolt; // タイマー割り込みで取得した電源電圧 volatile int BattVoltOld[4]; // 電圧の平均値をとるための過去の値 volatile uchar BattVoltP; volatile uchar SwitchFlag; // タイマー割り込みで取得したスイッチの情報 volatile uchar BeepTimer; // Beep音の長さ 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 Timer; // 汎用タイマ volatile uchar FlushTimer; // LED点滅タイマ volatile uchar RunningTimeDS; // 稼働時間(1/100秒) volatile uchar RunningTimeS; // 〃 (秒) volatile uchar RunningTimeM; // 〃 (分) volatile uchar RunningTimeH; // 〃 (時) char Debug; // デバッグフラグ //********************************* // タイマー2比較一致割り込み // 内蔵CR 8MHz発振モードだが、3.3Vなので約7.5MHz // 7.5MHz/1024/73=100.33 約100Hz //********************************* SIGNAL(SIG_OUTPUT_COMPARE2) { uchar old_sw; // 汎用タイマ if (Timer){ --Timer; } // Beepタイマ if (BeepTimer){ --BeepTimer; } // ローバッテリー検出による点滅 if (++FlushTimer >= 100){ if (BattVolt < BATT_LOW){ cbi(PORTC, LED); // LED OFF } FlushTimer = 0; } else if (FlushTimer == 50){ sbi(PORTC, LED); // LED ON } // A/D変換スタート if ((FlushTimer % 10) == 9){ // A/D変換開始 プリスケーラ 8MHz/64 = 125kHz outp((1<= 100){ RunningTimeDS = 0; if (++RunningTimeS >= 60){ RunningTimeS = 0; if (++RunningTimeM >= 60){ RunningTimeM = 0; ++RunningTimeH; } } } // スイッチチェック old_sw = SwitchFlag; SwitchFlag = 0; if (!(inp(PIND) & (1<= 20){ Volume += 8; if (Volume >= 0xB0) Volume = 0xA8; VolDownSw = 1; } SwitchFlag |= (1<= 20){ Volume -= 8; if (Volume >= 0xF8) Volume = 0x00; VolUpSw = 1; } SwitchFlag |= (1<= 4) BattVoltP = 0; BattVoltOld[BattVoltP] = volt; BattVolt = BattVoltOld[0]; BattVolt += BattVoltOld[1]; BattVolt += BattVoltOld[2]; BattVolt += BattVoltOld[3]; BattVolt /= 4; // 4回の測定値の平均 } //********************************* // INT0(EasyMP3 DREQ)割り込みルーチン //********************************* SIGNAL(SIG_INTERRUPT0) { // CPUを起こすだけ ; } //********************************* // SPI割り込みルーチン 未使用 //********************************* //SIGNAL(SIG_SPI){ // // outp(0xFF, SPDR); // SD/MMCカードへ次のデータリクエスト //} //********************************* // メインルーチン //********************************* int main (void) { uchar ret; cli(); outp((1<<7)|(1<<6)|(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 > 30){ // 300ms以上の長押しは STOP shutdown(0); // 電源を切る } else { // 短く押した場合は次の曲 PlayMusic++; Cluster = 0; } } 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_LOW){ beep(1, BEEP_LOW); // 警告電圧以下になったら曲間に警告音 } } } //********************************************************* // スイッチが確実に放されるまで待つ //********************************************************* 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; uint n; n = 0; sec = (ulong)DIRstart * 512; for (ent = 0; ent < RootDirEntriesCount; ent++ ){ read_32(sec); sec += 32; if (DataBuff[0] == 0){ break; // 最後まで探したが無かった } if (!(DataBuff[11] & 0x18)){ // ディレクトリやボリューム以外 if (((DataBuff[26] != 0)||(DataBuff[27] != 0))&& // クラスタ番号 (DataBuff[0] != 0xE5)&& // 削除マーク (DataBuff[8]=='M')&&(DataBuff[9]=='P')&&(DataBuff[10]=='3')){ // 拡張子MP3 n++; if (--num == 0) break; } } } return n; } //**************************************** // MP3ファイルを再生 // fn: 曲番号 // clst: 演奏開始クラスタ。==0 の場合は最初から // remainsec: 再生データ残りセクタ数 //**************************************** char play_music(uint fn, uint clst, long remainsec){ ulong sec; uint remain; uint cn; uint i; uchar *p; uint last_cluster; 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; // 途中から再生する場合はあらかじめ 2048個のゼロを送っておく cbi(PORTB, MP3_DATA_CS); for (i = 0; i < 2048; i++){ SPI_out(0); } sbi(PORTB, MP3_DATA_CS); } else { // 最初から再生 Cluster = (uint)DataBuff[26] + ((uint)DataBuff[27] << 8); RemainSec = MusicSize / 512; } remain = MusicSize % 512; // 1セクタ分に満たない分 if ((Cluster == 0xFFFF)||(Cluster < 2)){ return -2; // クラスタ異常 } last_cluster = check_cont_cluster(Cluster); StopSw = 0; RewSw = 0; FfSw = 0; cn = SectorsPerCluster; sec = (((ulong)Cluster - 2) * (ulong)SectorsPerCluster + DataStart) * 512; Timer = 100; // READYタイマーセット // MP3_init(); while (RemainSec > 0){ uchar j, n; set_volume(Volume); // データバッファに1セクタ分のデータ読み込み if (read_card(sec)) return -2; // VS1011へ転送 cbi(PORTB, MP3_DATA_CS); p = DiskCache; for (j = 0; j < 16; j++){ sbi(GICR, INT0); // DREQ立ち上がり割り込み許可 while (!(inp(PIND)&(1< 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; // 同じ曲を継続再生する場合に備えて残りセクタ数を補整 RemainSec += (SectorsPerCluster - cn); 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; } --RemainSec; if (--cn > 0){ sec += 512; // 次のセクタ } else { // 次のクラスタ if (++Cluster > last_cluster){ // クラスタが不連続だったので読み直し 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(); // バッテリーチェック } NoBusyRetry = 0; // 1セクタ(512bytes)未満の分を再生 if (remain > 0){ // データバッファに1セクタ分のデータ読み込み if (read_card(sec)) return -2; // VS1011へ転送 cbi(PORTB, MP3_DATA_CS); p = DiskCache; for (i = 0; i < remain; i++){ sbi(GICR, INT0); // DREQ立ち上がり割り込み許可 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; } //********************************* // 実際にレジスタ操作して音量設定 //********************************* void set_volume(uchar vol){ if (vol != VolumeReg){ MP3_command(11, ((uint)Volume << 8)|((uint)Volume)); // 音量設定 VolumeReg = vol; } } //**************************************** // MMC/SDカードから 2byteを読む // セクタ境界のアクセスはできないので // 上位工程で考慮すること //**************************************** uint read_word(ulong addr){ uint w; uint b; b = addr & 511; addr &= 0xFFFFFE00; read_card(addr); w = (uint)DiskCache[b++]; w |= ((uint)DiskCache[b] << 8); return w; } //**************************************** // MMC/SDカードから 32byteを読んで DataBuffに格納 //**************************************** void read_32(ulong addr){ uint b; uchar *p; int i; b = addr & 511; addr &= 0xFFFFFE00; read_card(addr); p = DataBuff; for (i = 0; i < 32; i++){ *p++ = DiskCache[b++]; } } //******************************************************* // SD/MMCカードから1セクタを DiskCacheに読み込み //******************************************************* int read_card(ulong addr){ uchar *p; int i; int r; // ディスクキャッシュに入っていたら何もせずに戻る if (addr == DiskCacheAddr) return 0; cli(); // 割込み禁止 sbi(SPSR, SPI2X); // SPIクロックを2倍 4MHz if (!SPI_read_open(addr)){ p = DiskCache; for (i = 0; i < 512; i++){ *p++ = SPI_in(); } SPI_read_close(); DiskCacheAddr = addr; r = 0; } else { r = -2; } cbi(SPSR, SPI2X); // SPIクロック 2MHzにもどす sei(); // 割り込み許可 return r; } //******************************************************* // バッテリー電圧を調べ、低すぎればシャットダウンする //******************************************************* int check_batt(void){ if (BattVolt < BATT_SHUTDOWN){ sbi(PORTB, SPI_CS); // SPI CS=H beep(4, BEEP_LOW); cli(); // 割込み禁止 if (RunningTimeH){ write_eeprom(EEPROM_RUNNING_TIME_OLD_M, RunningTimeM); write_eeprom(EEPROM_RUNNING_TIME_OLD_H, RunningTimeH); RunningTimeM = 0; RunningTimeH = 0; } shutdown(0); // シャットダウン } return BattVolt; } //********************************************************* // シャットダウン // flag != 0 の場合は EEPROM書き込みを行わずに電源を切る //********************************************************* void shutdown(char flag){ cli(); // 割込み禁止 cbi(PORTD, MP3_RESET); // VS1011 reset 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); write_eeprom(EEPROM_RUNNING_TIME_M, RunningTimeM); write_eeprom(EEPROM_RUNNING_TIME_H, RunningTimeH); } for (;;){ cbi(PORTC, POWER); // POWER保持解除 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){ unsigned char vol; cli(); // 割込み禁止 MP3_init(); MP3_command(0, 0x0820); // Reset解除, SM_SDINEW=1, SM_TESTS=1 #if BEEP_VOL_DIFF // ボリュームを下げる vol = Volume + BEEP_VOL_DIFF; // 下げ過ぎると聞こえなくなるので適当にクリッピング if (vol >= BEEP_VOL_DIFF){ if (vol > (256 - BEEP_VOL_DIFF)){ vol = 256 - BEEP_VOL_DIFF; } MP3_command(11, ((uint)vol << 8)|((uint)vol)); // 音量設定 } else { vol = 0; } #endif sei(); // 割り込み許可 SLEEP; // 少し Waitが必要らしい SLEEP; SLEEP; SLEEP; for (; n > 0; --n){ if (err){ MP3_sin(ErrorBeep); } else { MP3_sin(BeepData); } // 時間待ち。この間が BEEPの音の長さになる BeepTimer = 10; // 約100ms while(BeepTimer){ SLEEP; } MP3_sin(BeepStop); // BEEPの音間 約30ms BeepTimer = 3; while(BeepTimer){ SLEEP; } } #if BEEP_VOL_DIFF if (vol != 0){ cli(); // 割込み禁止 MP3_command(11, ((uint)Volume << 8)|((uint)Volume)); // 音量を戻す sei(); // 割り込み許可 } #endif MP3_command(0, 0x0800); // SM_SDINEW=1, SM_TESTS=0 } //**************************************** // VS1011 初期化 //**************************************** void MP3_init(void){ outp(0x5C, SPCR); // SPIマスタ, CPOL=1, CPHA=1, Clock 8MHz/4=2MHz SPI_in(); MP3_command(0, 0x0804); // Soft Reset InitWait(); MP3_command(0, 0x0800); // Reset解除, SM_TESTS=1 InitWait(); while(!(inp(PIND)&(1<> 8); SPI_out(arg); sbi(PORTB, MP3_CTRL_CS); } //**************************************** // VS1011 コマンド入力 //**************************************** uint MP3_command_read(uchar addr){ uint r = 0; sbi(PORTB, SPI_CS); sbi(PORTB, MP3_DATA_CS); cbi(PORTB, MP3_CTRL_CS); SPI_out(3); SPI_out(addr); r = SPI_in() << 8; r |= SPI_in(); sbi(PORTB, MP3_CTRL_CS); return r; } //**************************************** // 初期化時のクロックウエイト //**************************************** void InitWait(void){ uchar i; for(i=0; i< 255; i++){ NOP; } } //**************************************** // MMC/SDカード読み込みオープン //**************************************** uchar SPI_read_open(ulong adrs){ uint i; uchar r; uchar retry; // カードによってはリトライが必要のようだ for (retry = 0; retry < 240; retry++){ cbi(PORTB, SPI_CS); // SPI CS=L r = SPI_command(17, adrs); if (r == 0){ for (i = 0; i < 8000; i++){ // ものすごく待つ場合がある r = SPI_in(); if (r == 0xFE){ return 0; // 転送開始マーク } } } else if (r == 0xFE){ return 0; // 正常オープン } sbi(PORTB, SPI_CS); // SPI CS=H SLEEP; } return 1; // オープンできなかった } //**************************************** // MMC/SDカード読み込みクローズ // 1byteのずれがあっても気にしない //**************************************** void SPI_read_close(void){ uint i; SPI_in(); // CRC SPI_in(); // CRC // BUSYチェック for (i = 0; i < 3000; i++){ if (SPI_in() == 0xFF) break; } sbi(PORTB, SPI_CS); // SPI CS=H } //**************************************** // MMC/SD SPIコマンド出力 //**************************************** uchar SPI_command(uchar com, ulong arg){ uint i; uchar r; // BUSYチェック 某社のカードでは必須みたい for (i = 0; i < 3000; i++){ r = SPI_in(); if (r == 0xFF) break; } if (r != 0xFF) return 1; SPI_out(com|0x40); SPI_out(arg >> 24); SPI_out(arg >> 16); SPI_out(arg >> 8); SPI_out(arg); SPI_out(0x95); // 初期化コマンドのCRC for(i = 0; i < 2000; i++){ r = SPI_in(); if (r == 0xFE) break; if (!(r & 0x80)){ // if (r == 0x00){ // SPI_in(); // レスポンス入力後、8クロック必要なのだそうな break; } } return r; } //**************************************** // SPIデータ出力 //**************************************** void SPI_out(uchar data){ outp(data, SPDR); while(!(inp(SPSR) & 0x80)); } //**************************************** // SPIデータ入力 //**************************************** uchar SPI_in(void){ outp(0xFF, SPDR); while(!(inp(SPSR) & 0x80)); return inp(SPDR); } //**************************************** // SPIクロック終了待ち // SPDRをアクセスしてない場合は // SPIF=0になって無限ループする場合があることに注意 //**************************************** void SPI_wait(void){ while(!(inp(SPSR) & 0x80)); } //********************************************************* // A/Dコンバータデータ入力 // ch : A/Dチャンネル // 戻り値: A/D変換値(0〜1023) //********************************************************* uint ADCin(char ch){ uint val; outp(((1<