AVR入門 - 交流電圧をAD変換してPCに転送

AD変換とシリアル送信ができるようになったので、試しに電源トランスで降圧した交流電圧の測定をやってみた。

AD変換入力の最大値をAVRの電源電圧5Vを超えないように、2.5Vがセンターになるように、プルアップして、AVR の PC1 に接続。さらにセンターの2.5Vの電位はPC0 に接続し、PC1 との差をとって測定値が得られる。回路は arms22 さんのものと同じです。ただしトランスの電圧は異なるので、分圧抵抗値は 3.3KΩ:37KΩにしています。最初は 3.3KΩ:27KΩでしたが、測定値がオーバーしそうだったので、10KΩを足しました。

arms22 さんのスケッチでは、1サイクルのサンプル数が25個です。したがって、50Hzの場合は周期 0.02[sec]を25で割って 0.0008[sec]=0.8[mSec]間隔でサンプルしていかねばなりません。当初、AD変換と「同時に」データ送信もしなければならない、と考えていたので、サンプルした4個のデータ(単相3線なので、VとAを2個ずつ)を同時に送信できるシリアル通信のスピードを計算すると、250Kbbpsが必要ということになりました。が、後から考えると、単にストアしたデータをデータ収集が終わってから送ればよいことに気がつきました。

そもそも、実験では、これだけのデータを送信しますが、

実際、電力計としての機能を実現するにはこれほどのデータをホストに送る前にAVRで計算した値だけ送ればよいのですが、電圧と電流の波形のずれを実際にみてみたかったため、このようになりました。

値をエクセルでグラフに表示して、正弦波が得られるとちょっとうれしいです。

商用電源は1秒間に50回も繰り返して点滅しているのに、マイコンはその波をさらに多くの点に分割して、データを得ているのを見ると、この程度のマイコンで、最低速度のAD変換でも、こんなにコンピュータは早いのだ、と思えます。

画像

AVR 側プログラム

//AD_Conv3 はAD変換しながらデータ送信するようになっているが、

//その方法では、0.8mSec 以内にループが回らないようなので、

//データは一旦、配列にストアして、AD変換終了後に送信する

//方式に変更

// [Current Configration Options]に mega88 を設定すること

// コンパイラオプション:最適化なし -O0

// CKDIV8 0(チェックなし)

// CKOUT 0

// CK clock 8MHz

//シリアル変換IC ADM3202AN

//動作OK

// _delay_ms(1); // マルチプレクサが安定するまで待つ を当初入れいたら、AD変換速度を 1/2 に設定しても

//まったく追いつかなかった。これを外したら劇的に早くなった。AD変換速度は 1/128 でOK。

//AC からの入力で AD変換結果が ファイルにデータとして記録できる。

//CSVファイルフォーマット: Col1, Col2, Col3

//Col1: PC1 (測定値)

#define F_CPU 8000000UL //8MHz

#include

#include

#include

#define FOSC 8000000 // 8MHz

#define BAUD 19200

#define MYUBRR (FOSC/16/BAUD)-1 // UART分周率

#define NumOfSample 100

void wait( unsigned char );

//分周率設定 データシート表20-9

//CPU 8MHz, U2X=0, Baud=240K -> UBRR=1 (が、PC 側に240Kのスピードは無いので 19200bpsで断念)

