ニュース

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

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

前回の記事で無事にLEDを点灯させることができた。
コードは全てC言語で書いたが、もう一つの方法としてアセンブリで書いてみることにした。
アセンブリなんてコードを見ただけでウンザリしてしまう。

でもやってみる前から諦めてもしょうがない。
この機会に少し調べてみることにした。

開発環境を整える

アセンブリコードをコンパイルするためのコマンドは、最初に導入したCrossPackAVRというものに含まれているavr-asコマンドを使うことができる。
しかし、このコマンドでコンパイルできるアセンブリはGasと言われるもので、
いわゆるよく知られている、アセンブリコードのそれとは違うらしい。

そこで今回は、avraというアセンブラを導入してみることにした。
AVRA Home Page

下記サイトを参考にしながらインストール。
のぅわんべたぁ|AVRアセンブリ

まず、最新版をダウンロード後に展開し、srcディレクトリの中で以下のコマンドを実行してみた。
参考通りに上手くいかず、automakeの部分でコケてしまったので、touchコマンドでエラーが出るファイルを作ってみたところ成功。

$ touch NEWS README AUTHORS ChangeLog
$ aclocal
$ autoconf
$ automake -a
$ ./configure --prefix=/opt/local
$ sudo make install

これで開発するための環境は用意できた。
また、includesディレクトリに.inc拡張子のついたファイル群が入っているようで、マイコンの種類に応じた定義ファイルが格納されているようだ。
レジスタやポートに応じたアドレス、値がマッピングされているようで、初めにインクルードして使う感じだと思う。

LEDを点滅させる

なんと言っても最初はLEDを点滅させるところからだろう。
マイコンにはLEDを2つ接続しているが、手始めに一つ点灯させてみる。

.include "tn2313def.inc"
main:
ldi   r16, 0b00011000
out   DDRD, r16
ldi   r16, 0b00001000
out   PORTD, r16
rjmp  main

なんとこれだけである…実に簡単だ。

まず、.includeで定義ファイルをインクルード。コロンが付く行はラベルなので解説は不要だと思う。
ちなみにドットから始まるものは擬似命令と呼ばれるもので機械語に変換されることはないらしい。
続いて、各命令について調べてみた。

使える命令セットは以下のURLからダウンロード出来る模様。
http://www.avr.jp/user/DS/PDF/AVRinst.pdf

LDI命令は「即値バイト定数を汎用レジスタに取得」と書いてある。
レジスタr16に右オペランドの値を入れると考えればいいと思う。

OUT命令は「汎用レジスタからI/Oレジスタに設定」と書いてあった。
そのままで、I/Oレジスタに汎用レジスタの値を設定するときに使う命令のようだ。

RJMP命令は「PC相対無条件分岐」と書いてある。
要は無条件でこの場所に飛ばすってことみたいだ。今回の場合はmainラベルに飛ばすので、ループさせるということになる。
いわゆるGOTOみたいな感じだろう。

DDRDや「0b00011000」などの値は、前回の記事で説明していたと思うので今回は省く。
上のコードをavraコマンドでコンパイルしてみた。

$ avra main.asm
AVRA: advanced AVR macro assembler Version 1.3.0 Build 1 (8 May 2010)
Copyright (C) 1998-2010. Check out README file for more info
AVRA is an open source assembler for Atmel AVR microcontroller family
It can be used as a replacement of 'AVRASM32.EXE' the original assembler
shipped with AVR Studio. We do not guarantee full compatibility for avra.
AVRA comes with NO WARRANTY, to the extent permitted by law.
You may redistribute copies of avra under the terms
of the GNU General Public License.
For more information about these matters, see the files named COPYING.
Pass 1...
Pass 2...
done
Used memory blocks:
Code      :  Start = 0x0000, End = 0x0004, Length = 0x0005
Assembly complete with no errors.
Segment usage:
Code      :         5 words (10 bytes)
Data      :         0 bytes
EEPROM    :         0 bytes

こんなメッセージが表示されて成功した。
最初にエラーが出たのだけど、tn2313def.incファイル内にある「#」から始まる行の先頭に「;」を付けたところエラーは消えた。

# vimでこんな感じに処理した
:%s/^#/;#/g

出来上がったmain.hexというファイルを開いてみたところ、かなり小さくてびっくりした。わずか63バイトである…。

:020000020000FC
:0A00000008E101BB08E002BBFBCFE2
:00000001FF

バイナリの転送はavrdudeを使って以下のようなコマンドを実行した。

$ avrdude -c avrispmkII -P usb -p t2313 -U flash:w:main.hex:i

簡単なものだけど一発で成功したので抵抗感も薄らいだ。

アセンブリと聞くだけで拒否反応を示してしまいたくなるが、命令セットを覚えていくことで少しずつ理解が深まっていき、単純なコードながら深さと楽しさがあるように思える。

