Arduino

2020年11月12日 (木)

Arduino UNOでデューティー比測定回路 ケースに入れて完成

2020年8月24日:Arduino PWM波形のデューティー比測定 これで完成形か?
2020年8月18日:Arduino デューティー計測のためのインプットキャプチャータイミング
2020年1月28日:Arduinoから「タイマー0」を取り上げる(ユーザーが使う)
これの続き。

Arduino UNO基板を引っ張り出さなくても、「手軽」に
デューティー比を測定できるようにと、回路をケースに
入れました。

オシロを使えば、周波数や周期も表示してくれるし、
デューテューやパルスのH幅、L幅(時間系の測定機能)
も計ってくれます。

でも、「正確になんぼやねん?」にはちょい桁数不足。
  ※周波数や周期は周波数カウンタを使えば
   良いんですが・・・

せっかくだし、ということで実験していた回路を
ユニバーサル基板に組んで、ケースに入れました。

こんな回路。
Dutyck1
単3電池2本で動かします。

ケースの様子。
11_20201112162301

高い周波数の測定を目指したんじゃなくって、入力クロックの
H区間とL区間を調べようというのが目的。

デューテューは t1/(t1+t2) * 100で「xx.xx%」と表示。
周波数は clk / (t1+t2) で表示。
t1とt2はタイマー1のインプットキャプチャー機能で
数えたT1クロックの数。
T1クロックは16MHz~0.1MHzまでスイッチで選択
できるように。
最大クロック数が16bitで65535。
それ以上は(つまり低い周波数)はカウンターがオーバー
フローしてしまいます。

こんな表示になります。
12_20201112162301
0.1MHzのT1クロックで、t1+t2が50000カウント。
だから周波数は2.00Hz。
デューティーは49955 / 50000 = 99.91%。
  ※H/Lsetというスイッチで、t1とt2を入れ替え
   できるようにしています。
   負論理信号のデューティーを読めるように。

基板の様子。
13_20201112162301

片面基板に配線。

14_20201112162301
ハンダ面への部品取り付け(コネクタとVR)はこの↓応用。
2020年9月26日:手組みするときは片面基板で:1kHz PWM発生回路

液晶の下にATmega328P。
15_20201112162301

入力にいれたシュミット 74HCT14は無くてもok。
  ※ちょいスレッショルドを下げたかったんで。
入力プルアップ抵抗のon/off機能(ジャンパーピンで)
付けておくほうが良いかもしれません。

※スケッチ:ダウンロード - duty_ck1.c
  ファイルタイプを「.ino」ではなく
  「.c」にしています。

| | コメント (1)

2020年10月31日 (土)

Arduino-UNO 12bit×4chアナログ SDカードデータロガー完成形

前記事
2020年10月27日:Arduino-UNO 12bit×4chアナログ データロガーほぼ完成
2020年10月28日:データロガーのサンプリング日時出力形式

最終的にはこんな回路に。
Analog_log1_20201031105101

31_20201031105401

時間をちゃんとしておこうと、クロック発振素子を
セラロックから水晶に換えました。

★スケッチ:ダウンロード - sd_analog4ch1.c
  ファイルタイプを「.c」にしています。
  inoのままだとIDEが起動するんで。

START/STOP/ENTスイッチがちょん押しと長押しを区別。
CYC/↑とMENU/↓がオートリピート処理。

ややこしい操作がA/Dのキャリブレーション。
±2Vレンジと±20Vレンジを独立して調整します。
それぞれのレンジに切り替えてから、
 0V・+2V・-2V  0V・+20V・-20V
の電圧を、同時に4chの入力に与えてENTすると
設定完了に。
アナログ系の調整のため、この設定は必須。
6回操作しなくちゃなりません。
  ※調整値はEEPROMに記憶。

※A/D値から電圧値へのスケーリング処理に
 「浮動小数点にしたmap」を使っています。

2019年4月3日:線形補間って「LERP」って言うんだ!
2020年5月17日:Arduino なんとかして誤用を正したい:A/Dの1/1023とmap関数
2020年5月16日:Arduino 10bit A/D値をmap関数でスケーリングする例

SDカードのインターフェースやA/Dコンバータが無くても
液晶(20文字×4行)とスイッチをつないでもらえれば
試運転(操作の雰囲気)は分かってもらえるかと。

外付け12bitA/Dコンバータとの接続にPC0~3(AD0~3)
を使ってますんで、A/D無しでここを入力にして
adreadすれば、10bitで処理できるように改造可能かと。

これ以上のものとなると、Arduino-UNOではもうチカラ不足。
  ※いろんな制御しようとすると、ポートがとRAMが足りない。
ここらと、プリンターシールド をドッキングできればエエんですが。
しかし、手軽な「SD」ライブラリは捨てがたい。


| | コメント (0)

2020年10月28日 (水)

データロガーのサンプリング日時出力形式

Arduino-UNO 12bit×4chアナログ データロガーほぼ完成
記しましたが、データをサンプルした日時の出力をどうした
ものかと悩んでおりました。
GNUPLOTに食わせるんで食わせやすい書式で、1日を越えた
記録でも扱いやすくというのが要求仕様。

GNUPLOTをあれこれ触ってみて、なんとかなりそうなんで、
単純にこうしておきます。
 『 d H:M:S data1 data2 data3 data4』
先頭のdは1から始まる経過日
H:M:Sは時分秒。
最初のデータは「 1 00:00:00 data1~」からスタート。

GNUPLOTに食わせるサンプルデータ。

1 00:00:00  0.00
1 06:00:00 0.20
1 12:00:00 0.40
1 18:00:00 0.60
2 00:00:00 0.80
2 06:00:00 1.00
2 12:00:00 0.80
2 18:00:00 0.60
3 00:00:00 0.40
3 06:00:00 0.20
3 12:00:00 0.00
3 18:00:00 -0.20
4 00:00:00 -0.40
4 06:00:00 -0.60
4 12:00:00 -0.80
4 18:00:00 -1.00
5 00:00:00 -0.80
5 06:00:00 -0.60
5 12:00:00 -0.40
5 18:00:00 -0.20

まず、GNUPLOTの「set xdata time」での処理。
こんなスクリプト。

set grid
set xdata time
set timefmt "%j %H:%M:%S" # 「%j」の範囲が1~366
set xrange [0:"6T0:0:0"] # この「T」で日を指定
set xtics 3600*12
set xlabel "\nDate/Time"
set format x "%1dday\n%H:%M"
set yrange [ -2:+2]
set ytics format "%4.2f"
set ytics 0.5
set key right top
plot "L1.txt" using 1:3 with lines lw 2 ti "Test"

結果のグラフ。 X軸の目盛が日と時分に。

Cap001

次が「time」を使わずに、plotコマンドに書式指定を
導入して、日、時、分、秒に分けて、秒数を計算します。
日は1から始まるんで「-1」してゼロスタートに。
それがこれ。
X軸を「時間」にしています。

