トランジスタアレイを使う

トランジスタや抵抗の数を減らすためにトランジスタアレイを使うことにした。
トランジスタアレイは、TD62064APGというNPN型の4chのものを使用した。

7セグ3桁のトランジスタ・抵抗を置き換える

7セグで使っていたトランジスタと抵抗を置き換えることにした。
7セグ一つに対して、トランジスタ1つと抵抗が二つ減ることになるので、だいぶスッキリするはずだ。

f:id:happytar0:20130723182227p:plain

4chなので、O1~4、I1〜4が各セットあり、中央にGND、そしてCOM(コモン)が二つある。
Oはアウトプット、Iはインプットということらしい。
I1〜4にマイコン、O1〜4は7セグのコモンと接続する。
COMには、トランジスタを保護するためのダイオードが付いていて、これをフリーホイルダイオードというらしい。
誘導性コイル負荷(モータ、ソレノイド、リレー等)を使わなければ、特に接続する必要はないようだ。

ベース抵抗なども付いているので、計算などは必要なく、そのまま置き換えればいいだけなので簡単だった。

回路図を書いてみる

あまりにもさっくり終わってしまったので、回路図を書いてみることにした。
最初はドローソフトで適当に書いてみたのだけど、回路図を描くのが意外と楽しかったので、専用ツールを使って書き直した。

OSXでも使える回路図作成ツールを探してみると、BSchV3というのを見つけた。古くからあるツールで有名らしい。
回路図の読み方を調べつつ適当に描いてみた。合ってるかな?

f:id:happytar0:20130723183526p:plain

参考
電子部品使い方:マイコン出力のスイッチ(トランジスタ2)
トランジスタアレイ「TD62083AP」のホントの使い方
初歩のPIC【18】デジタルは5Vであらず・出力編
Qt-BSch3V の使い方 – ルギア君の戯言

USBシリアル変換モジュールを使う

またもや電子工作をサボってしまった…。
プライベートもプログラミングをバリバリやってる人はすごいと思う。

この間マイコン先生から電子工作用の部品を色々いただいた。
ジャンパー線やLCDなど便利なものばかり…感謝感謝。

どうやら半年くらいご無沙汰だったみたいだ。
今回さわったシリアル変換モジュールなどの部品は、実は正月に買っておいた。
ケースやら基盤なども買い込んだので、早いところ温度計を完成させてもいいのだけど、リハビリも兼ねてシリアル通信をやってみることにした。

USBシリアル変換モジュールをマイコンに接続

購入したシリアル変換モジュールは、秋月電子で売っていたもので、FT232RLというUSB-シリアル変換用のICが基盤にくっついるものだ。
接続は簡単で、バスパワーでUSBから電源を確保できるので、GND、TXDとRXDをマイコンと接続するだけで大丈夫らしい。
マイコンはATTINY2313を使用した。

TXDとRXD

変換モジュールに付属されていた説明書にも記載されているのだけど、TXDは「通信データ出力」、RXD「通信データ入力」ということらしい。
これをマイコン側のTXDとRXDにクロスして接続する。最初にクロスで接続することに気づかず焦った。
要は、変換モジュールのTXDをマイコンのRXDに、変換モジュールのRXDをマイコンのTXDに接続する。

変換モジュールとPCを接続

ドライバが必要なようだったので、下記のサイトからダウンロードした。MacBookAirを使っていたので、OSX版をインストールした。
Virtual COM Port Drivers

screenでシリアル通信も出来るようだったのだけど、他に使いやすいものがないか探したところ、CoolTermというアプリを発見した。とりあえず、こいつで行くことにした。
Roger Meier's Freeware

USART(UART)

詳しい説明はググれば出てくるのでざっくり説明すると、
1本の信号線上の信号をプロトコルに従い「1」、「0」でデータを送る通信方法。
全二重の非同期通信、半二重の同期通信の二通りがあるようだ。

ボーレート

変復調速度のことでマイコンのクロック数によって設定する値が変わる。マイコンのデータシートに一覧が載っているので、クロック数と通信速度から適切なものを選べばいい。
一般的な通信速度9600bpsで通信することにした。
マイコンのクロック数は8MHzなので、設定するボーレートは51となった。

シリアル通信してみる

よく使われている設定でシリアル通信をおこなうことにした。
設定内容は、非同期通信、パリティ禁止、停止ビット1bit、データビット長8ビット。