長くなってしまいそうなので、次の記事で2つのLEDを一定間隔で切り替えて点灯させる、ということをやってみたいと思う。

今回書いたコードもGitHubに上げてみた。
pontago/avr-LedTest-asm · GitHub

参考サイト
アセンブラなんて簡単じゃないか(1/3) − @IT MONOist
解説 AVRアセンブラ講座 (1)|freeml byGMO

AVRで温度計を作るために考える

作りたいと思ってから1ヶ月ほどが経過した。
時間を空けすぎるのはよくないので、作るのに何が必要か、どうやって作るのかを調べることにした。

温度を測るには温度計センサー(LM60BIZ)なるものがあるようで、
こいつを使えば温度によって電圧を返してくれるらしい。
比較的難易度は低そうだ。

実は湿度計も同時に作ろうと思ったのだけど、
安く入手できるHS-15Pを使うとなると、
湿度によって変動するインピーダンスを調べて対数演算が必要らしい。
あほの俺には無理だ。

TDKが出しているCHK-GSSというのを使うと、
電圧の変化で処理できる上に、温度も分かるらしい。俺でも出来そう。
しかし2000円くらいするので尻込みしてしまった。
今回は温度計のみで絞ることにした。

ACアダプタを使う

今まで乾電池から電源を取っていたけど、ACアダプタを使うことにした。
スイッチングACアダプター12V1A(NP12-1S120)を使う予定だ。
12Vの電圧を5Vまで降圧させる必要があるので、3端子レギュレータ(LM7805CV)を使う。

ノイズや発振を防ぐために、前後にコンデンサをかませる必要があるようだ。
0.33μF50Vと0.1μF50Vを使うことにした。

ブレッドボードでも簡単に使えるようになる、
ブレッドボード用DCジャックDIP化キットというものを使う。

7セグLEDを使う

計測した温度を表示するために7セグLEDを利用する。
3桁表示できるC-533Sを使うことにした。

各セグメントに対してマイコンの各ポート接続してしまうと、
ポート数が足りなくなってしまうので、トランジスタで電流を増幅させダイナミック点灯させる。

トランジスタは、2SC1815GRで60V150mAまで増幅出来るようだ。

各セグメント(8本)に対して、10mA程度流せば十分なようなので、
10mA * 8本 = 80mAが必要な計算だ。余裕をもって100mAとした。

トランジスタとマイコンの間には必要以上に電流が流れないように、電流制限抵抗というものが必要らしい。

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

トランジスタのコレクタとベース間には安定化させるために抵抗が必要らしいので、
10kΩの抵抗を使うことにした。
あとはエミッタと7セグLEDのコモンに接続すればいいはずだ。

続いて、各セグメントとマイコンのポートに接続する。
この時に抵抗も挟む必要があるので、7セグLEDの順方向電圧の1.8Vを差し引いて、抵抗値を以下のように計算した。
(5V – 0.7V – 1.8V) / 0.01A = 250Ω
近い240Ωの抵抗を使うこととした。

必要なもの

これでだいたいの流れを調べたので、必要なものをピックアップする。
マイコンはATTinyしか持っていなかったので、ATMegaも買うことにした。
あとこれから必要になるであろうテスターと、いくつか抵抗も追加で買う。

部品名 型番 値段
AVRマイコン ATMEGA328P-PU 250円
ポケット・デジタルマルチメータ(テスタ)[周波数+容量][オートレンジ] P-10 1000円
7セグメントLED表示器 超高輝度赤色3文字(3桁)(カソードコモン) C-533SR 200円
高精度IC温度センサ LM60BIZ 100円
ブレッドボード用DCジャックDIP化キット 100円
トランジスタ 2SC1815GR 200円
3端子レギュレータ[5V1A] LM7805CV 100円
絶縁型ラジアルリードタイプ積層セラミックコンデンサー0.33μF50V 100円
絶縁型ラジアルリードタイプ積層セラミックコンデンサー0.1μF50V 100円
カーボン抵抗(炭素皮膜抵抗)1/4W 10kΩ 100円
カーボン抵抗(炭素皮膜抵抗)1/4W 4.7kΩ 100円
カーボン抵抗(炭素皮膜抵抗)1/4W 240Ω 100円
カーボン抵抗(炭素皮膜抵抗)1/4W 1kΩ 100円

マイコン先生に確認してみたところ、これで大丈夫らしい。
チェックが入らなかったのは嬉しい。
注文したらいよいよ組み立てることになるからワクワクだ。

AVRでLEDを点滅させるプログラム

前回の記事で無事にマイコンを経由してLEDを点灯させる事ができた。
その時に利用したプログラムはサンプルをコピペしただけだったので、2つ接続したLEDを交互にランダム点灯させるプログラムに変更したいと思う。