#秒で読んでから時間に直してみる
# 1 12:00:00 1.000
# $1 $2 $3 $4 $5
set grid
set xrange [0:120]
set xtics 12
set yrange [ -2:2]
set ytics format "%4.2f"
set ytics 0.5
set key right top
plot "L1.txt" using (((($1-1)*3600*24)+($2*3600)+
($3*60)+($4))/3600):($5) '%lf %lf:%lf:%lf %lf'
with lines lw 2 ti "Test"

「plot」のところの「'%lf~」が入力文の書式指定。
$1が日、$2~$4が時分秒。
$5からデータ部を続けます。
「%lf」は小数点付の数値指定。
そのグラフ。 X軸の単位は「時」に。
Cap002
これで、まぁ、なんとかなりそうです。

※追記 10/30
gnuplotで文字列変数に入れてある文をコマンドとして差し込む方法(@を変数名の前に付ける)
を見ますと、長ったらしいし式を略して記せるとのこと。
しかし現用しているgnuplot4.6にはまだ搭載されてませんでした。
で、新しいgnuplotをダウンロード。
ちょっとテスト。
データ列をもう一つ増やして・・・

Yデータが一つだと。
  plot "L1.txt" using (((($1-1)*3600*24)+($2*3600)+($3*60)+($4))/3600):($5) '%lf %lf:%lf:%lf %lf' with lines lw 2 ti "Test"
二つになると、
  plot "L1.txt" using (((($1-1)*3600*24)+($2*3600)+($3*60)+($4))/3600):($5) '%lf %lf:%lf:%lf %lf %lf' with lines lw 2 ti "Test1" ,\
     "L1.txt" using (((($1-1)*3600*24)+($2*3600)+($3*60)+($4))/3600):($6) '%lf %lf:%lf:%lf %lf %lf' with lines lw 2 ti "Test2"
長い。
($6)に変わるのと%lfが増える。
それを、
  cc = "(((($1-1)*3600*24)+($2*3600)+($3*60)+($4))/3600)"
  cs = "'%lf %lf:%lf:%lf %lf %lf'"
として、プロット実行で
  plot "L1.txt" using @cc:($5) @cs with lines lw 2 ti "Test1",\
     "L1.txt" using @cc:($6) @cs with lines lw 2 ti "Test2"
と。
短くなります。

・・・ところが・・・
想定してる電文、
  1 00:00:00 1.084 0.262 0.000 0.000
  $1 $2 $3 $4 $5 $6 $7 $8
これだと、$nは1~8まで。

8つのデータを取り出してグラフにしようとすると、
gnuplotがこんなエラーメッセージで拒否。
 『format must have 1-7 conversions of type double (%lf)』
1~7の7コまでやで! 8コはあかん。っと。

※例
JIS C8708充放電中の電池電圧とLM35による温度出力。
取り込むとこんなデータが出てきます。

  経過時間 電圧 温度:LM35(10mV/度)
1 00:00:00 1.084 0.262 0.000 0.000
1 00:00:10 1.084 0.262 0.000 0.000
1 00:00:20 1.083 0.262 0.000 0.000
  :
1 04:06:00 1.479 0.269 0.000 0.000
1 04:06:10 1.479 0.268 0.000 0.000
1 04:06:20 1.479 0.268 0.000 0.000

10秒サイクルで取り込んで4時間あまり放置。
それをこんなgnuplotスクリプトでグラフ化。
~~~~~~~~~~~~~~~~~~~~~~~~~~~
#秒で読んでから分に直す
# 1 00:00:00 1.084 0.262 0.000 0.000
# $1 $2 $3 $4 $5 $6 $7 $8
set term wxt 0
set grid
set xrange [0:250]
set xtics 30
set yrange [0.8:1.8]
set ytics format "%4.2f"
set ytics 0.1
set y2range [10.0:60.0]
set y2tics format "%.1f"
set y2tics 10.0
set xlabel "経過時間(分)"
set ylabel "電圧(V)"
set y2label "温度(℃)"
set key right bottom
cc = "(((($1-1)*3600*24)+($2*3600)+($3*60)+($4))/60)" # 経過時間:分
cs = "'%lf %lf:%lf:%lf %lf %lf %lf'"
# $1 $2 $3 $4 $5 $6 $7 ($8がダメ)
plot "M10.txt" using @cc:($5) @cs with lines lw 2 ti "電圧",\
"M10.txt" using @cc:($6 * 100) @cs with lines lw 2 ti "温度" axes x1y2
~~~~~~~~~~~~~~~~~~~~~~~~~~~

出てきたグラフ。
21_20201030161301
  新しいgnuplot、これまでのと色がチョイ違います。

充放電電圧変化と電池側面の温度変化が2サイクル分。
-Δ検出の様子やその時の電池温度上昇が分かります。

チャートレコーダーのようにリアルタイムでは見られません。
けど、こまかいところを見ようとしたとき、データですんで
手があります。

※『set xdata time』だとこんなスクリプトに。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 1 00:00:00 1.084 0.262 0.000 0.000 memo...
# 1 2 3 4 5 6 7
set term wxt 0
set grid
set xdata time
set timefmt "%j %H:%M:%S"
set xrange [0:15000]
set xtics 1800
set format x "%H:%M"
set yrange [0.8:1.8]
set ytics format "%4.2f"
set ytics 0.1
set y2range [10.0:60.0]
set y2tics format "%.1f"
set y2tics 10.0
set xlabel "経過時間(時)"
set ylabel "電圧(V)"
set y2label "温度(℃)"
set key right bottom
plot "M10.txt" using 1:3 with lines lw 2 ti "電圧" ,\
"M10.txt" using 1:($4 * 100) with lines lw 2 ti "温度" axes x1y2
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

短縮するための「@cc」や「@cs」は不要。
普通に書けます。
出てくるグラフはこんなの。
Cap001_20201031100601
「波」は同じですが、X軸の目盛が異なり「時:分」の
表現になっています。

とりあえず備忘録ということで。


| | コメント (0)

2020年10月27日 (火)

Arduino-UNO 12bit×4chアナログ データロガーほぼ完成

Arduino-UNO 12bit×4chアナログ データロガー計画中
ざっとカタチになりました。
全体はこんな回路。
Analog_log1

SDカード部はaitendo製の基板。
SDカードスロットDIP化きっと [WV-N09WG]
レベル変換ICでATmega328Pにつないでいます。

A/Dコンバータは12bit 4chのMCP3204
14ピンDIPなんで配線は楽。

4行x20文字の液晶で表示。
11_20201027152801

中身。
12_20201027152801

SDカードに記録する電文フォーマットに悩み中。
現在は、こんな具合。

# 1 Cyc time 2sec  ←設定値の一部をヘッダーに置いて
# 2 Ch1 Range 2V
# 3 Ch2 Range 2V
# 4 Ch3 Range 2V
# 5 Ch4 Range 2V
# 6 Rx chr max 40
# 7 Rx start 0
#LOG on 2sec
00:00:00 0.896 0.896 0.897 0.897 21.23
00:00:02 1.452 1.451 1.451 1.451 21.23
00:00:04 1.504 1.504 1.504 1.503 21.23
00:00:06 0.961 0.961 0.961 0.960 21.24
00:00:08 0.494 0.492 0.493 0.493 21.23
00:00:10 -0.034 -0.034 -0.034 -0.034 21.31
00:00:12 -0.553 -0.553 -0.553 -0.553 21.38
 経過時間 A/D ch1 ch2 ch3 ch4  外部シリアル電文
                     max 40文字

