koheiのおもちゃ修理記録

宇部おもちゃ病院 毎月第2土曜日 13:00~16:00
宇部新天町の西の端、市民活動センターで開院してます。

7/28 チャーリープレクシングについての考察(続編1)

2019-07-28 | PIC・電子工作
さて先日、福岡から山口に、深夜車で帰りながら、「チャーリープレクシングについての記事を書こう~」と、つらつらと考えていたら、ふと「スイッチ入力にも使えるんじゃないかい?」と思い当たった!

PICは使えるポート数が少なく、ポート数を増やすと値段が高くなってしまう。
PICでポートを節約して入力点数を増やすには「抵抗分圧」という常套手段がある。
しかし、全てのデバイスにアナログ入力がある訳ではない。それに、優秀な定電圧電源を使うならいいけど、「おもちゃ」として応用するには、電池電圧の低下や、モーターが起動する時の電源電圧の変動等も考慮して、あまり無理せず1ポート当たり3点ぐらいに抑えるとか、電源電圧の参照を取るとか、いろいろと不便もありそう。

チャーリープレクシングでのLED点灯の理屈を応用できれば、3ポートで6入力、4ポートで12入力、5ポートで20入力…が実現できる。12個もスイッチがあるおもちゃのマイコン換装をするかどうかは分からないけど、考えておいたらいつか役に立つかも…。

チャーリープレクシングの「ミソ」は、LEDは逆電圧を掛けても点灯しないことを利用して、正負を逆にして個数を2倍にしてる所かも。極性のない電球を使ったのでは、実現できない。
これをスイッチに応用するには、やはり、スイッチに極性を持たせないといけない。つまり、余分にダイオードを入れてやれば、実現するのでは?

具体的に3ポートで書くと、こんな感じ。

プルアップは、マイコン側の内部プルアップを使用する。
使わないポートは入力設定でHi-Z(プルアップ)にしておいて、スキャンの条件は、
SW1検出 : b=0,a=IN → a=0
SW2検出 : a=0,b=IN → b=0
SW3検出 : c=0,b=IN → b=0
SW4検出 : b=0,c=IN → c=0
SW5検出 : c=0,a=IN → a=0
SW6検出 : a=0,c=IN → c=0
これで行けそうな気がする。但し「2個同時押し」といった時にはどうなるか?回路を眺めてても、組み合わせが多くてよく分からないので、実際に組んで試してみよう~!
8PINマイコンで、入力にもLED表示にもチャーリープレクシングを使って、6入力-6LED表示をやってみる!

最初、PICでやってみようと思ったけど、プログラムを作り始める前に、「PICはMCLRと兼用のポートは入力のみ」に制限されてることを思い出した。チャーリープレクシングの為にはTRIS(Tri-State)が全部必要。
14PINのPICに変更してしまったら「8PINマイコンで6入力-6LED表示!」の「限界にチャレンジ!」にならなくなるので、ここは、6ポートの全部を入出力に使えるATtiny13aを使う事にする。

ブレッドボードでの作業状況。

ブレッドボードでの試作は…、あんまり好きじゃないなぁ。考えようによっては学研の「電子ブロック」みたいかもしれないけど、縦が全部繋がってて制約多いし、接続今にも間違えそうだし…。
(実際、接続間違えてトランジスタが「バチッ」と焼けたのが1回、ICが焼けたのが1回あるww。でも、こんな試作にユニバーサル基板を1枚消費したくないww。)

プログラム組んで走らせてみた。案の定一発でうまくいかず、またもや点灯回路を確認するためだけのプログラムに書き換えてみたけど、点灯回路はOK。
ポートを設定した後の電圧安定待ち時間の為に「asm(NOP);」を1個入れてたのを、3個に増やしてもまだダメ。for文で10回ループさせたら、ちゃんと動くようになった。

最終のプログラムは、電圧安定待ち時間には、「delay.h」の「_delay_us」を使って2μs待たせた。LED表示は、スイッチ複数押し時の状態確認のため、ダイナミック表示にしてある。

#define F_CPU 9600000 // 9.6MHz

#include <avr/io.h>
#include <util/delay.h>

#define LED_a PINB0
#define LED_b PINB1
#define LED_c PINB2
#define SW_a PINB3
#define SW_b PINB4
#define SW_c PINB5

