Sim's blog

電子工作はじめてみました

カラーセンサ・モジュールを使ってみた?(2)

2008-10-10 01:39:16 | AVR
カラーセンサ・モジュールを使ってみた?の続きです。

いつものごとく、何を測っているのかさっぱりというとんでもない状態なわけですが、気を取り直して蛍光灯を測ってみました。前回のコメントでのりたんさんから蛍光灯の周波数について教えてもらいました。wikiを見てみると50Hzまたは100Hzということみたいです。

R、G、B、Cの4つを250個(計1000個)、ATMega88のメモリに蓄えて、測定後に一気にPCに送るプログラムを書いてみました。本当は測定値は10bitですが、メモリが1kバイトしかないので下の2ビットは捨てました。

結果のグラフです。横軸が時間で縦軸がセンサーの読み取り値です。




Cは飽和していたのでグラフにしませんでした。うえからR, G, BとRGBを重ねてたグラブです。レベルはGが一番高くてBが一番低いです。
測定毎に反転するポートを作りました。14.4Hzくらいだったのでサンプリングレートは倍の28.8Hzくらいになります。蛍光灯が50Hzだとしてもサンプリングレートが足りていません。
グラフは細かい上下があって大きくうなりみたいなのが見えます。まさにサンプリングレートが足りないときのグラフになっています。

ソースの一部です。
    for(i = 0; i < N; i++){
        write(CTRL, 0x01);  // 測定開始
        while(read(CTRL)) ; // 測定完了待ち
        // RED読み取り
        x = read(DATA_RED_HI) << 8 | read(DATA_RED_LO);
        rr[i] = x >> 2;
        // GREEN読み取り
        x = read(DATA_GREEN_HI) << 8 | read(DATA_GREEN_LO);
        gg[i] = x >> 2;
        // BLUE読み取り
        x = read(DATA_BLUE_HI) << 8 | read(DATA_BLUE_LO);
        bb[i] = x >> 2;
        // CLEAR読み取り
        x = read(DATA_CLEAR_HI) << 8 | read(DATA_CLEAR_LO);
        cc[i] = x >> 2;
    }

    for(i = 0; i < N; i++){
        sio_hex2(rr[i]);
        putch(' ');
        sio_hex2(gg[i]);
        putch(' ');
        sio_hex2(bb[i]);
        putch(' ');
        sio_hex2(cc[i]);
        putch(0x0d);
        putch(0x0a);
    }

測定したら、データを読み出してメモリに蓄えてを繰り返しています。Nは250です。


ここまで書いて、integration timeのことに気づきました。上の結果はintegration timeが2500のときの結果です(gainは10)。試しにintegration timeを1000にするとサンプリングレートが131Hzまであがりました。

integration time = 500(サンプリングレート167Hz) gain = 2で測定しなおしたデータです。

今度はCも表示しています。
左端の30ポイント分を拡大しました。

なんか、それっぽくなってきました。

まとめ
- カラーセンサーモジュールは蛍光灯を測るのには向いていない
- integration timeとgainをちゃんと調節すること
- 対象の周期の2倍以上のサンプリングレートで測定すること(今回はできてません)

元々、LEDの色とか明るさを測りたいと思って買ったのですが、まだそこまでは到達していません。なんというか、測定したり、グラフを書いたりといった実験ごっこの方が楽しくなっていて、元々何をしたかったかがどうでもよくなってきています。いつものことですね。

ストロベリーリナックスさんもArduino

2008-10-08 00:04:13 | AVR
一つ前の記事を書いているときに、ストロベリーリナックスさんを見てみたら、Arduino Diecimilaの販売が始まっていたことに気づきました。2940円って、かなり安いんじゃないでしょうか?(送料は+240円)。と、思ったら売り切れ中です。 10/13 追記 復活しています

Arduinoを売っているショップさんです(知ってるところ)。
MechRoboShopさん
アールティさん
スイッチサイエンスさん
共立エレショップさん
ストロベリーリナックスさん

他にATMEGA128タッチスクリーンマイコンボードという新製品もあります。「どこかで見たようなあの液晶がついています」なんて書いてあります。Palm Pilotでしたっけ?


TFT液晶が2枚で480円というのもあります。使うのはすごく難しそうです。

カラーセンサ・モジュールを使ってみた?

2008-10-07 23:51:47 | AVR
ストロベリーリナックスさんで販売しているカラーセンサ・モジュール ADJD-S371-QR999を使ってみました。AVRでI2Cが使えるようになったので、何か他のものも使ってみたくなりました。

カラーセンサ・モジュールです。

動作電圧は2.5~3.6Vで測定値はデジタル値としてI2Cを使って読み取れます。ストロベリーリナックスさんでは、このモジュールを使ったキットも販売しています(ADJDカラーセンサ評価キット)。

実験の様子です。


読み取った値です。

上からR, G, B, クリア(?)の値です。0~1023なので000から3ffまでの値をとります。蛍光灯の下の値を表示しています。Nokia5110のバックライトをつけてみました。暗くしても値が読み取れるようにです。

カラーセンサー・モジュールのおおざっぱな使い方です。
(1) 初期化する
(2) CTRLに0x01を書き込む(変換開始)
(3) CTRLが0になるまで待つ(変換終了待ち)
(4) 値を読み取る
初期化はR, G, Bとクリアの4つについてgain(増幅度?)とintegration time(積分時間?)を設定します。クリアはR, G, Bのフィルターのかかってない素の光センサーの読み取り値みたいです。gain(0~15)は大きいほどセンサーの読み取り値が小さくなります。integration time(0~4095)は大きいほどセンサーの値が大きくなります。

ソースです。
#include <avr/io.h>
#include <util/twi.h>
#include <util/delay.h>
#include "i2c.h"
#include "lcd.h"

// S371の定数
#define S371 (0x74 << 1) // S371のスレーブアドレス

#define CTRL 0
#define CAP_RED   6
#define CAP_GREEN 7
#define CAP_BLUE  8
#define CAP_CLEAR 9
#define INT_RED_LO 10
#define INT_RED_HI 11
#define INT_GREEN_LO 12
#define INT_GREEN_HI 13
#define INT_BLUE_LO 14
#define INT_BLUE_HI 15
#define INT_CLEAR_LO 16
#define INT_CLEAR_HI 17
#define DATA_RED_LO 64
#define DATA_RED_HI 65
#define DATA_GREEN_LO 66
#define DATA_GREEN_HI 67
#define DATA_BLUE_LO 68
#define DATA_BLUE_HI 69
#define DATA_CLEAR_LO 70
#define DATA_CLEAR_HI 71

// 指定したアドレスにデータを書く
void write(byte address, byte data)
{
    i2c_start(S371 | TW_WRITE); // アドレスとデータを送る
    i2c_write(address);
    i2c_write(data);
    i2c_stop();
}   

// 指定したアドレスのデータを読む
byte read(byte address)
{
    byte x;

    i2c_start(S371 | TW_WRITE); // アドレスを送る
    i2c_write(address);
    i2c_stop();
    i2c_start(S371 | TW_READ);  // データを読む
    x = i2c_read_nak();
    i2c_stop();
    return x;
}

