ATTINY2313で商用電源周波数監視

ATmega328で商用電源周波数の監視ができるようになりましたが、グラフを見てないと変動に気づきません。そこで、スタンドアローンで動くものを余っているATTINY2313で作ってみました。

基本的な構成はATmega328の時と同じです。100均のニッケル水素充電器のマイナス端子から内部のトランスの2次側電圧を引き出して、半波整流してLP2950L-3.3でATTINY2313の3.3V電源を生成します。一方でフォトカプラで商用電源の周期に同期した信号を生成して、ATTINY2313のTIMER1のICP信号に入力します。ATTINY2313の動作クロックは8MHzのクリスタルとして、適当に作っても最悪100ppm程度の精度は得られるようにします。スタンドアローンで動作するように、OC0B信号を圧電ブザーに接続して、TIMER0でブザーを鳴らせるようにします。

完成したものはこんな感じになります。

基板上のLEDはUARTのTXに接続してあるので、そこにトランジスタをつけたフォトダイオードをかざしてやると、非接触でシリアル通信がができます。

これをArduinoのシリアルプロッタに食わせると以下のような感じです。周波数は実際の周波数を1000倍したものです。

シリアルライブラリなどのArduinoのライブラリは使えないのでその部分は簡単なものを作っていますが、ソフトウェアはATmega328の時と大きくは変わりません。(下記の中で&が化けています・・・)

#include <avr/io.h>   // /usr/lib/avr/include/
#include <util/delay.h>
#include <util/atomic.h>
#include <string.h>

// FUSEは外部クリスタル8MHz、システムクロックを分周なしに設定(E:FF, H:DF, L:EF)

/////////////////////////////////////////////////
// 端子定義
#define BUZ 5	 // PORT D5 に圧電ブザー接続
#define ICPPIN 6 // PORT D6 = ICP
#define UARTTX 1 // PORT D1 = TX

/////////////////////////////////////////////////
// タイマ0関連
void Buzzer(uint8_t period){
    if(period>0){
	    // 高速PWM設定によるブザー駆動
	    TCCR0A = 0x23;     // COM0B[1-0]=10,WGM0[1-0]=11  高速PWM動作
	    TCCR0B = 0x0C;     // WGM02=1,CS[2-0]=4  分周=256分周=8MHz/256=31.25kHz
	    OCR0A = period;    // FREQ 31.25kHz / 31 ≒ 1008Hz / 63 ≒ 496Hz
	    OCR0B = period>>1; // DUTYを設定
	} else {
	    TCCR0A = 0x00;  // 出力停止
	}
}

/////////////////////////////////////////////////
// タイマ1関連
// 周期測定ののための変数
volatile uint16_t NumV;
volatile uint32_t SumV;
volatile uint16_t CapV;
volatile uint16_t PreCap,NowCap;

// 1秒間の最大最小平均を格納するための変数
uint16_t tNumV;
uint32_t tAveV;
uint32_t tSumV;

// 割り込みハンドラ
ISR(TIMER1_CAPT_vect){
  TIFR |= (1<<ICF1); 
  NowCap = ICR1;
  CapV = NowCap - PreCap;       // 差分を求めて周期を算出
  PreCap = NowCap;
  SumV += CapV;
  NumV++;
}

// タイマ1設定
void TIMER1_Init(void){
    // TCC1でICP信号(PD6端子)の周期を計測する
    TCCR1A = 0; //initialize Timer1
    TCCR1B = 0;
    TCNT1 = 0;

    TCCR1B =  0x02;   // 8MHzを8分周
    TIMSK  |= (1 << ICIE1); // キャプチャ割り込み許可
}

uint32_t Period(void){
    // 割り込みで更新している変数をコピー&初期化する
    cli(); //割り込み停止
    tSumV = SumV; tNumV = NumV;
    SumV = 0; NumV = 0;
    sei();   //割り込み再開
    tAveV = tSumV / tNumV;
    return(tAveV);
}

/////////////////////////////////////////////////
// UART関連
#define FOSC 8000000
#define BAUD 38400
#define MYUBRR FOSC/16/BAUD-1

uint8_t txbuf[16];
uint8_t txp=0,txn=0;

ISR(USART_UDRE_vect){
  if(txp<txn){
    while ( !(UCSRA & (1<<UDRE)) );
    UDR = txbuf[txp];
    txp++;
  } else {
    UCSRB &= ~_BV(UDRIE);
  }
}

void USART_Init(unsigned int baud){
    UBRRH = (unsigned char)(baud>>8);
    UBRRL = (unsigned char)baud;
    UCSRC = (1<<USBS)|(3<<UCSZ0);
    txp = 0;
    UCSRB = (1<<RXEN)|(1<<TXEN)|(1<<UDRIE);
}

void USART_txt(uint8_t *txt,uint8_t len){
    while(txp<txn);
    cli();
    memcpy(txbuf,txt,len);
    txp=0;
    txn=len;
    UCSRB |= _BV(UDRIE);
    sei();
}