void disp_LED(unsigned char disp_data)
{
   DDRB = 0b00000000; // PB0~5を全て入力
   // LED_1
   if ((disp_data & (1 << 0)) != 0 )
   {
      PORTB |= (1 << LED_a); // LED_aをHに
      PORTB &= ~(1 << LED_b); // LED_bをLに
      DDRB |= (1 << LED_a); // LED_aを出力に
      DDRB |= (1 << LED_b); // LED_bを出力に
   }
   _delay_ms(2);
   DDRB = 0b00000000; // PB0~5を全て入力

   // LED_2
   if ((disp_data & (1 << 1)) != 0 ) // LED_bをHに
   {
      PORTB |= (1 << LED_b); // LED_bをHに
      PORTB &= ~(1 << LED_a); // LED_aをLに
      DDRB |= (1 << LED_b); // LED_bを出力に
      DDRB |= (1 << LED_a); // LED_aを出力に
   }
   _delay_ms(2);
   DDRB = 0b00000000; // PB0~5を全て入力

   // LED_3
   if ((disp_data & (1 << 2)) != 0 )
   {
      PORTB |= (1 << LED_b); // LED_bをHに
      PORTB &= ~(1 << LED_c); // LED_aをLに
      DDRB |= (1 << LED_b); // LED_bを出力に
      DDRB |= (1 << LED_c); // LED_cを出力に
   }
   _delay_ms(2);
   DDRB = 0b00000000; // PB0~5を全て入力

   // LED_4
   if ((disp_data & (1 << 3)) != 0 )
   {
      PORTB |= (1 << LED_c); // LED_cをHに
      PORTB &= ~(1 << LED_b); // LED_bをLに
      DDRB |= (1 << LED_c); // LED_cを出力に
      DDRB |= (1 << LED_b); // LED_bを出力に
   }
   _delay_ms(2);
   DDRB = 0b00000000; // PB0~5を全て入力

   // LED_5
   if ((disp_data & (1 << 4)) != 0 )
   {
      PORTB |= (1 << LED_a); // LED_aをHに
      PORTB &= ~(1 << LED_c); // LED_cをLに
      DDRB |= (1 << LED_a); // LED_aを出力に
      DDRB |= (1 << LED_c); // LED_cを出力に
   }
   _delay_ms(2);
   DDRB = 0b00000000; // PB0~5を全て入力

   // LED_6
   if ((disp_data & (1 << 5)) != 0 )
   {
      PORTB |= (1 << LED_c); // LED_cをHに
      PORTB &= ~(1 << LED_a); // LED_aをLに
      DDRB |= (1 << LED_c); // LED_cを出力に
      DDRB |= (1 << LED_a); // LED_aを出力に
   }
   _delay_ms(2);
   DDRB = 0b00000000; // PB0~5を全て入力
}

int main(void)
{
   unsigned char LED_disp; // LED1~6の点灯をセットするための変数:対応ビットの1で点灯

   while (1)
   {

// キー入力スキャン部
      DDRB = 0b00000000; // PB0~5を全て入力
      PORTB = 0b00111000; // PB3~5を内部プルアップ、他はLow出力
      LED_disp = 0x00; // LED_disp初期値としてゼロを代入

      // SW_1
      PORTB = 0b00111000 & ~(1 << SW_b); // SW_bだけLowに
      DDRB = (1 << SW_b); // SW_bだけ出力に
      _delay_us(2);
      if ((PINB & (1 << SW_a)) == 0 ) LED_disp |= 0b00000001;

      // SW_2
      PORTB = 0b00111000 & ~(1 << SW_a); // SW_aだけLowに
      DDRB = (1 << SW_a); // SW_aだけ出力に
      _delay_us(2);
      if ((PINB & (1 << SW_b)) == 0 ) LED_disp |= 0b00000010;

      // SW_3
      PORTB = 0b00111000 & ~(1 << SW_c); // SW_cだけLowに
      DDRB = (1 << SW_c); // SW_cだけ出力に
      _delay_us(2);
      if ((PINB & (1 << SW_b)) == 0 ) LED_disp |= 0b00000100;

      // SW_4
      PORTB = 0b00111000 & ~(1 << SW_b); // SW_bだけLowに
      DDRB = (1 << SW_b); // SW_bだけ出力に
      _delay_us(2);
      if ((PINB & (1 << SW_c)) == 0 ) LED_disp |= 0b00001000;

      // SW_5
      PORTB = 0b00111000 & ~(1 << SW_c); // SW_cだけLowに
      DDRB = (1 << SW_c); // SW_cだけ出力に
      _delay_us(2);
      if ((PINB & (1 << SW_a)) == 0 ) LED_disp |= 0b00010000;

      // SW_6
      PORT = 0b00111000 & ~(1 << SW_a); // SW_aだけLowに
      DDRB = (1 << SW_a); // SW_aだけ出力に
      _delay_us(2);
      if ((PINB & (1 << SW_c)) == 0 ) LED_disp |= 0b00100000;

// LED表示
   disp_LED(LED_disp);
   }
}

