2020年9月21日 (月)

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

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

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

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

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

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

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

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


| | コメント (0)

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)

CR2032バックアップ用リチウム電池・・・0V

先日来、ガレージのPCの調子が良くなかったんで、
昨晩、フタを開けて清掃。
  立ち上がりをミスったり、
  動画を見てたら落ちたりっと。
PCはガレージの主テーブル(食事を作るんで油も煙もひどい)の
そば。
電源を抜いてから、ケースの吸気口やファンの汚れ(コテコテだぁ)
を清掃。

11_20200917082701

ところが・・・掃除を終えてからの電源投入。
いきなりBIOS設定画面が出てきちゃいました。
いろんな設定だけでなく、日付も忘れています。
あれまっ。

文鎮 の佐藤テック君のアドバイス。
  「電池とちゃうか?!」

12_20200917083001

アタリ!でした。
電池を外してテスターで電圧を測ったら「0V」。
放電しきってます。
「まぁよぅ動いてたな」ってことで新品電池に交換にして解決。

電源コードを外して小一時間ほど掃除でごそごそしてたせいで、
完全に電源が抜け落ちてしまったのでしょう。
それでもまぁ「0V」とは・・・

| | コメント (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)

2020年9月15日 (火)

今度はチャタリング除去、その考え方

2020年9月12日:ロータリーエンコーダーの2相パルスをピン変化割り込みで取り込む
に絡んで、まずはこんなものを作りました。
2020年9月14日:「チャタリング除去回路」じゃなくって「チャタリング発生回路」をどうぞ

エンコーダのA相・B相信号、チャタリングを取り除きながら
エッジ検出してCW・CCW方向のパルス計数をする方法。
このように考えて作っています。

・INT0やINT1、あるいはピン変化割り込みを使う場合は、
 ハードウェア的にチャタリングを取り除いておかないと
 不要パルスが入った時にミスカウントするかも。

・これらの直接的パルス駆動割り込みを使わない場合は、
 タイマー割り込みでの定期的なA相・B相信号の入力処理
 が必要。

・定期的な処理ができるのなら、チャタリング除去も
 ソフトでできる。

・ただし・・・「入力波形が安定するまで時間待ち」など
 という恥ずかしい処理は×。

・こんなロジックでチャタリングを防ぎます。

C1_20200915170901

とりあえずシフトレジスタは4段。
タイマー割り込みの周期で入力信号をシフトしていきます。
4つとも全部がH、あるいは全部がLなら信号が安定した
ということで、入力を確定。
H/Lが混じっていたら不安定=チャタリング中ということで
安定していた時の昔のデータを用います。

スイッチ入力だと、1mSサイクルで10段ほど読んでおけば
ひどいチャタリングのあるスイッチでも大丈夫。
スイッチのオートリピートや長押し処理も、タイマー割り込み内
で完結します。

そして、エンコーダのA相・B相から正逆をカウントする処理は、
そのパルスエッジの検出が不可欠です。

これもやはりシフトレジスタを使って処理、こんな
「回路」で↑↓エッジを作り出します。

C2_20200915170901

このロジックをマイコンの命令に置き換えてプログラム
するわけです。

チャタリング除去の所、全Lを検出している「負論理4入力のAND」
は「正論理4入力・負論理出力のOR」と等価です。
これもプログラムの命令で実現します。

実際のプログラムの様子はこの次に。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
※2相パルスの弁別回路について
2017年9月12日:C-MOS 2相パルスカウンタの入力部回路
2017年3月6日:29年前に製作したツール

| | コメント (0)

パナソニックeneloopスタンダード単3、JIS C8708:2019充放電テスト850サイクル目

パナソニックeneloopスタンダード単3 BK-3MCCのJIS C8708:2019(新JIS)
充放電実験を始めたのが2020年4月4日
ざっと5ヶ月で850サイクルの充放電を終えました。

21_20200915143001