まずは初期化処理のコードは下記のようにした。

UBRRL = 51; // 9600bps
UCSRB= (1 << RXEN) | (1 << TXEN);
UCSRC = (1 << UCSZ1) | (1 << UCSZ0);

UBRRLレジスタにボーレート値を入れる。
UCSRBレジスタで、RXENとTXENのビットを立てる。RXENが「受信許可」、TXENが「送信許可」になる。
UCSRCレジスタは、UCSZ1とUCSZ0が「データビット長」になる。
8ビットの場合は、「011」になるので、UCSZ1とUCSZ0の両方のビットを立てればOKだ。
本当は、UCSZ2もあるのだけど、これはUCSRBレジスタのほうに入っている…紛らわしい。今回は8ビットなので特に設定する必要はない。

コードは、ググればたくさん出てくるし、データシートにも書いてあるので特に迷うことはなかった。
とりあえず、シリアル通信の送受信を実装し、ターミナルから送信した文字列をそのままエコーバックさせるコードを書いてみた。

コードはこちら
pontago/avr-SirialTest · GitHub

f:id:happytar0:20130717002242j:plain
f:id:happytar0:20130717003117p:plain

CoreDataのFetchRequestテンプレートで配列を指定

クライアントアプリのコード内にSQL分を書くのがどうもしっくりこないので、FetchRequestのテンプレを使うようにしている。
単純なデータを格納することが多いせいか、テンプレだけで問題になったことはない。

fetchRequestFromTemplateWithNameメソッドでテンプレ名とプレースホルダの変数を渡すのだけど、IN句を使う必要が出てきて少し悩んだ。

テンプレのSQL分には最初、field_name IN ($hoge) のような形で設定してみたのだけど勝手に括弧が外れて、 field_name IN $hoge になった。
こういうもんなのかと思い、プレースホルダにカンマ区切りの文字列を渡してみたがエラー・・・。

どうやらNSArrayをそのまま渡せばいいだけのようだ。

NSArray *ids = @[@1, @2, @3, @4];
NSDictionary *subs = @{@"hoge":ids};
NSFetchRequest *fetchRequest = [managedObjectModel fetchRequestFromTemplateWithName:@"findByIds"
substitutionVariables:subs];

何度も使う場合、テンプレを使うことでSQL文のパース処理を省くことができるので若干高速化するようだ。
NSPredicateなんてのもあるんだなぁ・・・

PSCollectionViewでハマる

Pinterest風の表示にしたいと思って色々ライブラリをあさって見たところ、PSCollectionViewという便利そうなライブラリを発見した。
iOS6以上であれば、UICollectionViewというそのものズバリのものがあるようだ。

heightForRowAtIndexメソッドの戻り値に個々の高さを指定してやることで、高さを個別に設定することも可能だった。
戻り値にCGFloatで値を返すのだけど、小数部の桁数が増えると誤差が生じて、上手くタップを認識しなくなることがあるみたい。

調べてみると、セルの大きさをNSStringFromCGRectでNSString化して管理しているようだ。

// Calculate index to rect mapping
self.colWidth = floorf((self.width - kMargin * (self.numCols + 1)) / self.numCols);
for (NSInteger i = 0; i < numViews; i++) {
NSString *key = PSCollectionKeyForIndex(i);
// Find the shortest column
NSInteger col = 0;
CGFloat minHeight = [[colOffsets objectAtIndex:col] floatValue];
for (int i = 1; i < [colOffsets count]; i++) {
CGFloat colHeight = [[colOffsets objectAtIndex:i] floatValue];
if (colHeight < minHeight) {
col = i;
minHeight = colHeight;
}
}
CGFloat left = kMargin + (col * kMargin) + (col * self.colWidth);
CGFloat top = [[colOffsets objectAtIndex:col] floatValue];
CGFloat colHeight = [self.collectionViewDataSource collectionView:self heightForRowAtIndex:i];
CGRect viewRect = CGRectMake(left, top, self.colWidth, colHeight);
// Add to index rect map
[self.indexToRectMap setObject:NSStringFromCGRect(viewRect) forKey:key];
// Update the last height offset for this column
CGFloat heightOffset = colHeight > 0 ? top + colHeight + kMargin : top;
[colOffsets replaceObjectAtIndex:col withObject:[NSNumber numberWithFloat:heightOffset]];
}