void USART_num(uint16_t num){
    uint16_t d=10000;
    uint16_t n=num;
    uint8_t  c,p=0,b[8];
    while(d>0){
        c=n/d; n=n%d;
        d=d/10;
        b[p++]=c+0x30;
    }
    b[p++]='\r'; b[p++]='\n';
    USART_txt(b,p);
}

void USART_znum(uint16_t num){
    uint8_t s=1;    // ゼロサプレス中
    uint16_t d=10000;
    uint16_t n=num;
    uint8_t  c,p=0,b[8];
    while(d>0){
        c=n/d; n=n%d;
        d=d/10;
        if(d==0) s=0;
        if(s==0 || c!=0){
            b[p++]=c+0x30;
            s=0;
        }
    }
    b[p++]='\r'; b[p++]='\n';
    USART_txt(b,p);
}

/////////////////////////////////////////////////
// メイン
int main(void)
{
    uint32_t f,p,d;

    PORTB  = 0xFF; // 入力設定時のプルアップ有効
    PORTD  = 0xFF; // 入力設定時のプルアップ有効
    DDRD   = _BV(BUZ);     // PORT D5を出力に
    DDRD  &= ~_BV(ICPPIN); // ICPピンを入力に
    PORTD &= ~_BV(ICPPIN); // ICPピンのプルアップ無効化
    PORTD  = _BV(UARTTX);  // PORT D1を出力に

    TIMER1_Init();

    USART_Init(MYUBRR);

    for(;;){
        p = Period();
        f = 1000000000UL/p;  // 周波数の1000倍を求める
        if(f > 50000){
            d = f - 50000;
        } else {
            d = 50000 - f;
        }
        if(d > 200){      // 0.20Hz以上ずれた場合
            Buzzer(31);  // 高速PWM設定によるブザー駆動 31=1kHz
        }else if(d > 150){ // 0.15Hz以上ずれた場合
            Buzzer( 78);  // 高速PWM設定によるブザー駆動 78=400Hz
        }else if(d > 100){ // 0.10Hz以上ずれた場合
            Buzzer(250);  // 高速PWM設定によるブザー駆動 250=125Hz
        }else {
            Buzzer(0);
        }
        USART_znum(f);
        _delay_ms(1000);
    }
}

【2021年2月21日追記】
回路図のリクエストをいただきましたので、現物から書き起こしました。
(注:ATtiny2313への書き込み回路は描いてありません。別途ICクリップで挟んで書き込んでいます。)
追記2: 下記回路図ではRESETのプルアップがないですが、プルアップしておいたほうがいいと思います。何かの拍子にリセットがかかるようです。(データシートの読み込み不足だと思います)

左側の入力には、100均ニッケル水素充電器の2つある電池マイナス端子をそれぞれつなぎます。
入力をショットキーダイオードで半端整流して3端子レギュレータで3.3V電源を作ります。入力はフォトカプラに通して入力をデジタル信号化して、ATTINYのICP端子へ接続、インプットキャプチャ機能でエッジからエッジまでの時間を1us単位で測定します。整流時にショットキーダイオードを使ったのは、電圧降下を小さくしたいためです。
大したことはしていないので、インプットキャプチャ機能のあるマイコンであれば、(ソフトウェアは当然作り直しになりますが)大抵のものが使えるものと思います。

“ATTINY2313で商用電源周波数監視” への6件の返信

    1. 投稿に気づくのが遅れてすみません。実は、回路図描いてないんです^^;
      いま現物から起こす形で描いてますので、少々お時間ください。
      描いたら、この記事に追加しますね。

      1. わざわざありがとございます!
        328Pで周波数測定したデータを7セグに表示したいのですが、どのようにしたら出来ますか….??

        1. やり方はいろいろな方法がありますし、空いているポートや電源などの制限事項や優先したい事項などにもよってベストな方法は変わると思います。
          一品物でしたら、
          https://akizukidenshi.com/catalog/g/gM-06681/

          https://akizukidenshi.com/catalog/g/gM-09162/
          のようなシリアル制御のモジュールを接続するのが比較的配線の手間や難易度が低そうに見えます。(自分は使ったことがないのでわかりませんが)

          1. 分かりました。ありがとうございます!
            プログラムの方が全く分からなくて、
            どうしたらよろしいですか….??

        2. 返信の階層が深いと返信できなくなるようです。
          ソフトウェアをどう作るかですが、まずは秋月のページ内の「メーカー製品情報」「参考資料」のリンクをたどったりすると、サンプルプログラムにたどり着いたりすることが少なくないですよ。
          その他には、製品名の「SPI(3線式) LED Module 8 Digital」「Grove-4桁7セグメント赤色LEDモジュール」でググったり、そこから得られるコントローラICの「TM1637」でググったりして探すなどして情報を集めるしかないのではないでしょうか。
          最後の最後はデバイスのデータシートと回路図を突き合わせて自力でスクラッチ開発ですね。(本業で売り物としてやる場合には普通の話ですが)
          頑張ってください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)