24時間を越えたら「1 00:00:01」と出力。

サンプリング周期は1秒、2秒、5秒、10秒、30秒
1分、2分、5分、10分、30分、1時間の11種類。

長期間の記録を考えると24時間「12:34:56」表記では
不足。
「日」を入れなければなりません。

ところが・・・
グラフ化はGNUPLOTを使います。
しかし、GNUPLOTの日時処理はカレンダーになっています。

例えば、時分秒の前に「日」を付加して「123 01:12:34」という
「日時分秒」という書式は扱えるんですが、これは経過日数が
123ということではなく、年の初めから123日目の日を示すのです。
ですんで、この数値範囲は「1~366日」。
ログ開始からの経過日、「最初は0」で「0 00:00:00」から始めたい。
でも、「0日」は許してもらえません。
最初のデータは「1 00:00:00」にしておかないと怒られます。
そして、1年、閏年もあるんで最大が366日。
これも越えられません。
これに従っていれば、日時のフォーマット変換が簡単に済ませられ
ます。

GNUPLOT、
  set xdata time
  set timefmt "%j %H:%M:%S"
で、「12 12:34:56」と日付+時刻が得られ(秒に換算されて)
ます。

いろんな機能があるGNUPLOT、何か方法はあるんでしょうが、
まだ探し出せてません。


※日時の規約
日と時分秒の間に「T」を入れるかどうか・・・
日時を分ける「T」、調べたらこんな規約があるんですな。
    https://ja.wikipedia.org/wiki/ISO_8601


※おまけでシリアル電文を付加
12bit 4chのA/Dデータに加えて、シリアルで受けた電文を
40文字だけ付加できるようにしています。
他の装置が出すデータをシリアルで受けて、SDカードに
同時に記憶しておこうという魂胆です。
バッファを大きくできないんで、1行40文字で制限してい
ます。
ただし、1行の文字の先頭何文字目までは捨てちゃえという
設定もありますんで、行のうちの必要な所だけを取り込
めるかと。

 

| | コメント (0)

2020年10月20日 (火)

Arduino:関数ポインタを使ったテーブルジャンプ ROMにテーブルを置く

2020年4月10日:JIS C8708:2019対応ニッケル水素電池充放電実験回路(回路図とプログラム)
の中の「batcyc3b.zip」を見ていただければ良いかと
思うんですが、連続実行させるプログラムの処理に
関数ポインタ」を使っています。

いろんな処理の流れを場面に分けて記述。
それをテーブルにして、したい処理だけを実行させる
という手順です。
「batcyc3b」では二つの処理を並列に稼働させています。
こんな具合です。

/*****  充放電実行        *****/
void (*cycexc[])(void)={
cycstby, // 0 待機
cycstart, // * 1 充放電開始
cyc0dchg, // 2 CYC 0 最初の放電開始
cyc0dchg1, // 3 CYC 0 最初の放電完了待ち
cyc0dwait, // * 4 CYC 0 放電完了後の時間待ち
cyc49chg, // * 5 CYC 1~49 充電開始
cyc49chg1, // * 6 CYC 1~49 充電完了待ち
cyc49chg2, // 7 CYC 1~49 充電後の時間待ち
cyc49dchg, // * 8 CYC 1~49 放電開始
cyc49dchg1, // 9 CYC 1~49 放電完了待ち
cyc49dchg2, // 10 CYC 1~49 放電後の時間待ち
cyc50chg, // * 11 CYC 50 充電開始
cyc50chg1, // 12 CYC 50 充電完了待ち
cyc50chg2, // 13 CYC 50 充電後の時間待ち
cyc50dchg, // * 14 CYC 50 放電開始
cyc50dchg1, // * 15 CYC 50 放電完了待ち
};

/***** 測定実行 *****/
void (*mesexc[])(void)={
mesttl, // * 0 タイトル表示
mesttl1, // 1 タイトル表示続き
messtby, // * 2
messtby1, // 3
mesmenu, // * 4
mesmenu1, // 5
mesmenu2, // 6
mespwm, // * 7 PWM電流テスト
mespwm1, // 8 PWM電流テスト#1
mespwm2, // 9 PWM電流テスト#2
mespwm3, // 10 PWM電流テスト#3
mespara, // * 11 パラメータ設定
mespara1, // 12
mespara2, // 13
mescyc, // * 14 充放電サイクル開始
};

※それぞれの実行ルーチン
cycexc[cyc_exc](); // 充放電サイクル実行
mesexc[mes_exc](); // 測定実行区分で処理

このテーブルですが、置かれているのは「RAM」領域
関数のアドレスを示していますんで、固定的な値です。
   ※実行中にこれを変更する必要はありません。

これが合わせて30コほど。
一つが2バイトですので合わせて60バイト
固定データにRAMを使うのって、Arduino(ATmega328P)では
ちょっともったいない。 (RAMが少ないので)
ROMに置ければ、60バイトのRAM領域が浮いてきます。

しかし・・・
AVRマイコンでのC言語、ROM領域にデータを置くには
PROGMEM」を使って記述するという、独特の制限が
あります。
そしてROM領域からの読み出しは、pgm_read_byte()
pgm_read_word()を使わなくてはなりません。
RAMに置いたように記述しても、ROMだと直接実行してくれない
のです。

そこで、こんなテストプログラムを書いてみました。
まずは、いつものようにRAMでの実行

/*****  テーブルジャンプのテスト    *****/
// テーブルをROMに入れたい
// まずはいつも使っているRAMのテーブルで
byte ex_t = 0; // 実行番号 0~3
/***** 実行ルーチン *****/
void t0(void){
Serial.println(" test0");
}
void t1(void){
Serial.println(" test1");
}
void t2(void){
Serial.println(" test2");
}
void t3(void){
Serial.println(" test3");
}
void t4(void){ // これは動作確認用おまけ
Serial.println(" test4");
}
/***** RAMでの実行 *****/
// 配列に実行ルーチンを入れる
void (*ramexc[])(void)={
t0, // 0
t1, // 1
t2, // 2
t3, // 3
};
/***** 実験ルーチン *****/
void setup() {
Serial.begin(9600); // 9600BPSで
}
/***** LOOP *****/
void loop() {
Serial.print("RAM ");
delay(100);
ramexc[ex_t](); // RAMでテーブルジャンプ
ex_t++;
if(ex_t >= 4) ex_t = 0; // 0に戻す
delay(1000); // ちょい待ち
}

関数(サブルーチン)をテーブルに置いて、
  ramexc[ex_t]();
とすれば、「ex_t」の値で該当サブルーチンを実行し
てくれます。

次はROMの場合
まず、関数の置く方法。
「typedef」で型宣言してから「PROGMEM」でROM内の
固定データ(関数のアドレス)の配列にします。
関数の名前の置き方はRAMといっしょ。