int main()
{
    uint16_t x;
    lcd_init(); // Noki5110初期化
    i2c_init(); // I2C初期化

    // S371初期化
    write(CAP_RED,   10);   // 0~15
    write(CAP_GREEN, 10);   // 0~15
    write(CAP_BLUE,  10);   // 0~15
    write(CAP_CLEAR, 10);   // 0~15
    write(INT_RED_LO, 2500 & 255);
    write(INT_RED_HI, 2500 >> 8);
    write(INT_GREEN_LO, 2500 & 255);
    write(INT_GREEN_HI, 2500 >> 8);
    write(INT_BLUE_LO, 2500 & 255);
    write(INT_BLUE_HI, 2500 >> 8);
    write(INT_CLEAR_LO, 2500 & 255);
    write(INT_CLEAR_HI, 25000 >> 8);

    while(1){
        write(CTRL, 0x01);  // 測定開始
        while(read(CTRL)) ; // 測定完了待ち
        // RED読み取り
        x = read(DATA_RED_HI) << 8 | read(DATA_RED_LO);
        lcd_locate(0, 0);
        lcd_hex4(x);        // 16進4桁出力
        // GREEN読み取り
        x = read(DATA_GREEN_HI) << 8 | read(DATA_GREEN_LO);
        lcd_locate(0, 1);
        lcd_hex4(x);        // 16進4桁出力
        // BLUE読み取り
        x = read(DATA_BLUE_HI) << 8 | read(DATA_BLUE_LO);
        lcd_locate(0, 2);
        lcd_hex4(x);        // 16進4桁出力
        // CLEAR読み取り
        x = read(DATA_CLEAR_HI) << 8 | read(DATA_CLEAR_LO);
        lcd_locate(0, 3);
        lcd_hex4(x);        // 16進4桁出力
        _delay_ms(500);     // 0.5秒待つ
    }
}

とりあえず何か動いているものはできました。
明るい所で測定すると、周囲の光のせいで測定値が飽和してしまいます。フードか何か遮光するものがいります。LEDの光を直接当てると暗闇の中でも飽和してしまいます。測定の方法としては、モジュールの白色LEDをつけて反射光で測るのもあるみたいです。その際は2.5mmくらいに近づけないといけないみたいです。回路をつなげることより、測定環境を整える方がたいへんかもしれません。あくまで絶対的な値を測定するのではなく、相対的な値の測定に使用するものです。
内部でプルアップもされているのでつなぐだけで測定できるのは気楽です。
I2Cさえできれば気軽に使えるのでフィジカルコンピューティングなんかにいいんじゃないでしょうか。

Nokia5110液晶の高速描画

2008-10-05 17:11:13 | AVR
AVRでNokia5110を使ってみる (初ネギ)の続きです。

ネギ振りに使った画像(56×48ドット×6)です。チラチラするドットは修正しました。

髪の毛が黒くつぶれているので、腕がどこにあるのか分かりません。変換元の画像はInterfaceのダウンロードページからダウンロードしたものです。


プログラムを色々変えて、ネギ振りの速度を測ってみました。

測定は1秒間のタイマー割り込みで何回振るかを数えています。左下に秒間振り回数を表示しています。速すぎてネギが見えていませんが、実物は残像が見えます。

(1) 元々のコード 秒間100振り
// (x0, y0)から大きさ(dx, dy)のPROGMEMの絵pを描く
void lcd_image_p(byte *p, byte x0, byte y0, byte dx, byte dy)
{
    byte x, y;

    for(y = 0; y < dy; y++){
        lcd_locate(x0, y0 + y);
        for(x = 0; x < dx; x++){
            lcd_data(pgm_read_byte(p++));
        }
    }
}

lcd_data( )を使って1バイトずつ転送しています。呼び出すときは、
PROGMEM byte h1[56*6] = {0x00,...,};
みたいなデータを作って、
lcd_image_p(h1, 28, 0, 56, 6);
のように呼び出します。(28, 0)に大きさ56×6の画像を表示します。pgm_read_byteはコードの格納されているフラッシュからデータを読み出す関数です。この関数を使うときはavr/pgmspace.hをインクルードしておく必要があります。
全画面が84×48なので、はちゅね画像のサイズ56×48は2/3のサイズになります。また一振りが6枚の画像を使ったアニメーションです。

(2) インライン展開+先読み 秒間123振り
// (x0, y0)から大きさ(dx, dy)のPROGMEMの絵pを描く
void lcd_image_p(byte *p, byte x0, byte y0, byte dx, byte dy)
{
    byte x, y, c;

    c = pgm_read_byte(p++);                     // 1バイト先読み
    PORTB &= ~(_BV(SCE));                       // SCE=0
    for(y = 0; y < dy; y++){
        PORTB &= ~(_BV(DC));                    // DC=0 (コマンド)
        SPDR = SETX | x0;                       // x座標送信
        loop_until_bit_is_set(SPSR, SPIE);      // 送信完了待ち
        SPDR = SETY | (y0 + y);                 // y座標送信
        loop_until_bit_is_set(SPSR, SPIE);      // 送信完了待ち
        PORTB |= _BV(DC);                       // DC=1 (データ)

        for(x = 0; x < dx; x++){
            SPDR = c;                           // データ送信
            c = pgm_read_byte(p++);             // 次のデータを読み込む
            loop_until_bit_is_set(SPSR, SPIE);  // 送信完了待ち
        }
    }
    PORTB |= _BV(SCE);                          // SCE=1
}

lcd_locateとlcd_dataをインライン展開しました。SCEの制御はまとめて最初と最後だけにしています。SPIの送信完了待ちの間に次のデータを読み込むようにしました。loop_until_bit_is_set( )というマクロはsfr_defs.hで定義されています。avr/io.hを読み込むと自動的に読み込まれるので明示的にインクルードする必要はありません。I/Oポートの特定のビットが1になるまで待ちます。sfr_defs.hで定義されているマクロは他にbit_is_set、bit_is_clear、loop_until_bit_is_clearがあります。

(3) ループをアンロールする 秒間153振り
    for(x = 0; x < dx / 4; x++){
        SPDR = c;                           // データ送信(1)
        c = pgm_read_byte(p++);             // 次のデータを読む
        loop_until_bit_is_set(SPSR, SPIE);  // 送信完了待ち

        SPDR = c;                           // データ送信(2)
        c = pgm_read_byte(p++);             // 次のデータを読む
        loop_until_bit_is_set(SPSR, SPIE);  // 送信完了待ち

        SPDR = c;                           // データ送信(3)
        c = pgm_read_byte(p++);             // 次のデータを読む
        loop_until_bit_is_set(SPSR, SPIE);  // 送信完了待ち

        SPDR = c;                           // データ送信(4)
        c = pgm_read_byte(p++);             // 次のデータを読む
        loop_until_bit_is_set(SPSR, SPIE);  // 送信完了待ち
    }

xのforループの内側だけ示します。他は(2)と一緒です。ループを展開して4つずつ送信しています。横方向が56ドットなので8個ずつまでいけます。

(4) ウェイトをnopにしてみる 秒間210振り
    for(x = 0; x < dx; x++){
        SPDR = c;               // データ送信
        c = pgm_read_byte(p++); // 次のデータを読む
        asm("nop"); // (1)
        asm("nop"); // (2)
        asm("nop"); // (3)
        asm("nop"); // (4)
        asm("nop"); // (5)
        asm("nop"); // (6)
    }

xのforループの内側だけ示します。他は(2)と一緒です。loop_until_bit_is_set( )による待ち合わせを止めてみました。nopの個数は試行錯誤した結果6個がちょうどでした。ただし、関数から抜ける前にloop_until_bit_is_set( )を入れる必要がありました。今回は内部発振クロックの8MHzが動作クロックで、4MHz通信を行っています。コンパイラのはくオブジェクトが変わったりするとnopの個数が変わります。環境や条件によって動作しなくなるので、通常はやってはいけないコードです。
loop_until_bit_is_set( )による送信完了判定自体が数クロックかかっているので、実際にSPIEが1になってから、数クロック後に送信が完了したことが分かります。この分の無駄が結構あったみたいです。


