実験:AVR SPIインターフェース(ドットマトリックスLEDの制御)

マイコンで多数の入力・出力を必要とする回路を作っているとき、ポートの本数が足りなくなってしまう、ということがよくあります。 汎用ポート数の多いデバイスに変更する、というのが簡単で確実な解決策ですが、状況によっては、シリアル→パラレル変換を利用してポート数を増やすこともできます。

今回の実験では、AVRマイコンのSPIインターフェースと汎用ロジックICを使用して出力ポートの本数を増加させ、ドットマトリックスLEDを点灯する実験を行ってみたいと思います。


SPIについて

小型マイコンでは汎用ポートの数が非常に限られている場合が多く、シリアル・バスを使って周辺回路とデータのやりとりをする事例が数多くあります。 SPIインターフェースは、シリアル・バスの代表的なものの一つで、3本の信号線+セレクト信号を使ってデータの送受信を行います。 通信方式は、クロックを利用する同期通信ですが、その構造は比較的単純であり、簡単な回路で構成することが出来ます。 今回の実験では、SPIの動作原理を理解するため、ATmega88をSPIマスターとして使い、SPIスレーブには汎用ロジックICで構成した回路を使います。

使用するパーツ

  1. 74HC595
  2. 74HC595(HC595)は8ビットのシリアル→パラレル変換シフト・レジスタです。 互換品は各メーカーから出ていますが、今回データシートはこちらを参照しました。 →Texas Instruments SN74HC595 (ti.com)


  3. ドットマトリックスLED
  4. SPIによるシリアル→パラレル変換の動作を観察するためにドットマトリックスLEDを使います。 ここでは、秋月電子で購入した8x8・赤緑2色のものです。 スタンレー製"BU5004-RG"とのですが、いまのところ付属の紙データしか資料がありません。 カソード・コモン接続で、LEDのVfは、赤が1.7V(typ)、緑が2.2V(typ)となっています。


    以下は回路図を作成するために、Eagleの部品ライブラリに追加作成したものです。


    (8x8 赤緑LED Eagle用ライブラリ)

    (libraryファイルはこちらからダウンロードできます→ "8x8LED.lbr" Eagle 5.10.0 Linuxで作成)

さて、このLEDを用いてグラフィック表示をするためには、単純に考えると、赤LEDのアノード:8ビット、緑LEDのアノード:8ビット、コモン・カソード:8ビットの24ビットの信号線を使ってダイナミック点灯する必要があり、少数のポートしかもたないマイコンで駆動するのは骨が折れます。 今回の実験では、この24ビット全てをSPIで制御するため、HC595を3個直列接続して24ビットのシフト・レジスタを構成しています。

SPI部分の設計

SPIの動作原理

下の図は、SPIインターフェースの動作原理図です。



SPIインターフェース 動作原理図

マスターとスレーブには、それぞれシフト・レジスタがあり、MOSI信号(マスター→スレーブ)とMISO信号(スレーブ→マスター)を経由して環状に接続されています。

このシフト・レジスタにデータをセットして、SCK信号にシフト・クロックを供給すると、マスターとスレーブの間でデータの送受信が同時に行われます。

スレーブ側の構造は非常にシンプルで、SCK信号とSS信号を適切に処理してラッチ回路などによってデータを取り出すことによって、「シリアル入力・パラレル出力」の出力ポートや、「パラレル入力・シリアル出力」の入力ポートを作ることができます。


HC595を接続する

74HC595の内部は、8ビットのシフト・レジスタとラッチにより構成されており、「シリアル入力・パラレル出力」の変換を行うのに適しています(下記ブロック図参照)。 シフト・レジスタ部のシフト・クロックと、ラッチ部のラッチ・クロックは独立した同期回路となっており、ともに立ち上がりエッジで動作します。 シフト・レジスタ部のリセット入力とラッチ部の出力イネーブルは非同期動作(クロックに関係なく動作)となっています(状態遷移表参照)。

74HC595 内部ブロック図