問題はこの関数テーブルの実行方法です。
アドレスは2バイトデータですので、pgm_read_word()
取り出します。
それをプログラムとして実行する本体、関数ポインタに与え
ます。
この時、ちょいとややっこい「キャスト」をしないと
warningが出てしまいます。

最終的に落ち着いたキャストの方法がこれ。
   p = (void (*)(void))a;
p=a; とか
p=(void *)a;
だとwarningが出ちゃうのです。

実行は
  romexc(ex_t);
でok。
ex_tの番号で指定した関数(サブルーチン)が実行されます。

具体的にはこんなスケッチです。

/*****  テーブルジャンプのテスト    *****/
// テーブルをROMに入れたい
// ROMでの実行を試す
byte ex_t = 0; // 実行番号 0~3
/***** 実行ルーチン *****/
void t0(void){
Serial.println(" test0");
}
void t1(void){
Serial.println(" test1");
}
void t2(void){
Serial.println(" test2");
}
void t3(void){
Serial.println(" test3");
}
void t4(void){ // これは動作確認用おまけ
Serial.println(" test4");
}
/***** ROMでの実行 *****/
typedef void(*romtt)(void); // typedefで形態を指定
// テーブルの実態
romtt const romtbl[] PROGMEM ={ // PROGMEMでROMに配置
t0, // 0
t1, // 1
t2, // 2
t3, // 3
};
// 実行ルーチン
void romexc(byte n){
word a; // 読み出しアドレス
void (*p)(void); // 実行する本体
a = pgm_read_word(&romtbl[n]); // アドレスリード
// p = a; // warning
// p = (void *)a; // warning
p = (void (*)(void))a; // ★ここのキャストどうにかしたいが
p(); // 実行
}
/***** 実験ルーチン *****/
void setup() {
Serial.begin(9600); // 9600BPSで
}
/***** LOOP *****/
void loop() {
Serial.print("ROM ");
delay(100);
romexc(ex_t); // ROMでテーブルジャンプ
ex_t++;
if(ex_t >= 4) ex_t = 0; // 0に戻す
delay(1000); // ちょい待ち
}

ROMでのテーブルジャンプ、もうちょい簡単に記述でき
れば良いのですが・・・

RAMやI/Oの読み書き場所とROMの読み出し場所、方法
が異なるというAVRマイコンのアーキテクチャに起因し
ますんでどうにもできません。

機械語レベルの話になりますが、AVRマイコン、プログラムを
入れるROMが16ビットなんで、ROM内のテーブルを読む時の
アドレス指定がちょいとややこしい。(2倍しなくちゃならない)


※この話、もうちょい続きます。
どうやら、ramexc()とromexc()を同時に使うと
コンパイラがバグる(最適化のミス)ようなのです。
RAM用とROM用、それぞれのテーブルの中を
t0~t3を同じ順で並べると、RAMのほうの実行が
暴走。
「同じ値のテーブルやから処理を省略したろ」と
しているっぽいのです。
t0~t3の順番を変える、t4を入れるなどして異なった
テーブルにすると、ちゃんと動くのです。

※暴走するスケッチ
   ダウンロード - test_progmem1.c
ファイルタイプを「.c」にしています。
★マークの実行サブルーチンの配列、例えばt3をt4に
変えるとちゃんと走るようになります。

| | コメント (0)

2020年9月25日 (金)

クリック無しロータリーエンコーダー

Bournsクリック無しロータリーエンコーダ(PEC16-4020F-N0024)
が入ってきましたんでどんなものかを確かめてみました。

こんな形状。
A1_20200925111001

Arduino-UNOのATmega328PのINT0とINT1につなぐんですが、
ATmega328P入力ポートのヒステリシス幅がちょいと小さいんで、
C-MOSのシュミット入力ゲート「4584」を間に入れてみました。

A2_20200925111101
チャタリング除去の時定数は、
2020年9月12日:ロータリーエンコーダーの2相パルスをピン変化割り込みで取り込む
のプルアップ抵抗10kΩにCRフィルタが10k+0.01uF

この時の様子をオシロで観察。
A相/B相波形は「クリック有り」の時のような偏りはありません。
平均して2相パルスの列が続きます。

さて、出てくるチャタリングはこんな具合です。

まず、平和な時。 キレイなエッジが見えます。

B000_20200925111901
CH4の波形はINT1割り込み受付で出しているパルス。
  ※4584をスキップすると、INT1パルスが
   バラバラと出ます。
   ATmega328pのヒステリシスがもうちょい
   大きければば良かったのに・・・

ちょいと大きなチャタリング。
B001_20200925112101

このくらい↑なら除去してくれるんですが、
こうなると↓、10k+0.01uFでのフィルタでは除けません。

B002
4584を入れても、INT1割り込みをミスしている様子が見えます。
   ※割り込みは正しく働いてますよ。

このエンコーダのスペックには、
   Contact Bounce (15 RPM) ・・・ 5.0ms. max
と記してあるんで、このくらいのチャタリングは出てもあた
りまえと考えておかなくちゃなりません。

そこで、CRフィルタのコンデンサを10倍の0.1uFにしてみました。
時定数は10倍の約1mSに。

その結果。

B004_20200925112201
もうひとつ
B005

まぁこんなもんでしょう。
INT0とINT1でのエッジ検出後に、ソフト的にもう1~2ms
遅らせてからA相とB相のH/Lを確認することにします。

これで完成形にもって行きます。
ただし、クロック周波数を変えるんでArduinoからは離れます。
  どのみち、Arduino独自の関数ってシリアル出力だけしか
  使ってないし。

| | コメント (0)

2020年9月21日 (月)

『アトミック操作』・・・8bitマイコンに限り何か別の言い方なかったか?

2019年3月25日:割り込みで処理させるwordデータの扱い
に書きましたが、Arduino-UNOで使われているAtmega-328Pは
8ビットマイコンです。
ですんで、割り込みで処理される2バイト以上のデータをメイン側で
読み書きする時は「割り込み禁止状態にして」というのが基本です。

この手順を「何て言うの?」っと調べたら・・・
  ・アトミックに処理を
  ・アトミック操作
  ・不可分処理
っと、出てきます。

でも、アトミック操作とはで調べると、8ビットマイコンでの
割り込み処理の話が出てこずに、
  スレッドやプロセスや
  RISCやCISCや
  マルチスレッドやシングルスレッドや
っという8ビットマイコンの世界とはちょいと縁遠いお話し
ばかりが出てきます。

この「アトミック操作」ですが、昔々の8ビットマイコンの
世界じゃ(8080,6800~Z80あたりの年代で)なんかもっと他の
言い方、無かったでしょうか?

「8ビットマイコンの割り込み処理で多バイトデータを扱う時、
 メインでは割り込み禁止で読み書きを」を一言で表現できる
言葉、こんなの無かったですかね?

当時、アトミック処理や不可分処理なんて言葉で言わなかった
よう・・・

『アトミックにアクセス』なんて言い回し・・・
ラジオペンチさん に教えてもらった。
   (この↑記事のコメントの前にもあったはずだけど探し出せてない)


| | コメント (5)

2020年9月19日 (土)

クリック有りのロータリーエンコーダーで

2020年9月17日:ロータリーエンコーダーの2相パルス、クリック有りの場合は
を具体化。
Arduino-UNOを使ってこんな回路に。