最初のコードから2倍以上速くなりました。もっとちゃんとオブジェクトを見たり、出力波形を観測したりすればよかったのですが、試行錯誤が楽しかったので手を抜いています。書き換える必要のある最小限のデータだけ送るようにすれば、もっと速くなるはずです。全画面転送だと、画面サイズが1.5倍、画面枚数が1/6なので計算上は秒間840枚(=210*6/1.5)はいきそうです(実際は座標設定とかも減るのでもっと速い?)。約1.2ミリ秒で一画面の転送が終わることになります。画面全体でも504バイト(=84×48÷8)なので、SRAM上に画面全体のコピーを持っていて必要なときだけ全画面転送するという使い方も可能です。

AVRはなくなるの?

2008-10-03 23:42:25 | AVR
陰気な男でいいですか?さん経由で知ったのですが、PICのMicrochip社がAVRのAtmel社を買収しようとしているそうです(Microchip社のプレスリリース)。MicrochipがAtmelを買収したらAVRの今後はどうなるんでしょうね。

(1) AVRそのものがなくなる
(2) AVRという名前は消えるがchipは生き残って、PIC MEGA88とかになる。
(3) 現行製品はある程度生産されるが、新製品の開発は行われず、徐々に終息する。

タイトルは「ベータマックスはなくなるの?」という新聞広告のパロディです。「答えはもちろんノー」と続きます。シャレなので怒らないでください。


10/4 追記 日経BPのtech-onに「MicrochipとON SemiconductorがAtmelを買収へ」という記事が出ていました。

AVRでNokia5110を使ってみる (初ネギ)

2008-10-03 00:51:30 | AVR
ノキア液晶5110の続きになります。
その後、マイコン工作実験日記さんからトラックバックをいただきました。同じLCDを使っておられます(記事)。


その後見つけた参考になりそうなコードです。
(1) LCD Nokia 3310 (PCD8544) Driver in WinAVR(avr-gcc)。ソースコードもダウンロードできます。
(2) トラ技2008年1月号のp.106「フォント内蔵のグラフィック液晶制御IC」という記事です。こちらはソースのダウンロードはできませんが付録CD-ROMにソースが格納されています。


前回紹介させていただいたトラ技2006年3月号の記事のプログラムですが、初期化の部分にバグがあります。GLCD.cの285行目です。元のコードは
SPI_tx_command(0b00001000 | 4); /* Bias=4 (0-7) */
となっています。biasを設定するコマンドは0x10なのでビット位置が違っています。また4に設定したいときは実際には3を書く必要があります。正しくは以下になります。
SPI_tx_command(0b00010000 | 3); /* Bias=4 (0-7) */


AVRのSPIで遊びたいときって、どうすればいいのか不思議でした。プログラマのISPと同じピンを使うからです。信号がぶつかったりとかしないのかと思っていました。そのあたりの話はすんさんの掲示板で色々教えてもらいました。プログラマはプログラムが終わるとハイインピーダンスになるので、気にせずそのままつないでいいみたいです。リセットしてから実際にSPIを使い始めるまではSPI関連の線が浮いた状態になるのがちょっと気にかかるところです。


AVRでSPIを使うには3つのレジスタを使います。SPCRは制御、SPSRはステータス、SPDRはデータです。今回はマスターで使用します。I2Cと違って面倒なことがありません。初期化したあとは、SPDRに書き込むとデータ送信が始まってSPSRのSPIFビットを見て送信完了を待つだけです。


Nokia5110の端子ですが、用心のために3pinのSCEはpull-up、4pinのRESETはpull-downしています。SCEはISP中に変なコマンドが飛んだりしないように、RESETは電源投入直後に確実にLOWになるようにです。また、Vcc(1pin)とGND(2pin)の間にパスコン(0.1uF)をいれています。

ソースです。
#include <avr/io.h>
#include <util/delay.h>

// pin接続
// 1 Vcc
// 2 GND
// 3 SCE   PB2(16) pull-up
// 4 RESET PB1(15) pull-down
// 5 D/C   PB0(14)
// 6 SDIN  PB3/MOSI(17)
// 7 SCLK  PB5/SCK(19)
// 8 LED+

typedef unsigned char byte;

// ピン割り当て
#define SCK   PB5
#define MOSI  PB3
#define SCE   PB2
#define RESET PB1
#define DC    PB0

// LCDコマンド
// H=0 or 1    76543210
#define FSET 0b00100000 // 2:PD 1:V 0:H
// H=0         76543210
#define DISP 0b00001000 // 2:D 0:E
#define SETY 0b01000000 // 2-0:Y (0-5)
#define SETX 0b10000000 // 6-0:X (0-83)
// H=1         76543210
#define TMP  0b00000100 // 1-0:TC (0-3)
#define BIAS 0b00010000 // 2-0:BS (0-7) 3のとき1/48duty
#define VOP  0b10000000 // 6-0:Vop (0-127) コントラスト

// コマンドを送る
void lcd_cmd(byte cmd)
{
    PORTB &= ~(_BV(SCE) | _BV(DC)); // SCE=0 DC=0
    SPDR = cmd; // コマンド送信
    while(!(SPSR & _BV(SPIE))); // SPIE=1になるまで待つ
    PORTB |= _BV(SCE) | _BV(DC); // SCE=1 DC=1
}

// データを送る
void lcd_data(byte d)
{
    PORTB &= ~_BV(SCE); // SCE=0 DC=1
    SPDR = d; // データ送信
    while(!(SPSR & _BV(SPIE))); // SPIE=1になるまで待つ
    PORTB |= _BV(SCE); // SCE=1 DC=1
}

// LCDクリア
void lcd_clear(void)
{
    int i;

    for(i = 0; i < 84 * 6; i++) lcd_data(0);
}

// 座標変更 x:0-83 y:0-5
void lcd_locate(byte x, byte y)
{
    lcd_cmd(SETX | (x & 0x7f));
    lcd_cmd(SETY | (y & 7));
}

// LCD初期化
void lcd_init(void)
{
    PORTB = _BV(SCE); // SCLK=0 SDIN=0 D/C=0 RESET=0 SCE=1
    DDRB =  _BV(SCK) | _BV(MOSI) | _BV(DC) | _BV(RESET) | _BV(SCE);

    // SPI初期化 (Nokia5510LCDは4MHzまで)
    SPCR = _BV(SPE) | _BV(MSTR); // SPI許可、マスター、fosc/4
    SPSR = _BV(SPI2X); // 2倍速

    // reset
    _delay_ms(100); // 100ミリ秒待つ
    PORTB |= _BV(RESET);

    // LCD初期化
    lcd_cmd(FSET | 1);  // H=1
    lcd_cmd(BIAS | 3);  // BIAS n=4 1:48 dup rate
    lcd_cmd(VOP  | 16); // Vop コントラスト(変わらない、なんで?)
    lcd_cmd(TMP  | 1);  // TC=1
    lcd_cmd(FSET);      // H=0
    lcd_cmd(DISP | 4);  // display mode 4:normal 5:reverse

    lcd_clear();
    lcd_locate(0, 0);
}

int main()
{
    lcd_init();

    // 描画 lcd_locateで移動してlcd_dataでデータを送る
    // コード省略

    while(1) ; // 無限ループ
}