74HC595 状態遷移
入力 動作
シフト
入力
シフト・クロック クリア ラッチ・クロック 出力イネーブル
X X X X H Qa ... Qh端子をハイ・インピーダンスにする
X X X X L Qa ... Qh端子に、ラッチされているデータを出力
X X L X X シフト・レジスタをクリアする
L H X X シフト、最初のビットは0にクリア
H H X X シフト、最初のビットは1にセット
X X X X シフト・レジスタの内容をラッチ

タイミングを考察すると、接続や設定は以下のようになります。

まず、マスター出力(MOSI)について考えてみます。 HC595は、シフト・クロックの立ち上がりでシフト動作が行われるので、シフト入力に新しいビット値をセットしてから、シフト・クロックを立ち上げると1ビットのデータがシフト・レジスタ部に入力されます。 したがって、シフト入力にMOSIを、シフト・クロックにSCKを接続してクロックを供給する場合、1クロック内の動作を、

  • クロック立ち下がりを前方エッジとして使用:MOSI出力=HC595シフト入力のセットアップ
  • クロック立ち上がりを後方エッジとして使用:HC595がシフト入力を取り込み、シフト・レジスタをシフト
と設定すれば動作がうまくいきそうです。

次に、HC595のシフト・レジスタに8ビット分のデータが順次シフト・インされたところで、ラッチ・クロックを立ち上げるとシフト・レジスタの内容がラッチ部に入力されます。/SS出力を「High = 動作なし」、「Low = スレーブ選択状態」として制御すれば、そのままラッチ・クロックに接続して使用することができます。なお、この場合、Qhには最初に送信されたビット、Qaには最後に送信されたビットがセットされるので、「MSBから送信」の設定の場合、「Qh = MSB、Qa = LSB」となります。



HC595 タイミング・ダイアグラム

ATmega88のSPIとHC595を下記のように接続して、8ビットのSPI対応出力ポートの出来上がりです。



AVR SPIとHC595の接続 (8ビット)

AVR(ATmega)のSPIの設定は

  • クロック・モード:モード3(前方エッジ = 立ち下がり:ラッチ、後方エッジ = 立ち上がり:シフト)
  • SPIマスター動作、/SS端子は出力
  • データ方向:MSBから送信(用途によって、どちらでも可)
とします。なお、マスター動作で/SSを出力に設定する場合は、ユーザー・プログラムにより、通常のポート出力と同様に/SS端子を操作するので必ずしも/SS端子を使用する必要はありません。

