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

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

AVRでLEDを点滅させてみた

前回LEDを点灯させる所まで何とかできた。と言っても、電池ボックスとLEDを接続しただけ…。
今回はいよいよマイコン(AVR)を使って、LEDをコントロールしてみようと思う。

まず、ライターであるAVRISPmkIIとマイコンを接続しなくてはいけない。パソコンとの接続はUSB経由で接続するだけなので簡単。
AVRISPmkIIのコネクタはISPコネクタ(6ピン)のため、このままだとブレッドボードに接続するのに大変なようだ。
そこで、共エレで販売されている「AVRWRT用ブレッドボードISPケーブル」のコネクタに付け替えた。

色々なサイトを参考にしつつ、マイコンに接続してみた。
続いて、マイコンに書き込むためのソフトをインストールするが、Mac環境だったため、正規のソフト(AVR Studio)が使えないようなので、CrossPack for AVR Developmentを使うことにした。
CrossPack – A Development Environment for Atmel’s AVR Microcontrollers

/usr/local配下にインストールされるようで、/usr/local/CrossPack-AVRというシンボリックリンクが作成される。
CrossPackインストール後に、avr-projectコマンドを実行することで、XCodeのプロジェクトファイルが生成されるようだ。

$ avr-project avr-LedTest

かなりシンプル…XCodeからいじるのも面倒なので、直接ファイルをいじることにした。
firmwareディレクトリにMakefile、main.cの2つのファイルが生成された。大事なのはこの2つのようだ。

Makefileでライターとマイコンの種類を書き換える必要があるようで、以下のように書き換えた。

DEVICE = attiny2313
PROGRAMMER = -c avrispmkII -P usb -p t2313

makeでプログラムのコンパイル、make flashでマイコンにプログラムを転送させるようだ。make fuseでヒューズの書き換えも可能とのこと。
とりあえず、マイコンにプログラムを転送してみようと思い、デフォルトのままmain.cをコンパイルし、make flashを実行してみたがエラーになってしまう。

// firmwareディレクトリで実行
$ make
avr-gcc -Wall -Os -DF_CPU=8000000 -mmcu=attiny2313 -c main.c -o main.o
avr-gcc -Wall -Os -DF_CPU=8000000 -mmcu=attiny2313 -o main.elf main.o
rm -f main.hex
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avr-size --format=avr --mcu=attiny2313 main.elf
AVR Memory Usage
----------------
Device: attiny2313
Program:      58 bytes (2.8% Full)
(.text + .data + .bootloader)
Data:          0 bytes (0.0% Full)
(.data + .bss + .noinit)
$ make flash
avrdude -c avrispmkII -P usb -p t2313  -U flash:w:main.hex:i
avrdude: stk500v2_command(): command failed
avrdude: stk500v2_program_enable(): bad AVRISPmkII connection status: Target not detected
avrdude: initialization failed, rc=-1
Double check connections and try again, or use -F to override
this check.
avrdude done.  Thank you.
make: *** [flash] Error 1

調べまくった結果、マイコンへの接続の仕方が間違っていたようで、一からコネクタを接続し直してみた。もともと付いていたコネクタと、付け替えたコネクタをよく見比べて繋いでいくと、やはり間違えていた…。
参考サイトを見ながらコネクタの色だけで判断して接続したのがいけなかったようで、しっかり仕様を把握して進めていかなくちゃいけないと反省。
トリパマ Toripama: AVRISPmkII ライタのピンの配列
サンプル・プログラムで学ぶAVRの実践【2/4回目】マイコン書き込み環境の構築 (PIC,78K,R8,HC(S)08/RS08,AVR,MSP430などのマイコン活用)

f:id:happytar0:20120714033630j:plain
こんな感じで接続すると上手くいった。

$ make flash
avrdude -c avrispmkII -P usb -p t2313  -U flash:w:main.hex:i
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e910a
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: writing flash (670 bytes):
Writing | ################################################## | 100% 0.28s
avrdude: 670 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex contains 670 bytes
avrdude: reading on-chip flash data:
Reading | ################################################## | 100% 0.20s
avrdude: verifying ...
avrdude: 670 bytes of flash verified
avrdude: safemode: Fuses OK
avrdude done.  Thank you.

ひゃっほー、悩んだ末に解決した時の嬉しさはたまらない!

肝心のLEDを点灯させるプログラムを書いてみることにした。
マイコンを経由してLEDを接続するにはどうすればいいんだと少し考えたが、PD4(ポート8)にLEDと抵抗を接続することで解決。思ったより簡単だった。
サンプルコードはCrossPackのサイトに書いてあったので、それをコピペすればいいだけだ。

#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 */
}

f:id:happytar0:20120714050150j:plain
LEDが点滅してる…肝心の点滅箇所はいともあっさり出来てしまった。
しかしながら、コピペして終わりというのはあまりにもつまらない。
実際はLEDを2つ接続してランダムに交互に点滅を繰り返す、というプログラムを組んだのだが、謎の変数やら関数が出現しているので、次回に詳しく掘り進めていきたいと思う。

電子工作はじめました

ついに電子工作に手を出してみた。やるやる詐欺をすること1年…はじめるまで長かったです。
三日坊主にならないようにコツコツやっていきたいと思う。

電子工作やらマイコンなんてまったく知らない人間なので、まずはLEDを点灯させてみようという事で以下の部品を購入。

  • プログラマ(ライター?) AVR ISP MKII
  • ブレッドボード EIC-108J
  • AVR ATTINY2313-20PU
  • 3mm赤色LED LT3U31P 250mcd
  • カーボン抵抗 1/4W 200Ω
  • カーボン抵抗 1/4W 150Ω
  • 電池ボックス 単3×3本
  • AVRWRT用ブレッドボードISPケーブル

f:id:happytar0:20120704201002j:plain

まずブレッドボードの使い方を覚えるためにLEDを電池ボックスと接続して点灯させてみた。
さすがにこれが出来なかったらやばいよな…と思いつつ、あっさり点灯して安心した。そしてブレッドボードも案外簡単な仕組みだった。

ブレッドボードに150Ωの抵抗とLED、そして電源を接続しただけ。
必要な抵抗は以下の式で求められるようだ。というかオームの法則ってやつだ。
中学の時に習ったけど勉強に熱心ではなかったのですっかり忘れてしまった…。

R(抵抗) = V(電圧) / A(電流)

LEDのスペックは以下のようだ。

Vf=1.85V〜2.5V If=20mA

そして電圧については、電池ボックスに単三乾電池3本ということなので以下になる。

1.5V x 3本 = 4.5V

これをもとに抵抗を計算してみると、

(4.5V - 1.85V) / 0.02A = 132.5Ω

どうやら132Ωくらいの抵抗でちょうどいいようだ。今回は適当なものがなかったので150Ωと念のために200Ωの抵抗を買ってみた。

f:id:happytar0:20120708151644j:plain
無事に点灯した!!全然むずかしい事してないのに嬉しい。
やっぱり形があるものを触って思い通りに動かせると、ソフトをいじるのと違った喜びを感じる。

20mAを越えた電流を流すと壊れると聞いたので、興味本位で抵抗を外してそのまま電源と接続してみた。
f:id:happytar0:20120708151803j:plain
あれ…壊れない。でも弱々しい光り方でなんかおかしい。

とにかく無事?に成功出来てよかった。困った事があれば詳しい先生が居るので心強い。
次はマイコンと接続してLEDの点灯を制御してみたいです。