LCDの初期化は以下のようにします。
(1) リセットを100ミリ秒以上Lにする(電源投入直後からずっとLにしてもいい)。
(2) H=1にして拡張コマンドを使えるようにする
(3) BIASの設定。48ラインなので3にします(データシートのn=4の設定)。
(4) Vopの設定。コントラストの設定です。後述
(5) TCの設定。温度補償らしいのですが、よく分かりません。他と同じ1にしています。
(6) H=0にして通常コマンドに切り替える
(7) display modeをnormalまたはreverseにする。
(8) ビデオRAMをクリアする。

(4)のVopの設定でコントラストを変えれるはずなのですが、なぜかうまくいきません。値を変えてもコントラストは変わりません。Vopは変な値にすると定格を超えてしまうので注意深く設定する必要のあるレジスタなのですが、設定がききません。何か変です。
(6)の時点で、縦書きモードと横書きモードを選択できます。データを送った後に座標が縦に進むか横に進むかの切り替えです。

Nokia5110のデータ形式はマイコン工作実験日記さんの解説が分かりやすいです。LCDの上の方がLSBになるところが注意点です。


何を表示しようかと思いましたが、最近のお約束みたいなのでネギ振りに挑戦してみました。絵心がないので、データを変換しただけです。今回、一番時間がかかった所です。ubuntsuに入っていたGIMPというグラフィックツールで縮小とモノクロ化しています。動かしてみると、ちらちらするドットがいたりしますが、修正していません。画像サイズは56x48で1枚あたり336バイトです。6枚あるので2016バイトになります。SRAMには展開できないのでROMから直接読み込むようにしています。
SPIの通信速度は4Mbpsです(Nokia5110の最大)。336バイトを送るのにオーバーヘッドを考えないと0.7ミリ秒くらいです。
データが大きいので描画部分のソースは省きました。
撮影は携帯なのでぶれています。

描画の高速化について少し考えてみました。
(1) 連続してデータを送るときは、SCEの制御を行わない。
(2) 送信待ちの間に次に送信するデータを読み出す。
(3) 縦書きモードと横書きモードを利用して、x, y座標の設定を減らす


絵が動くと楽しいですね。変換した画像はあまりきれいでないので、もう少しなんとかしたいものです。

クロックプリスケールレジスタ

2008-10-02 00:17:54 | AVR
メモがわり

ATMega88にはクロック分周器が内蔵されています。CLKPSレジスタの値で1, 2, 4, 8, 16, 32, 64, 128, 256分周のどれかを選べます。ヒューズのCKDIV8はCLKPSレジスタの初期値を決めるヒューズです。CKDIV8ヒューズは初期値を決めるだけなので、動作中にCLKPSレジスタを書き換えることでクロック分周値を変更することが可能です。

CLKPSレジスタ(4bit)の内容と分周値です。

CLKPS
0000 1分周
0001 2分周
0010 4分周
0011 8分周
0100 16分周
0101 32分周
0110 64分周
0111 128分周
1000 256分周
1001~1111 予約

書き換え手順は、
(1) CLKPRレジスタのCLKPCEビットを1にします。このときCLKPSn (n=3~0)も0にします。
(2) 4クロック以内にCLKPSnを書き換えます。

具体的には以下のようにします。
    CLKPR = _BV(CLKPCE); // CLKPCEビットを1にする
    CLKPR = 0b0011;      // 8分周にする

ENC28J60のデフォルトのCLKOUTを25MHzだと勘違いしていたので、最初はCKDIV8にして8分周した3.125MHzでAVRを動作させて後で2分周の12.5MHzに切り替える方法を調べていました。でもENC28J60のデフォルトのCLKOUTは25MHzを4分周した6.25MHzだったので、あまり意味はなくなりました。動作している最中に外部クロックの周波数を切り替えるのって、誤動作したりしないのでしょうか。

AVRイーサネットを作ってみました(2)

2008-10-01 01:14:06 | AVR
AVRイーサネットを作ってみましたの続きです。

前回はキットをはんだづけして、最初から書き込まれているファームウェアで遊んでみました。今回は初期ファームウェアを改造してENC28J60のリビジョン番号を表示してみます。

写真のようにAVRライターをつないでみました。ライターはいつものトラ技8月号付録の78K0基板で、書き込みソフトはでんし研さんのです。いつもはジャンプワイヤーでつないでいますが、AVRイーサ基板にはISP用のソケットがあるので、オスとメスが逆になります。そこで秘密兵器というか共立エレショップで売っているAVRWRT用ブレッドボードISPケーブル/QII6SQI(註7C1412)を逆さまにつなぎました(ISPケーブルの写真ページへのリンク)。

してみたことを列挙します。

- 元のAVRの中身を吸い出して、別のAVRにクローンを作って、元のAVRを予備にしました。これで壊しても平気です。
- クローンもオリジナルと同じ動作をすることを確認しました。
- スイッチサイエンスさんが公開されている初期ファームウェアのソースをダウンロードしてきました(キットの説明ページ)。
- AVR Studioで新しいプロジェクトを作って、ダウンロードした.hと.cを放り込んでコンパイルしてみました。
- eeprom用のhexファイルの作り方がわからなかったので、元のeepromを吸い出した.hexファイルを直接書き換えてIPアドレスを設定しました。1f0と1f1番地がMACアドレスの下位2バイト、1f2~1f5の4バイトがIPアドレスです。チェックサムは電卓で計算しなおしました。
- 今作ったファームを書き込んでみてオリジナルと同じ動作をするかどうか確認しました。
- ここまでは、ISPではなく、AVRを抜き差ししながらしていました。ここでAVRライターをISPコネクタにつないで書き込みができるかどうか確認しました。ここまでISPにしていなかったのは、うまくいかなかったときに原因の切り分けができるようにと考えました。
- ソースを読み始めました。ソース中fill_data_pという関数を使って、送信したい内容をパケットに追加しています。この関数のパラメータを変えることでブラウザが表示する文字が変わります。
- まだ全部読み終わったわけではありませんが、気になっていたチップのリビジョンを表示してみました。print_webpageという関数の最初のfill_tcp_data_pの後に以下のコードを追加しました。
{
    char s[3];
    s[0] = '0' + enc28j60getrev();
    s[1] = ' ';
    s[2] = 0x00;
    plen = fill_tcp_data(buf, plen, s);
}

enc28j60getrev()はENC28J60のリビジョンを読み出す関数です。fill_tcp_dataは文字列をパケット用のバッファに追加する関数です。fill_tcp_data_pはROM上の文字列を追加します。_pのない方はSRAM上の文字列です(このあたりはAVRがハーバードアーキテクチャなせいですね)。
- 結果としてリビジョンはB4であることが分かりました。ENC28J60の表面を見ると年コードが07週コードが29になっています。

してみたことは、ここまでです。


microchipのENC28J60のページから日本語データシートがダウンロードできます(リンク)。Errataも同じページからダウンロードできます。

チップのリビジョン番号を気にしていたのはhamayanさんの昔の記事を見つけたからです(2006年9月27日の記事)。この記事ではENC28J60のリビジョンB4の不具合に関する解説があります。リビジョンB4ではSPIに不具合があって、回避方法として8~10MHzで通信を行うのだそうです。Errataには「When the SPI clock from the host microcontroller is run at frequencies of less than 8 MHz, reading or writing to the MAC registers may be unreliable.」と書かれています。hamayanさんも触れられているオプティマイズさんの記事にも同様の注意が書かれています(リンク)。AVRイーサネット基板ではAVRは3.3Vで動作していて動作クロックは内蔵クロックの8MHzです。SPIの速度は最高で動作クロックの半分なので、4MHzで通信していることになります。これでは上のErrataにひっかかってしまうはずですが、なぜか動いています。unreliableと書かれているので、常に間違うわけでなく、たまに間違うということなのでしょうか。

