下が回路図です.EEPROM(24FC512)と音源IC(YMZ294)は一つずつしか描かれていませんが,いちおう最大8つずつまで増やすことができます.

74HC165は,パラレルをシリアルに変換するシフトレジスタです.今回の回路では,曲の再生開始を指示する端子が8つ必要ですが,このICを使うことで入力を3つに削減しています.
74HC565は,シリアルをパラレルに変換するシフトレジスタです.YMZ294にデータを送るには8本のピンが必要で,さらに将来YMZ294を最大8つまで接続した場合にはそのチップセレクトにも8本のピンが必要なので,合計16本のピンが必要になるわけですが,この回路では74HC565を2つ使うことで,必要なピンの数を3本に削減しています.そのうち1本はクロックで,74HC165と共通で使っているので,実質的には2本しかピンを使っていません.(あとから気付きましたが,74HC565のSTCPと74HC165の/PLも一緒にすればさらにピンの本数を減らせました.)
そしてこちらがブレッドボードで回路を作ったところです.左から,発振器,YMZ294,74HC565が2つ,ATTINY2313,24FC512,74HC165 の順で並んでいます.

まずは,音源ICとシフトレジスタの動作を確かめるために,以下のようなプログラムを作りました.動作としては,74HC165の入力(プルアップしている)を読み,GNDとショートしていたらYMZ294からそれに対応する高さの音を出すというだけのシンプルなものです.
#include <avr/io.h>
#define setBit( port , bit , flag ) ( (port) = ( (port) & ~(1<<(bit)) ) | ( (flag) << (bit) ) )
#define getBit( port , bit ) ( ( (port) >> (bit) ) & 1 )
void wait()
{
for ( volatile uint16_t i = 0 ; i < 0xFF ; ++i ) ;
}
namespace ShiftRegister
{
const uint8_t DS = PB2;
const uint8_t STCP = PB1;
const uint8_t SHCP = PB0;
const uint8_t nPL = PD2;
const uint8_t Q7 = PD3;
inline void initialize()
{
DDRB |= (
( 1 << DS ) |
( 1 << STCP ) |
( 1 << SHCP ) );
DDRD |= 1 << nPL;
setBit( PORTB , SHCP , 0 );
setBit( PORTB , STCP , 0 );
setBit( PORTD , nPL , 1 );
}
void writeWord( uint8_t msb , uint8_t lsb )
{
for ( int8_t i = 7 ; i >= 0 ; --i )
{
setBit( PORTB , DS , ( msb >> i ) & 1 );
setBit( PORTB , SHCP , 1 );
setBit( PORTB , SHCP , 0 );
}
for ( int8_t i = 7 ; i >= 0 ; --i )
{
setBit( PORTB , DS , ( lsb >> i ) & 1 );
setBit( PORTB , SHCP , 1 );
setBit( PORTB , SHCP , 0 );
}
setBit( PORTB , STCP , 1 );
setBit( PORTB , STCP , 0 );
}
uint8_t readByte()
{
setBit( PORTD , nPL , 0 );
setBit( PORTD , nPL , 1 );
uint8_t result = 0;
for ( int8_t i = 0 ; i < 8 ; ++i )
{
result <<= 1;
result |= getBit( PIND , Q7 );
setBit( PORTB , SHCP , 1 );
setBit( PORTB , SHCP , 0 );
}
return result;
}
}
namespace YMZ294
{
const uint8_t nWR = PB4;
const uint8_t A0 = PB3;
const uint8_t nRESET = PB6;
void initialize()
{
DDRB |=
( 1 << A0 ) |
( 1 << nWR ) |
( 1 << nRESET );
setBit( PORTB , nWR , 1 );
setBit( PORTB , nRESET , 1 );
}
void send( uint8_t device , uint8_t addr , uint8_t data )
{
setBit( PORTB , A0 , 0 );
ShiftRegister::writeWord( ~( 1 << device ) , addr );
setBit( PORTB , nWR , 0 );
setBit( PORTB , nWR , 1 );
setBit( PORTB , A0 , 1 );
ShiftRegister::writeWord( ~( 1 << device ) , data );
setBit( PORTB , nWR , 0 );
setBit( PORTB , nWR , 1 );
}
}
int main(void)
{
ShiftRegister::initialize();
YMZ294::initialize();
wait();
YMZ294::send( 0 , 0x07 , 0x3E );
YMZ294::send( 0 , 0x08 , 0x0F );
while(1)
{
switch ( ( ~ShiftRegister::readByte() ) & 0xFF )
{
case 0x01 : YMZ294::send( 0 , 0x00 , 240 ); break;
case 0x02 : YMZ294::send( 0 , 0x00 , 200 ); break;
case 0x04 : YMZ294::send( 0 , 0x00 , 160 ); break;
case 0x08 : YMZ294::send( 0 , 0x00 , 120 ); break;
case 0x10 : YMZ294::send( 0 , 0x00 , 100 ); break;
case 0x20 : YMZ294::send( 0 , 0x00 , 80 ); break;
case 0x40 : YMZ294::send( 0 , 0x00 , 60 ); break;
case 0x80 : YMZ294::send( 0 , 0x00 , 50 ); break;
}
if ( !getBit( PIND , PD4 ) )
YMZ294::send( 0 , 0x00 , 0x00 );
wait();
}
}
これを動作させた様子が次の動画です.
この通り,最終的にはうまく動いたのですが,初めて扱うAVRということで,引っかかった点がいくつかありました.
まず,そもそもの問題として,どうやってプログラムを書いたらいいのかわかりませんでした.アセンブリ言語で書く方法はデータシートを読めばなんとなくわかりましたが,CやC++で書く方法がわからず,リファレンスのようなものがあるのかと思って探しても見つかりませんでした.
しかたなく,データシートに載っているコードの断片と開発環境に附属するヘッダファイルを,時間を掛けて真面目に読んでいったところ,たどり着いた結論はいたって単純で,レジスタの名前が付いた,アドレスを直接参照するマクロに対して,ただ書き換えを行えばいいということでした.
マイコンのプログラミングに慣れている人にとっては疑いようのない常識なのでしょうが,なにをするにも関数ありきの通常のプログラミングから考えると,むしろその発想はなかったというようなスタイルです.
というわけで,プログラミングの方法はなんとなく理解できたので,あとはひたすらデータシートを読みながらプログラムを書きます.そして,なんとなく動くであろうというプログラムが完成しました.
さっそくジャンパー線を使ってマイコンとライターをつなぎ,さらにライターをUSBでパソコンに繋ぎ,いざ書き込もうと思ったところで問題が発生しました.AVRISP mkIIが,認識はされるものの,Atmel Studioから接続しようとするとエラーが出てうまく接続できません.
……と,今これを書いていて,再現性を確認するためにもう一度試したのですが,なぜか接続できるようになっていました.先日は,何度やってもうまくいかず,ドライバを何度も再インストールしたり,OSを再起動したり,いろんなUSBポートに刺したりしてみたのですが,まったくだめでした.エラーコードをググってもそれっぽい情報は見つからず,同じような症状の報告もなさそうだったので,環境の問題だろうと思って別のマシンで試したところ,予想通り一発でうまくいったのですが,結局原因はわかりませんでした.
さて,こうしてなんとかAVRISPを接続し,プログラムを書き込み,動作させてみました.当たり前ながら,初めて書いたプログラムがいきなりうまく動作するはずもなく,音声出力に繋いだスピーカーはうんともすんとも言わないので,ソースを睨んでバグを探していきます.便利なデバッガのあるパソコン上でのプログラミングと違い,マイコンのデバッグはほぼ手探り状態です.(いちおう,動作しているマイコンに接続して,ステップ実行や変数の内容表示などを可能にする装置も存在するようです)
そして,データシートも読みながらデバッグすること数時間.原因は,レジスタの使い方の勘違いでした.正しくは,DDRxで入力/出力の設定,PORTxで出力値の設定,PINxで入力値の読み出し,となっているのですが,入力と出力でレジスタが違うことを理解しておらず,PORTxから値を読み出そうとして正しい値が得られていませんでした.
この点を修正したのがさきほどのソースコードで,動画の通りめでたくうまく動きました.
これで,AVRのプログラミングの基本はなんとなくわかった気になったので,次はUSI (Universal Serial Interface) を使ったEEPROMの読み出しをやります.この記事ではここまで.
コメント