タクトスイッチを使う

温度計にモード切り替え(最高温度、最低温度)を付けたかったので、タクトスイッチの使い方を調べることにした。
まずは、マイコンを使わずにタクトスイッチを使って、LEDを点灯・消灯させる。
かなり苦戦したのだけど、いろいろな人に教えてもらって何とか完成させることができた。

タクトスイッチの構造

タクトスイッチは押すとカチッと手応えのある押しボタンだ。押している間だけ電流が流れるらしい。
足は4本付いていて、押すことで2本の足が接続され電流が流れるという、単純な仕組みのようだ。

下記のページを参考にさせてもらった。
arduino使い方:スイッチの入/切でLEDを点灯

タクトスイッチを使ってLEDを操作

押している間だけLEDを点灯させる回路を作ってみることにした。
回路は下記のページを参考に組んでみた。

スイッチ回路 – Hinemos amo!

f:id:happytar0:20130729232254p:plain

マイコンを使ってタクトスイッチを操作

続いて、マイコンを使ってLEDを点灯させてみることにした。

PD3にLEDを接続、PD4にタクトスイッチのアノードを接続した。
PD4は入力になるため、DDRDレジスタでビット4を0にし、PD3は出力で使うため、ビット3を1にする。
また、内部プルアップを有効にするために、PORTDレジスタのビット3を1にする必要がある。

これは、スイッチがどこにも接続されていない場合、電圧が不安定になり0なのか1なのか分からなくなってしまうためだ。
それを防ぐためにスイッチとの間に抵抗をはさむ必要があり、これをプルアップ抵抗という。
AVRの場合、プルアップ抵抗を内蔵しているので、上記のようにPORTDレジスタのビットを立てると、内部プルアップ抵抗が有効になる。

あとは、bit_is_clear関数でPD4が0になっているかチェックし、LEDの点灯させるだけだ。
bit_is_clear関数は、第一引数にチェックするレジスタ(PORTBやPORTD)、第二引数にチェックするビット番号を指定する。
0になっているとtrueが返ってくる。同様にbit_is_set関数は、1になっているとtrueが返ってくるようだ。
この関数を使えば、簡潔に記述することができる。

この回路はそのものズバリのものが、下記ページに書いてあったので参考にさせてもらった。
LED点灯 ( IO入出力 )
top

長押しを判定する

スイッチのオン・オフの他に、長押しを実装してみることにした。
モード切り替えはスイッチの短押しを使って、最高温度や最低温度は長押しでリセット出来るようにするためだ。

長押しを判定するためにタイマ割り込みを使うことにした。しかし、とてもむずかしく長い道のりとなった…。
短押しと、長押しを判断するのがこんなにむずかしいとは思わなかった。
普段何気なく使うことが多いのだけど、単純な仕組みでも一から作るとなるとむずかしいもんだ。

回路図は下記のようになった。
f:id:happytar0:20130729234821p:plain

タイマ割り込みを使う

タイマ割り込みについては説明しているサイトがたくさんあるので割愛する。
下記のサイトが詳しく説明されていて参考になった。
http://d.hatena.ne.jp/hijouguchi/20100620/1276997802

簡単に説明すると、カウンタ用レジスタ(TCNT0)があって、一定周期毎にこのレジスタの値がカウントアップされていく。
レジスタがオーバーフローしたり、設定した値に到達すると割り込みハンドラが呼び出されるというものだ。

大事なのは、クロック数と分周比から何秒ごとに割り込みハンドラが呼び出されるのかを考えることだと思う。
今回は、ATTINY2313を8MHzで動かしていて、分周比を1024に設定した。(他にも256や8など設定できる)
分周比は、「TCCR0B」レジスタで設定する。分周比1024なので、0b00000101となった。

8000000(8MHz) / 1024(分周比) = 7812.5Hz

となるので、1秒間に7812回動作するとうことだ。

1(秒) / 7812(Hz) = 約0.000128秒 = 0.128ms(ミリ秒)

0.128ms毎にカウントアップされていくことになる。
今回はカウンタ用レジスタがオーバーフローしたときに割り込みハンドラを呼び出すようにした。
カウンタ用レジスタは8ビットなので、256になるとオーバーフローすることになる。

0.128(ms) * 256 = 32.768ms

約32ms毎に割り込みハンドラが呼び出されるようだ。

タイマ割り込みの実行

タイマ割り込みを使うためには、「interrupt.h」をインクルードする。
TCCR0Bレジスタ、TIMSKレジスタを設定した後、sei関数を呼び出すことで実行される。
sei関数は、全割り込みを有効にさせる関数だ。逆にcei関数は、全割り込みを無効にさせる関数になる。

割り込みハンドラは、ISRマクロを使って設定する。
最初ISRは関数だと思ったのだけど、よくよく見てみるとマクロになっていた。
第一引数に使うハンドラ名を入れる。
今回は、TIMER0のオーバーフローということで以下のようになった。

ISR(TIMER0_OVF_vect) {
}

長押しをどのように判断するか

短押しと長押しを区別させるのに苦労した。
32ms毎にスイッチが押されているか判断し、3秒以上押されてい場合に、長押しと判断させることにした。
3秒未満でスイッチを離した場合は、短押しとなるようにした。

押した場合に判定させてしまうと、短押しと長押しのイベントが同時に起きてしまい、動作がおかしくなってしまう。
なので、長押しと区別させるために、短押しの場合、リリース時(スイッチを離した時)に判定させることで上手くいった。

また、長押しされている時間を調べるために変数にハンドラ呼び出された回数を保存することにしたのだけど、ずっと長押ししているとカウンタ変数がオーバーフローしてしまい誤動作するということがあった。
カウンタ変数を大きいものして一時しのぎすることも出来るのだけど、今回は「長押し判定時間の3秒を越えた場合、カウントアップさせない」ことで対応した。

今回のコードもGitHubにアップした。
pontago/avr-TactSwitchTest · GitHub

参考サイト
[AVRexample] Timer0 オーバーフロー | 花夢電科雑多猫
無機物の週末 超☆ゆとり的電子工作 その2!
http://d.hatena.ne.jp/hijouguchi/20100620/1276997802
http://www.hokutodenshi.co.jp/PUPPYSupportPage/ensyu/timer/timer2.html
AVR timer(1) | stastaka's Blog
タイマ/カウンタ1を使う
[example] 外部割り込み – 花夢電科雑多猫 マニュアル&メモ
つくろぐ 技術系

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

トランジスタや抵抗の数を減らすためにトランジスタアレイを使うことにした。
トランジスタアレイは、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

温度センサーと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