450サイクル目が2020年7月7日でしたんで、充放電時間が徐々に短くなって
きてるのが経過日数からも分かります。
  ※製作した充放電実験装置、連続して試験できるのが
   400サイクルです。ですので、400サイクル、800サイクル
   と試験が進むはずなんですが、850サイクルと中途半端なの
   は、途中でプログラムを変更したから。
   その時点で50サイクルほど充放電が進んでいたのです。

まず、50サイクルごとの0.2C放電(5時間率)のグラフ。
JISの寿命判定はこの放電時間が3時間未満になった時。
Vd850
  ※まだもうちょい、というところですんで実験を
   継続します。
   850サイクル終了後に電池を外して内部抵抗を
   計ったら208mΩでした。
   まだもうちょい行けそうです。
    初期の内部抵抗は15mΩ

そして、毎1~49サイクルでの0.5C(2時間率)充放電時間と
充電完了電圧を記録したのがこのグラフ。

E850

0.5Cですので、2時間は放電していてもらいたい。
0.2Cのゆっくり放電で寿命を見るより、このくらいの
放電電流のほうが実用に近いかと。

500サイクルは安心して使える結果がでています。
また、0.5Cでの充電開始後、「偽の-ΔV」は出ていません。
ReVOLTESではこれが激しく出たんですが、エネループでも
見てみたい。
ダイソーReVOLTE単3 JIS C8708:2019充放電試験(-ΔV検出有) 偽の-ΔV発生

ほんとの寿命まであともうちょいイジメてみます。

ブログ記事まとめ:電池あれこれ

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
※JIS C8708:2019の試験手順ですでに結果を出した電池

ダイソーReVOLTES
V517_20200512173401

富士通の黒(HR-3UTHC)
V0259a

エネループ・プロ(BK-3HCD)
Tv0170

| | コメント (0)

操作スイッチ接触不良 →大阪魂の出番に

ずいぶん昔に使ったとある装置のテストジグ。(30年以上前だわ!)
ひさしぶりに引っ張り出してきて使おうとしたら、操作スイッチが接触不良に。

ミヤマ電器 DS-193 という小型のプッシュスイッチ。
ずっと押してれば、ちゃんと接点は接触はしてくれます。
しかし、「ちょん・ちょん」と寸動するとダメ。
ツン・ツン」と動くはずが「バ・バ・バ・バ・バ」っと振動して
しまうのです。
回路的にはハードウェアーで10mS程度のチャタリング除去はしてい
ますんで、たいていは大丈夫なはずなんですが・・・

とりあえずオシロで接点の状態を見たら、チャタリングなんてもんじゃ
ありませんでした。
「チョン」押しで100mSほどオンしたら、その間、明確なオン・オフ
の断続が2~3回。
接点のチャタリングじゃなく、接触不良状態というか、素早く手で
オン・オフしたような信号、正規の信号のようになってます。
これじゃチャタリング除去回路では吸収できません。
同じような不良のスイッチが2つ。

とりあえず手持ちの新品押しボタンスイッチに交換。
小型のスイッチだったんで、元の穴が大きすぎたんでワッシャ
をかまして無理やり取り付け。

取り外したDS-193↓
11_20200915114001

ジグを使い終わってから、アウトになったスイッチをバラしてみました。
接点部が取り付け金具部にねじ込まれています。
これを緩めると、接点金具と小さなバネが出てきます。

12_20200915114201

接点部の奥に、ハンダ付けする接触が見えてます。
   (リングライトを出すのが面倒だったんで
    懐中電灯で照明)
13_20200915114201

接点も接点金具もキレいです。
これで接触不良?というのがちょい不思議。
  ※過去、ひどい接触不良を見てますんで

さてここからが「ものは試し」。 使うのは「大阪魂 」。
接点と接触片の両方をプシューしてから組み直します。
すると・・・
あらま、ちゃんと回復しちゃいました。
単純な構造の接点ですんでチャタリングは発生します。
しかし、「バ・バ・バ・バ」というむちゃな接触不良は無くなりました。
導通チェック しても大丈夫。
  ※抵抗値の変動があれば音ですぐに分かるんで。

