2022年2月23日水曜日

Arduino UNOでパルスコンバーター(周波数コンバーター)を試作して、ATTYNY85に移植する

パルス入力のパルス立ち上がり間隔に対し、換算係数をかけた割合のパルス間隔でデューティ比50%のパルス出力に変換するパルスコンバーターのスケッチ。


壊れた旧式のタコメーターにステッピングモーター式のタコメーターから内部部品を移植をするのにタコパルスとかの間隔を変換して騙くらかしてそれっぽく誤魔化せないかと、Arduinoを使って周波数を一定割合で増減するプログラムが無いかと探してみたものの見つけられなかったので、自作するしかないか?と考える。

ハード的な接続はUnoの場合、パルスの入力ピンが3、出力ピンが12という設定で、パルスの電圧は入出力共に5V。
(出力でよく使われるピン13はArduino起動時に内蔵LEDを点滅させる信号が流れたり、接続されている内蔵LEDが電気的に悪さをする時があるので、LEDの点灯目的以外での使用はできるだけしないようにしている。)
ピンが余りまくりなので、ATTYNY85あたりでもできそうな気はする?

ただし、このプログラムは入力が0Hz付近だと、発振してしまったり、0Hzになる前の値が残ったりする、バグがあるので車速の変換には使えないと思う。
ま、数Hz以下は出力しないようにするの条件分岐を追加すればいいだけの気がしなくもない???

中ほどにある「PulsWidth_Adj」の値を変えるとパルス幅の増減割合を変更可能。サンプルはパルス幅を0.9倍して、周波数が約1割増えるようにしています。

#define PulsInput_PIN 1  //pin3(int.1)
                          // attachInterrupt(interrupt, function, mode)
                          // Uno: pin2(int.0) pin3(int.1)
                          // Leonardo: pin3(int.0) pin2(int.1) pin0(int.2) pin1(int.3) pin7(int.4)
const int PulsOut = 12;  // the number of puls output pin

unsigned long previousMicros = 0;
unsigned long currentMicros = 0;
long OUT_interval = 0;
int OutPuls_State = LOW;

unsigned long dTime = 0;
unsigned long lastTime = 0;


void setup() {
  attachInterrupt(PulsInput_PIN, PlusWidthCounter, RISING);
  pinMode(PulsOut, OUTPUT);
}

void loop() {
  const float PulsWidth_Adj = 0.9;  //パルス幅の変換係数

  currentMicros = micros();
  OUT_interval = dTime /2 * PulsWidth_Adj;
  
  if (currentMicros - previousMicros >=  OUT_interval) {
    previousMicros = currentMicros;

    if (OutPuls_State == LOW) {
      OutPuls_State = HIGH;
    } else {
      OutPuls_State = LOW;
    }

    digitalWrite(PulsOut, OutPuls_State);
  }    
}

void PlusWidthCounter() {
  unsigned long t = micros();
  dTime = t - lastTime;
  lastTime = t;
}


周波数を1割増加させるためにパルス幅を0.9倍。30Hzの入力で出力33.3Hzは普通の4気筒のタコメーターパルスで1000rpm相当

出力333Hzで10000rpm相当。この周波数で、この精度ならばアナログメーターの針では誤差範囲かと。

入力周波数を上げながら見ていた感じでは、ざっくり出力周波数で800Hzのあたりが限界?ま、24000rpm相当、絶対使うことはなさそうだ。
ここから上になると、徐々に処理落ちが増えてくるようで出力周波数の増加割合が下がっていき、最終的には入力周波数より出力周波数のほうが低く、パルス幅もマチマチな状態になっていき、10kHzまで行くと出力パルスの幅が明らかにバランバラン。


ちゃんと動いているのかを確認するために、多目に出力を振り追加計測

PulsWidth_Adjの値を4にして、パルス幅を4倍にしてみる。入力が297.6Hzで出力が74.85Hzで周波数は1/4ぐらいなので狙い通り。

PulsWidth_Adjの値を0.25にして、パルス幅を1/4にしてみる。入力が297.4Hzで出力が1136Hzで周波数は3.8倍ぐらい。出力周波数が高くて処理が追い付いていない?
確認のため入力周波数を落としてみる。
PulsWidth_Adjの値を0.25でパルス幅を1/4のまま入力を200.0Hzに変更すると出力が793.7Hzで周波数は約4倍。やはり、このプログラムとハードでは出力800Hzぐらいが処理の限界のようだ。



入力が0Hz付近で出力が発振する問題は、出力が5Hz以下の場合は強制LOW張り付きにする条件分を追加したことで大丈夫と思われる?ちなみに閾値を2Hz設定にしたらうまくいきませんでした。ま、タコメーター用に使うならば元のプログラムでも問題なさそうだけど?
ついでに周波数の高い発振も出力もしないように出力周波数800Hzを超えた場合も強制0張り付きにして上限も切っておく。


