バイナリクロックの設計:多重化の制約を理解をする

APDahlen Applications Engineer

バイナリクロックは、デジタルエレクトロニクス、マイクロコントローラのプログラミング、実物を組み立てる能力、ある程度複雑な回路に関する知識をアピールできる魅力的なプロジェクトです。このトピックについては多くの記事が書かれています。しかし、設計上の決定事項や必要な妥協点について詳しく調べている人はほとんどいません。これは残念な事です。なぜならそのトピックは、マイクロコントローラの物理的制約、オームの法則の応用、多数のLEDを多重化することによる配線の削減、そしてトラブルシューティングが簡単でクリーンなシステムの部品レイアウトを探求する優れた学習機会を提供しているからです。

この記事では、図1に示すバイナリクロック形式の6列 x 4行のLEDマトリクスに焦点を当てます。利用し易さを考慮し、Arduino Nano EveryにあるI/Oピンと電源の物理的な機能に話を限定します。この記事で紹介したアイデアは、7セグメントLEDディスプレイや、より一般的にはすべての多重化LEDアレイに直接応用できることに注意してください。

これは低レベルのハードウェアと多重化の側面に焦点を当てるので、バイナリ クロックの動作を見直す良い機会となります。出発点として、時計の読み方を説明したこのDigiKey Arduinoの記事からお読みください。また、多重化操作をより深く理解するのに役立つ、対照的な設計とArduinoコードも示しています。

図1: 写真はブレッドボードに構築したArduino Nano EveryとLEDアレイ、後方はDigilentのAnalog Discovery(パソコンに接続し、オシロスコープなどの機能を有する計測アダプタ)

多重化の定義

多重化は、限られたリソースをタイムシェアリングする方式です。この例では、マイクロコントローラのI/Oピンの数は限られています。時分割多重(TDM)では、ラウンドロビンシーケンス(時間の均等配分による循環的な処理)を使用して、1つのLEDまたはLEDのグループを連続的にアクティブにします。LEDのアクティブ化(すなわち発光)のシーケンスを迅速に繰り返すことにより、すべてのLEDが均一に点灯しているように見えます。この品質は、人間の目では多重化アレイにおけるLEDの素早い「点滅」を検出できないフリッカフュージョン(ちらつきを感じさせない点灯の閾値)に依存します。

LEDは通常、共通アノードまたは共通カソード構成によりグループにして配置されます。図2に、共通アノード構成を備えた6列 x 4行のLEDマトリクスの例を示します。

技術的なヒント: 時分割多重(TDM)という用語は通常、Ethernetなどの通信システムに関連します。Ethernetのネットワークでは、多くのコンピュータが通信チャンネルを共有します。チャンネルのリソースは限られているため、各コンピュータは確立された通信プロトコル内で順番に動作します。TDMという用語をLEDの多重化に適用するのは単純化しすぎですが、複雑な通信システムを理解するための良い出発点となります。

図2: Arduino Nano Everyを使用した6列 x 4行の多重化LEDマトリクスの回路図

共通アノードおよび共通カソードという用語は混乱を招く可能性があります。たとえば、図2のLEDマトリクスを調べると、多くのダイオードが列ごとにアノード接続を共有し、行ごとにカソード接続を共有していることがわかります。アクティブ化シーケンスを調べて初めて、共通アノード構成が明らかになります。

図3は、マルチプレクサのタイミングシーケンスをロジックアナライザで表示したものです。共通ダイオード接続に関しては、常に1つ、そしてただ1つの列だけがアクティブになることに注意してください。したがって、共通接続はアノードです。この例では、関連する列トランジスタが、関連するすべてのLEDのアノードを5.0V DCレールにプルアップします。次に、それぞれの行ドライバがグランドに接続して、目的のLEDをアクティブにします。

