MC9S08JS8 でLEDマトリックスを駆動する ~ ソフトウェア編 (1) ~ [HCS08]
ハードウェアは、ひとまず放っておいて、ソフトウェアの話を書きます。
LATCH*信号をトグルするプロトコル
ハードウェアを工夫する事によって、SPIモジュールが使えるようになったのですが、実際に 16×16 LED マトリックス基板を使うには、もう一工夫必要です。 理由は、この基板のプロトコルが純粋なSPIではないからです。
通常のSPIプロトコルは、通信の前に Slave Select (SS) 信号をアサートして、データの転送が終わったら SS 信号をネゲートして通信の終了を知らせます。 ところが、この 16×16 LED マトリックス基板では、通信の終了時に LATCH* 信号を一回だけトグルするプロトコルが使われています。 また、データ通信の単位は、接続する 16×16 LED マトリックス基板の枚数に依存していて、今回のように3枚の基板を使用した場合には144ビット (18バイト)の通信が必要です。
この図では、24ビット(3バイト)のデータを送信する場合のタイミングを示しています。 SPIを使用して1バイトのデータを3回送信してからLATCH*パルスを発生させています。
SPIは、送受信バッファを持っている
MC9S8JS8のSPIモジュールは、1バイトずつの送受信バッファを持っています。 また、送信バッファが空になったことを示す SPTEF (SPI Transmit Buffer Empty Flag) というフラグと受信バッファにデータが到着した事を示す SPRF (SPI Read Buffer Full Flag) というフラグがあり、ソフトウェアは、これらのフラグを調べて送信データを送信バッファに書き込み、受信データを受信バッファから読み込みます。
今回、LATCH*パルスを発生させるために採用したのは、SPRFフラグだけに着目する方式です。 SPRFフラグは、SPIの通信が終了したときにセットされます。 そのため、確実に通信が終了してからLATCH*パルスを発生させることができます。
この時、SPTEFフラグも併用して、送信バッファを有効に使うことができないかと考えたのですが、うまくいかない事がわかってきました。 理由は、SPRFフラグを確実に検出しないと、SPRFフラグ検出の間に二つ以上のSPI通信が入ってしまう可能性があるからです。 この例では、メインループの動作が間に合わなかった場合を図示しています。 一番目のSPRFフラグの処理に手間取ったため、二番目のSPRFフラグ(赤い枠を付けたもの)がクリアされてしまいます。 そのため、メインループは、二番目のSPRFフラグの発生を検知することができません。 このような状況が発生してしまうと、SPI通信の終了が正しく検出できなくなり、LATCH*パルスを発生させる直前でプログラムの実行が止まってしまいます。
SPRFフラグとSPTEFフラグを併用する方法は、もちろんあります。 それは、割り込みを使用する方法です。 フラグの発生と同時に処理を開始すれば、フラグの取りこぼしはほぼなくなると思います。 ところが、このアプリケーションでは、USBインターフェースで優先的に割り込み処理を行うために、他のモジュールが発生する割り込みはなるべく使いたくありません。 このような事情から、SPRFフラグだけをメインループで監視する方法を取り入れました。
ノンプリエンプティブなステートマシン
SPIにデータを送り込む処理は、ステートマシンで記述できます。 また、ステートマシンの動作は、割り込みを使わずにメインループに取り込みます。 そこで、ステートマシンの処理単位を細切れにして、自発的にプログラム実行件を返納する「ノンプリエンプティブ」なステートマシンとして実装しました。
この状態遷移図が、データ送信を行うためのステートマシンです。 まず、SPIの送信バッファが空いていることを確認します。。 そして、SPRFフラグを監視しながら、16ビットのデータを9回送信し、LATCH*をトグルします。 これで、1フレーム分の処理が終わりです。 最後に次のフレームに備えて、RTCフラグを待ちます。 この処理を延々と繰り返すのが、ステートマシンの仕事です。
ステートマシンの状態コードは、このように割り当てました。
状態 | 状態コード | お仕事 |
---|---|---|
Generate ROW data | 0 | LEDマトリックスに送る行指定データ"row"を作成します。 |
Confirm TX buffer empty | 1 | 送信バッファが開いている事をあらかじめ確認します。 |
Send column 5 | 2 | 5桁目のデータを送信します。 |
Wait for column 5 transfer ends | 3 | 通信の終了を待ちます。 |
Send column 4 | 4 | 4桁目のデータを送信します。 |
Wait for column 4 transfer ends | 5 | 通信の終了を待ちます。 |
Send row for column 4 and 5 | 6 | 4、5桁目の行指定データを送信します。 |
Wait for row transfer for column 4, 5 ends | 7 | 通信の終了を待ちます。 |
Send column 3 | 8 | 3桁目のデータを送信します。 |
Wait for column 3 transfer ends | 9 | 通信の終了を待ちます。 |
Send column 2 | 10 | 2桁目のデータを送信します。 |
Wait for column 2 transfer ends | 11 | 通信の終了を待ちます。 |
Send row for column 2 and 3 | 12 | 2、3桁目の行指定データを送信します。 |
Wait for row transfer for column 2, 3 ends | 13 | 通信の終了を待ちます。 |
Send column 1 | 14 | 1桁目のデータを送信します。 |
Wait for column 1 transfer ends | 15 | 通信の終了を待ちます。 |
Send column 0 | 16 | 2桁目のデータを送信します。 |
Wait for column 1 transfer ends | 17 | 通信の終了を待ちます。 |
Send row for column 0 and 1 | 18 | 0、1桁目の行指定データを送信します。 |
Wait for row transfer for column 0, 1 ends | 19 | 通信の終了を待ちます。 |
Assert LATCH | 20 | LATCH*出力をアサートします。 |
Negate LATCH | 21 | LATCH*出力をネゲートします。 |
Assert STROBE | 22 | STROBE出力をアサートします。 |
Go to next line | 23 | 行カウンタを進めます。 |
Prepare for 1msec timer | 24 | RTCフラグをクリアします。 |
Wait for 1msec timer | 25 | RTCフラグを監視し、フレームの開始タイミングを待ちます。 |
Return to state 0 | >25 | 最初の状態#0に戻ります。 |
たとえば、それぞれの状態での処理は、こんな風になります。
case 3: // Wait for a transfer ends if (SPIS_SPRF) { vwDummy = SPID16; st++; } break;
SPRFフラグが検出されたら、フラグをクリアし、次の状態に遷移します。 検出されなければ、今の状態に留まります。
case 4: // Send column 4 SPID16 = buf[line][4]; st++; break;
次の16ビット・データの送信を指示し、次の状態に遷移します。
次回予告
次回は、最初のテスト・プログラムのご紹介と実行の様子をごらんに入れます。
コメント 0