出力ビット数を増やす場合は、前段のHC595のシフト出力(Qh')を後段のシフト入力(SER)に接続して、シフト・クロックとラッチ・クロック信号を共通接続にすると、16ビット、24ビットと延長することができます。 (下記の図、およびターゲットの回路図を参照してください)



AVR SPIとHC595の接続 (16ビット)

回路



SPI実験用ターゲットの回路図 (HC595の電源、パスコンなどは記載を省略しています)

メインはATmega88(V)。 内部RC発振の8MHz/8分周の工場出荷デフォルト動作です。 電源は電池2本の3.0Vです。 HC595で構成した24ビット・シフト・レジスタの出力を、ドットマトリックスLEDに電流制限抵抗(赤330Ω、緑180Ω)を介して接続しています。

注意: LED一個あたりの電流を赤=約4.0mA、緑=約4.5mAとして計算しているので、1ライン全点灯の場合、コモン線を接続しているHC595(回路図中のIC3)には、(4.0 + 4.5) x 8 x 2 = 68mA程度流れることになります。 74HC595の総電流定格は70mAですので、複数のラインが同時に点灯しないように注意する必要があります。

上で考察したとおり、MOSIをシフト入力に、SCKをシフト・クロック入力に接続しています。MISOの配線はなくても動作しますが、実験で使用するために最終ビットのシフト出力を接続しています。 HC595の出力イネーブルをポート出力PB1に接続して、HC595のシフト・レジスタのデータを消去することなくLEDの全消灯ができるようにしています。

なお、MOSI、MISO、SCK各端子は、ISPでフラッシュ・メモリをプログラムする際にも使用するため、AVRプログラマからの信号との競合を回避する手段が必要です。 ここでは、簡単に直列抵抗10kΩを接続して出力が短絡されるのを防止し、また、HC595のクリア入力端子を/RESETに接続することでISP実行中にターゲット回路が誤動作することを防止しています。


LED表示部(TOP)/ (BOTTOM)

ドットマトリックスLED周辺の配線はユニバーサル基板に組み立てて、AVRマイコンとブレッド・ボードを使って接続しています。写真は、表示部の外観です。


ソフトウェア

全体概要

プログラムの構造は下記の通りです。短いので、すべて1ファイルに押し込んであります。

  • タイマー割込み
    • カウント・アップによって点灯するラインを選択
    • 赤、緑の点灯ビット・パターンを取り出す
    • 点灯ラインのコモン線へ出力するビット・パターンを作る
    • SPIへ「赤」、「緑」、「コモン線」のデータを出力
  • main関数
    • ポート、SPI、タイマー割込み、変数の初期化
    • 割込み許可
    • ループ(なにもしない)

ここでは、タイマー0割込みを約2mSごとに設定して、水平1ラインごとに、赤8ビット、緑8ビット、そしてコモン線のビット(カソード直結なので反転)を選択してSPIに出力しています。 出力完了後、/SS信号の立ち上げによるラッチの前後に出力イネーブルを使って全消灯してから再点灯することで、確実に目的のラインのみが点灯されるようにします。 2mS x 8 = 16mSごとに全LEDを走査することになるので、1秒あたり60回程度の点滅となり、ディスプレイ目的には十分な速度です。

全ソースコード(avr-gcc用)

SPI出力部

SPI出力部分は、ATmega88のデータシート中のサンプル・コードを、ほぼそのまま使用しています。 SCKクロックは、fosc/4 = 250kHz (4µS周期)に設定しているので、8ビットの転送に32µS、24ビット(1ライン)では96µSの時間がかかることになります。 待ち時間が全体からみて影響が小さいので、SPIの割込みは使用せずにSPIFフラグ(SPSRレジスタ)により転送完了をソフトウェアで検出しています。

SPI出力部分抜粋 (avr-gcc使用)
void spi_init(void) {
  // SPI設定
  // マスター、クロックモード = 3、f_sck = f_osc / 4
  // (SPI割込み無効、MSBから転送)
  SPCR = _BV(SPE) | _BV(MSTR) | _BV(CPOL) | _BV(CPHA);
}

void spi_begin(void) {
  // /SS = lowにする。SPI転送開始をスレーブに伝える
  PORTB &= 0b11111011;
}

void spi_end(void) {
  // /OE = highにして全消灯し、
  // /SS = highで新しいデータをセットした後、
  // /OE = lowにして点灯
  PORTB |= 0b00000010;
  PORTB |= 0b00000100;
  PORTB &= 0b11111101;
}

char spi_send(char cData) {
  SPDR = cData;
  while (! (SPSR & _BV(SPIF))) {
    ;  // SPIFがセットされるのを待つ
  }
  return SPDR;  // SPIスレーブからのデータを返す
}

実行結果

表示結果


表示結果
赤 (led_pattern_red[]) 緑 (led_pattern_green[])
  0b00000111,
  0b00000110,
  0b00000100,
  0b00000001,
  0b00000011,
  0b00000101,
  0b00001111,
  0b00010001,
  0b01100111,
  0b11110110,
  0b11110100,
  0b01100000,
  0b00000000,
  0b00000000,
  0b00000000,
  0b00000000,

写真は、上記のソースコードをコンパイルして実行した表示結果です。 赤と緑のテストパターンが正しく表示されているのが分かります。 (ソースコードのはじめの方にある定数配列、 led_pattern_red[], led_pattern_green[] がLEDのビット・パターンです。 赤と緑を両方点灯している部分はオレンジ色に光っています。)


ロジアナによる観測

下の図は、ロジック・アナライザーで主要な信号を観測したものです。 /SS信号の立ち下がりでトリガーをかけています。 SCKの立ち下がりエッジでMOSI(マスター出力)が変化している様子がわかります。 また、シフトレジスタの出力 = MISO(マスター入力)は立ち上がりエッジによってシフト動作が行われていることもわかります。


ロジアナによるSPI信号の観測結果

1キャラクタ(8ビット)のデータ転送に32µSが必要という予想でしたが、さらに関数呼び出しなどのオーバーヘッドがあるため、実際には40µS、24ビットで約120µSの時間がかかっています。 より高速に連続して24ビットを出力するためには、3バイトの送信データをつづけてSPDRレジスタに書き込むように、spi_begin(), spi_send(), spi_end()の関数をまとめてしまう必要があるようです。

SPI入力の読みとり

今回の回路では、マスター側でデータを読み取る必要はありませんが、SPIの動作確認として実験をしてみます。

ソースを一部変更して、偶数ライン0,2,4,6の赤データを今までどおりSPIに書き込み、SPIからの読み取りデータを奇数ライン1,3,5,7に緑データとして出力しています。

SPI入力のテスト
変更前
  // SPI転送 (24bit)
  spi_begin();
  spi_send(red);
  spi_send(green);
  spi_send(row);
  spi_end();
変更後
  spi_begin();

  if ((led & 0x01) == 0) {
    // 偶数ライン:赤にはテストパターン、緑は全消灯
    spi_send(red);
    spi_send(0);
  } else {
    // 奇数ライン:赤は全消灯、一つ前のラインの赤データを読み取る
    green = spi_send(0);
    // 緑に一つ前のラインの赤データを出力
    spi_send(green);
  }

  spi_send(row);
  spi_end();

SPI読み取りのテスト

写真は、実行結果です。 書き込んだデータ(赤のライン)と同じ内容が読み取れている(緑のライン)ことがわかります。

注意: この接続によりシフト出力(Qh')をマスター入力(MISO)として使用する場合、ロジックアナライザの波形観測でわかるように、クロックの立ち上がりによってシフト動作とラッチ動作の両方が行われており、あまり望ましい動作状態ではありません。 本来であれば、HC595のシフト出力を半クロック遅延させ、安定した信号を取り込むような工夫が必要です。


まとめ

74HC595について

ATmega88のSPIは、クロックとデータのタイミングを柔軟に設定できるため、HC595とほぼ直結でき部品点数も非常に少なくできました。 また、HC595は(というか、74HCシリーズは)動作電圧範囲も2〜6Vと広く、高速CMOSのため使いやすく、表示装置など出力ポートを多数必要とする用途では、いろいろと応用ができそうです。

欠点としては、HC595は最大定格として電源の総電流を70mA程度までしか流せません。 全点灯時を考慮すると一個のLED電流をあまり多くとることができないため、いくぶん暗い表示になってしまいました。(特に緑) LEDに電流を流すために、HC595の出力にドライバを追加するか、コモン線をAVRのポートで直接制御するなどの必要がありそうです。

その他

LEDについて気づいたことですが、グラフィックを美しく表示するためには、赤と緑の明るさバランスを調整する必要がありそうです。 今回、タイマー割込みをLEDの走査表示の制御に用いたので、PWM出力を利用することで輝度調節が同時にできるのではないかと考えています。 これは別の機会に考察してみたいと思います。

リファレンス

あとがき

マイコンの周辺回路としてシリアル通信を行うモジュールには、SPIインターフェースだけでなく、I2CやUSARTなど色々なものがあります。 どのモジュールも「通信相手」がいて、はじめて動作するものですので、システム全体を正しく動作させるにあたって考えることが多くなりますが、別の見方をすれば、デバイス内で完結するポートやタイマーにはない面白さがあるともいえます。

今回はAVRをSPIマスターとし、一つだけのSPIスレーブを構成しているので、比較的単純な回路によってシステムを作ることができました。動作原理を勉強するにあたっては非常によい題材であったと思います。 別の機会にAVR側をSPIスレーブとする実験や、USART、I2Cを使った実験も行ってみたいと思います。



表紙に戻る