最初にサンプルコードで意味が分からない変数や関数があったので、それを調べることにしてみた。サンプルコードは以下のような感じだ。

#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRD = 1 << 4;           /* make the LED pin an output */
for(;;){
char i;
for(i = 0; i < 10; i++){
_delay_ms(30);  /* max is 262.14 ms / F_CPU in MHz */
}
PORTD ^= 1 << 4;    /* toggle the LED */
}
return 0;               /* never reached */
}

定義済みグローバル変数

DDRDとPORTDにビット演算の結果を代入しているようだけど、まずここが謎だった。

DDRD = 1 << 4;
PORTD ^= 1 << 4;

DDRD変数の中身が気になったので、宣言元である「avr/io.h」を覗いてみたところ、マイコンの種類判定後に「iotn2313.h」をインクルードしているようだ。

#elif defined (__AVR_ATtiny2313__)
#  include <avr/iotn2313.h>

「iotn2313.h」の中にDDRDの宣言があったけどマクロを呼んでいるようだ…。

#define DDRD    _SFR_IO8(0x11)

_SFR_IO8マクロの宣言元は「sfr_defs.h」にあった。どうやらアセンブラとCからの呼び出しでマクロの内容も変化するようだ。

// アセンブラ
#define _SFR_IO8(io_addr) ((io_addr) + __SFR_OFFSET)
// C
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)

uint8_t型でアクセスできるようで、__SFR_OFFSETは、0x20のアドレスになっているようだ。

AVRのSFRは基本的にメモリ空間にマッピングされているが、低位の64バイトはI/O空間にもマッピングされていて、どちらでもアクセスできる。 しかし、メモリ空間の最初の32バイトには汎用レジスタがマッピングされているのに対し、I/O空間にはそれがないのでアドレスが32バイトずれている。 つまり、メモリ空間で0x20~0x5Fのレジスタが、I/O空間で0x00~0x3Fに見える。 これらのレジスタはIN命令やOUT命令などでアクセスが可能だが、io.h ではレジスタ名がメモリ空間のアドレスで定義されいているので変換が必要になる。 このズレ(0x20)を示すマクロが__SFR_OFFSETで、メモリ空間からI/O空間に変換するためのマクロが_SFR_IO_ADDR。 _SFR_MEM_ADDRというのもあり、こちらは何も変換しない。

このサイトで詳しく解説されていた。
AVR libcを使ってみる you/junkbox

0x11をデータシートで確認してみると、DDRDレジスタ(0x31)となっていた。DDD0〜6とビット位置は同じになっているようだ。
そもそもDDRDレジスタってなんだろって事なんだけど、実は先生に概ね教わっていた。
DDR○は入力・出力を決定するレジスタらしい。この場合、ポートDを入力にする場合は「0」、出力にする場合は「1」に設定するようだ。
同じようにPORTDは、ポートDの出力をHで出す場合は「1」、Lで出す場合は「0」にするらしい。HはHigh、LはLowを表すようで、Hにした場合にプルアップになり、電流が流れるようだ。

これで変数の意味はだいたい分かった。「iotn2313.h」にはこの他にも定義されている変数があるようだ。

関数

続いて関数について調べてみた。今回使用されているのは一つで、_delay_msという関数だ。
調べなくともだいたい分かるが、指定ミリ秒遅延させるものだろう。
気になるのはコメントの「max is 262.14 ms / F_CPU in MHz」というもの。
今回クロック周波数は8MHzに設定していたので、262.14ms / 8Mhz = 32.7675ms になる。最大32msまで設定可能という事だと思う。
32ms以上遅延させたい場合は複数回呼び出す必要があるようで、forループなどで指定回数呼び出す。

for(i = 0; i < 10; i++){
_delay_ms(30);  /* max is 262.14 ms / F_CPU in MHz */
}

30ms * 10 = 300ms(0.3秒遅延させている)

ランダム点滅させるプログラムを組む

お勉強はこのくらいで実際にプログラムを組んでみた。LEDは2つ接続していて、PD3とPD4に接続している。
DDRDのビット3と4を「1(出力)」に設定した。ランダム点滅は、rand関数を呼び出す事で遅延時間を調整する。
定義済み定数・マクロをフル活用するために、指定ビットを立てる_BVマクロ、PD3・PD4定数を利用した。

#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include <stdbool.h>
int main(void) {
DDRD = 0b00011000;
bool flag = false;
for (;;) {
char i;
int r = rand() % 10 + 1;
for(i = 0; i < r; i++) {
_delay_ms(30);
}
PORTD = flag ? _BV(PD3) : _BV(PD4);
flag = !flag;
}
return 0;
}

今回のコードとMakefileはGitHubにも上げてみた。
pontago/avr-LedTest · GitHub

アセンブラで書いてみるのもいいかな。。。
次に作るものは温度計・湿度計になると思うけど、一気にハードルも上るから苦労しそうだ。