Errataでは回避方法として2つの方法をあげています。一つは8MHzより速く通信することで、もう一つはSPIのクロックをENC28J60に入力している25MHzを分周したクロックにすることです。今回で言うとSPIを8MHzより速くするにはAVRのクロックを16MHz以上にする必要がありますが、そのためには動作電圧を5Vで動作させる必要があります。また、内部発振は8MHzまでなので外部にクロックを設ける必要があります。後の回避方法は、ENC28J60のCLKOUTからAVRのクロックをもらうことが考えられます。ただしENC28J60とAVRのリセットが共通なのでAVRをISPしようとして、リセットがLになるとENC28J60からのクロックも止まってしまうので、ISPできなくなってしまいます。
3.3VのATMega168は13.3MHzまで動作するはずなので、25MHzの半分の12.5MHzを入力するのがよさそうです。

一番速いのは、もっとリビジョンの新しいENC28J60を入手することです。Errataを見るとB7というリビジョンもあるみたいです。こちらではB6のENC28J60を売っています。

後でJUN猫さんがどうしているのか調べてみるつもりです。

なんか色々思い悩んでいますが、とりあえず動いてるからいいのかな。

AVRイーサネットを作ってみました

2008-09-28 10:35:46 | AVR
AVRイーサネットの続きです。

前回は、スイッチサイエンスさんがAVRイーサネットキットのモニターを募集中という記事でした。その後、オリジナルキット「AVRイーサネットプラットフォーム(キット) 」として発売も開始されました。私も一応、応募していたのですが返事がなかったので抽選にはずれたと思っていました。そんなある日のこと、応募メールが迷惑フォルダーにはいっていたそうで、試してみますか?というメールがきました。ぜひともと、返事をしたところ速達で送ってくれました。こんなこと言うと怒られちゃいそうですが、もう商品化されていることですし、無視しちゃってもよかったのに、わざわざ連絡をくださるなんて本当に誠実な方だと思いました。

とりあえず組み立てたてて、動作するところまで確認しました。途中撮った写真を並べます。

送られてきたパッケージの中身です。

部品がはいっている袋の右上にはMACアドレスがシールで貼ってあります。一緒に入っていた紙は部品表です。

部品のはいっている袋です。


部品を広げてみました。

部品数はそんなに多くないです。AVR ATMega168にもMACアドレスのシールが貼ってあります。シールのない方がENC28J60です。

基板です。

部品の形が印刷されていて分かりやすくなっています。足の間に二本線がついているのがコンデンサです。抵抗のところにも抵抗の形がついていて分かりやすくなっています。

中央に「SSCi AVR Ethernet v0.1」と書かれています。


お約束どおり、背の低い部品からつけていきます。

積層セラミックコンデンサは足の幅が2.54mmのものが入っているので部品の足の加工が不要です。右下の黒いのがフェライトビーズです。

R4'は回路図にはあったのですが、部品にはありませんでした。

チップ抵抗もつけることができるということなのでしょうか。

残るはモジュラージャックだけです。

ICソケットの間のピンはICソケットより前につけています。狭そうだったので後でつけるのが難しそうだったからです。R2の1kΩがLEDの電流制限抵抗です。青とか他の色に変えるときはR2も変える必要がある場合があります。ブレッドボードに挿すことを考えているので足は丸ピンのものにしています。秋月の丸ピンICソケット・両端オスピン(シングル40P)を折って使いました(通販番号P-01382)。手前が18pin、奥が6pinです。

完成です。

モジュラージャックは足がいっぱいあるので、折らないように慎重に挿します。また、平らになっていないので、前の方からつけないと浮いてしまいます。完成しておら思ったのは、AVRの方のICソケットは丸ピンにしておいた方がよかったかもしれません。抜き差しがあるかもしれないからです。

3V3とGNDがショートしていないことをテスターで確認しました。

動作チェックです。

電源は秋月の3.3VのACアダプタです。サンハヤトのコネクター変換基板CK-23を使って給電しています。この変換基板はブレッドボードで使うのにぴったりのサイズです。
このボードのIPアドレスは192.168.18.24です。AVRのEEPROMに書き込まれているそうです。PCとはクロスケーブルでつないでいます。PCのIPアドレスを192.168.18.1に変更しました。ping 192.168.18.24とすると返事が返ってきます。また、arp -aとするとMACアドレスも見れます。ブラウザ(firefox)でhttp://192.168.18.24/secret/につなぐとLEDのオンオフができます。写真はLEDがついているときの様子です。


とりあえず、してみたことは、ここまでです。

作るときの注意は、J2(ICソケットの間の6pin)は、ICソケットより先につけることと、モジュラージャックを浮かないようにつけることの2つです。
改造としては、pinを丸pinにしたことだけです。他に考えられるのはpinのかわりにソケットをつけるとか、LED(と電流制限抵抗R2)を変えることです。
他の部品があたるのでISPコネクタにボックス型のソケットをつけることはできません。
AVCCと電源の間にノイズ低減用のインダクタを入れれるとよかったかもしれません。
スイッチサイエンスさんも書いておられますがリセットは切り離せるとよかったかもしれません。ただし、ジャンパーピンを設置する余裕が基板にはありません。
あと要望としては、AVRの外部クロック動作です。やはり内蔵クロックの8MHzより高速に動かしてみたい気がします。現状ではENC28J60のCLKOUTがAVRのXTAL1につながっています。


とりあえず、スイッチサイエンスさんのAVRイーサネットを作ってみました。ブレッドボードにいいサイズでちっちゃくてかわいいです。
ファームウェアのソースも公開されているので、できれば改造して遊んでみたいのと、超低コスト インターネット・ガジェット設計の例題を動かせないか試してみたいと思っています。

秋月でAVRISP mkIIが4000円

2008-09-24 22:31:13 | AVR
秋月でAVRISP mkIIの取扱いが始まっています。お値段は4000円とかなりお安くなっています(通販コードM-02582)。ちゃんと調べたわけじゃないですが国内最安じゃないでしょうか?
AVRISP mkIIはATMEL純正のライタで、ほとんど全てのAVRへの書き込みが可能です。USB接続なのでシリアルポートやパラレルポートのないPCでも気軽に使えます。ソフトは純正のAVR Studioを使えます。ISPも可能です。
注意点としては、高電圧書き込みには対応していないことと、デバッグはできないことです。また、給電する機能はありません。接続端子はATMEL標準の6pinのものです。

pickit2の4000円といい、いいぞ秋月!!

温度センサーLM73を使ってみる

2008-09-23 18:54:02 | AVR
エレキジャックNo.8に、ナショナルセミコンダクタの温度センサーLM73が載った基板が付録についてきました。マイコンとはI2Cで通信します。

LM73基板です。

足とパスコン(0.1uF)をつけています。パスコンはつけなくても、基板の外(2pinと3pinの間)につけてもいいです。穴が開いているのでチップコンデンサでなくてもいいのですが、チップ部品のはんだづけの練習もかねてつけてみました。ちょっとななめってます。

pin配置です。エレキジャックにpin配置が載っていなかったので、データシートから写しました。
1 ADDR I2Cのslave addressを決めるpin。openで0b1001100になる
2 GND
3 Vdd 2.7~5.5V
4 SMBCLK I2CのSCL (要pull up)
5 #ALERT 範囲外出力
6 SMBDAT I2CのSDA (要pull up)