Cc1_20200918181201
3桁の7seg LEDに設定角度(0~359度)を表示します。
実験中のバラック↓

Cc2_20200918181201
エンコーダは「読み取れるかどうかの実験のため」に
2回路付けてますが、設定値の表示は1系統だけ。
これをベースに電池駆動にしたジグを製作する予定です。

Arduino-UNO、クロックが16MHz。
PWMの周波数は1kHzが目標なんですが、PWMを450分割しな
ければなりません。
Duty 10%~90%を0度~360度にということで、こんな分割比
になります。→「45 + 360 + 45」
1kHzに近いところで16MHz÷450÷35で1015.9Hzになっています。
本チャンではクロック発振子を「7.2MHz」にして、
7.2MHz÷450÷16で1000.0Hzというふうにします。


なぜ「1kHz・10%~90%」のPWMかは、旭化成のAK7401のデータシートを
参照のこと。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2020年9月12日:ロータリーエンコーダーの2相パルスをピン変化割り込みで取り込む
で示した、メーカー推奨のチャタリング吸収回路・・・
  ※10kΩ抵抗に0.01uFコンデンサのCRローパスフィルタ。

B1_20200912144901
これだと、エンコーダのon/off波形が鈍ってしまって、ATmega328Pの
入力シュミット回路でミスが発生する(ことがある)のです。
ATmega32P、それぞれのデジタル入力にはシュミットゲートが入ってい
るのですが、シュミットのヒステリシス幅が小さいので、「鈍った波形」を
うまく処置(HかLかの決め打ち)ができない(ことがある)のです。

入力信号がなだらかに増減すると、スレッショルド電圧付近で
不安定に・・・
入力して出力するだけのテストプログラムを走らせると、
「振動波形」が現れる(ことがある)のです。

キレイな信号が入ることを前提としたエッジ検出入力回路、
これがスカタンすると、ちょいと問題。

ですんで、とりあえずは「内蔵プルアップ抵抗+外付け1000PF」
が「まだマシでした」ということで、試作したのです。
  ※内蔵プルアップ抵抗が20~50KΩ。
   直列抵抗無しでそのままGND間にコンデンサ。
コンデンサが無いと・・・そりゃひどいものです。
ソフトでどうにかしろ・・・ご勘弁を・・・・

ゲートICが増えることを許容して、HC14やHC132、4584、4093
などのちゃんとしたシュミット入力ゲートICを設けるべきでしょうね。
  ※仕事の設計だと「ケチらない!」。

エッジ検出による割り込み駆動だと、チャタリング吸収がちょいと
やりにくい。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

※スケッチはこちら → ダウンロード - test_enc3.c
  (ファイルタイプを「C」にしています)

特徴的なところを。

/*****  エンコーダカウントアップダウン     *****/
// 359度以上なら0に,0度未満なら359度に
// カウンタのポインターと加算値
// 加算値は±で
void cntupdn(volatile word *p, short a)
{
short d;
d = *p; // 現在値
d += a; // ±加算
if(a >= 0){ // count upの時
if(d > ENC_TOP) d -= (ENC_TOP + 1); // 0~359
}
else{ // count downの時
if(d < 0) d += (ENC_TOP + 1); // 0~359
}
*p = d;
}
/************************************/
/* 2相パルスA相↓エッジ割り込み */
/************************************/
// D13ポート 割り込みでH/Lパルス
// D12ポート カウントup/down処理でH/Lパルス
/***** INT0割り込み *****/
// ch1 A相↓エッジ, B相がHなら+1 Lなら-1
ISR(INT0_vect)
{
short d;
D13_H; // (!!!) LED on
if((tm_enc1 >= 5) && // 前のパルスから5ms経過で新↓エッジ
(INP_1A == 0)){ // A相がHならミストリガ
D12_H; // (!!!)
if(tm_enc1 >= 40) d = 1; // 40ms経過してたら+1
else d = 2; // 以内に操作したら+2
if(INP_1B) cntupdn(&enc_cnt1, d); // up
else cntupdn(&enc_cnt1, -d); // down
f_cnt = 1; // カウント値更新
tm_enc1 = 0; // タイマー1クリアー
D12_L; // (!!!)
}
D13_L; // (!!!) LED off
}

割り込み処理の確認のため、
・D13(LEDポート)に割り込み応答パルスを出力。
・D12に、カウントアップダウンが成功したらパルスを出力。

誤パルス除去のため、
・前パルスより5ms内の新パルスは無視。
・A相立ち下がりエッジの確認。
  割込直後のA相がHなら無視。

回転をちょっとスピードアップするため、40ms内のカウントアップ
ダウンを「±2」に。
ラジオペンチさんところ の、「50ms内は10倍速」という処理を
真似たのですが、でも10倍速は早すぎました!

試しに使った「クリック有り」のロータリーエンコーダー
(BOURNSE社製)の回転、素早く回すと1クリック
数ミリ秒で回せます。

こんな感じ。
A000_20200919102201

△(1)」のところで、立ち下がりではなく立ち上がりで
割り込みが入ったことが見えています。
△(2)」ではラインが太く見えているので、割り込みが続け
さまに2発入ったようです。
  ※ATmega328Pのシュミット入力のヒステリシスが
   もうちょい大きければ避けられたかも。

このくらいのミスパルスなら、先ほどの「誤パルス除去」
が防いでくれて、「◎」のカウントだけが有効になっています。

シュミット入力のミス、もうちょい時間を広げると
こんな感じです。
右端の「△」部がミストリガー。
チャタリング除去用のコンデンサで立ち上がりが波形が
鈍っているせいかと。

A001_20200919102901
「誤パルス除去」処理で、「◎」の1発カウントだけになって
いて、なんとか正しくカウントが進みました。



3桁7seg LEDのダイナミック表示はタイマー2割り込み
を使ってます。
コンペアマッチAで1ms(1kHz)周期を作ります。
その割り込みでLEDをブランクに。

そして、20us後に起動するコンペアマッチB割り込みで、
LEDのセグメントとコモンラインを駆動して点灯します。
この中で1msのタイマーをカウント。

3バイトの「led_bff[]」に書いたLEDのセグメント点灯情報を
割り込みが勝手に表示するという仕掛け。

ライブラリは使わず、ATmega328Pのレジスターを直接操作。
処理の遅いdigitalWritedigitalReadも使いません。

/*******************************/
/* タイマー2割り込み */
/*******************************/
// 1kHz(1mS)で割り込み
// コンペアAでLEDブランクに
// 20us後のコンペアBでLED点灯
/***** タイマー2コンペアマッチA割込み *****/
// LED出力ブランクに
ISR(TIMER2_COMPA_vect)
{
PORTC |= 0b00111111; // セグメントをオフ(Hでoff)
// ++++++---- seg F~A
PORTB |= 0b00001000;
// +------- seg G
PORTD |= 0b01110000; // COM駆動オフ(Hでoff)
// +++-------- COM3,2,1
}
/***** タイマー2コンペアマッチB割込み *****/
// ブランク後20usに起動
// LED出力オンとタイマー処理
ISR(TIMER2_COMPB_vect)
{
byte d;
d = ~led_bff[led_ptr]; // Lアクティブ
PORTC &= (d | 0b11000000); // bit6~0:seg F~A
d >>= 3; // 3bit右に
PORTB &= (d | 0b11110111); // seg G
d = led_com[led_ptr]; // COM1~COM3
PORTD &= d; // Lでオン
// 次桁
led_ptr++; // 次ポインター
if(led_ptr >= DIMSIZ(led_bff)) led_ptr = 0;
// タイマー処理
if(tm_enc1 != 255) tm_enc1++; // タイマー1 +1
if(tm_enc2 != 255) tm_enc2++; // タイマー2 +1
}


