はじめに
久しぶりに純粋な電子工作をした気がします。
アナログ周波数メータ(トランスデューサ型)を手に入れたのでそれを使ってみたいってことで、今回の作成に至りました。
条件と回路の概要
条件として、
・メータはPWMで駆動させる
・ACACコンバータ(トランス)を使用
・コンパレータで基準電圧と入力(交流)を比較
があります。ちょうどAC100からAC24Vに降圧するトランスがあったのでそれを使うことにしました。
使うマイコンは迷ったのですが、ESP8266を使用しました。今思えば、Arduino Nanoを使えば、内臓のコンパレータを使用できたので、部品点数の大幅削減ができました。
回路図
電源は半波整流となっています。電源がAC24Vなので、直流33.9Vとなります。ただ、負荷が無いと若干上振れしてAC28V、直流39.6Vとか出ており、使用した3端子レギュレータTA7805Sが35Vまでだったので、150Ω3つの50Ωとツェナーダイオード1N4762で35V以下になるように降圧しています。
3.3VはESP8266が乗ったマイコンボードD1 Mini上のレギュレータによって生成しています。
コンパレータ側は4回路入りのUPC177を使用しました。1回路しか使ってないのでもったいない感じがします。半波整流されたものを分圧して基準電圧3.3Vと比較しています。
マイコン周りですが、表示しているレンジを表すために2色LEDを使用しています。D1 Mini上に青のLEDが乗っているので、RGBすべてそろいました。ブザーは±0.2Hz以上の逸脱を検知した際に鳴ったらいいなってことで設置しました。メータは10kオームの可変抵抗で微調整をできるようにしています。摺動子がマイナス側になってしまっている(プラス側にするのが鉄則)のは、配置的なものが原因です。GPIO0につながっているボタンは、ESP8266のBootボタンを兼ねています。ボタンでアラームの解除、レンジの手動指定ができるようになっています。
回路作成
めんどくさかったのでブレッドボードで組みました。ユニバーサル基板に移し替えるのは壊れたときです。下の写真は、緑のLEDとブザーにつながっている抵抗が違いますが、このあと変更しています。
ソフトウェア
適当に書きました。ここに書くと長くなる(適当で汚いので見せたくない)ので、下の方に書いておきます。割り込みを使用してコンパレータからの入力がHighになるのを検知しています。1周期ずつ時間差を計測しその10回平均を出力してます。
動作説明
【ボタン】
青色LEDが点滅orブザー動作中・・・リセット
それ以外のとき・・・レンジの調整ができます。100倍(ディフォルト)→10倍→1倍→100倍…
【LED表示】
青点滅・・・過去に±0.2Hzの逸脱を検知
緑・・・レンジ100倍
赤・・・レンジ10倍
無灯・・・レンジ1倍
【ブザー】
±0.2Hzの逸脱の発生から20秒以内
小さいレンジのとき、振り切れると自動で大きなレンジでの表示に切り替わります。
筐体作成
折角そこそこ綺麗にブレッドボードに回路を組めたので透明なケースに入れて見えるようにしたいです。そのため3Dプリンターのでの設計はやめて、ダイソーで適当なプラスチックケースを仕入れて超音波カッターで加工することにしました。今回は「クリアボックスMサイズ」がちょうどよかったのでこれを使用しました。
あとは、メータ、ボタン、DCジャックに合わせて穴を開けます。超音波カッターを使いました。バリ取りが若干めんどくさかったですが、円形も綺麗に切り出せたので良かったです。メータは蓋側に取り付けしました。
組み立て
メータ、ブレッドボード、ボタン、DCジャックを作成した筐体に取り付けして完成です。
商用電源周波数測定機ができた!
— Unagi Dojyou (@Unagi_Dojyou) June 16, 2024
瞬停、±0.2Hz逸脱検知でブザー
1倍、10倍、100倍(動画は100倍)のオートレンジ pic.twitter.com/XdSHLXqIRk
ちょっとボタンからの線が長くて見栄えが悪いので、このあと、カットしています。
おわりに
なかなか良いものができたと思います。割とパラパラ数字が変わるので見ていてい楽しいです。
コード
最後に使用したコードを乗っけておきます。人に見せるようには書いてないので変数の命名などなかなか酷いです。
#define METER_PIN 14
#define RED_PIN 4
#define GREEN_PIN 13
#define BLUE_PIN 2
#define BUZZER_PIN 12
#define BUTTON_PIN 0
#define READ_PIN 5
#define PWMRANGE 2048
#define MAX_RATE (PWMRANGE*0.9)
#define BASE_MHZ 50000
#define BASE_HZ 50.0
#define MAX_COUNT 15 //15*20ms = 300ms
#define MAX_HZ 50.20
#define MIN_HZ 49.80
#define MAX_BUZZER_COUNT 1000 //1000*20ms = 20s
#define ON_BLUE_COUNT 30 // 30*20ms =0.6s
#define OFF_BLUE_COUNT 200 // 200*20ms =4s
volatile unsigned long lastRisingEdgeCycleCount = 0;
volatile unsigned long cycleCountDifference = 0;
volatile unsigned long upbasecount = 0; //upper limmit of diff count
volatile unsigned long downbasecount = 0; //lower limmit of diff count
void ICACHE_RAM_ATTR handleRisingEdge() {
unsigned long currentCycleCount = ESP.getCycleCount();
if (lastRisingEdgeCycleCount != 0) {
cycleCountDifference = currentCycleCount - lastRisingEdgeCycleCount;
}
if (downbasecount > cycleCountDifference && cycleCountDifference < upbasecount){ //check valid diffcount
cycleCountDifference = UINT32_MAX;
}
lastRisingEdgeCycleCount = currentCycleCount;
}
unsigned long basecount = 0;
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(RED_PIN, OUTPUT);
digitalWrite(RED_PIN, HIGH);
pinMode(GREEN_PIN, OUTPUT);
digitalWrite(GREEN_PIN, HIGH);
pinMode(BLUE_PIN, OUTPUT);
digitalWrite(BLUE_PIN, HIGH);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
pinMode(READ_PIN ,INPUT);
//analogWriteFreq(25000);
analogWriteRange(PWMRANGE);
// 割り込みを設定: GPIO 5 の Rising Edge (Low から High) で handleRisingEdge を呼び出す
attachInterrupt(digitalPinToInterrupt(READ_PIN), handleRisingEdge, RISING);
basecount = ESP.getCpuFreqMHz() * 1000 * 1000 / BASE_HZ;
upbasecount = 1.2 * basecount;
downbasecount = 0.8 * basecount;
delay(20);
}
double herz = 0;
double diffhz = 0;
double diffrate = 0;
int count = 0;
unsigned long sum = 0;
int range = 1; //100:45~55Hz, 10:49.5~50.5, 1:49.95~50.05
int setrange = 1;
bool oldbutton = true;
bool nowbutton = true;
void rangeled(){
switch(range){
case 1: //GREEN 44.95~50.05Hz
digitalWrite(RED_PIN, HIGH);
digitalWrite(GREEN_PIN, LOW);
break;
case 10: //RED 44.5~50.5Hz
digitalWrite(GREEN_PIN, HIGH);
digitalWrite(RED_PIN, LOW);
break;
case 101:
case 100: //none 45~55Hz
digitalWrite(RED_PIN, HIGH);
digitalWrite(GREEN_PIN, HIGH);
break;
default:
range = 1;
break;
}
}
bool alarm = false;
bool buzzer = false;
int buzzer_count = 0;
bool blue = false; //blue led ON(LOW):true, OFF(HIGH):false
bool oldvalid = false;
bool ooldvalid = false;
double oldherz = 0;
double aherz = 0; //averaged Hz
void loop() {
if(cycleCountDifference != 0){
// sum diff
if(cycleCountDifference != UINT32_MAX){ //check valid data
sum += cycleCountDifference;
herz = (ESP.getCpuFreqMHz()) / (cycleCountDifference / (1000.0 * 1000.0));
count++;
//if((oldherz < MIN_HZ) && (herz < MIN_HZ) && oldvalid && ooldvalid){ //-0.2Hz
if((oldherz < MIN_HZ) && oldvalid && ooldvalid){ //-0.2Hz
Serial.print("W:");
Serial.println(oldherz);
alarm = true;
buzzer = true;
//}else if((MAX_HZ < oldherz) && (MAX_HZ < herz) && oldvalid && ooldvalid){ //+0.2Hz
}else if((MAX_HZ < oldherz) && oldvalid && ooldvalid){ //+0.2Hz
Serial.print("W:");
Serial.println(oldherz);
alarm = true;
buzzer = true;
}
oldvalid = true;
oldherz = herz;
//invalid data
}else if(cycleCountDifference == UINT32_MAX){
oldvalid = false;
}
ooldvalid = oldvalid;
oldherz = herz;
cycleCountDifference = 0;
}
//average and disp diff
if (count >= MAX_COUNT) {
aherz = (ESP.getCpuFreqMHz()) / ((sum / count) / (1000.0 * 1000.0));
//Serial.print((sum / count));
//Serial.print(" ");
Serial.println(aherz, 4);
diffhz = aherz - BASE_HZ;
do{
diffrate = 0.5 + (diffhz / (range / 10.0));
rangeled();
if(range == 1){
range = 10;
}else if(range == 10){
range = 100;
}else{ //out of range
range = 101;
if(diffrate < 0.0){
diffrate = 0.0;
}else if(1.0 < diffrate){
diffrate = 1.0;
}
}
}while(((diffrate < 0.0) || (1.0 < diffrate)) && range != 101);
range = setrange;
float pwmming = MAX_RATE * diffrate;
analogWrite(METER_PIN, pwmming);
count = 0;
sum = 0;
}
//read button
oldbutton = nowbutton;
nowbutton = digitalRead(BUTTON_PIN);
if(oldbutton && !nowbutton){
if(alarm || buzzer){
alarm = false;
buzzer = false;
buzzer_count = 0;
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(BLUE_PIN, HIGH); //OFF
}else{
switch(range){
case 100:
range = 10; //RED 44.5~50.5Hz
setrange = 10;
break;
case 10:
range = 1; //GREEN 44.95~50.05Hz
setrange = 1;
break;
case 1:
range = 100; //none 45.0~55.0Hz
setrange = 100;
break;
default:
range = 1;
break;
}
}
rangeled();
}
//buzzer
if(buzzer){
digitalWrite(BUZZER_PIN, HIGH);
buzzer_count ++;
if(buzzer_count > MAX_BUZZER_COUNT){
digitalWrite(BUZZER_PIN, LOW);
buzzer = false;
buzzer_count = 0;
}
}else if(alarm){
buzzer_count ++;
if((buzzer_count > ON_BLUE_COUNT) && blue){ //on to off
digitalWrite(BLUE_PIN, HIGH); //off
blue = false;
buzzer_count = 0;
}else if((buzzer_count > OFF_BLUE_COUNT) && !blue){ //off to on
digitalWrite(BLUE_PIN, LOW); //on
blue = true;
buzzer_count = 0;
}
}
delay(20);
}