今まで kaise の SK-6201 というテスターを使っていたけど、中学生ぐらいから使っていて、電流計の表示にオフセットがあったり、測定レンジが狭かったりで不便だったのでこの際に新調した。
電流のレンジが大幅に増えてすごく便利になった。ただ、前のテスターだと値に大きな変化があったときに音が鳴る機能があって気に入っていたんだけど、これにはなくて残念。
サンワは測定器メーカーとして有名なようです。
今まで kaise の SK-6201 というテスターを使っていたけど、中学生ぐらいから使っていて、電流計の表示にオフセットがあったり、測定レンジが狭かったりで不便だったのでこの際に新調した。
電流のレンジが大幅に増えてすごく便利になった。ただ、前のテスターだと値に大きな変化があったときに音が鳴る機能があって気に入っていたんだけど、これにはなくて残念。
サンワは測定器メーカーとして有名なようです。
PC からモールス符号を発生させて無線機に入力するものが欲しいと思っていた。当然既にそういうのはあるんだけど、どうも気に入るのがないので、必要な機能だけ自分で実装する。
基本の要件としては
なので、簡単そうでいろいろ教材としても楽そう。
クロスプラットフォームで面倒なドライバを書かなくてすむ方法は HID デバイスとして作ることだと思うので、そのように作ることにした。また、USB 用の外部チップとか用意したくないので V-USB (AVR マイコン上で USB を処理できるライブラリ) を使うことにした。
いろいろやってなんとか2日ぐらいで最低限の機能の実装ができたのでいろいろメモしておく。
コード: https://github.com/cho45/AVR-USB-CW
現状制作途中のものなので、実際使う際に必要なリレー部分とかは書いてない。
現状だとダイオード2本で約1.4V程度電圧降下させて 3.3V〜3.6V 程度を作りだす方法を使ってる。V-USB の Wikiに書いてある方法の1つ。
左下のはそのうち試してみたい回路で、AVR自体には5Vをそのまま供給して、信号ラインだけ 3.6V にツェナーダイオードで電圧降下させる。電源電圧を3.3V程度にしてしまうと、動作周波数が 12MHz 程度までしか保証されないので、16MHz や 20MHz で動かすならこちらのほうが安定しそう。
まず examples/hid-{data,mouse} を動かして、ふむふむと思いながらやってみたけど、なかなかハマった…… HID デバイスを作ろうとするとデフォルトの usbconfig.h から結構書きかえないといけないんだけど、それを知っていないとだめなのでつらかった。
usbconfig-prototype.h から作るのではなく、example/hid-* のをコピってくるほうが確実に動くと思う。
まだコントロール転送しかできてない。少なくともインタラプト転送はやることになりそうだけどデバッグがだいぶ大変 (追記:インタラプト転送は Mac + libusb だとできなかった)。
あと、自分で設定している割込みの頻度が高すぎると STALL しやすい…… クロック周波数にしても、こういった処理にしても結構シビアなので気を使わざるを得ない……
HID のレポートデスクリプタ定義は、大本の仕様書がどこにあるのかわからないのでコピペと感で書いて試した。
_delay_ms は定数しかスリープできないのと、ビジーループなのでその間割込み以外何も走らなくなってしまう。V-USB は usbPoll() を少なくとも50msecに一回は呼ばないといけないとかなので、これは使えない。
なので、まず割込みで時間を測るタイマー変数を作る。
必要な精度以上のタイマー(あまり短くないほうがいい)を用意 (この例だと1ms) して、グローバルな timer という変数をインクリメントするようにする。(F_CPU / 分周設定 / OCR0A) CTC のほうが自由に時間を設定できて楽
/** * timer interrupt * CTC 16MHz / 64 / 250 = 1kHz = 1msec */ TCCR0A = 0b00000010; TCCR0B = 0b00000011; OCR0A = 250; TIMSK0 = 0b00000010;
unsigned long timer; ISR(TIMER0_OVF_vect) { timer += 100; }
以下のようなマクロを用意して DURATION(msec) で、引数に与えた時間が timer 換算でいくつになるかを出す。浮動小数点演算を使った瞬間、出てくるバイナリサイズが長大になるのでそうならないようにしてる。
#define DURATION(msec) (unsigned int)(msec * 100)
delay_ms を実装する。_delay_ms におけるビジーループ部分が任意に書けるので、ここで usbPoll() をする。
void delay_ms(unsigned int t) { unsigned long end; cli(); timer = 0; end = timer + DURATION(t); sei(); while (timer <= end) { wdt_reset(); usbPoll(); } }
これでナイーブに delay_ms() を使ったコードでも可読性と効率をそこそこ両立できそう。しかし、なんかもっといい感じにコード書けそう。。普通どうやるものなのかよくわからない。
デバッグするとき、何かしら文字を表示できるインターフェイスがないとつらいため導入。シリアルポートに出力してデバッグするのがたぶん一番楽なんだと思うけど、レベル変換が必要で手元にあるものではできなかった。
I2C を使うにあたって、原理を理解していないのと、なおかつそれを AVR で実現する方法がわからないのと、さらには「デファクトスタンダード」な通信プロトコルも存じあげないため、全ての箇所でハマりにハマった。チップは ATmega168P なのでそこまで細かい原理を知っていて実装できる必要はないけど、だいぶハマった。
やり終わってみれば、別に難しくはないんだけど、どこに問題の原因があるのかが、見える現象が少なすぎて大変だった。面倒でもいちいちLEDチカチカさせて動作確認をすべき。
以下に I2C 液晶 (アドレス 0x7c) を操作するコード。何をするにせよ、とにかくデータシートをよく読んで理解するのが大変に重要だと実感した。
#include <string.h>
#define START 0x08
#define ReSTART 0x10
#define MT_SLA_ACK 0x18
#define MT_DATA_ACK 0x28
#define MR_SLA_ACK 0x40
#define MR_DATA_ACK 0x50
#define MR_DATA_NACK 0x58
#define SLA_W 0xA0
#define SLA_R 0xA1
#define ADDRESS 0x00
#define DATA 0x55
void Error () {
// unsigned i = 0;
// for (i = 0; i < 3; i++) {
// set_bit(PINB, 0);
// _delay_ms(500);
// clear_bit(PINB, 0);
// _delay_ms(500);
// }
// clear_bit(PINB, 0);
}
void i2c_start () {
// start
TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
while (!(TWCR & (1<<TWINT)));
if ((TWSR & 0xF8) != START) return Error();
}
void i2c_stop () {
TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
}
unsigned char i2c_write (unsigned char data) {
TWDR = data;
TWCR = (1<<TWINT) | (1<<TWEN);
while (!(TWCR & (1<<TWINT)));
switch (TWSR & 0xF8) {
case MT_SLA_ACK:
case MR_SLA_ACK:
case MT_DATA_ACK:
return 1;
default:
Error();
return 0;
}
}
void display_write_instruction (unsigned char address, unsigned char data) {
i2c_start();
i2c_write(address);
i2c_write(0b10000000);
i2c_write(data);
i2c_stop();
_delay_ms(1);
}
void display_write_data (char* string) {
unsigned int i = 0;
unsigned int len = strlen(string);
display_write_instruction(0x7c, 0b00000001);
i2c_start();
i2c_write(0x7c);
i2c_write(0b01000000); // Co=0 RS=1
for (i = 0; i < len && i < 8; i++) {
i2c_write(string[i]);
}
i2c_stop();
_delay_ms(1);
i2c_start();
i2c_write(0x7c);
i2c_write(0b10000000); // Co=1 RS=0
i2c_write(0b11000000); // set ddram address to line 2
i2c_write(0b01000000); // Co=0 RS=1
for (; i < len && i < 16; i++) {
i2c_write(string[i]);
}
i2c_stop();
}
void display_init () {
TWBR = 0x24;
TWSR = 0b00000001;
TWCR = 1<<TWEN;
display_write_instruction(0x7c, 0b00111000); // function set to 0 (default)
display_write_instruction(0x7c, 0b00111001); // function set to 1 (extended)
display_write_instruction(0x7c, 0b00010100); // internal osc frequency
display_write_instruction(0x7c, 0b01110000); // contrast set
display_write_instruction(0x7c, 0b01010110); // power/icon/contrast control
display_write_instruction(0x7c, 0b01101100); // follower control
_delay_ms(300);
display_write_instruction(0x7c, 0b00111000); // function seto to 0
display_write_instruction(0x7c, 0b00001101); // display on/off control
display_write_instruction(0x7c, 0b00000001); // clear all
_delay_ms(10);
}
ハードウェアが絡むコーディングは、一度ハマると
のどちらで発生しているかの見極めができないとかなりつらい…… 配線ミスはあってはならないので、最初の段階でよくよく確認しておかないとあとあとつらい。どんなに確認しても確認したりないぐらい確認する必要がある。あと配線後は必ずテスターをあてたほうがいい……
USB コネクタのピンの半田付けを適当にしすぎたせいで、試している最中に1本がはずれて通信できなくなるということがあった。こういうアホみたいなことでもコーディングミスを疑ったりすると時間を食ってしまう。
出力を操作したいときは PORTB なのに、PINB と書いていてしばらく気付かなかった。この場合、微妙に出力されたりされなかったり、みたいな不安定な挙動になって、全く動かないわけでもないのでなかなかコーディングミスであることに気付けない。
最初は feature レポートだけでごにょごにょしていたのでよかったけど、インタラプト転送をしようとしたところで、claim_interface が ERROR_ACCESS エラーを出して止まることがわかった。ググった感じだと、HID デバイスの場合、そのへんの処理を OS のドライバが握っているため、ユーザレベルだと直接扱えないらしい。
とりあえずどうしようもないっぽいので、feature レポートだけで情報をやりとりするように変えた。現状のプログラムだとそれでも問題がないっぽい。
AVR は思ったより難しくなくて結構拍子抜けするぐらいだけど、今までやったことがない分野なので適度に難しい問題がたくさんころがっていてめちゃくちゃ楽しい。久しぶりに深夜3時ぐらいまでひたすらコーディングするとかしてしまった。
USB デバイスとか「作れない」という状態だとほんと全然アイデアが沸いてこないんだけど、やってみると結構「ああ、あれもできそう」「あれができたら楽しそう」みたいなのがでてきていい。
ウェブアプリのように誰かにすぐ使ってもらえるものを作るという感じではないのがもったいない感じだけど、なんか別に、自分そういうのなくてもいいなって感じなので良いです。
僕みたいな普段ウェブアプリみたいなかなりレイヤーが上のほうのことやってる人間でも、最近だとアセンブリとか全く書かなくてもよくていい。
ちょっと考えてみた感じ、何も持っていない状態からの AVR におけるとりあえずの開発に必要なのは以下あたりかなあと思った。
そんなに必要なものない気がしたけど、何もなしからだと結構ある。1万ぐらいあればぎりぎり集められるかな。