セルのタップは、UITapGestureRecognizerを使っているようで、shouldReceiveTouchメソッドで、どのセルがタップされたか判定して、didSelectメソッドが呼びされるようだ。
heightForRowAtIndexで返される高さの小数部が増えてくると、下記のrectStringと値が一致しなくなり、タップを認識しなくなる。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (![gestureRecognizer isMemberOfClass:[PSCollectionViewTapGestureRecognizer class]]) return YES;
NSString *rectString = NSStringFromCGRect(gestureRecognizer.view.frame);
NSArray *matchingKeys = [self.indexToRectMap allKeysForObject:rectString];
NSString *key = [matchingKeys lastObject];
if ([touch.view isMemberOfClass:[[self.visibleViews objectForKey:key] class]]) {
return YES;
} else {
return NO;
}
}

解決方法は簡単で、heightForRowAtIndexで値を返す際に、小数部が増えないようにしたり、丸めてやればいい。

- (CGFloat)collectionView:(PSCollectionView *)collectionView heightForRowAtIndex:(NSInteger)index {
return floorf(100.123456789f);
}

投稿しようとして気づいたのだけど、Objective-C(Cocoa)ネタ書いたの初めてだった。
iPhoneアプリを作っていると、ブログに書くようなネタがなかなか見つからない…
ちょっとググれば情報がたくさん見つかるし書くまでもないかなーとか思っちゃうせいだろうか。

温度センサーとA/D変換

7セグも使うことが出来たので、次は温度センサーを使っていくことに。
これで無事に温度が表示出来れば、ほぼ完成したようなものだ。

温度センサーはLM60というものを使う。
このセンサーには3つの足が付いていて、それぞれVs、Vout、GNDとなる。
Vs(VCC)は電源、GNDにマイナスを接続すると、Voutに現在の温度を表す電圧が出力される。

AVRにはA/D変換の機能が標準で付いているので、これを利用することで現在の温度を取得できる。

A/D変換とは

そのまんまなのだけど、アナログ値をデジタル値として変換することだ。
変換するには、マイコンの変換用ポートPD0〜5を利用する。
変換するために使う基準電圧は、内部基準電圧使うか、外部電圧を使うか選択できます。
ATMEGA328Pの場合、内部基準電圧は1.1Vです。

LM60のデータシートを見る

まずはLM60の仕様を把握することにした。

データシートはこちら
http://akizukidenshi.com/download/LM60.pdf

  • 40度〜125度まで測定出来るらしく、DCオフセット424mV、6.25mV/℃ということらしい。

使用する電圧の範囲は174mV〜1205mVとなる。

基準電圧は出力される電圧以上なければいけないので、1.1Vだと以下になる。
(1100mV – 424mV) / 6.25mV = 108.16度
内部基準電圧だと108度くらいまでしか計測出来ないが、今回は良しとした。(108度を越える環境で使わないし…)

A/D変換を使って温度を取得

まず配線は、マイコンのAREFとGND間に0.1μFのコンデンサ、GNDとAVCCは電源に接続する。
PC0には温度センサーのVoutを接続する。

続いてコードだが、A/D変換を利用するには、ADMUXとADCSRA、ADCH、ADCLレジスタなどを使う。
ADMUXの7ビットと6ビット目で基準電圧を指定する。両方「1」にすることで内部基準電圧となる。
5ビット目は、左揃えにするか右揃えにするかという設定だが、これは取得できる電圧が0〜1023の10ビットの値なので、レジスタ2つ分使うことになる。
その際に上位ビットをどのように格納するかの設定が、この5ビット目になる。

0〜4ビット目は接続しているポートを表すビットを指定する。

ADCLは変換した値の下位ビット、ADCHは上位ビットになる。
ADCSRAで実際にA/D変換を有効にする設定だ。
詳しくは下記のサイトを見るのが手っ取り早いかもしれない。

A/D 変換でボリューム(可変抵抗)値を読む

変換される電圧は前述した通り、0〜1023の範囲で変換されて返される。
174mV〜1205mVを1.1Vの基準電圧を使って、0〜1023の範囲で変換されて返されるのだけど、この精度のことを分解能と言うらしい。

1.1V / 1024 = 0.001
3.0V / 1024 = 0.002

基準となる電圧によって精度も変わってくるようだ。

A/D変換された値を使って温度を求める計算式は下記のようになる。