つなぐ必要があるのはGND(2pin)、Vdd(3pin)、SMBCLK(4pin)、SMBDAT(6pin)の4本です。内部レジスタに設定した範囲を超えるとALERT信号を出すことができますが、使わなければつながなくていいです。SMBCLKとSMBDATのpull up抵抗はエレキジャックのミッション2第1章では3.3kΩ、第2章では1kΩです。ちなみに第1章ではLM73をPICに、第2章ではHCS08につないでいます。

このところAVRづいているので、AVRにつないでみます。

I2C通信の参考にしたサイトです。
趣味の電子工作の部屋 by すん I2Cコントロール実験
AVR試用記 I2C通信
エレキジャック 1、2、3線シリアル・インターフェース

I2Cでマスターからスレーブにデータを送るには以下の手順になります。
(1) スタートビットを送る
(2) スレーブのアドレスとWを送る(SLA+Wといいます)。
(3) ACK/NAKをもらう
(4) データを送る
(5) ACK/NAKをもらう。必要なだけ(4)と(5)を繰り返す
(6) ストップビットを送る

I2Cでマスターからスレーブのデータを受け取るには以下の手順になります。
(1) スタートビットを送る
(2) スレーブのアドレスとRを送る(SLA+Rといいます)。
(3) ACK/NAKをもらう
(4) データを受信する
(5) ACKを返して、さらにデータをもらうために(4)に戻るか
NAKを返して、もうデータがいらないことを伝える
(6) ストップビットを送る

上の2つを組み合わせたパターンもあります。そのときは、受信時のスタートビットはリピーテッド・スタートと名前が変わります。また、上の手順ではエラーチェックをしていませんが、やたら細々とエラーチェックが必要になります。

ATMega88はTWIというI2Cのハードがついているので、レジスタをいじることでI2C通信ができます。全部ソフトで書くのよりは随分と楽になります。

実験している様子です。

上に見えているのがライター代わりの78K0付録基板(トラ技8月号)です。左側がRS232C通信用の秋月製FT232RL USBシリアル変換モジュールです。中央がAVR ATMega88で、右側が今回のLM73付録基板です。秋月で売っている小型温度計モジュール(通販番号M-01063)で温度を測っています。温度計の影になっていますが電源は3.3VのACアダプタです(実測3.5V)。
0.5秒毎に、LM73で測った温度データはI2CでATMega88に読み込まれてUSBシリアル変換モジュール経由でPCに送られます。

ソースです。<と>は全角になっています。
#include <avr/io.h>
#include <util/twi.h>
#include <util/delay.h>

typedef unsigned char byte;

// USART関連は省略

// 16進1桁出力
void puthex1(byte d)
{
    d += '0';
    if(d > '9') d += 'a' - '9' - 1;
    putch(d);
}

// 16進2桁出力
void puthex2(byte d)
{
    puthex1(d >> 4);
    puthex1(d & 0x0f);
}

// 10進2桁出力 (x < 100のみ可)
void putdec2(byte x)
{
    putch('0' + x / 10);
    putch('0' + x % 10);
}

// エラー
void i2c_error(void)
{
    putch('['); puthex2(TWSR); putch(']'); // TWSRの内容
    putch(0x0d); putch(0x0a); // 改行
    PORTB = 0x01; // LED点灯
    while(1) ;
}

// 通信速度の初期化
void i2c_init(void)
{
    TWSR = 0b00000000;  // 1分周
    TWBR = 32;          // 100k = 8MHz / (16 + 2 * TWBR * 1)
}

// master 1byte送信
void i2c_write(byte d)
{
    TWDR = d; // 送信データ
    TWCR = _BV(TWINT) | _BV(TWEN);
    while( !(TWCR & _BV(TWINT)) ) ; // データの送出完了待機
    if((TWSR & TW_STATUS_MASK) != TW_MT_DATA_ACK) i2c_error();
}

// master 1byte受信(ackを返す)
byte i2c_read_ack(void)
{
    TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWEA);
    while( !(TWCR & _BV(TWINT)) ) ; // 受信完了待ち
    if((TWSR & TW_STATUS_MASK) != TW_MR_DATA_ACK) i2c_error();
    return TWDR; // データを返す
}

// master 1byte受信(noackを返す)
byte i2c_read_nak(void)
{
    TWCR = _BV(TWINT) | _BV(TWEN);
    while( !(TWCR & _BV(TWINT)) ) ; // 受信完了待ち
    if((TWSR & TW_STATUS_MASK) != TW_MR_DATA_NACK) i2c_error();
    return TWDR; // データを返す
}

// master送信開始、IDはslave address << 1
void i2c_start(byte id)
{
    byte s;

    // 開始条件を送る
    TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);
    while( !(TWCR & _BV(TWINT)) ) ; // 開始条件の送出完了待機
    s = TWSR & TW_STATUS_MASK;
    if(s != TW_START && s != TW_REP_START) i2c_error();

    // アドレスを送る
    TWDR = id;
    TWCR = _BV(TWINT) | _BV(TWEN);
    while( !(TWCR & _BV(TWINT)) ) ; // アドレスの送出完了待機
    s = TWSR & TW_STATUS_MASK;
    if(s != TW_MT_SLA_ACK && s != TW_MR_SLA_ACK) i2c_error();
}

// master送信終了
void i2c_stop(void)
{
    TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN);
    while( !(TWCR & _BV(TWSTO)) ) ;
    // statusはTW_NO_INFOになる
}


#define LM73 (0x4c << 1)    // LM73のslave address

int main()
{
    byte th, tl;

    DDRB  = 0x01;
    PORTB = 0x00;

    usart_init();
    i2c_init();

    // LM73設定
    i2c_start(LM73 | TW_WRITE);
    i2c_write(0x04); // register 4
    i2c_write(0x60); // 14bit resolution
    i2c_stop();

    // ポインタを0にしておく(readするだけで温度が読めるようになる)
    i2c_start(LM73 | TW_WRITE);
    i2c_write(0x00); // register 0
    i2c_stop();

    while(1){
        // 温度受信
        i2c_start(LM73 | TW_READ);
        th = i2c_read_ack(); // 上位読み込み
        tl = i2c_read_nak(); // 下位読み込み(最後はNAKを返す)
        i2c_stop();

        // 結果出力 (マイナスは考えない)
        puthex2(th);
        puthex2(tl);
        putch(' ');
        putdec2(th << 1 | tl >> 7); // 結果出力 整数部
        putch('.');
        putdec2(((tl & 0x7f) * 200) >> 8); // 結果出力 小数部
        putch(0x0d);    // 改行
        putch(0x0a);

        _delay_ms(500); // 0.5秒待つ
    }
}

USART関連(usart_init、getch、putch)は前回と同じなので省略しました。
i2c関係は以下です。
i2c_init(初期化)、
i2c_start(スタートビットを送る)、
i2c_stop(ストップビットを送る)、
i2c_write(1バイト、マスター→スレーブ通信)、
i2c_read_ack(マスター←スレーブ通信とack返答)、
i2c_read_nak(マスター←スレーブ通信とack返答)
エラーを見つけたらi2c_errorに飛んで無限ループになります。
i2c_startは厳密には4種類考えられます(R/Wとstart, repeat start)が、たいへんなので1つにまとめています。

TeraTermで受信している様子です。

左側が受け取った生データで右側が換算した値です。27℃くらいです。
受信データは、16ビット中最上位が符号ビット、8ビットが整数部、7ビットが小数部になっています。11ビット精度のときは、符号1 + 整数部8 + 小数部2です。14bitだと小数部が5ビットに伸びます。

I2Cは、今回初めて使いました。なかなか複雑でデバッグがたいへんでした。