図3について、次のことが分かります。

  • 列の駆動信号は、ArduinoのD2~D7ピンに対応する最初の6つの信号として表示されます。便宜上、信号名は赤色でハイライト表示されたテキストとして追加されています。常に1つの信号がアクティブであることがわかります。列ドライバとしてPNPトランジスタが使用されることに注意してください。また、このトランジスタはベースをグランド側にプルダウンするとアクティブになることに注意してください。

  • 最下位の4行はArduinoの出力ピンD19~D21に対応する行ドライバです。図2に示されているように、これらもアクティブローであり、ダイオード(LED)をグランド側にプルダウンすることでダイオードがオンになります。

  • Digilent Analog Discoveryは、4行ドライバに関連付けられた2進数を表示するように構成されています。これは、オペレータが複数の行を1つの便利なASCII、16進数、またはバイナリ表示に「結合」できるようにする一部のロジック アナライザの機能です。便宜上、これは青色でハイライト表示されています。

技術的なヒント: LEDの多重化を構成するには、共通アノードと共通カソードの2つの方法があります。この記事では、共通アノードLEDを備えたPNPの列ドライバについて取り上げます。ここでは、PNPトランジスタが共通アノードを正の電源レールに接続し、個々のカソードは抵抗を通してグランドに接続されます。前述のDigiKey Binary Clockの記事では、共通カソードのLEDを備えたNPNの列ドライバを備えたマトリクスを紹介しています。そこでは、NPNトランジスタが共通のカソードをグランドに接続し、正電圧が目的の行に印加されます。共通ドライバを列ではなく行に適用して回路を90度回転する場合は、慎重な確認が必要です。.

図3: テスト信号とハイライトを追加し明確にしたLEDタイミングを示すロジックアナライザの画面キャプチャ

技術的なヒント: 多重化と共通アノード/共通カソードに関するこの議論は、7セグメントLEDディスプレイやLEDアレイに適用できます。

電流の制限

この記事は、Arduino Nano Everyの機能によって制約されます。これには、利用可能な電源と、Nano Everyのオンボード電源、個々のピン、およびピンのグループが含まれます。

ATMega4809マイクロコントローラ仕様に関連するArduino Nano Everyの電流は次のとおりです。

ABX00028-datasheet.pdf (arduino.cc)

  • 5.0V DC電源の電流限度 = 950 mA

  • 個々のピン(吐き出しまたは吸い込み)電流 = 40 mA

  • 電源レールピン(高温時における吐き出し、または、吸い込み)電流 = 100 mA

選択したLEDのSSL-LX21573GDでは、次のとおりです。

  • 室温での定常電流 = 25mA

  • 10usパルスの最大電流 = 150mA

これらの数値のほとんどは設計上の絶対最大定格です。長寿命を実現するには、広い安全マージンを確保する必要があります。出発点として、それぞれの現在の値を半分に減らしてみましょう。これから説明するように、この決定は多重化オプションに重大な影響を及ぼします。これは図2の回路図に影を落とし、トランジスタの列ドライバと多重化LEDの数に影響を与えます。

技術的なヒント: マイクロコントローラの内部チップは、小さなボンドワイヤを使用してI/Oピンに接続されます。これらのワイヤとシリコントレースの電流伝送能力は限られています。これは、デバイスの設計最大仕様に反映されています。前の例では、個々のI/Oピンの設計最大値が40mAであることがわかります。ただし、このような3つのピンが最大電流で動作すると、チップから電源、チップからグランドへのボンディングワイヤやシリコントレースが圧倒されてしまいます。この累積電流は重要ですが、設計上の考慮事項として見落とされがちです。これはマイクロコントローラを破壊する誤りであり、おそらくすぐには破壊されませんが、信頼性の低い製品を生み出すことになります。

デューティサイクルと輝度

トランジスタの列ドライバと抵抗の選択について詳しく説明する前に、まずフリッカとLEDの輝度の関係を理解する必要があります。LEDマトリクスが時分割多重(TDM)であることを思い出してください。図3のタイミング図に示すように、各LEDは順番にオンまたはオフになり、「ちらつき」ます。LEDのちらつきに関する関連実験をすでに実行している人も多いでしょう。

Arduinoの学習の初期段階では、次のようなコマンドを使用して、PWMでLEDを調光したことがあるでしょう。

void loop() {
    static uint8_t val;             // Maintain contents across loop iterations
    analogWrite(LED_PIN, val++);    // About 5 seconds to reach 100% duty cycle
    delay(20);
}