(A/D変換された値 * 基準電圧 / 1024 – 0.424) / 0.00625 = 温度
(500 * 1.1V / 1024 – 0.424) / 0.00625 = 18.0975度

あとは計算して出てきた値を7セグへ表示するだけだ。
無事に成功!
f:id:happytar0:20130204061212j:plain

実はドハマリしてた

なぜか上手く温度が取得出来なくて、あーだこーだと試行錯誤していた。
原因はマイコン右側ポートのAVCCとGNDを接続していなかったことだ。
内部基準電圧を使うから必要ないと思っていたが、内部基準電圧はAVCCとGNDから確保されるらしい…。

今回の温度を測るコードはこちら
pontago/avr-7SegLedTemp · GitHub

7セグLEDの3桁表示に挑戦

前回の記事で1桁の7セグ表示することができたので、今回は3桁点灯させてみることにした。
3桁点灯させるにはトランジスタが必要ということで、いよいよトランジスタを使うことになる。

ここでトランジスタやダイナミックドライブについて調べていくと、
ATMEGA328PのI/Oピンは最大40mAなので、カソードコモンをマイコンに接続すると最大電流量を越えてしまうようだ。

各セグメント10mAで8本、合計80mA流すと、カソード側が80mAになる。
これを回避するためにトランジスタを使うらしく、少ない電流から大きい電流に増幅出来るので、最大40mAを超えないで制御できるそうだ。

トランジスタを使う

とにかくトランジスタを使ってみることにした。
使うトランジスタはNPN型の2SC1815GRというものだ。
他にPNP型というものもあるようだけど、今回はカソードコモンの7セグなので出番はないようだ。

トランジスタには、コレクタ、ベース、エミッタという3本の足があり、ベースに電流を流すことで、エミッタに増幅された電流が流れていくらしい。
この増幅率をhFEというようだ。

エミッタを7セグのコモン端子に繋ぎ、ベースにマイコンのPB0〜2ポートへと繋ぐ。
また、エミッタとベース間には安定化させるための抵抗10kΩ、ベースとマイコンポート間には4.7kΩを繋ぐ。

以前にすでに計算しているのだけど、マイコンとトランジスタ間に繋ぐ電流制限抵抗値は、以下のようにして計算した。

このトランジスタの電流増幅率(hFE)は100で、
トランジスタを通すことによって0.7Vほど電圧降下がすることを考慮して、以下ように計算した。
(5V – 0.7V) * 100hFE / 100mA = 4.3kΩ
4.3kΩに近い4.7kΩの抵抗を使うこととした。

ダイナミックドライブさせる

3桁の7セグを高速で切り替えることで、3桁とも点灯しているように見せるのが、ダイナミックドライブ(ダイナミック点灯)と言うらしい。

表示する桁のポートにビットを立てながら切り替えるだけなので、コードはさほど難しくなかった。
下記のように関数を作って、表示したい桁と数字(ドットなど)を指定できるようにした。

#define DIG_MAX 3
#define NUM_MAX 11
void showNumber(char dig, char num) {
char nums[] = {0b00111111, 0b00000110, 0b01011011, 0b01001111,
0b01100110, 0b01101101, 0b01111101, 0b00000111, 0b01111111,
0b01101111, 0b10000000};
if (dig < DIG_MAX && num < NUM_MAX) {
PORTB = 1 << dig;
if (num == -1) {
PORTD = 0b00000000;
}
else {
PORTD = nums[num];
}
}
}

3桁順番に数字などを表示させてみた。配線が汚くて恥ずかしい…。
f:id:happytar0:20130204043707j:plain

今回のコードはこちら
pontago/avr-7SegLed3Dig · GitHub

参考
今から始めるAVR #2 ATtiny2313 7セグ4桁ボード〜そこ(7セグ)んとこ、詳しく

7セグLEDを使う

いよいよ7セグLEDを使う時がきた。
7セグLEDを使えば、0から9までの数字(あとドット)を表示させることができるのだ!
温度計には無くなてはならないものと言っていい。

購入した7セグLEDは、赤色で3桁表示するもので、C-533SRという型のものだ。
7セグメントLED表示器 超高輝度赤色3文字(3桁)(カソードコモン)C−533SR: LED(発光ダイオード) 秋月電子通商 電子部品 ネット通販

