2015年07月16日 | 公開。 |
それでは、いよいよロジックアナライザを使い、LCDの制御信号を観察します。なお、この記事の趣旨は、今回使ったLAP-C(16064)というロジックアナライザの使い方の説明ではないので、ロジックアナライザの操作法については、あまり詳細に説明しません。
LCDの動作テストのスケッチでは、まず最初にLCD(AQM1248A)に初期化信号を送ります。そこで、まず、その初期化信号をロジックアナライザで観察してみます。
下の図は、/CS信号(Arduinoの10番ピン)が初めてHからLに変化する部分でトリガをかけた時の、LCD制御信号の波形です。
ここで、各信号について、改めて説明しておきます。
RS信号(Arduinoの9番ピン)は、/CS、MOSI、SCKの3つの信号で構成される、SPIバスで送信される信号が、コマンドなのか、データ(ディスプレイデータRAMに書き込むビットマップデータ)なのかを区別するための信号線です。RSがHなら、SPIバスからはデータが、RSがLなら、SPIバスからはコマンドが送られています。上の波形の場合、RSはずっとLなので、SPIバスからはコマンドが送信されています。
/CS信号(Arduinoの10番ピン)は、SPIインターフェースにおいて、チップセレクトを表わす信号で、SPIバスを通じて情報をやり取りするには、マスタ側(SPIバスの制御権を握っている側、この場合はArduino側)が通信の続く間、Lに保つ必要があります。
MOSI信号(LCDピッチ変換キットにはSDIと表記、Arduinoの11番ピン)は、マスター側からスレーブ側(SPIバスの制御権を持っていない側、この場合はLCD側)へ送られるシリアル信号です。この後説明するSCK信号にタイミングを合わせて信号を送ります。
SCK信号(LCDピッチ変換キットにはSCLKと表記、Arduinoの13番ピン)は、マスター側からスレーブ側へ送られるクロック信号です。SPIバスでやりとりされる信号は、みなこのSCLKに同期して送信されます。
本来なら、SPIインターフェースには、MISO信号という、スレーブ側からマスター側へ情報を送る信号線があるのですが、AQM1248AというグラフィックLCDモジュールは(というより、SPI接続のグラフィックLCDモジュールはたいてい)、LCD側からマスター側への信号線(MISO信号)を持っていません。LCDを制御するマイコンの側からすれば、何の返答もLCD側から返ってこないので、LCDがちゃんと待機状態になっているのかが分かりませんし、それどころか、SPIバスの先にLCDがつながっているのかどうかすら分かりません。また、LCDに内蔵されているディスプレイデータRAM(VRAM)の内容をマイコンが読み取る手段がありませんから、表示されている内容を知りたければ、マイコンは、自身のRAM上に、LCDに表示させた内容のコピーをおいておく必要があります。
先ほどの波形を読み解く前に、簡単にSPIバスを使った情報の送信方法について説明します。とはいっても、SPIバス一般の話を書く余裕がありませんので、今回のLCD動作テストに関係の深いところだけを説明します。SPIバス一般の話については、用語集のSPIの項目をご覧ください。
SPIバスでやり取りされるデータの形式(フォーマット)には、いくつかバリエーションがあるのですが、ここでは、AQM1248Aの制御に使う、ビット長8、MSBファースト、モード3という形式の送信方法に絞って説明します。
図7に、マスターがスレーブに、5AH(01011010B)というデータをSPIバスで送る場合の波形を示します。なお、RSはSPIインターフェースに含まれない信号線なので、図7には書いていません。
この図に示す様に、SPIバスでは、/CSがLの間に8ビットの情報を送ってしまいます。(/CSがHならば、データを送っても、スレーブ側で無視します)データは、MOSI信号において、SCK信号と同期し、MSB(最上位ビット)側からLSB(最下位ビット)側に向けて8ビット送信されます。スレーブ側では、SCKの立ち上がりでMOSIを取り込みます。
先ほどのロジックアナライザの画面では、トリガがかかって8μs位で情報を送っていました事が分かりましたが、この部分を拡大して、一体どういう情報を送っているのかを、解読してみましょう。
情報を送信している部分を、ロジアナで拡大して観察すると、次の様な波形になります。
MOSI信号には、SCKのクロックに同期して、順に1、0、1、0、1、1、1、0の2進数が送られています。つまり、2進数で10101110B、16進数表記だと、AEHを送信している事になります。
ここで、前のページに載せたスケッチのInitializeLcdという関数を再掲します。この関数は、SPIインターフェースの初期化が終わった直後に呼ばれる関数です。
void InitializeLcd() { LcdCommand(0xae); LcdCommand(0xa0); LcdCommand(0xc8); LcdCommand(0xa3); LcdCommand(0x2c); delay(2); LcdCommand(0x2e); delay(2); LcdCommand(0x2f); LcdCommand(0x23); LcdCommand(0x81); LcdCommand(0x1c); LcdCommand(0xa4); LcdCommand(0x40); LcdCommand(0xa6); LcdCommand(0xaf); } // InitializeLCD
LcdCommand関数が多く使われていますが、この関数は、RS信号をLにした状態で、引数の8ビットの数をSPIバスに送信する関数です。
最初に呼ばれるLcdCommand関数には、引数として0xaeが渡されていますので、その部分をロジックアナライザで観察したのが、先ほどの拡大波形という訳です。
なお、今回紹介しているLAP-C(16064)というロジックアナライザには、プロトコルアナライザの機能があります。プロトコルアナライザは、SPI、I2C、UART、USB、パラレルバスなどの、各種インターフェース・バスを流れる情報を解析する装置の事です。
それでは、プロトコルアナライザ機能を用いて、SPI バス上の信号を解析してみましょう。/CSとMOSIとSCKの3つがSPIバスの信号線ですから、それを一つのバスとして登録します。
上の様に、/CS、MOSI、SCKの信号を、ひとつのバスにまとめ、そのバスにSPIという名前を付けました。バスを登録すると、デフォルトではパラレルバスと解釈されます。SPIの行には、0X4や0X6などの16進数が見えますが、これはバスをパラレルバスだと仮定して、バス上の情報を解読した結果です。
実際にはパラレルバスではなく、SPIバスなので、その事を登録します。Bus Propertyの設定で、ZEROPLUS LA SPI MODULEを選択して、登録したバスをSPIバスとして、信号を解析させます。
また、SPIバスの詳細な設定をするために、Parameters Config...のボタンをクリックして、次の画面の様に各種設定を行います。
バスのモードは、モード3の場合、CPHA=1,CPOL=1と指定します。詳しくは、「SPI」の解説というページに、SPIバスのモードの話が載っていますので、そちらを参照してください。
以上の様にSPIバスの設定をすると、SPIバス上のデータは、ロジックアナライザ(内蔵のプロトコルアナライザ)が自動的に解析するようになります。
また、パケット表示のモードを選択すると、SPIバスで送信されたデータを、表の形式で見ることもできるようになります。
この表示から、最初のデータはAEHで、トリガ後0nsから送信されており、2番目のデータはA0Hで、トリガ後24.31μsから送信されており…といった事が読み取れます。
AEH、A0H、C8H、A3H、2CHというデータの並びは、先ほど掲載したInitializeLcd関数の冒頭の5つのLcdCommand関数の引数に対応しています。分かりやすいように、該当部分だけ再掲します。
LcdCommand(0xae); LcdCommand(0xa0); LcdCommand(0xc8); LcdCommand(0xa3); LcdCommand(0x2c);
ところで、SPIバスの波形を見ていると、/CS信号が立ち下がってからデータを送信し始めるまでと、データを送信し終わってから/CS信号が立ち上がるまでに、かなり時間がかかっていることに気がつきます。
この様に、データ送信前後に長い待ち時間が発生するのは、/CS信号やRS信号の制御にdigitalWrite関数を使っているのが原因です。digitalWrite関数は、Arduinoのピン番号からATmega328Pのポートへの変換や、ピンがPWM出力に割り当てられていないか監視しているなど、オーバーヘッドが多く、呼び出しに時間がかかる関数です。digitalWrite関数を使う代わりに、ATmega328Pのポートを直接操作する様にすれば、動作がかなり高速化します。ここら辺の事情は、なんでも独り言というブログのArduinoの高速化という記事や、gajeというブログのArduino core の実装メモという記事に説明があるので、参考にしてください。
前のページで紹介したスケッチでは、LcdCommand関数内で、digitalWrite関数を3回呼び出していました。
void LcdCommand(uint8_t cmd) { SPI.beginTransaction(SPISettings(RATE,MSBFIRST,SPI_MODE3)); digitalWrite(CS_PIN,LOW); digitalWrite(DI_PIN,LOW); SPI.transfer(cmd); digitalWrite(CS_PIN,HIGH); SPI.endTransaction(); } // LcdCommand
Arduino Uno/Duemilanoveの9番ピン(D9ピン)がATmega328PのPB1(ポートBの第1ビット)に割り当てられており、Arduino Uno/Duemilanoveの10番ピン(D10ピン)がATmega328PのPB2(ポートBの第2ビット)に割り当てられている事を考慮し、dititalWrite関数を使わないようにLcdCommand関数を書き換えると、次の様になります。
void LcdCommand(uint8_t cmd) { SPI.beginTransaction(SPISettings(RATE,MSBFIRST,SPI_MODE3)); //digitalWrite(CS_PIN,LOW); __asm__ volatile("cbi %0,2\n\t" :: "I"(_SFR_IO_ADDR(PORTB))); // clear PB2 //digitalWrite(DI_PIN,LOW); __asm__ volatile("cbi %0,1\n\t" :: "I"(_SFR_IO_ADDR(PORTB))); // clear PB1 SPI.transfer(cmd); //digitalWrite(CS_PIN,HIGH); __asm__ volatile("sbi %0,2\n\t" :: "I"(_SFR_IO_ADDR(PORTB))); // set PB2 SPI.endTransaction(); } // LcdCommand
digitalWrite関数は、ひとつのCBI命令またはSBI命令に置き換えられています。CBI命令とSBI命令は、指定されたポートの指定されたビットを、それぞれ0および1にする機械語命令です。インラインアセンブラの書き方が少しややこしいですが、AVR Libc Home PageというサイトのInline Assembler Cookbookというページ(英文)が参考になります。
dititalWrite関数を使わないようにLcdCommand関数を書き換えると、次に示す様にデータの送信が高速化します。
以上の説明で、ロジックアナライザを使うと、バス上の信号の解析が、非常に分かりやすく行える事が理解できたと思います。スケッチの改善により信号の送信が高速化する様子なども、手に取るように分かります。
ただし、ロジックアナライザに、観察したい信号を引き出すのが少々面倒です。今回はSPIバスの観察をしたので、4本の信号線で済みましたが、パラレルバスを観察する場合などは、10本以上の信号線を引き出す必要が生じます。この様な時に、ヘッダシールドを作っておき、そこから信号を引き出すと、デバッグの時間を節約する事ができます。