まぁ、一度トラブったスイッチです。
とりあえず非常用の予備品に。

しかしまぁ「大阪魂」。こいつはなかなかやってくれます。

※関連
2018年8月27日:モノタロウのパーツクリーナ
2018年8月24日:はじめてのArduino MICRO
2018年2月22日:ローランド ミキシングアンプ「VX-55」修理!
2018年10月22日:タクトスイッチ接触不良
2011年01月27日:電源スイッチ接触不良 
2013年06月12日:タクトスイッチ接触不良
2013年04月26日:照光式タクトスイッチ…接点が死んでました

| | コメント (0)

2020年9月14日 (月)

コテペンの柄、割れてきた・・・

大事に使ってきたコテペン40(もう作っていないんでパーツも含めて入手不可)。
今朝のこと、ハンダ付け作業をしてましたら・・・
コテを手に持った時に突然の違和感。
「くにゃっ」とした感触。
コテの柄を見たら、ひび割れがぁぁ。

もう2本同じものをストックしてあるんですが、お気に入りの
ツールです。
ひび割れに瞬着を流し込んで(百均のじゃなくちょいとエエやつで)
から、「アクリルレジン」で固めちゃいました。

A11_20200914175901

以前にも、
2013年03月05日:コテペン落下→折損→応急修理
てなことがありました。
  ↑これ、まだ生きてます。
  おもちゃ病院で使ってます。
今回は落としはしていません。
使っていたら、ある時「んんん? なに?」でした。
ほんと、手になじむハンダゴテを探しとかなあかんです。

2014年12月06日:半田ごて落下→ヒータ折損

| | コメント (0)

「チャタリング除去回路」じゃなくって「チャタリング発生回路」をどうぞ

2020年9月12日:ロータリーエンコーダーの2相パルスをピン変化割り込みで取り込む
に絡んで、接点式ロータリーエンコーダーの「チャタリング」を
ソフトでどうにかできないかと試しています。

ただ・・・
件の接点、たいへん状態が良くって、オシロで見てても頻繁に
は発生しません。
たまに出る」という状態です。
しかし、チャタリング除去回路無しでは、「出ればカウントミス
が発生します。
やはり、これではあきません。
外付けチャタリング除去回路(抵抗+コンデンサ)無しでなんとかな
らないかと、試行錯誤中です。

その試行錯誤するためには、信号源として
チャタリングが出る接点」が必要です。
「リレーをカチャカチャ」なんてこともありなんですが、
エンコーダーなんで2相パルスが欲しい所です。

そんな時のためのツールが『チャタリング発生回路』。
1chだけのを作ってあったんで2chに拡張しました。
これで、2相パルスの両方に模擬的な「チャタリング信号」
を乗せることができるようになりました。

こんな回路です。

C11_20200914081201
まず、X-ORゲートを使ったエッジ検出回路で↑↓エッジからちょいと
遅れたゲート信号を作ります。
別の発振回路で作ったチャタリング信号を、そのゲートの時間だけ
X-ORゲートで元の2相パルスと合成します。

こんなタイミング。
C12_20200914081201
X-ORゲート、2つの入力信号のH/Lが一致で出力L。
2つの入力のH/Lが異なったら出力H。
ということは、
片方がLならもう片方の信号をそのまま出力。
片方がHならもう片方を反転して出力すると
いう性質があります。
これでエッジ検出やチャタリング信号の合成を
行っています。

オシロで見たところ。

C022  

手組みした基板の様子。
C24

チャタリング時間とチャタリング信号の周波数を半固定ボリュームで
可変できるようにしてるんで、あれこれ試せます。


※2相パルス発生回路はあれこれ製作しています。
2005年09月09日:トラ技2005年10月号
2015年12月08日:んっ? トグルスイッチが
2014年07月01日:高速2相パルス発生回路



| | コメント (0)

«ロータリーエンコーダーの2相パルスをピン変化割り込みで取り込む