// attachInterrupt(interrupt, function, mode)
// Uno: pin2(int.0) pin3(int.1)
// Leonardo: pin3(int.0) pin2(int.1) pin0(int.2) pin1(int.3) pin7(int.4)
#define PulsInput_PIN 1

const int PulsOut =  12;             // Puls output pin
int OutPuls_State = LOW;
unsigned long previousMicros = 0;
unsigned long currentMicros = 0;
long OUT_interval = 0;

unsigned long dT1 = 0;
unsigned long lastTime = 0;

void setup() {
  attachInterrupt(PulsInput_PIN, PlusWidthCounter, RISING);
  pinMode(PulsOut, OUTPUT);
}

void loop() {
const float PulsWideth_Adj = 0.9;  //パルス幅の変換係数

  currentMicros = micros();
  OUT_interval = dT1 /2 * PulsWideth_Adj;

  if (OUT_interval <  625) {
      OutPuls_State = LOW;          // HiCUT OUT_interval 625us 1周期1250us = 800Hz 
    } else if (OUT_interval > 100000) {
      OutPuls_State = LOW;          // LowCUT OUT_interval 100000us 1周期200000us = 5Hz
    } else if (currentMicros - previousMicros >=  OUT_interval) {
      previousMicros = currentMicros;

      if (OutPuls_State == LOW) {
        OutPuls_State = HIGH;
      } else {
        OutPuls_State = LOW;
      }

    digitalWrite(PulsOut, OutPuls_State);
    }    
  }

void PlusWidthCounter() // interrupt handler
{
  unsigned long t = micros();
  dT1 = t - lastTime;
  lastTime = t;
}



堕話

勢いで作り始めたものの・・・。
最初は、出力パルスを作るのにLEDの点滅と同じように「delay」を使ったところdelay中は決まった時間を待っている以外は何も出来無いせいで?出力パルス幅がメタメタになり、delay無しに改修。更に取得したパルス幅を周波数に計算しなおして、換算係数かけて、またパルス幅に計算しなおして出力なんて処理をさせていたら、無駄な掛け算割り算が増えて処理が重たくなり、周波数が上がるほどパルス幅のバラツキが大きくなり、出力が安定せず(下記に書いた、マイコンのせいもあるかも?)ので、単純にパルス幅を、増減するだけにする。
(掛け算はオーバーフローの危険があるし、割り算や小数点のある掛け算は処理が重い、割り算でも2の倍数ならシフト演算を使えば早いけど、使わないで済むなら使いたくない。)

結局、いろいろなプログラムを参考にさせてもらいつつ、"attachInterrupt"を使用しパルス間隔を取得するプログラムを参考に、取得した入力パルスの立ち上がり時間間隔に、変換したい割合の係数をかけて、Arduinoのチュートリアルにある"Blink Without Delay"をベースに変換した幅のパルスを出力させる、というように切った張ったで、プログラムはなんとか形になる。


最初は表面実装タイプのUno互換機を使用していたのですが・・・。
出力パルス幅が安定せず。

他の方が公開している、同じ割り込み処理を使用しているタコメーターとかのソースを見ても、パルス幅から回転数への変換するのに割り算したり、表示用のLEDやLCDの処理もしているのに問題ないようだし、それに比べても重たい処理をしているわけではないのだけれども?と、配線を確認したり、ソースいじったり半日ほど無駄にする。
USB Bのケーブルを探すのがメンドクくて使っていなかったDIP版を試してみたところ安定して動くようになる。

どちらも328Pを16MHzで動かしているし、ヒューズ設定も一緒だし、普段は表面実装タイプのUNO互換機を使っていろいろと試していますが特に問題が出たことはなかったのだけれどな~。


Arduino UNOで作成したパルスコンバーターをATTINY 85に移植。(とりあえず入力周波数が50Hzでしか動作を確認していないので周波数が変わったらだめなところが有るかも?)


ArduinoにATTinyCore というライブラリーを追加して、移植の確認。書き込みはUsbasp経由でブートローダーなし設定で書き込みをする。
入出力のピン設定を変えて、クロックが16MHzから8MHzと半分になるので出力上限周波数を400Hz(4気筒のタコメーター入力でで12000rpmぐらい)変えただけで、移植というより、ほぼコピペ。ま、入出力で使用するピン設定でのプチ嵌りはありましたケド。


一頃よく使用していた同じ8ピンATTINYの13Aと、ピン配置・機能は一緒だろうと13Aを使用しいていたときのメモ書きを見ながら使用するピンを決めて動かそうとしたところ出力パルスが出てこない orz

ATTINYはArduino環境だとピン入力割り込み使えないの?と調べても良くわからず、ATTINY85のデータシートを見たところ、INT0が振り分けられているピンが13Aと違うということがわかる。
 ATTINY 85 PB2(Pin7)
 ATTINY 13A PB1(Pin6)
しかも
 #define PulsInput_PIN 0
  |
 const int PulsOut = 2;