7セグには足が12本付いていて、どこから手を付けていいか全くわからない。
今回はこんな流れでやってみようと計画を立てる。

  • 7セグの各配線の意味を調べる
  • 1セグメント点灯させてみる
  • マイコンから数字を表示させてみる

7セグの足(各配線)はどうなっているのか

3桁表示できる7セグなので3つの7セグがくっついている。
まずは、各配線の意味を調べるためにデータシートを見てみることにした。
http://akizukidenshi.com/download/ds/paralight/C-533SR.pdf

分かるような分からないような…集中して何度も確認してみた。
どうやら各セグメント(数字を構成する各LED)ごとに、AからGまでのアルファベットが割り当てられている。
そのアルファベットの下には数字が書いてあるので、配線の順番ということだろう。

f:id:happytar0:20121226233158p:plain
f:id:happytar0:20121226233206p:plain

アルファベットを各配線に割り当てるとこうなる。

f:id:happytar0:20121226233205p:plain

1セグメント点灯させる

続いて1セグメントだけ点灯させてみることにした。
図でいうと左上の「F」の箇所だ。
ちなみに、DIG1〜3にはカソードコモンなのでGNDを接続する。

データシートを見るとVf1.8〜2.2で、最大20mAまで流せるようだ。
以前と同じ240Ωの抵抗をそのまま使うことにした。

無事に左上が点灯したところ
f:id:happytar0:20121225003048j:plain

マイコンから数字を表示させる

次はマイコンに接続し、数字を表示させることにした。
ここからが勝負の時だ…。

トランジスタを間に挟んで接続する予定だったが、
とりあえず動作確認のためにマイコンと一桁(8本)直結させることにした。

ATMEGA328Pの最大電流量は200mAなので、各セグメントに10mAずつ流しても十分に足りると考えた。

マイコンには、PB0〜7まで各セグメントA〜Gを接続。
こうすることでマイコンから制御する際に分かりやすくなるとのこと。

指定セグメントを点灯させるには、LEDと同じように指定ポートのビットを立てるだけだ。

各セグメントを順番に点灯させるコードはこちら
pontago/avr-7SegLedTest · GitHub

0〜9の数字と.(ドット)を順番に表示させるコードはこちら
pontago/avr-7SegLedNum · GitHub

無事に数字を表示させることができた。
f:id:happytar0:20121225030559j:plain

数字を表示させただけでも嬉しくなってしまった。
たいした事をしてなくてもその気になってしまうのだから危険だ。
そろそろトランジスタと温度センサーの出番なので気は抜けない。

ATMEGA328Pを使う

LED点灯を試すためにATTINY2313を一番最初に買ったのだけど、
7セグLEDを使うためには少しポート数が足りなくなってきた。
思い切ってATMEGA328Pを買うことにした。

ただ単純にATTINYと置き換えればいいと思っていたがそうは問屋が卸さない。
割り当てられているポートの順番なども違うようで、
ATTINYと比べながら置き換えていく必要があるようだ。

流れとしてはこんな感じだ。

  • 三端子レギュレータを使って5V降圧した電源を使う
  • ATMEGAにプログラマを接続して認識させる
  • 以前作ったLED点灯回路・プログラムをそのまま流用し、動作を確認する

プログラマとATMEGAを接続する

各ポートに接続する配線については、以前に接続したことがあるATTINYを参考にした。

また、下記のサイトも見ながら試行錯誤してみた。
始めるAVR

各ポートの用途はデータシートを参考に。
http://akizukidenshi.com/download/mcu/avr/attiny2313.pdf
http://akizukidenshi.com/download/mcu/avr/atmega48-88-168-328_A_P_PA.pdf

結果的には、こんな感じで接続してみると上手く認識させることができた。
f:id:happytar0:20121224234928j:plain

ATMEGAでLEDを点灯させてみる

試しに以前作った2つのLEDを交互に点灯させる回路を組んでみることにした。
PD3とPD4それぞれにLEDと抵抗を接続した。
プログラムもそのまま同じもので問題ないのだが、
コンパイルする際にATMEGA用に一部Makefileの書き換えが必要になる。

こんな感じでMakefileをATMEGA328P用に書き換えた。

DEVICE     = atmega328p
PROGRAMMER = -c avrispmkII -P usb -p m328p

以前作ったLEDを点滅させる記事はこちら
AVRでLEDを点滅させるプログラム – フタなしカンヅメ