AVRのUSARTを使ってみる

2008-09-23 16:12:13 | AVR
AVRでUSARTを使ってみました。
MAX232のようなレベル変換のICでなくて、秋月で売っているFT232RL USBシリアル変換を使っています(通販番号K-01977)。
FT232RLは3.3VのLDOを内蔵していて、外部に3.3Vを給電することができます。データシートを見ると(4.2)最大50mAを供給できるそうです。入出力の電圧はVCCIOというpinにいれる電圧で決まります。秋月モジュールではJ1を1-2ショートで5V、2-3ショートで3.3Vを選べます。VCCIOはCN2の4pinに出ているので、ジャンパを取り去って、外部から電圧を決めてやることもできます。

秋月のモジュールにはリセット用抵抗端子PU1, PU2というのがあります。PU2の電圧を10kΩ2個で分圧してPU1に出ています(PU2-10kΩ-PU1-10kΩ-GND)。PU1がPU2の半分の電圧になる回路になっています。ついてきた紙には、この回路の使い方が書いてありませんでした。FT232RLのデータシートを見ると、RESET#の説明に
Active low reset pin. This can be used by an external device to reset the FT232R. If not required can be left unconnected, or pulled up to VCC.
と書かれています。resetはオープンにするかプルアップでいいので、秋月のモジュールにあるリセット用回路は使う必要がないみたいです。何のためについている回路なんでしょうね?

秋月のモジュールは次の4本を接続するだけで使えるようになります。

1pin TXD PC→マイコンへのデータ伝送
5pin RXD マイコン→PCへのデータ伝送
7pin GND (24pin GND、内部でつながっている)
4pin VIO I/Oの電圧を外部から給電する(J1のジャンパは取り外す)

秋月モジュールから給電するときは、VIOのかわりに5VならVCC(15pin, 21pin)、3.3Vなら3V3(19pin)をつなぎます。

ATMega88側はRXD(2pin)を秋月モジュールのTXDに、TXD(3pin)を秋月モジュールのRXDにつなぎます。あべこべにつなぎます。お約束通り、最初はTXD同士、RXD同士つないで動かない理由が分かりませんでした。

AVRの初期設定は次の3つです。
(1) UBRR0でボーレートの設定(必要ならUCSR0AのU2X0も設定)
(2) UCSR0Cでパリティ、データビット長、ストップビットの設定
(3) UCSR0Bで送受信の許可

ソースです。
#include <avr/io.h>

typedef unsigned char byte;

byte getch(void)
{
    while(!(UCSR0A & _BV(RXC0))) ;
    return UDR0;
}

void putch(byte c)
{
    while(!(UCSR0A & _BV(UDRE0))) ;
    UDR0 = c;   
}

void usart_init(void)
{
#if 0
    UBRR0 = 51; // 9600bps @ 8MHz
#else
    UBRR0 = 25; // 38400bps @ 8MHz
    UCSR0A = _BV(U2X0);
#endif

    // 19.10.4 USART制御/状態レジスタC
    //         ++------ UMSEL01:UMSEL00 00 非同期動作
    //         ||++---- UPM01:UPM00     00 パリなし
    //         ||||+--- USBS0            0 ストップビット 1ビット
    //         |||||++- UCSZ01:UCSZ00   11 データビット 8ビット 
    //         |||||||+ UCPOL0           0 クロック極性立ち上がり
    UCSR0C = 0b00000110;

    // 19.10.3 USART制御/状態レジスタB
    //         +------- RXCIE0 0
    //         |+------ TXCIE0 0
    //         ||+----- UDRIE0 0
    //         |||+---- RXEN0  1 受信許可
    //         ||||+--- TXEN0  1 送信許可
    //         |||||+-- UCSZ02 0 データビット長
    //         ||||||+- RXB80  0 受信データビット8
    //         |||||||+ TXB80  0 送信データビット8
    UCSR0B = 0b00011000;
}

int main()
{
    // LED設定
    DDRB = 0x01;
    PORTB = 0x01;

    usart_init();

    while(1){
        putch(getch());
        PORTB ^= 0x01;
    }
}

送られてきたデータをエコーバックして、1文字毎にLEDを反転します。
送受信の関数名は最初getcとputcだったのですが、内部モジュールと名前がぶつかっているという警告が出たのでgetchとputchに名前を変えました。

とりあえず、外とお話しできるようになるとデバッグが楽になります。

AVRでタイマー割り込み

2008-09-20 16:57:04 | AVR
このところAVRで遊んでいます。タイマー割り込みを使ってみました。
タイマー割り込みを使うときは以下のことをします。

- avr/interrupt.hのinclude
- 割り込みハンドラを作る
- 割り込み許可レジスタの設定

割り込みハンドラはISR(ベクトル名)というマクロの直後に関数の中身を書きます。
割り込み許可レジスタはTIMSK1というレジスタに値を設定します。

動作確認はLEDチカチカです。14pin(PB0)にLEDと電流制限抵抗をつないでいます。

AVR Libcのページ割り込みのページを参考にさせていただきました。

ソースです。
#include <avr/io.h>
#include <avr/interrupt.h>

// 割り込み処理ハンドラ
ISR(TIMER1_COMPA_vect)
{
    PORTB ^= 0x01; // PB0反転
}

// タイマー初期化
void timer_init(unsigned t)
{
    // 15.11.5 タイマ/カウンタ1比較レジスタA
    OCR1A = t;

    // 15.11.1 タイマ/カウンタ1制御レジスタA (初期値は0x00なので必要ない)
    //         ++-------COM1A1:COM1A0 00 OC1A切断
    //         ||++---- COM1B1:COM1B0 00 OC1B切断
    //         ||||  ++ WGM11:WGM10   00 波形生成種別(4bitの下位2bit)
    TCCR1A = 0b00000000;

    // 15.11.2 タイマ/カウンタ1制御レジスタB
    //         +------- ICNC1          0
    //         |+------ ICES1          0
    //         || ++--- WGM13:WGM12    01  波形生成種別(4bitの上位2bit) CTC top=OCR1A
    //         || ||+++ CS12:CS11:CS10 101 1024分周
    TCCR1B = 0b00001101;

    // 15.11.8 タイマ/カウンタ1割り込みマスクレジスタ
    //           +----- ICIE1  0
    //           |  +-- OCIE1B 0
    //           |  |+- OCIE1A 1 タイマ/カウンタ1比較A割り込み許可
    //           |  ||+ TOIE1  0
    TIMSK1 = 0b00000010;

    sei(); // 割り込み許可
}

//
int main()
{
    DDRB  = 0x01;  // PB0(14pin) 出力
    PORTB = 0x01;

    timer_init(100); // 100 * 1024us毎に割り込み (約4.88Hz) @ 1MHz

    while(1) ; // 無限ループ
}

割り込み処理は特別なprologとepilogが必要になるので、なんらかの形でコンパイラに割り込みハンドラであることを教えてあげないといけません。指定方法はC言語の標準の範囲ではないので、CPU毎、コンパイラ毎に異なります。AVRのgccはマクロが用意されているので簡単です。

割り込みベクトルの名前は、ヘッダファイルiomx8.hから見つけました。TIMER1_COMPA_vectは_VECTOR(11)を#defineしたものです。

最初思っていたほど難しくなかったというのが感想です。

AVRでLEDダイナミック点灯

2008-09-19 01:09:41 | AVR
AVRでLEDホタル(3)の続きです。

秋月のフルカラーLEDを複数点灯しています。

