要約
MAX7456オンスクリーンディスプレイ(OSD)ジェネレータは、SPI™対応の制御インタフェースを装備しています。このアプリケーションノートでは、SPIインタフェースの動作について説明します。また、この記事では、ビットバングされたSPIインタフェースを通じて製品を制御するために、マイクロコントローラが使用することができるCコードについても記載しています。
MAX7456シリアルインタフェース
MAX7456シングルチャネル、モノクロームオンスクリーンディスプレイ(OSD)ジェネレータには、あらかじめ256個の文字と絵文字がロードされています。また、MAX7456は、SPIポートを使用してインサーキットで再プログラムすることができます。SPI対応のシリアルインタフェースは、動作モード、ディスプレイメモリ、および文字メモリをプログラムします。読出し機能によって、書込みの検証と、ステータス(STAT)レジスタ、ディスプレイメモリデータアウト(DMDO)レジスタ、および文字メモリデータアウト(CMDO)レジスタの読出しの両方が可能になります。MAX7456のレジスタとメモリ構成の詳細については、製品のデータシート、およびアプリケーションノート4117、「MAX7456のメモリとEVキットファイルのフォーマットを用いてカスタム文字とグラフィックスを生成」を参照してください。
MAX7456は、最大10MHzのインタフェースクロック(SCLK)をサポートしています。データの書込みを図1に、デバイスからのデータの読出しを図2に示します。
レジスタに書き込むには、アクティブローCSをローにして、シリアルインタフェースをイネーブルにします。データは、SDINにおいて、SCLKの立上りエッジで同期入力されます。アクティブローCSがハイに遷移すると、データは入力レジスタにラッチされます。遷移中、アクティブローCSがハイになると、シーケンスは中止されます(すなわち、データはレジスタに書き込まれません)。アクティブローCSがローになった後、デバイスはSDINに同期入力される最初のバイトを待ちます。この後、デバイスは、実行されるデータ転送のタイプを特定することができます。
レジスタを読み出すには、上述のようにアクティブローCSをローにします。上述したとおり、SDINにおいて、SCLKの立上りエッジでアドレスが同期入力されます。データは、SDOUTにおいて、SCLKの立下りエッジで同期出力されます。
SPIコマンドは16ビット長で、8つの最上位ビット(MSB)はレジスタアドレスを、8つの最下位ビット(LSB)はデータを表します(図1および図2)。この配列には、次の2つの例外があります。
- ディスプレイメモリのアクセスに使用される自動インクリメント書込みモードは、単一の8ビット動作です(図3)。データを書き込む前に、開始アドレスを設定する必要があります。ディスプレイメモリの自動インクリメント書込みを実行すると、8ビットのアドレスが内部で生成されます。図3に示すように、シリアルインタフェースでは8ビットのデータのみが必要とされます。
- 16ビット動作モードのとき、ディスプレイメモリからの文字データの読出しは24ビット動作(8ビットのアドレス + 16ビットのデータ)です。
図2に示すように、読出し動作を実行するときには、8ビットのアドレスのみが必要となります。
図1. 書込み動作
図2. 読出し動作
図3. 自動インクリメントモードでの書込み動作
Cコードルーチン
以下に記述したCコードは、MAXQ2000マイクロコントローラ用にコンパイルしたもので、MAX7456の評価(EV)キットで使用します。このアプリケーションノートで、ソフトウェアルーチンの完全セットを利用することができます。これらのルーチンは、自己文書化コードであるため、追加説明はほとんど記載されていません。以下のCコードは、ファイルspi.cおよびファイルMAX7456.hでも利用することができます。
このコードは、SPIラインの標準的な用語を使用します。MAXQ2000プロセッサがSPIマスタで、MAX7456がSPIスレーブです。
CSは、MAX7456のデータシートで使用されているCSと同じものです。
SDINはMOSI (マスタアウトスレーブイン)と呼ばれます。
SDOUTはMISO (マスタインスレーブアウト)と呼ばれます。
SCLKはCKと呼ばれます。
プレフィックスSPI_は、すべてのラインで使用されます。
データ構造
以下に示すデータ構造は、そのまま、またはビット単位でデータにアクセスするために使用します。このデータ構造は、SPIポートのピンに個別にアクセスするために使用します(C++と一部の新しいCコンパイラは、ビットフィールドのunion/struct構文をサポートしています)。
/*ポート5の出力レジスタ*/ __no_init volatile __io union { unsigned char PO5; struct { unsigned char bit0 : 1; unsigned char bit1 : 1; unsigned char bit2 : 1; unsigned char bit3 : 1; unsigned char bit4 : 1; unsigned char bit5 : 1; unsigned char bit6 : 1; unsigned char bit7 : 1; } PO5_bit; }
このコードは、マイクロコントローラの出力ポートアドレスであるPO5にシングルバイトを割り当てます。次に、このコードは、ビット単位でアクセス可能な同一メモリアドレスに別のバイトを割り当てます。
したがって、以下のコマンドを使用して、ポートのアドレスをじかに指定することができます。
PO5 = 0x10;
あるいは、以下のコマンドを使用して、ビット単位でポートのアドレスを指定することができます。
PO5_bit.bit4 = 1;
この構造をカスタマイズすれば、別のプロセッサでこのコードを使用することができます。
ビットフィールド幅の指定子に対応していない旧式のCコンパイラを使用する場合は、以下に示すようにビット単位のブール演算子を使用して、ビットのセットとクリアを行うことができます。
/*ビットセットとビットクリアの移植可能なマクロ*/ #define BIT_SET(sfr,bitmask) sfr |= (bitmask) #define BIT_CLR(sfr,bitmask) sfr &=~ (bitmask) #define BIT0 0x01 #define BIT1 0x02 #define BIT2 0x04 #define BIT3 0x08 #define BIT4 0x10 #define BIT5 0x20 #define BIT6 0x40 #define BIT7 0x80 example: BIT_SET(PO5,BIT0); BIT_CLR(PO5,BIT6);
マクロ
ルーチンをさらに移植しやすくするための簡単な秘訣があります。それは、以下に示すように、マクロを使用して、コントローラのピン配置を定義することです。
#define SPI_CS PO5_bit.bit4 // PO5_bit.bit4 = アクティブローCS—チップ選択 #define SPI_MOSI PO5_bit.bit5 // PO5_bit.bit5 = MOSI—マスタアウトスレーブイン、 // MAX7456へのデータ #define SPI_MISO PI5_bit.bit7 // PO5_bit.bit7 = MISO—マスタインスレーブアウト、 // MAX7456からのデータ #define SPI_CK PO5_bit.bit6 // PO5_bit.bit6 = SCK - SPIクロック
これらのマクロと上記のデータ構造を使用すると、以下のコマンドで、IOポートの各ピンのセットとリセットを個別に行うことができます。
SPI_CS = 1;
マクロを変更すると、ピンを変更することができます。これが役立つのは、SPIポートの別のピンを割り当てる別の設計でこのコードを使用する場合であり、またPCBの配線パターンの引き回しを改良するためにこれらのピンを再割り当てする必要がある場合です。
シングルバイトの書込みのコード
シングルバイトの書込み動作(図1)のコードを以下に示します。アクティブローCSとCKのラインが入力時に正しい状態にあることを保証することができる場合は、最初の2つの命令を削除することができます。
ルーチンは最初にアドレスを、続いてデータを送信します。これには、2つのループを使用します。単一のループと16ビットのデータストアを使用すれば、このルーチンを簡素化することができます。MAXQ2000コントローラでは、16ビットの「int」の循環は、8ビットの「char」の循環よりも時間がかかるため、トレードオフを行っています。
/************************************************************************************** * spiWriteReg * * SPIポートを用いて8ビットレジスタに書込み **************************************************************************************/ void spiWriteReg(const unsigned char regAddr, const unsigned char regData) { unsigned char SPICount; // データの同期出力に使用するカウンタ unsigned char SPIData; // SPIデータのデータ構造を定義 SPI_CS = 1; // アクティブローCSがハイで、CKがローの状態で SPI_CK = 0; // 開始することを確認 SPIData = regAddr; // 送信するデータにAddressをあらかじめロード SPI_CS = 0; // アクティブローCSをローにセットして // SPIサイクルを開始 // SPIDataを「int」として実装することも可能だが // (この場合、1つのループになる)、SPIDataを2つの // 「char」として実装した状態で、2つのループを実装しているとき、 // SPIData implemented as two "char"s. for (SPICount = 0; SPICount < 8; SPICount++) // Addressバイトを同期出力するための準備 { if (SPIData & 0x80) // 1かどうかを確認 SPI_MOSI = 1; // MOSIラインを適切にセット else SPI_MOSI = 0; SPI_CK = 1; // クロックラインを切替え SPI_CK = 0; SPIData <<= 1; // 循環して次のビットを取得 } // ループバックして次のビットを送信 // Dataバイトのための繰り返し SPIData = regData; // 送信されるデータにDataをあらかじめロード for (SPICount = 0; SPICount < 8; SPICount++) { if (SPIData & 0x80) SPI_MOSI = 1; else SPI_MOSI = 0; SPI_CK = 1; SPI_CK = 0; SPIData <<= 1; } SPI_CS = 1; SPI_MOSI = 0; }
バイトの読出し動作のコード
バイトの読出し動作(図2)のコードを以下に示します。このコードは、上記のルーチンと似ています。最初にアドレスを送信します。クロックを切り替えて、データを読み返した後、MISOラインからデータを読み出します。
/************************************************************************************** * spiReadReg * * SPIポートを用いて8ビットレジスタを読み出し * データが返される **************************************************************************************/ unsigned char spiReadReg (const unsigned char regAddr) { unsigned char SPICount; // データの同期出力に使用するカウンタ unsigned char SPIData; SPI_CS = 1; // アクティブローCSがハイで SPI_CK = 0; // CKがローの状態で開始することを確認 SPIData = regAddr; // 送信するデータにAddressとDataをあらかじめロード SPI_CS = 0; // アクティブローCSをローにセットして、SPIサイクルを開始 for (SPICount = 0; SPICount < 8; SPICount++) // AddressとDataを同期出力するための準備 { if (SPIData & 0x80) SPI_MOSI = 1; else SPI_MOSI = 0; SPI_CK = 1; SPI_CK = 0; SPIData <<= 1; } // ループバックして次のビットを送信 SPI_MOSI = 0; // MOSIデータラインをリセット SPIData = 0; for (SPICount = 0; SPICount < 8; SPICount++) // 読み出すデータを同期入力するための準備 { SPIData <<=1; // データの循環 SPI_CK = 1; // クロックを上げて、MAX7456からデータを同期出力 SPIData += SPI_MISO; // データビットの読出し SPI_CK = 0; // 次のビットに備えてクロックを下げる } // ループバック SPI_CS = 1; // CSを生成 return ((unsigned char)SPIData); // 最後に読出しデータを戻す }
自動インクリメントを使用したバイトの書込み動作のコード
自動インクリメント機能を使用したバイトの書込み動作(図3)のコードを以下に示します。このコードも、上記のシングルバイトの書込みルーチンと似ています。最初にアドレスを送信します。クロックを切り替えて、データを読み返した後、MISOラインからデータを読み出します。
/************************************************************************************** * spiWriteRegAutoIncr * * SPIポートを用いて8ビットレジスタに書込み(MAX7456の自動インクリメントモードを使用) **************************************************************************************/ void spiWriteRegAutoIncr(const unsigned char regData) { unsigned char SPICount; // データの同期出力に使用するカウンタ unsigned char SPIData; // SPIデータのデータ構造を定義 SPI_CS = 1; // アクティブローCSがハイで SPI_CK = 0; // CKがローの状態で開始することを確認 SPIData = regData; // 送信するデータにAddressとDataをあらかじめロード SPI_CS = 0; // アクティブローCSをローにセットして、SPIサイクルを開始 for (SPICount = 0; SPICount < 8; SPICount++) // AddressとDataを同期出力するための準備 { if (SPIData & 0x80) SPI_MOSI = 1; else SPI_MOSI = 0; SPI_CK = 1; SPI_CK = 0; SPIData <<= 1; } // ループバックして次のビットを送信 SPI_MOSI = 0; // MOSIデータラインをリセット }
自動インクリメントを使用してディスプレイメモリに書き込むためのコード
以下のルーチンは、自動インクリメント機能を使用して、ディスプレイメモリにデータを書き込みます。このコードは、「data」と呼ばれるグローバル変数の配列を使用します。これは、以下に示すように定義されます。
extern volatile unsigned char data[DATA_BUF_LENGTH]; DATA_BUF_LENGTH = 968
ルーチンを呼び出すとき、以下に示すように、書き込むディスプレイメモリをdata[]に含めます。
data[0] = ignored (EVキットのGUIソフトウェアが使用するコマンドバイトが含まれます) data[1] = 文字バイト1 data[2] = 属性バイト1 data[3] = 文字バイト2 data[4] = 属性バイト2 など
0xFFを書き込むことで、自動インクリメントモードは終了します。このため、このモードでは、文字0xFFをディスプレイに書き込むことはできません。0xFFの書込みが必要な場合は、シングルバイトの書込みを使用することができます。
/************************************************************************************** * spiWriteCM * * 「data」エクスターンからディスプレイメモリ(960バイト)への書込み。 * 960 = 16行 × 30列 × 2面 {char vs. attr} 画面位置のインデックス付きメモリ **************************************************************************************/ void spiWriteCM() // 入力時:グローバルデータ[1..960]には // char+attrのバイトが含まれる // (状況に応じて、0xFFデータによって終了) // 最初に、data[1,3,5,...] Character面を書き込み // MAX7456 WriteReg (0x05,0x41) // 「文字メモリアドレスをハイ」 // 0x02:属性バイト // 0x01:文字メモリアドレスの最上位ビット { volatile unsigned int Index = 0x0001; // data[1..960]検索のためのインデックス spiWriteReg(DM_ADDRH_WRITE,0x00); // ディスプレイメモリの上位バイトと spiWriteReg(DM_ADDRL_WRITE,0x00); // 下位バイトを初期化 spiWriteReg(DM_MODE_WRITE ,0x41); // MAX7456 WriteReg (0x04,0x41) 「ディスプレイメモリモード」 // 0x40:8ビット演算を実行、0x01:自動インクリメント Do // 文字データを書き込むループ { if (data[Index] == 0xFF) { // ブレーク文字があるかどうかをチェック break; } // ブレーク文字があれば終了 spiWriteRegAutoIncr(data[Index]); // 文字を書き込む Index += 2; // 次の文字にインデックスをインクリメント // 属性をスキップ } while(Index < 0x03C1); // 0x03C1 = 961 // ループバックして、次の文字を送信 spiWriteRegAutoIncr(0xFF); // 「エスケープ文字」を書き込んで // 自動インクリメントモードを終了 spiWriteReg(DM_ADDRH_WRITE,0x02); // 次に、data[2,4,6,...] Attribute面を書き込み // MAX7456 WriteReg (0x05,0x41) // 「文字メモリアドレスをハイ」 // 0x02:属性バイト // 0x01:文字メモリアドレスの // 最上位ビット spiWriteReg(DM_ADDRL_WRITE,0x00); spiWriteReg(DM_MODE_WRITE,0x41); // MAX7456 WriteReg (0x04,0x41) 「文字メモリモード」 // 0x40:8ビット演算の実行 // 0x01:自動インクリメント Index = 0x0002; do { if (data[Index] == 0xFF) break; spiWriteRegAutoIncr(data[Index]); Index += 2; } while(Index < 0x03C1); spiWriteRegAutoIncr(0xFF); }
文字メモリに書き込むコード
以下のルーチンは、単一の文字を文字メモリに書き込みます。各文字は、12ピクセル x 18ラインで合計216ピクセルです。各バイトは4つのピクセルを定義するため、各文字を定義するには54バイトが必要です。文字用のデータは、入力時にdata[]に格納されます(これは、ディスプレイメモリに書き込むための上記のルーチンに似ています)。
文字メモリへの書込みについては、追加でいくつかの説明を行う必要があります。メモリは不揮発性であるため、このメモリへの書込みには約12msかかります。また、この書込みは、MAX7456自体が行います。文字メモリには、全54バイトの文字のみを書き込むことができます。
デバイスには、54バイトのシャドウメモリが搭載されています。最初に、書き込む文字データをこのメモリに格納します。次に、デバイスを作動させて、NVM文字メモリにこのデータを書き込みます。
以下のいくつかのレジスタを使用して、文字メモリに書き込みます。
- 文字メモリモード = 0x08。このレジスタに0xA0を書き込んでデバイスを作動させ、シャドウメモリをNVM文字メモリに書き込みます。
- 文字メモリアドレスハイ = 0x09。これには、書き込む文字のアドレスが含まれています。
- 文字メモリアドレスロー = 0x0A
- 文字メモリデータイン = 0x0B
- 状態 = 0xA0。このレジスタから読み出して、文字メモリをいつ書込みに使用することができるかを判断します。
入力時、data[1]には書き込む文字のアドレスが、data[2...54]には文字のデータが格納されています。
文字をNVM文字メモリに書き込むには、まず文字のアドレスを書き込みます。次に、各バイトをシャドウメモリに書き込みます。シャドウメモリの書込みには自動インクリメントモードがないため、毎回、シャドウメモリ内のアドレスを書き込む必要があります。文字メモリモードのレジスタに0xA0を書き込むことによって、シャドウメモリをNVM文字メモリに書き込むことができます。デバイスは、状態レジスタのビット5をハイにして、文字メモリを書込みに利用することができないことを示します。完了したら、デバイスはこのビットをローにリセットします。シャドウメモリが文字メモリに転送されている間は、シャドウメモリへの書込みを行わないようにしてください。
好ましくない画面のちらつきが生じないようにするため、このルーチンでは、OSDをディセーブルにしてから、文字メモリに書き込んでいます。
/************************************************************************************** * spiWriteFM * * 「data」エクスターンから文字メモリ(54バイト)への書込み **************************************************************************************/ void spiWriteFM() { unsigned char Index; spiWriteReg(VIDEO_MODE_0_WRITE,spiReadReg (VIDEO_MODE_0_READ) & 0xF7); // ビット0x08をクリアして、OSDディスプレイをディセーブルにする spiWriteReg(FM_ADDRH_WRITE,data[1]); // 書き込む文字のアドレスを書き込み // MAX7456のグリフタイルの定義 // 長さ = 0x36 = 54バイト // 単一文字/グリフタイルの形状が格納されている // FM_DATA_.. FM_ADDR..を通じてアクセスされる // MAX7456 64バイトのシャドウRAM for(Index = 0x00; Index < 0x36; Index++) { spiWriteReg(FM_ADDRL_WRITE,Index); // シャドウRAM内のアドレスを書込み spiWriteReg(FM_DATA_IN_WRITE,data[Index + 2]); // シャドウRAMにデータを書込み } spiWriteReg(FM_MODE_WRITE, 0xA0); // MAX7456「フォントメモリモード」に0xA0を書き込むことで // 64バイトのシャドウRAMからNV配列にコピー while ((spiReadReg(STATUS_READ) & 0x20) != 0x00); // NVメモリの状態がビジーの間は待機 // MAX7456 0xA0状態ビット0x20:NVメモリ状態 // Busy/~Ready }
MAX7456のヘッダファイル
以下のリストは、MAX7456用のヘッダファイルです。このコードは、デバイスのレジスタマップを定義します。
/************************************************************************************** * spiWriteRegAutoIncr * * SPIポートを用いて8ビットレジスタに書込み(MAX7456の自動インクリメントモードを使用) **************************************************************************************/ // MAX7456 VIDEO_MODE_0レジスタ #define VIDEO_MODE_0_WRITE 0x00 #define VIDEO_MODE_0_READ 0x80 #define VIDEO_MODE_0_40_PAL 0x40 #define VIDEO_MODE_0_20_NoAutoSync 0x20 #define VIDEO_MODE_0_10_SyncInt 0x10 #define VIDEO_MODE_0_08_EnOSD 0x08 #define VIDEO_MODE_0_04_UpdateVsync 0x04 #define VIDEO_MODE_0_02_Reset 0x02 #define VIDEO_MODE_0_01_EnVideo 0x01 // VIDEO MODE 0ビットマップ #define NTSC 0x00 #define PAL 0x40 #define AUTO_SYNC 0x00 #define EXT_SYNC 0x20 #define INT_SYNC 0x30 #define OSD_EN 0x08 #define VERT_SYNC_IMM 0x00 #define VERT_SYNC_VSYNC 0x04 #define SW_RESET 0x02 #define BUF_EN 0x00 #define BUF_DI 0x01 // MAX7456 VIDEO_MODE_1レジスタ #define VIDEO_MODE_1_WRITE 0x01 #define VIDEO_MODE_1_READ 0x81 // MAX7456 DM_MODEレジスタ #define DM_MODE_WRITE 0x04 #define DM_MODE_READ 0x84 // MAX7456 DM_ADDRHレジスタ #define DM_ADDRH_WRITE 0x05 #define DM_ADDRH_READ 0x85 // MAX7456 DM_ADDRLレジスタ #define DM_ADDRL_WRITE 0x06 #define DM_ADDRL_READ 0x87 // MAX7456 DM_CODE_INレジスタ #define DM_CODE_IN_WRITE 0x07 #define DM_CODE_IN_READ 0x87 // MAX7456 DM_CODE_OUTレジスタ #define DM_CODE_OUT_READ 0xB0 // MAX7456 FM_MODEレジスタ #define FM_MODE_WRITE 0x08 #define FM_MODE_READ 0x88 // MAX7456 FM_ADDRHレジスタ #define FM_ADDRH_WRITE 0x09 #define FM_ADDRH_READ 0x89 // MAX7456 FM_ADDRLレジスタ #define FM_ADDRL_WRITE 0x0A #define FM_ADDRL_READ 0x8A // MAX7456 FM_DATA_INレジスタ #define FM_DATA_IN_WRITE 0x0B #define FM_DATA_IN_READ 0x8B // MAX7456 FM_DATA_OUTレジスタ #define FM_DATA_OUT_READ 0xC0 // MAX7456 STATUSレジスタ #define STATUS_READ 0xA0 #define STATUS_40_RESET_BUSY 0x40 #define STATUS_20_NVRAM_BUSY 0x20 #define STATUS_04_LOSS_OF_SYNC 0x04 #define STATUS_02_PAL_DETECTED 0x02 #define STATUS_01_NTSC_DETECTED 0x01 // MAX7456が、リセット後、OSD黒レベル // レジスタビット0x10のクリアを要求 #define OSDBL_WR 0x6C #define OSDBL_RD 0xEC #define OSDBL_10_DisableAutoBlackLevel 0x10
結論と性能
MAX7456のEVキットは、20MHzのクロックで動作するMAXQ2000マイクロコントローラを使用します。MAXQ2000マイクロコントローラには、ハードウェアSPIコントローラが内蔵されています。このため、MAX7456のSPIポートはフルスピードでの動作が可能です。上記のソフトウェアSPIルーチンの実行速度は、ハードウェアコントローラよりも遅くなります。ただし、お客様のアプリケーションにハードウェアSPIポートがない場合、移植性を考慮してルーチンを最適化しました。
{{modalTitle}}
{{modalDescription}}
{{dropdownTitle}}
- {{defaultSelectedText}} {{#each projectNames}}
- {{name}} {{/each}} {{#if newProjectText}}
-
{{newProjectText}}
{{/if}}
{{newProjectTitle}}
{{projectNameErrorText}}