無事に点灯したところ
f:id:happytar0:20121224235240j:plain

三端子レギュレータの問題が解決してからはスムーズに進んで気持ちがいい。
次はいよいよ7セグLEDを使うことになるので、わくわくが止まらない。

12VのACアダプタから5Vに変換

温度計作りを構想してからどのくらい経っただろうか…
なかなか手を付けるタイミングがなく今年が終わろうとしていたのだけど、
意を決してマイコンいじってみることにした。

まずは、今まで電池ボックスを利用していた箇所をACアダプタから電源を確保することにした。
12VのACアダプタを買った理由は、高い電圧を必要とするものでも使い回せそうな気がしたのと、
三端子レギュレータを使ってみたかったのが理由だ。

12Vではマイコンに繋ぐことができないので、5Vほどに降圧する必要がある。
三端子レギュレータはLM7805CVを利用する。これはかなりメジャーなものでよく使われるものらしい。

ACアダプタのDCジャックそのままでは、ブレッドボードに挿すことができないので、
秋月電子で販売されている「ブレッドボード用DCジャックDIP化キット」を使うことにした。

ブレッドボード用DCジャックDIP化キットを組み立てる

このキットには、DCジャック、ブレッドボードに挿すための足、基盤が付属されている。
半田を使って組み立てる必要があるようだ。
半田ごてを使うのは久しぶりな上に、半田する箇所が小さかったので緊張したのだけど、あっさりできた。

しかし、半田した後に基盤を上下逆にしていたことに気づく…これが後々悩む理由になるとは。
本当ならばプラスとマイナスが印刷されている面を上にした方がいい。

三端子レギュレータを使って5Vに降圧

ACアダプタをブレッドボードに挿した後、テスターを使い12Vが出ていることを確認した。
いよいよ三端子レギュレータの出番だ。

型番が印刷されている方を前面と見て、左がIN(12V)、中央がGND、右がOUT(5V)ということらしい。
発振を防ぐために、INとGND間に積層コンデンサ0.33μF、GNDとOUT間に0.1μFを接続する。

わくわくしながらOUTの5Vをテスターで計測してみる…
ん?5Vになってないどころか電圧がふらふらしている。
しかも三端子レギュレータが異常な熱を帯びていて持てないくらいだ。

コンデンサの繋ぎ方が悪いのかと、繋げ方を変えてみるが変化なし。
数日悩んだところで、お手上げ状態なのでマイコン先生に聞いて見ることにした。

三端子レギュレータの謎が解ける

先生に状況を説明している段階で、あることにふと気づいた。
ACアダプタの基盤を逆に半田付けしていたことだ。
どうやらそのせいで、プラスとマイナスも逆転していたらしい。

なんと、INにマイナス、中央にプラスを接続していたのだ…。
話を聞いたところ、これはかなり危ないミスらしい。三端子レギュレータの異常な熱の原因も分かった。
プラマイ逆にしたところ、無事に5Vにすることができた。

なぜテスターで12V測れていたのだろうと考えたところ、
よくよく見るとテスターの左側に「-」の記号が…どうやら「-12V」と表示されていたことに気づかなかったようだ。

f:id:happytar0:20121224234920j:plain

今回はソフトのようにエラーコードを吐かないハードの難しさを思い知ることとなった。
もう少し慎重に進めていきたいと思う。

アセンブリでLEDを点灯させる【後編】

後編ということで、前回の続きをだらだらと書きたいと思う。
2つのLEDを交互に点灯させたい、ということなので一定間隔で点灯させるために、ディレイ処理を行う必要がある。
C言語であれば_delay_ms関数が使えるのでかなり簡単に実装できるが、アセンブリとなるとクロック周期をカウントしていく必要があるようだ。

指定秒数待機するルーチンを実装する

いろいろと参考サイトを見ていくと、下記のような実装が一番シンプルなようだ。
AVRŽŽ—p‹L-assembly

delay1s:
ldi  r16, 100
mov  r2,  r16
dly2:
ldi  r16, 100
mov  r1,  r16
dly1:
ldi  r16, 200
mov  r0,  r16
dly0:
nop
dec  r0
brne dly0
dec  r1
brne dly1
dec  r2
brne dly2
ret

最初にこれを見せられたらちんぷんかんぷんである。
まず上から順に実行されていくので、r2に100、r1に100、r0に200とレジスタにそれぞれの値が入っていく模様。