(ココのgooブログで、プログラム内の「<」と「>」の入力・表示がうまくいかない…。とりあえず1回は入力・保存できるようになったけど、もう一度編集・保存すると、中身が変わってしまう。問い合わせをしたけど、完全解決しない…。なんとかして~!)
AVRのポート操作は、なんでこんなめんどうなんだろう。調べ直して思い出しながらなんとか出来上がったけど、出来てから見ても暗号みたいww。

これで、1~6まで単独で押して、対応するLEDがちゃんと点灯するのが確認できた。
同時押しは…、期待通りに点灯する組み合わせもあるが、余分なLEDが点いてしまう組み合わせもある…。
1と2、3と4、5と6はOK。1と5はOKだけど、1と6を押すと、4も点いてしまう…など。

実機で組んで、誤判断する組み合わせが分かれば、誤判断の理由も分かる。
LED点灯回路の時に、「LEDを2個通る別の経路があるけど、Vfの関係でそっちは電流が流れても点灯しない」のと同じ状態。
例えば、4を検出するために、「b=0,c=IN」で「C=0」を確認するのだが、1と6をONにしてると、4を直接通る経路ではなく、6→1を経由して2回ダイオードを通る経路が出来てしまって、あたかも4がONしてるかのように、CをLと判断してしまうんですね…。
ダイオードのVf×2でCをLと判断しないようにできないか、電源電圧をLEDが光るギリギリ:2.2Vぐらいまで下げてみても、誤判断は解消できませんでした。
使った信号用のダイオード、電流が低いとVfがかなり低い様です。(LEDも似たような性質とは思いますが。)

結果とすれば、同時押しを考慮しなくていいなら、チャーリープレクシングの理屈で、ポート数:nに対して、n×(n-1)の入力の検出ができそうです。
SW1から順にスキャンしていって、1個ONが検出されたらbreakして残りはスキャンしないようにすれば、同時押しによる誤作動は回避できると思います。(後日:いや、それじゃあ甘いなぁ。もちょっと良く考えなければ…)
アナログ入力による「抵抗分圧」でも、普通は「同時押し」は検知できないので、これでもいいと思いますが、例えば、ペアの1と2、3と4、5と6は、同時押ししても誤判断しないので、同時押しを考慮したい組み合わせに制限を掛ける手もあるかもしれません。

さてこれで、チャーリープレクシングの理屈を応用して、入力点数に対してポート数を減らせる見込みが立ちました。これがいつか役に立つ日が来るか、誰か「役に立った~!」という人が現れるか?
もし、「役に立った~!」という人が居れば、是非コメント下さいww。

(後日:wikiの英語版には、inputへの応用の記事がちゃんとありました。回路は載ってなかったけど、PICを入力&プルアップにして検知する方法も載ってました。まあ何事も、自分でやってみる事に意義があるという事でw。)

(後日2:wiki英語版の「Input data multiplexing」の個所には「スイッチ複数同時押しを考慮しなくていいなら、ダイオード4個で組める。ダイオード12個なら、スイッチとデータは必ず1対1で対応する」と書いてある。
「ダイオード4個で組める」の回路は、多分下記になると思う(検証はしてない)。

しかし「ダイオード12個なら、スイッチとデータは必ず1対1で対応する」は、テストでは実現できなかった…。入力ポートのプルアップには、マイコン内部の「Weak pull-up」を使ったが、もっと強いプルアップで、Lのポートに数mAぐらい流してダイオードのVfを上げるか、元々Vfの高いダイオードを使ったりすれば、実現できるかなぁ…)
コメント    この記事についてブログを書く
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする
« 7/28 断線チェッカー続... | トップ | 8/3 チャーリープレクシ... »

コメントを投稿