3桁数字の表示ルーチンはこれだけ。
下位桁からセグメント情報に変換し、上位はゼロサプレス処理
しています。

/*******************************/
/* LED表示 */
/*******************************/
/***** LED 3桁変換表示 *****/
void led3disp(word d)
{
byte i;
for(i = 0; i < 3; i++){ // 3桁 LSBから
if((i != 0) && (d == 0)) // ゼロサプレス
led_bff[i] = 0b00000000; // ブランク
else
led_bff[i] = led_seg[d % 10]; // 0~9
d /= 10; // 1/10
}
}

3バイトのled_bff[]に表示データ(LEDのセグメントデータ)を一度
書き込めば、あとは勝手にタイマー2割り込みが表示処理を実行し
てくれます。
毎回表示ルーチンを呼ばなくても、メモリーの内容に従い割り込みが
勝手に表示してくれるのです。

 ※「delay()」を使って表示タイミングを作っているような
  スケッチは実用的ではありません。
  しかし、「arduino 7seg LED ダイナミックスキャン」を検索
  しても、なかなかタイマー割り込みによる表示実行例に行き着
  きません。
  もう一つ。
  このようなスピード優先、マイコンチップの機能を直接操作する
  ようなプログラムでは、汎用的関数digitalWriteなんかを使わな
  くってもイイじゃないですか。
  速度優先で「素」のチップを制御するということで、ポートの
  直接操作、これでエエでしょう。
 

タイマーと割り込み関連の初期設定もそんなにややこしいこ
とはありません。
ATmega328Pのマニュアルをよく読めば、へんにライブラリーを
探すより早いかと。
マイコンが持つ機能、これを「素」で使います。

//  タイマー1 PWM出力 (A:PB1,B:PB2)
// エンコーダ値 0~359度でPWM 10%~90%を出力
// 16MHz/35/450 = 1015.9kHz
// ICR1AのTOP値は450*35 - 1
// 16MHz / 450
TCCR1A = 0b11110010;
// |||| ++---- PWM 高速PWM ICR1 モード
// ||++-------- PWM B (Negモード)
// ++---------- PWM A (Negモード)
TCCR1B = 0b00011001;
// || ||+++---- クロックセレクト 16MHz / 1 16MHz
// || ++------- PWM ICR1 モード
// |+---------- ICES1
// +----------- ICNC1
ICR1 = PWMTOP; // 0~449で0%~99.78%
OCR1A = OCR1B = 0xFFFF; // PWM off
// タイマー2
TCCR2A = 0b00000010; // モード設定
// |||| ++--- WGM: CTCモード
// ||++------- COM0B
// ++--------- COM0A
TCCR2B = 0b00000100;
// |+++--- CS:1/64 : 250kHz 4uS
// +------ WGM02
OCR2A = 250 - 1; // 1kHz(1mS)
OCR2B = 5 - 1; // 20uS
TIMSK2 = 0b00000110;
// |+----- OCIE2A コンペアマッチA割り込み有効
// +------ OCIE2B コンペアマッチB割り込み有効
// INT0,INT1 ↓エッジ割り込みに
EICRA = 0b00001010; // 割り込み入力エッジ
// ||++---- ISC0 INT0 ↓
// ++------ ISC1 INT1 ↓
EIMSK = 0b00000011; //外部割り込み有効
// |+---- INT0
// +----- INT1

 

| | コメント (0)

2020年9月17日 (木)

ロータリーエンコーダーの2相パルス、クリック有りの場合は

2020年9月12日:ロータリーエンコーダーの2相パルスをピン変化割り込みで取り込む

2020年9月16日:ロータリーエンコーダーの2相パルスをタイマー割り込みで
は、クリック無しでグルグル回しのロータリーエンコーダーを
想定しています。
ですんで、A相とB相の↑↓各エッジ、4つを使ってカウントし、
いわゆる4逓倍の処理を考えました。
1回転24パルスのロータリーエンコーダーだと1回転96パルスになります。

この、小型エンコーダー(コパルRE12D-300) だと、
1回転300パルスが1200パルスに。
ちょっと得した気分。

で、「クリック有りのロータリーエンコーダー」だったらどうした
ものかというお話しをちょっと。
観測波形を見ますと、静止時はA相・B相ともHレベルを保持。
左右どちらかの1クリックで2相パルスが出現という仕掛けに
なっています。

1クリックでA相とB相両方にLパルスが生じます。
その「後先」±1が決まります。
ですので、判断はどこか1つのエッジだけを使えば良いわけです。
例えばこんな具合。

A000_20200917095401
A001

下側波形の立ち下がりを使うことにして、このパルスを検出した時、
反対相のH/LによってCWかCCWを決めます。
ただし、クリックのメカ的な機構によりけっこう早い応答が必要。
パルスの抜けを防ぐにはエッジ検出は割り込みでの処理が必須でしょう。

この場合、割り込みが必要なのは片側の相だけ。
反対相はH/Lを入力するだけになりますんで、割り込みは不要。
また、エッジを検出しないほうの相が変化しても、カウント処理には
関係ありません。
  ※4逓倍処理なら、片相が変化するだけで±1します。
   しかし、エッジ検出が1つの場合だと、相が進まずに
   その片相だけがH/Lを繰り返すとミスカウントしてし
   まいます。
   クリック有りのロータリーエンコーダーはメカ的
   信号の相が進むようになっていますので、片相だけ
   の変化は心配しなくてかまいません。
     ※クリック無しのだと簡単にはいかない

ArduinoのINT0とINT1の割り込み駆動は両エッジだけでなく、
↑エッジ、↓エッジが設定できますので、↑↓どのエッジで
駆動されたのかの判断は不要です。
クリック有りタイプのロータリーエンコーダーだと2つ接続
できるということになります。


| | コメント (0)

2020年9月16日 (水)

ロータリーエンコーダーの2相パルスをタイマー割り込みで

2020年9月12日:ロータリーエンコーダーの2相パルスをピン変化割り込みで取り込む
2020年9月14日:「チャタリング除去回路」じゃなくって「チャタリング発生回路」をどうぞ
2020年9月15日:今度はチャタリング除去、その考え方

これらの続きということで、2kHzのタイマー割り込み(ATmega328Pのタイマー2)で
ロータリーエンコーダの入力を処理してみました。
   ※無理やりチャタリングを入れてます
そのスケッチを示します。