このPWM動作は、1個のLEDへの影響を考慮すると、TDM動作と直接関連します。どちらの場合も、LEDは一定の点灯時間と消灯時間で点滅します。どちらの場合も、LEDの明るさを決めるのはこのデューティサイクルです。

その結果を図4に示します。LEDのデューティサイクルは100、50、25、12.5、6.25%で、時分割がそれぞれ1、2、4、8、16のTDMスロットによる多重化に対応します。低デューティサイクルのLEDでは結果は良くありません。実際、デューティサイクルが1/16のLEDの発光を見分けるのは非常に困難です。

図4: 様々なデューティサイクルで駆動したときのLEDの相対的な輝度を示す写真

多重化の方式

多重化の目的は、ハードウェアの電流制限内に収めながら、LEDの最大輝度を得ることです。ハードウェアの物理的な限界について理解が深まったところで、多重化の方法を探ってみましょう。

  • 一度に1つのLED:列ドライバを排除すると、マイクロコントローラが列と行の両方を直接制御できるようになるでしょう。これは回路のコストと簡素化の観点から非常に望ましいことですが、すぐに電流の制限に遭遇します。列ドライバがないと、関連するマイクロコントローラピンが必要な電流を供給しなければなりません。これは、累積電流がマイクロコンローラの設計最大値を超えるため、一度に使用できるLEDは1つまたはおそらく2つまでに制限されます。 6 x 4アレイの場合、これでは4%またはおそらく8%のデューティサイクルになります。 LEDは非常に暗くなります。

  • マイクロコントローラによって制御される列ドライバと行ドライバを使用して、一度に1列ずつ:PNPトランジスタが列に電流を供給することで、すべての行を自由にオンにすることができます。6 X 4アレイの場合、各LEDのデューティサイクルは17%になります。この記事で示した結果としては、薄暗い部屋では最低限許容できるものです。

  • 列と行の駆動トランジスタを使用して一度に1列ずつ:このアプローチにより、マイクロコントローラの容量を超える電流でLEDを駆動できるようになります。LEDはデューティサイクルが低いため、電流を増やすことで輝度を高めることができます。LEDのデータシートには一定のあいまいさがありますが、LEDを損傷することなく電流を設計最大値まで、おそらく2倍まで増加させることは可能です。私たちが選択したLEDは、連続25mA、10usのパルスで150mAと指定されていることを思い出してください。このアプローチではLED温度を慎重に考慮する必要があり、LEDの寿命が大幅に短くなる可能性があります。自己責任で使用してください。

  • 並列制御:この投稿全体では、マイクロコントローラのピンが制限リソースであると想定しています。LEDの直接制御を可能にするポート拡張オプションは数多くあります。例として、8 ビットシフトレジスタのTLC6C598があります。最大 V_{DS} が40V DCの50mAオープン ドレイン ドライバを備えています。74HC595は、もう1つの一般的なオプションで、ブレッドボードのプロトタイプ作成に適しています。

抵抗の選択

LEDの動作電流を設定するには、適切な抵抗の選択が必要です。列駆動トランジスタのベースに適切な抵抗を選択することも重要です。アクティブ化されたLEDの数は表示される数に応じて変化するため、このベース抵抗はLEDの明るさを一定に保つために重要です。

LEDの計算は比較的簡単です。最初のステップは、電流制限を決定することです。選択した多重化スキームを使用すると、常に最大4個のLEDがアクティブになります。最初に遭遇する制限は、マイクロコントローラの累積電流です。私たちの保守的な設計では、これは50mAです。したがって、各LEDは約13mAに制限されます。抵抗は次のように計算されます。

R_{LED} = \dfrac{V_{rail} – V_{LED}}{I} = \dfrac {5.0-2.0}{0.013}= 220\, \Omega

ここで、PNP列ドライバの V_{CE} はゼロに非常に近いと仮定しています。

トランジスタのベース抵抗の計算では、強制ベータ条件を用います。このトランジスタの動作点により、トランジスタが確実に深い飽和状態になり、すべてのLEDが同じ明るさになることが保証されます。この条件を設定するには、ベース電流がコレクタ電流の1/10になるように抵抗を選択します。アクティブなLEDが4個ある場合、コレクタ電流は約50mAになります。強制ベータ動作では、ベース電流を強制的に5mAにします。