void sio_init(unsigned int baud){

UBRR0H = (unsigned char)(baud>>8); // ボーレート上位

UBRR0L = (unsigned char)baud; // ボーレート下位

// UCSR0A = (UCSR0A | 0b00000010); //U2X0 設定 (倍速=1) (デフォルト 0)

UCSR0B = (1<

UCSR0C = (1<

}

void sio_putint4(int16_t num1,int16_t num2,int16_t num3,int16_t num4) //4個のint値を一度にハンドル

{

while ( !(UCSR0A & (1<

UDR0 = (int8_t)(num1>>8); //上位バイトを先に送信したほうが受信側処理がしやすい

while ( !(UCSR0A & (1<

UDR0 = (int8_t)(0x00ff&num1);

while ( !(UCSR0A & (1<

UDR0 = (int8_t)(num2>>8); //上位バイトを先に送信したほうが受信側処理がしやすい

while ( !(UCSR0A & (1<

UDR0 = (int8_t)(0x00ff&num2);

while ( !(UCSR0A & (1<

UDR0 = (int8_t)(num3>>8); //上位バイトを先に送信したほうが受信側処理がしやすい

while ( !(UCSR0A & (1<

UDR0 = (int8_t)(0x00ff&num3);

while ( !(UCSR0A & (1<

UDR0 = (int8_t)(num4>>8); //上位バイトを先に送信したほうが受信側処理がしやすい

while ( !(UCSR0A & (1<

UDR0 = (int8_t)(0x00ff&num4);

}

int main( void )

{

int ref,volt;

int volt_array[NumOfSample];

sio_init(MYUBRR); // USART設定

for(int16_t i=2;i<30;i++) {

//最初の数秒はPCとの同期のため、先頭を 1 で送信しておく

sio_putint4(1,i,i,i);

_delay_ms(100);

}

ADCSRA = 0b10000111; // ADイネーブル

// | +++-------1/16クロック,1/16=62.5kHz。1/128で最遅7.8125KHz。これでも電話音声程度なら十分。

// +--------------ADイネーブル

for(int8_t i=0;i

ADMUX = 0b00000000; // 基準,入力選択

// || ++++-------PC0選択

// |+------------右そろえ

// +-------------基準電圧の内部接続切り,Arefピンの電圧を使う

// _delay_ms(1); // マルチプレクサが安定するまで待つ

ADCSRA = 0b11000111; // ADイネーブル

// | +++-------1/16クロック,1/16=62.5kHz。1/128で最遅7.8125KHz。これでも電話音声程度なら十分。

// +--------------ADイネーブル

while( ADCSRA & 0b01000000 ) // 変換終了待ち

;

ref = ADC;

ADMUX = 0b00000001; // 基準,入力選択

// || ++++-------PC1選択

// |+------------右そろえ

// +-------------基準電圧の内部接続切り,Arefピンの電圧を使う

// _delay_ms(1); // マルチプレクサが安定するまで待つ

ADCSRA = 0b11000111; // ADイネーブル

// | +++-------1/16クロック,1/16=62.5kHz。1/128で最遅7.8125KHz。これでも電話音声程度なら十分。

// +--------------ADイネーブル

while( ADCSRA & 0b01000000 ) // 変換終了待ち

;

volt = ADC;

volt_array[i]=volt-ref;

}

//シリアル送信

for(int8_t i=0;i

sio_putint4(volt_array[i],0,0,0);

}

sio_putint4(0,0,0,111); //sending end marker

}

ホスト側プログラム

//TwoWaySerialComm の改良版 2012/11/8 (TwoWaySerialComm2 よりこちらの方が新しい)

//1. 一度に4バイトのデータ受信

//2. こちらから送信することは無いので、関連ルーチン削除

//使い方:AVR起動時は最初の数秒は先頭が1の値を送信してくるので、AVRの電源ON前に

//このプログラムの実行を開始しておき、同期をとる必要がある。

import gnu.io.CommPort;

import gnu.io.CommPortIdentifier;

import gnu.io.SerialPort;

import java.io.*;

public class TwoWaySerialComm

{

public TwoWaySerialComm()

{

super();

}

void connect ( String portName ) throws Exception

{

CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);

if ( portIdentifier.isCurrentlyOwned() )

{

System.out.println("Error: Port is currently in use");

}

else

{

CommPort commPort = portIdentifier.open(this.getClass().getName(),2000);

if ( commPort instanceof SerialPort )

{

SerialPort serialPort = (SerialPort) commPort;

serialPort.setSerialPortParams(19200,SerialPort.DATABITS_8,SerialPort.STOPBITS_1,SerialPort.PARITY_NONE);

InputStream in = serialPort.getInputStream();

OutputStream out = serialPort.getOutputStream();

(new Thread(new SerialReader(in))).start();

// (new Thread(new SerialWriter(out))).start();

}

else {

System.out.println("Error: Only serial ports are handled by this example.");

}

}

}

/** */

public static class SerialReader implements Runnable {

public static String toHex(byte digest) {

StringBuilder sb = new StringBuilder();

for (byte b : digest) {

sb.append(String.format("%1$02X", b));

}

return sb.toString();

}

InputStream in;

public SerialReader ( InputStream in ) {

this.in = in;

}

public void run () {

int int_value1,int_value2,int_value3,int_value4 = 0;

byte buffer = new byte[8];

byte tmp_buf = new byte[2];

int len = -1;

FileWriter fw=null;

java.io.BufferedWriter bw=null;

try {

fw = new FileWriter("c:\\a\\log_adconv.csv");

bw = new BufferedWriter(fw);

} catch (Exception e) {

e.printStackTrace();

}

try {

//AVRから来るデータとの同期を最初にとる。先頭の値は0x0001が来るはず

while (!(tmp_buf[1]==0x01&&tmp_buf[0]==0x00)) {

if (this.in.available()>1) {

len = this.in.read(tmp_buf);

System.out.print("1st len="+len+" ");

System.out.println(toHex(tmp_buf));

}

}

this.in.read(tmp_buf);//先頭が得られたので、残りは読み捨てておく

this.in.read(tmp_buf);

this.in.read(tmp_buf);

System.out.println("Sync OK");

while (true) {

while ( this.in.available()>7) {

len = this.in.read(buffer);

// System.out.print("len="+len+" ");

// System.out.println(toHex(buffer));

//0xFF で&しないと、結果が2の補数になってしまう。

//参照 http://www.creativegear.jp/2011/05/09/java_byte_to_int/

/*ファイル出力ルーチン */

int_value1=(int)((buffer[0]<<8)|(buffer[1]&0xFF));

int_value2=(int)((buffer[2]<<8)|(buffer[3]&0xFF));

int_value3=(int)((buffer[4]<<8)|(buffer[5]&0xFF));

int_value4=(int)((buffer[6]<<8)|(buffer[7]&0xFF));

bw.write(int_value1+","+int_value2+","+int_value3+"\r\n");

}

if (int_value4==111) break;

}

bw.close();

} catch ( IOException e ) {

e.printStackTrace();

}

System.out.println("End running");

//end func

}

} //end class

/** */

public static class SerialWriter implements Runnable

{

OutputStream out;

public SerialWriter ( OutputStream out )

{

this.out = out;

}

public void run ()

{

try

{

int c = 0;

while ( ( c = System.in.read()) > -1 )

{

this.out.write(c);

}

}

catch ( IOException e )

{

e.printStackTrace();

}

}

}

public static void main ( String args )

{

try

{

(new TwoWaySerialComm()).connect("COM1");

} catch ( Exception e )

{

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}