A相入力がA0、B相がA1。
D8出力とD9出力にチャタリング除去して平滑化したA相とB相を出力。
D10出力はA相B相の↑↓エッジ検出タイミング。
D13出力は(LED)は2kHzタイマー割り込み処理でパルスを出力しています。

/*****  ロータリーエンコーダーテスト    *****/
// A相+B相の4エッジを使って4逓倍
// タイマー2割り込みでチャタリング除去とともにエッジ検出
// ポート使用
// A0 PC0 A相入力 (pull up)
// A1 PC1 B相入力 (pull up)
// D8 PB0 A相入力安定モニター出力
// D9 PB1 B相入力安定モニター出力
// D10 PB2 エッジ検出出力
// D13 PB5 LED 割り込みタイミング出力
// タイマー2周波数設定 → OCR2A = 125 - 1; // 2kHz(0.5mS) ★1
// ポート制御
#define D8_H (PORTB |= (1 << PB0)) // PB0 A相入力安定モニター出力
#define D8_L (PORTB &= ~(1 << PB0)) //
#define D9_H (PORTB |= (1 << PB1)) // PB1 B相入力安定モニター出力
#define D9_L (PORTB &= ~(1 << PB1)) //
#define D10_H (PORTB |= (1 << PB2)) // PB2 エッジ検出出力
#define D10_L (PORTB &= ~(1 << PB2)) //
#define D13_H (PORTB |= (1 << PB5)) // PB5 (LED)割り込みタイミング
#define D13_L (PORTB &= ~(1 << PB5)) //
// 配列のデータ数を返すマクロ
#define DIMSIZ(a) (sizeof(a)/sizeof(*a))

/******************************/
/* エンコーダ入力 */
/******************************/
// データ
#define ENC_TOP 359 // 360度回転 0~359
volatile word enc_cnt; // 0~359カウンタ
volatile byte f_cnt; // カウンタup/down検出フラグ
// A相 B相入力 on,offチェック (Hで1)
#define INP_A (PINC & (1 << PC0)) // A相入力チェック
#define INP_B (PINC & (1 << PC1)) // B相
// ↑↓エッジのビット位置
#define ENC_A 0 // A相↑
#define ENC_B 1 // B相↓
// bit0,1にエンコーダ入力データが入る
volatile byte enc_sft[4]; // チャタリング除去用シフトデータ
// 4回ともH/L同じなら安定
volatile byte enc_inp; // 入力のon/off安定状態
// エッジをチェック
/***** エンコーダカウントアップ *****/
// 359度なら0に
void cntup(void)
{
if(enc_cnt >= ENC_TOP) enc_cnt = 0;
else enc_cnt++;
}
/***** エンコーダカウントダウン *****/
// 0度なら359に
void cntdn(void)
{
if(enc_cnt == 0) enc_cnt = ENC_TOP;
else enc_cnt--;
}
/***** カウント値読み出し *****/
// いったん割り込み禁止にして
word readcnt(void)
{
word d;
cli(); // いったん割り込み禁止して
d = enc_cnt; // 0~359度カウンタ読み出し
sei(); // 割り込み有効に戻す
return d;
}
/***** エンコーダ入力スキャン *****/
// タイマー割り込みで呼出
// 4サイクルでチャタリング除去
// ↑↓エッジを見つけてup/downカウント
// CW : A↑ B=L , A↓ B=H , B↑ A=H , B↓ A=L
// CCW : A↑ B=H , A↓ B=L , B↑ A=L , B↓ A=H
void encscan(void)
{
static byte p = 0; // 最新入力書き込み位置
static byte q1 = 0; // 安定したエンコーダ入力
byte d0, d1; // L安定,H安定 ↓↑エッジ
byte n, q2; // n:一時データ、q2:エッジ検出用
// エンコーダA,B相入力
n = 0;
if(INP_A) n |= 0b00000001; // 0:A相
if(INP_B) n |= 0b00000010; // 1:B相
enc_sft[p] = n; // 最新エンコーダ入力状態
// シフトデータをANDとORして変化点を見つける ★2
d0 = d1 = enc_sft[0]; // d0:全L / d1:全Hチェック
d0 |= enc_sft[1]; // [0]~[3]の4シフトデータ
d1 &= enc_sft[1];
d0 |= enc_sft[2];
d1 &= enc_sft[2];
d0 |= enc_sft[3];
d1 &= enc_sft[3];
n = (~d0) | d1; // 1のところが安定データ
q2 = q1; // 前の状態
q1 = (q2 & (~n)) | (enc_sft[3] & n); // 新入力状態確定
// シフトデータのポインタ+1
p++; // 次書き込み位置
if(p >= DIMSIZ(enc_sft)) p = 0; // ポインタ一周
// A相/B相安定状態をモニター出力
if(q1 & (1 << ENC_A)) D8_H; // A相 D8
else D8_L;
if(q1 & (1 << ENC_B)) D9_H; // B相 D9
else D9_L;
// エッジチェック
d1 = (~q2) & ( q1); // ↑エッジをチェック ★3
d0 = ( q2) & (~q1); // ↓エッジ
if(d0 | d1){ // エッジあり
D10_H; // A相かB相に↑↓エッジあり
f_cnt = 1; // カウントup/down検出
if(d1 & (1 << ENC_A)){ // A相↑ ★4
if(!(q1 & (1 << ENC_B))) cntup(); // B=L CW, +1
else cntdn(); // B=H CCW,-1
}
if(d1 & (1 << ENC_B)){ // B相↑
if( q1 & (1 << ENC_A)) cntup(); // A=H CW, +1
else cntdn(); // A=L CCW,-1
}
if(d0 & (1 << ENC_A)){ // A相↓
if( q1 & (1 << ENC_B)) cntup(); // B=H CW, +1
else cntdn(); // B=L CCW,-1
}
if(d0 & (1 << ENC_B)){ // B相↓
if(!(q1 & (1 << ENC_A))) cntup(); // A=L CW, +1
else cntdn(); // A=H CCW,-1
}
}
else{
D10_L; // エッジなし
}
}
/*******************************/
/* タイマー2割り込み */
/*******************************/
/***** タイマー2コンペアマッチA割込み *****/
// 2kHz(0.5mS)で割り込み
ISR(TIMER2_COMPA_vect)
{
D13_H; // (!!!)
encscan(); // エンコーダ入力処理
D13_L; // (!!!)
}
/*******************************/
/* SETUP */
/*******************************/
/***** SETUP *****/
void setup()
{
// ポートI/O指定
PORTB = 0b00000000; // data/pull up
DDRB = 0b00111111; // I/O (0:in 1:out)
// |||||+---- PB0 IO8 out A相入力安定モニター出力
// ||||+----- PB1 IO9 out B相入力安定モニター出力
// |||+------ PB2 IO10 out ↑↓エッジありモニター出力
// ||+------- PB3 IO11 out
// |+-------- PB4 IO12 out
// +--------- PB5 IO13 out (LED) 割り込みタイミング
PORTC = 0b00111111; // data/pull up
DDRC = 0b00000000; // I/O (0:in 1:out)
// |||||+---- PC0 AD0 in A相入力(pull up)
// ||||+----- PC1 AD1 in B相入力
// |||+------ PC2 AD2 in
// ||+------- PC3 AD3 in
// |+-------- PC4 AD4 in
// +--------- PC5 AD5 in
PORTD = 0b11111111; // data/pull up
DDRD = 0b00000010; // I/O (0:in 1:out)
// |||||||+---- PD0 IO0 in RXD
// ||||||+----- PD1 IO1 out TXD
// |||||+------ PD2 IO2 in
// ||||+------- PD3 IO3 in
// |||+-------- PD4 IO4 in
// ||+--------- PD5 IO5 in
// |+---------- PD6 IO6 in
// +----------- PD7 IO7 in
// タイマー2
TCCR2A = 0b00000010; // モード設定
// |||| ++--- WGM: CTCモード
// ||++------- COM0B
// ++--------- COM0A
TCCR2B = 0b00000100;
// |+++--- CS:1/64 : 250kHz
// +------ WGM02
OCR2A = 125 - 1; // 2kHz(0.5mS) ★1
TIMSK2 = 0b00000010;
// +----- OCIE2A コンペアマッチA割り込み有効
// シリアル
Serial.begin(9600);
// 最初の表示
delay(100); // 入力安定にちょい時間待ち(timer0使う)
cli(); // いったん割り込み禁止して
enc_cnt = 0; // カウンタをゼロクリア
sei(); // 割り込み有効に戻す
f_cnt = 1; // カウント値表示フラグon
}
/*******************************/
/* LOOP */
/*******************************/
/***** LOOP *****/
void loop()
{
if(f_cnt){ // カウンタup/downした
f_cnt = 0;
Serial.println(readcnt()); // カウント値をシリアル出力
}
}
/*===== end of "test_enc2.ino" =====*/