ATMega88のPWM出力は最大6個なので、全部をPWMにつなげるわけにはいきません(RGBx4 = 12)。ダイナミック点灯で、左をちょっと光らせて、一つ右に移ってを繰り返しています。
このLEDはカソードコモンなのでNPNのトランジスタ(2SC1815)をカソード側につないで、点けるか消すかを制御しています。アノード側はPWMの出力を4つのLEDに分配しています。緑の配線がそれなんですが、痛々しいくらい高密度配線です。

携帯のカメラを一番暗くしてみました。


ソースコードです。
#include <avr/io.h>
#include <util/delay.h>

typedef unsigned char byte;

void pwminit(void)
{
    // MEGA88 タイマー関連ポート
    // OC0A/PD6 12pin 赤
    // OC0B/PD5 11pin 緑
    // OC1A/PB1 15pin
    // OC1B/PB2 16pin
    // OC2A/PB3 17pin ... MOSI
    // OC2B/PD3  5pin 青

    //       76543210
    DDRD |= 0b01101000; // PD6(12), PD5(11), PD3(5) 出力

    // 14.9.1 タイマ/カウンタ0制御レジスタA (p.64)
    //         ++--------COM0A1:COM0A0 10 上昇時一致でL下降時一致でH
    //         ||++------COM0B1:COM0B0 10 上昇時一致でL下降時一致でH
    //         ||||  ++--WGM01:WGM00   01 位相基準PWM動作
    TCCR0A = 0b10100001;

    // 17.11.1 タイマ/カウンタ2制御レジスタA (p.99)
    //         ++--------COM2A1:COM2A0 10 OCR2A停止
    //         ||++------COM2B1:COM2B0 00 上昇時一致でL下降時一致でH
    //         ||||  ++--WGM21:WGM20   01 位相基準PWM動作
    TCCR2A = 0b00100001;

    // 14.9.2 タイマ/カウンタ0制御レジスタB (p.65)
    //         ++--------FOC0A:FOC0B    00
    //         ||  +-----WGM2           0   TOP = 0xff
    //         ||  |+++--CS02:CS01:CS00 001 clkio/1
    TCCR0B = 0b00000001; // start timer

    // 17.11.2 タイマ/カウンタ0制御レジスタB (p.100)
    //         ++--------FOC2A:FOC2B    00
    //         ||  +-----WGM22          0   TOP = 0xff
    //         ||  |+++--CS22:CS21:CS20 001 clkio/1
    TCCR2B = 0b00000001; // start timer
}

int main()
{
    byte led[4][3] = {
        {0x80, 0x00, 0x00}, // PC3のR, G, B
        {0x00, 0x80, 0x00}, // PC2のR, G, B
        {0x00, 0x00, 0x80}, // PC1のR, G, B
        {0x80, 0x80, 0x00}, // PC0のR, G, B
    };
    byte p = 0;

    pwminit();

    //       76543210
    DDRC = 0b00001111; // PC3(26), PC2(25), PC1(24), PC0(23) 出力

    while(1){
        _delay_ms(4); // 4ミリ秒 250Hz
        PORTC = 0;
        OCR0A = led[p][0]; // duty比設定 赤
        OCR0B = led[p][1]; // duty比設定 緑
        OCR2B = led[p][2]; // duty比設定 青
        PORTC = 8 >> p;

        p = (p + 1) & 3; // p = 0~3の繰り返し
    }
}

PWMの初期化は毎回同じなので、次からは省略できるようにサブルーチンにしました。LEDの左から順にPC3, PC2, PC1, PC0につながっています。PC3に1を書くと一番左が点灯します。
ダイナミック点灯は結構高い周波数で切り替えないとちらつきます。今回は4ミリ秒毎に切り替えているので250Hzです。
本来ならダイナミック点灯の切り替えは割り込みを使ってやるぺきですが、割り込みの使い方を分かっていません。

今回わかったこととしては、LED毎に明るさのばらつきが微妙にあることです。同じ色を出しているつもりでも、4つが各々微妙にちがったりします。もうひとつはRGBでも明るさのカーブが違うことです。明るいときはうまくバランスしてても暗めにすると青みがかったりします。完璧を目指すならLED一つ一つについて、PWMのパラメータを変えたりしないといけないでしょうが、とても大変そうです。

とりあえずタイマー1が空いているので、次は割り込みに挑戦してみたいと思っています。


AVRでLEDホタル(3)

2008-09-16 21:20:29 | AVR
AVRでLEDホタル(2)の続きです。

ELMさんというかchanさんのところで、フルカラーLEDを使って遊ぶ記事を見つけました。8ピンAVRの活用という記事の中の「LEDフラッシャー その2」です。赤→黄→緑→水色→青→紫→赤の順に色を変化させていくと「色相が回転」するのだそうです。途中をなめらかに変化させるために中間値をPWMで出力します。

chanさんの元のコードはアセンブラでしたが、Cにしちゃいました。たぶん、そんなに間違ってないと思います。
#include <avr/io.h>
#include <util/delay.h>

typedef unsigned char byte;

byte r, g, b;

void ledfunc(char rd, char gd, char bd)
{
    byte i;

    for(i = 0; i < 0xff; i++){
        _delay_ms(6);

        OCR0A = r; // duty比設定 赤
        OCR0B = g; // duty比設定 緑
        OCR2B = b; // duty比設定 青
        r += rd;
        g += gd;
        b += bd;
    }
}

int main()
{
    // MEGA88 タイマー関連ポート
    // OC0A/PD6 12pin 赤
    // OC0B/PD5 11pin 緑
    // OC1A/PB1 15pin
    // OC1B/PB2 16pin
    // OC2A/PB3 17pin ... MOSI
    // OC2B/PD3  5pin 青

    //       76543210
    DDRD = 0b01101000; // PD6, PD5, PD3出力

    // 14.9.1 タイマ/カウンタ0制御レジスタA (p.64)
    //         ++--------COM0A1:COM0A0 10 上昇時一致でL下降時一致でH
    //         ||++------COM0B1:COM0B0 10 上昇時一致でL下降時一致でH
    //         ||||  ++--WGM01:WGM00   01 位相基準PWM動作
    TCCR0A = 0b10100001;

    // 17.11.1 タイマ/カウンタ2制御レジスタA (p.99)
    //         ++--------COM2A1:COM2A0 10 OCR2A停止
    //         ||++------COM2B1:COM2B0 00 上昇時一致でL下降時一致でH
    //         ||||  ++--WGM21:WGM20   01 位相基準PWM動作
    TCCR2A = 0b00100001;

    // 14.9.2 タイマ/カウンタ0制御レジスタB (p.65)
    //         ++--------FOC0A:FOC0B    00
    //         ||  +-----WGM2           0   TOP = 0xff
    //         ||  |+++--CS02:CS01:CS00 001 clkio/1
    TCCR0B = 0b00000001; // start timer

    // 17.11.2 タイマ/カウンタ0制御レジスタB (p.100)
    //         ++--------FOC2A:FOC2B    00
    //         ||  +-----WGM22          0   TOP = 0xff
    //         ||  |+++--CS22:CS21:CS20 001 clkio/1
    TCCR2B = 0b00000001; // start timer

    r = 0xff;
    g = 0x00;
    b = 0x00;

    while(1){
        ledfunc( 0,  1,  0); // 赤→黄
        ledfunc(-1,  0,  0); // 黄→緑
        ledfunc( 0,  0,  1); // 緑→水色
        ledfunc( 0, -1,  0); // 水色→青
        ledfunc( 1,  0,  0); // 青→紫
        ledfunc( 0,  0, -1); // 紫→赤
    }
}