NOP命令「無操作」と書いてある。何もせずに1クロック消費するということだ。

DEC命令「汎用レジスタを減少」と書いてある。要はデクリメント(-1減算)ということみたいだ。

BRNE命令「不一致で分岐」と書いてある。0の時に実行されるラベルを指定すると、そこにジャンプするようだ。

RET命令「サブルーチンからの復帰」と書いてある。サブルーチンとして呼び出された場合に、呼び出し元にジャンプするようだ。

これでだいたい分かると思うが、r2で100ループ、r1で100ループ、r0で200ループと、子ルーチンが呼び出されていく。
nop、dec r0、で2クロック、brne dly0の呼び出しは、条件成立時は2、不成立時は1と変化するようだ。ほとんど成立して動作するので、2クロックとカウントしていいと思う。

4 * 100 * 100 * 200 = 8000000 = 8Mhz となる。

解説しているサイトによると、内側のループ処理に5クロックかかるので、

5 * 100 * 100 = 50000 = 50Khz となるようだ。1%未満の誤差ようなのでおよそ1秒となる。

また、r0〜r15までは、使える命令が限定されている。r16~r31については何でも使えるようで使い分けが重要みたいだ。
例えば、LDI命令はr16以上でしか使えないので、一度r16を経由させたあとにMOV命令でr0に値をコピーしている。

待機処理を入れてLEDを交互に点灯させる

先ほどの待機ルーチンを使ってみて以下のように書いてみた。

.include "tn2313def.inc"
main:
ldi   r16, 0b00011000
out   DDRD, r16
ldi   r16, 0b00010000
out   PORTD, r16
rcall delay1s
ldi   r16, 0b00001000
out   PORTD, r16
rcall delay1s
rjmp  main
delay1s:
ldi  r16, 100
mov  r2,  r16
dly2:
ldi  r16, 100
mov  r1,  r16
dly1:
ldi  r16, 200
mov  r0,  r16
dly0:
nop
dec  r0
brne dly0
dec  r1
brne dly1
dec  r2
brne dly2
ret

RCALL命令「PC相対サブルーチン呼び出し」と書いてある。そのままの意味でサブルーチンの呼び出しである。
さっそくマイコンに転送してみたところ、非常にゆっくり点灯しているようだ…。
なぜ…という感じだが、クロック周波数がおかしいのかもしれない。

マイコンに設定されいてるクロック周波数を確認してみることにした。

マイコンのクロック周波数を確認する

「CKDIV8」というワードが重要なようだ。
調べてみると、CPUクロックの分周比を設定します、と書いてある。

マイコンに設定されているヒューズ情報を確認する必要があるので、以下のコマンドを実行してみた。

# 対話モードに入る
$ avrdude -c avrispmkii -P usb -p attiny2313 -t
$ read lfuse
0000   64
$ read hfuse
0000   df

これだけでは意味がさっぱりである。
ヒューズビットの意味を調べる必要があるので、以下のデータシートを確認してみた。
http://www.avr.jp/user/DS/PDF/tiny2313.pdf

lfuseは、ヒューズの下位ビット。hfuseは、上位ビットとなる。
「CKDIV8」が設定されているのは、下位ビットのほうになる。

ヒューズビットについては以下のサイトが参考になるようだ。
◆ヒューズビット

要は、7ビット目が「0」になっていると、クロックが1/8になってしまうらしい…なんてこったい。
初期設定では全てこのようになっているとのことだ。
設定されている値をビット値に変えてみると…

64 = 0110 0100

7ビット目が0である…そういうことか。こいつを1に書き換えてやる必要がある。

1110 0100 = e4

16進数でe4という値に書き換えることで、1/8動作を変更できる。
以下のコマンドを実行して書き換えてみたところ無事に正常な動作となった。
なお、ヒューズの書き換えは失敗すると、動作しなくなってしまう場合もあるようなので注意して欲しい。

$ avrdude -c avrispmkII -P usb -p t2313 -U lfuse:w:0xe4:m

あと、こんなサイトも見つけた。
マイコンの種類を選択するとWeb上で適切な値を表示してくれるようだ。便利そう。
Engbedded AVR Fuse Calculator

今回利用したコードもGitHubにアップしてみた。
pontago/avr-LedTest-asm · GitHub