なんていうような設定をしたので、Pin6から入力してPin7出力と設定にしたつもりが、Pin6から入ってPin6に出力という矛盾した設定にしているし。動くわけないじゃんと。
これでもエラーは出ないので、たぶん後ろにある初期化のほうが有効になっていたのですかね?

ま、横着しないでデータシートを確認すれば良かったわけで・・・。
const int PulsOutを 0 に変更して、Pin7から入ってPin5に出力される設定に変更する。

ATTINY85とATTINY13Aは、メモリー容量が違うだけの互換マイコンではないのだと今頃知った。
13Aで作成していて、どうしてもプロフラムサイズが収まらなくなり、85に変更なんていうのがありがちですが、各ピンの機能からクロックから似ているようで微妙に違い・・・。


// attachInterrupt(interrupt, function, mode)
//ATTYNY13A Pin6(int.1)
//ATTINY85  Pin7(int.0)
#define PulsInput_PIN 0

const int PulsOut =  0;             // Puls output pin 7
int OutPuls_State = LOW;
unsigned long previousMicros = 0;
unsigned long currentMicros = 0;
long OUT_interval = 0;

unsigned long dT1 = 0;
unsigned long lastTime = 0;

void setup() {
  attachInterrupt(PulsInput_PIN, PlusWidthCounter, RISING);
  pinMode(PulsOut, OUTPUT);
}

void loop() {
  const float PulsWidth_Adj = 0.9;  //パルス幅の変換係数


  currentMicros = micros();
  OUT_interval = dT1 /2 * PulsWideth_Adj;

  if (OUT_interval <  1250) {
      OutPuls_State = LOW;          // HiCUT OUT_interval 1250us 1周期2500us = 400Hz 
    } else if (OUT_interval > 100000) {
      OutPuls_State = LOW;          // LowCUT OUT_interval 100000us 1周期200000us = 5Hz
    } else if (currentMicros - previousMicros >=  OUT_interval) {
      previousMicros = currentMicros;

      if (OutPuls_State == LOW) {
        OutPuls_State = HIGH;
      } else {
        OutPuls_State = LOW;
      }

    digitalWrite(PulsOut, OutPuls_State);
   }    
}

void PlusWidthCounter() {
  unsigned long t = micros();
  dTime = t - lastTime;
  lastTime = t;
}



最後にボリュームを追加し、A/D変換で読み込んだ値で変換幅を0.75~1.25倍の間で調整出来るようにして、クロックが半分の8MHzになるので上限のカット周波数を変更。
クロックが下がっているのに処理を追加したせいか出力200Hzあたりから出力が不安定になっている感じ?内蔵クロックのRC発振使用で精度が悪いのかも?外付けクロック追加して16MHzに改修したほうがいいかも?
ピンの設定の関係で、今ボリュームの読み込みに使用しているpin2をクリスタルへの接続に使わないといけないので、調整機構をやめるか、禁断のresetピンを入力に割り当てるか?となり面倒なので、動かして駄目だったら考えようかと?
記憶が間違っていなければ、resetを入出力に割り当てると高電圧書き込みモードしか使えなくなるので扱いがめんどくさくなる。


// ATTYNY13A Pin6(int.0)
// ATTINY85  Pin7(int.0)
#define PulsInput_PIN 0        // Pin7, Puls input

const int PulsOut =  0;        // Pin5, Puls output 
int OutPuls_State = LOW;
float sensorValue = 0;           // variable to store the value coming from the sensor


float PulsWideth_Adj = 0;
unsigned long previousMicros = 0;
unsigned long currentMicros = 0;
long OUT_interval = 0;

unsigned long dT1 = 0;
unsigned long lastTime = 0;

void setup() {
  attachInterrupt(PulsInput_PIN, PlusWidthCounter, RISING);
  pinMode(PulsOut, OUTPUT);
}

void loop() {
//sensorValue = analogRead(sensorPin);
sensorValue = analogRead(A3);       // Adj pin 2(PB3,ADC3)
PulsWideth_Adj = 0.75 + sensorValue/2000;

  currentMicros = micros();
  OUT_interval = dT1 /2 * PulsWideth_Adj;

  if (OUT_interval <  1000) {
      OutPuls_State = LOW;          // HiCUT OUT_interval 1000us 1周期2000us = 500Hz 
    } else if (OUT_interval > 100000) {
      OutPuls_State = LOW;          // LowCUT OUT_interval 100000us 1周期200000us = 5Hz
    } else if (currentMicros - previousMicros >=  OUT_interval) {
      previousMicros = currentMicros;

      if (OutPuls_State == LOW) {
        OutPuls_State = HIGH;
      } else {
        OutPuls_State = LOW;
      }

    digitalWrite(PulsOut, OutPuls_State);
   }    
}

void PlusWidthCounter() {
  unsigned long t = micros();
  dTime = t - lastTime;
  lastTime = t;
}

0 件のコメント:

コメントを投稿