R_{Base} = \dfrac{V_{rail} – V_{diode}}{I} = \dfrac {5.0-0.7}{0.005} \approx 820\, \Omega

改良の余地

この記事では、LEDの多重化の側面に焦点を当てています。デバイスを信頼できるタイムキーパーとして動作させることについては、ほとんど考慮されていません。このメモに追加されたコードは、信頼できるリアルタイムキーパーとは認識されていないArduino millis( )関数を使用しています。このようなデバイスはすべて、適切に実装された32.768kHzの時計用水晶ほど正確ではないマイクロコントローラの高速発振器に依存しています。

性能向上のために、リアルタイムクロックモジュールを統合することをお勧めします。さらに挑戦するには、GPSまたはWWVB(電波時計)ベースのアンテナ付きタイムキーパーを試してください。ご興味があれば今後の投稿で、これについて検討することも可能です。

ご意見やご質問がございましたら、下の欄にご記入ください。

ご健闘をお祈りします。

APDahlen

#define C0_PIN 2
#define C1_PIN 3
#define C2_PIN 4
#define C3_PIN 5
#define C4_PIN 6
#define C5_PIN 7

#define R0_PIN 21
#define R1_PIN 20
#define R2_PIN 19
#define R3_PIN 18

void set_column(uint8_t c) {

  digitalWrite(C0_PIN, HIGH);
  digitalWrite(C1_PIN, HIGH);
  digitalWrite(C2_PIN, HIGH);
  digitalWrite(C3_PIN, HIGH);
  digitalWrite(C4_PIN, HIGH);
  digitalWrite(C5_PIN, HIGH);

  switch (c) {
    case 0: digitalWrite(C0_PIN, LOW); break;
    case 1: digitalWrite(C1_PIN, LOW); break;
    case 2: digitalWrite(C2_PIN, LOW); break;
    case 3: digitalWrite(C3_PIN, LOW); break;
    case 4: digitalWrite(C4_PIN, LOW); break;
    case 5: digitalWrite(C5_PIN, LOW); break;
    default: break;
  }

}


void set_row(uint8_t n) {

  bool D0 = ((n & 0x01) == 0);
  bool D1 = ((n & 0x02) == 0);
  bool D2 = ((n & 0x04) == 0);
  bool D3 = ((n & 0x08) == 0);

  digitalWrite(R0_PIN, D0);
  digitalWrite(R1_PIN, D1);
  digitalWrite(R2_PIN, D2);
  digitalWrite(R3_PIN, D3);

}


void set_LED(uint8_t c, uint8_t n) {

  set_column(c);
  set_row(n);

}


void setup() {
  pinMode(C5_PIN, OUTPUT);
  pinMode(C4_PIN, OUTPUT);
  pinMode(C3_PIN, OUTPUT);
  pinMode(C2_PIN, OUTPUT);
  pinMode(C1_PIN, OUTPUT);
  pinMode(C0_PIN, OUTPUT);

  pinMode(R3_PIN, OUTPUT);
  pinMode(R2_PIN, OUTPUT);
  pinMode(R1_PIN, OUTPUT);
  pinMode(R0_PIN, OUTPUT);
}


void loop() {

  uint8_t i;

  uint32_t now = millis() /1000;

  uint8_t seconds = now % 60;
  uint8_t minutes = (now / 60) % 60;
  uint8_t hours = (now / 3600) % 24;

  uint8_t hoursHigh = hours / 10;
  uint8_t hoursLow = hours % 10;

  uint8_t minutesHigh = minutes / 10;
  uint8_t minutesLow = minutes % 10;

  uint8_t secondsHigh = seconds / 10;
  uint8_t secondsLow = seconds % 10;

  for (i = 0; i < 6; i++) {

    switch (i) {

      case 0: set_LED(0, secondsLow); break;
      case 1: set_LED(1, secondsHigh); break;
      case 2: set_LED(2, minutesLow); break;
      case 3: set_LED(3, minutesHigh);break;
      case 4: set_LED(4, hoursLow); break;
      case 5: set_LED(5, hoursHigh); break;
      default: break;
    }
    delay(2);
  }
}




オリジナル・ソース(English)