★1でタイマー2割り込みの周波数を設定。
2kHz(0.5ms)にしています。
チャタリング除去用のシフトレジスタが4段ですので、入力した信号が2mS遅れます。
これで2相パルスに対する応答速度が決まります。
ダイヤルを早く回すと追いつかないという状態に。

★2がチャタリング除去の処理。
ANDとORを重ねながらシフトレジスタを読み出しています。

★3がエッジ検出処理。
チャタリング除去されて確定したq1とq2からエッジを得ます。

★4で、A相B相の↑↓エッジ(4つある)から、相対する相の
状態を見て、カウントアップとカウントダウンの処理を行っています。


その状態。 オシロスコープで。
A000

一番下の波形、このタイミングでカウンタのup/downが
行われて、カウント値をシリアル出力しています。

エンコーダのぐるぐる回しをちょいと早くすると、
こんな感じに。
A003

模擬的なチャタリングをうまく取り除いている様子が見えます。
しかし、応答速度はこのあたりが限界。
対策は、タイマー割り込みの周期をもっと短くっという方法に
なるのですが、毎割り込みで取られる時間がちょいもったいな
いかと。
パルスが入ってカウント処理されると10usくらいです。
待機時は7~8usくらい。

割り込み機能の無い入力ピンを使っての2相パルスカウント、
高速応答はできませんが、手動で操作するエンコーダだと
こんなものでしょう。

シフトレジスタは8bitです。
今は2bitしか使っていないんで、エンコーダは4系統まで
拡張できるかと。


※試行錯誤

シフトレジスタのAND、OR処理、Cの書き方を
ちょいと変えると、出てくるコードが変わりました。
これが最初の。
enc_sft[0]~enc_sft[3]の並びをうまくやってくれるかと、
コンパイラに期待しましたが・・・

//  シフトデータをANDとORして変化点を見つける ★2
d0 = d1 = enc_sft[0]; // d0:全L / d1:全Hチェック
d0 |= enc_sft[1]; // [0]~[3]の4シフトデータ
d1 &= enc_sft[1];
d0 |= enc_sft[2];
d1 &= enc_sft[2];
d0 |= enc_sft[3];
d1 &= enc_sft[3];
n = (~d0) | d1; // 1のところが安定データ
q2 = q1; // 前の状態
q1 = (q2 & (~n)) | (enc_sft[3] & n); // 新入力状態確定

500: 90 91 18 01 lds r25, 0x0118 ;enc_sft[0]
504: 20 91 19 01 lds r18, 0x0119 ;enc_sft[1]
508: 29 2b or r18, r25
50a: 80 91 19 01 lds r24, 0x0119 ;enc_sft[1]
50e: 98 23 and r25, r24
510: 80 91 1a 01 lds r24, 0x011A ;enc_sft[2]
514: 28 2b or r18, r24
516: 80 91 1a 01 lds r24, 0x011A ;enc_sft[2]
51a: 98 23 and r25, r24
51c: 40 91 1b 01 lds r20, 0x011B ;enc_sft[3]
520: 80 91 1b 01 lds r24, 0x011B ;enc_sft[3]
524: 24 2b or r18, r20
526: 20 95 com r18
528: 98 23 and r25, r24
52a: 92 2b or r25, r18
52c: 80 91 17 01 lds r24, 0x0117 ;q1
530: 20 91 1b 01 lds r18, 0x011B ;enc_sft[3]
534: 49 2f mov r20, r25
536: 40 95 com r20
538: 48 23 and r20, r24
53a: 92 23 and r25, r18
53c: 94 2b or r25, r20
53e: 90 93 17 01 sts 0x0117, r25 ;q1

別の書き方で。
こっちのほうがメモリの読み出しを省いている
分、ちょいだけと早そう。


// シフトデータをANDとORして変化点を見つける ★2
d0 = d1 = enc_sft[0]; // d0:全Lチェック,d1:全Hチェック
n = enc_sft[1]; // [0]~[3]の4シフトデータ
d0 |= n;
d1 &= n;
n = enc_sft[2];
d0 |= n;
d1 &= n;
n = enc_sft[3];
d0 |= n;
d1 &= n;
n = (~d0) | d1; // 1のところが安定データ
q2 = q1; // 前の状態
q1 = (q2 & (~n)) | (enc_sft[3] & n); // 新入力状態確定


500: 90 91 18 01 lds r25, 0x0118 ;enc_sft[0]
504: 80 91 19 01 lds r24, 0x0119 ;enc_sft[1]
508: 29 2f mov r18, r25
50a: 28 2b or r18, r24
50c: 89 23 and r24, r25
50e: 40 91 1a 01 lds r20, 0x011A ;enc_sft[2]
512: 24 2b or r18, r20
514: 98 2f mov r25, r24
516: 94 23 and r25, r20
518: 80 91 1b 01 lds r24, 0x011B ;enc_sft[3]
51c: 28 2b or r18, r24
51e: 20 95 com r18
520: 89 23 and r24, r25
522: 92 2f mov r25, r18
524: 98 2b or r25, r24
526: 80 91 17 01 lds r24, 0x0117 ;q1
52a: 20 91 1b 01 lds r18, 0x011B ;enc_sft[3]
52e: 49 2f mov r20, r25
530: 40 95 com r20
532: 48 23 and r20, r24
534: 92 23 and r25, r18
536: 94 2b or r25, r20
538: 90 93 17 01 sts 0x0117, r25 ;q1

 

 

| | コメント (0)

より以前の記事一覧