痛みを伴う5日間の待機の後、魔法の箱が手元にありました。 LEDで10分間遊んだ後、もっと面白いことをしたかったのです。たとえば、時計です!
手元には古いシーメンスA65がいました。彼は私の小さなプロジェクトのドナーになりました。 私たちはそこから画面を取り出し、それをどのように接続するかを考えます。 しばらくグーグルで調べた後、 RadioKotフォーラムスレッドに成功しました。そこでは、ピン配置と画面の初期化について議論しました。 画面をマイクロコントローラーに接続するタスクに直面している人は、コマンドを知る必要がある通信のために画面にコントローラーがあるため、接続図を知るだけでは不十分であることを知っています。 たとえば、画面をオンにしてメモリからガベージを表示するには、一部のコントローラーは数十のコマンドを送信する必要があり、一部は10未満のコマンドを必要とします。 。 しかし、幸運なことに、私の画面(私の場合、EPSON S1D15G14コントローラーを備えたLPH8731-3C)の初期化とコマンドが解体されただけでなく、その上にデータシートが見つかりました。
そして、ピン配置を見て、配線をはんだ付けし、マイクロコントローラーに接続します。
LPH8731-3Cのピン配列
LPH8731-3Cのピン配列。 ( RadioCotフォーラムから取得 )
どこで:
ところで、ここでSPIがデータ転送に使用され、CLKとDATをMSP430 SPIピンに接続できることに既に気づいている人がいるかもしれません。
LPH8731-3Cのピン配列。 ( RadioCotフォーラムから取得 )
どこで:
- CS-チップセレクト。 Low状態のとき、チップは情報を受信する準備ができています。
- リセット-コントローラーをリセットするための足。 リセット信号は、High-> Low-> Highからの移行です(コントローラーの仕様によると、最小時間は5msです)。
- RS-転送されるデータのタイプを判別するのに役立ちます(データシート内で、CDとして指定されています)。 コマンドを送信するには、コマンドをLow状態にしてデータを送信する必要があります(High)。
- CLK-データ送信のクロック信号として機能します。
- DAT-データ転送用。
- VDD-仕様による+ 1.6V〜+ 3.6V。
- GND-自分で推測できるといいのですが?)
- LED_A-バックライトに電力を供給するための両方のコネクタ。 ここでは、抵抗を介して電圧を与える方が良いです(抵抗なしでも可能ですが、私の場合は、LEDの1つが過熱し始め、画面が点灯しました)。
- LED_KはGND用です。
ところで、ここでSPIがデータ転送に使用され、CLKとDATをMSP430 SPIピンに接続できることに既に気づいている人がいるかもしれません。
「バレルオルガン」を取得します
ここで、コントローラーと通信する方法を理解する必要があります。 スクリーンコントローラーの受信データには、コマンドまたはデータの2種類があります。 別のピンを使用してデータ型を選択します。 それ以外の場合、データ転送手順は同じです。
データシートから取得した、コントローラーにデータを送信する手順。 何らかの理由で、RS / CDピンのステータスはここでは示されません。 ちなみに、データ転送中にCSステータスがLow-> Highに変わると、データの受信は停止します。 ただし、データ転送の終了時には、CSをプルアップする必要はありません(ただし推奨)。
悪のコードのビット
コードはCSS(TIのCode Composer Studio)用です。
ここでは完全にではなく、たとえば断片のみです。 私はそれが好きなので、英語のコメント:)
LPH87313C.h
LPH87313C.s
ここでは完全にではなく、たとえば断片のみです。 私はそれが好きなので、英語のコメント:)
LPH87313C.h
/**********************************************************/ /* Pins and outputs */ /**********************************************************/ // Chip Select line pin1.0 #define LCD_CS BIT7 #define LCD_CS_DIR P1DIR #define LCD_CS_OUT P1OUT // Hardware Reset pin1.1 #define LCD_RESET BIT6 #define LCD_RESET_DIR P1DIR #define LCD_RESET_OUT P1OUT // Command/Data mode line pin1.4 #define LCD_CD BIT3 #define LCD_CD_DIR P1DIR #define LCD_CD_OUT P1OUT // SPI #define SPI UCA0TXBUF
LPH87313C.s
void LCD_SendCmd(unsigned char Cmd) { LCD_CS_OUT |= LCD_CS; // set CS pin to High LCD_CD_OUT &= ~LCD_CD; // set CD pin to Low LCD_CS_OUT &= ~LCD_CS; SPI = Cmd; } void LCD_SendDat(unsigned char Data) { LCD_CD_OUT |= LCD_CD; // set CD pin to High SPI = Data; }
これで、データをコントローラーに送信する方法がわかりました(まあ、少なくともアイデアがあります)。 幸いなことに、データシートにはすべてのコマンドが記載されているだけでなく、画面の初期初期化の例さえあります。 一般的に、3段階に分けることができます。コントローラーのリセット(ハードウェアとソフトウェアのリセット)を行い、初期設定を行い、ディスプレイをオンにします。
そして、ここに送信するコマンドがたくさんあります
原則として、ほとんどのパラメーターはそのまま設定されますが、変更できるものもあります。 たとえば、値がメモリに書き込まれる順序(上から下-右から左/下から上-右から左/など)、コントラスト、色深度(256色または4096)。
LPH87313C.s
LPH87313C.s
void LCD_Init() { // Set pins to output direction LCD_CS_DIR |= LCD_CS; LCD_RESET_DIR |= LCD_RESET; LCD_CD_DIR |= LCD_CD; LCD_CS_OUT &= ~LCD_CS; LCD_RESET_OUT &= ~LCD_RESET; LCD_CD_OUT &= ~LCD_CD; __delay_cycles(160000); //wait 100ms (F_CPU 16MHz) LCD_RESET_OUT |= LCD_RESET; __delay_cycles(160000); LCD_SendCmd(0x01); //reset sw __delay_cycles(80000); LCD_SendCmd(0xc6); //initial escape LCD_SendCmd(0xb9); //Refresh set LCD_SendDat(0x00); __delay_cycles(160000); LCD_SendCmd(0xb6); //Display control LCD_SendDat(0x80); // LCD_SendDat(0x04); // LCD_SendDat(0x0a); // LCD_SendDat(0x54); // LCD_SendDat(0x45); // LCD_SendDat(0x52); // LCD_SendDat(0x43); // LCD_SendCmd(0xb3); //Gray scale position set 0 LCD_SendDat(0x02); // LCD_SendDat(0x0a); // LCD_SendDat(0x15); // LCD_SendDat(0x1f); // LCD_SendDat(0x28); // LCD_SendDat(0x30); // LCD_SendDat(0x37); // LCD_SendDat(0x3f); // LCD_SendDat(0x47); // LCD_SendDat(0x4c); // LCD_SendDat(0x54); // LCD_SendDat(0x65); // LCD_SendDat(0x75); // LCD_SendDat(0x80); // LCD_SendDat(0x85); // LCD_SendCmd(0xb5); //Gamma curve LCD_SendDat(0x01); // LCD_SendCmd(0xbd); //Common driver output select LCD_SendDat(0x00); // LCD_SendCmd(0xbe); //Power control LCD_SendDat(0x54); //0x58 before LCD_SendCmd(0x11); //sleep out __delay_cycles(800000); LCD_SendCmd(0xba); //Voltage control LCD_SendDat(0x2f); // LCD_SendDat(0x03); // LCD_SendCmd(0x25); //Write contrast LCD_SendDat(0x60); // LCD_SendCmd(0xb7); //Temperature gradient LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendDat(0x00); // LCD_SendCmd(0x03); //Booster voltage ON __delay_cycles(800000); LCD_SendCmd(0x36); //Memory access control LCD_SendDat(0x48); // LCD_SendCmd(0x2d); //Color set LCD_SendDat(0x00); // LCD_SendDat(0x03); // LCD_SendDat(0x05); // LCD_SendDat(0x07); // LCD_SendDat(0x09); // LCD_SendDat(0x0b); // LCD_SendDat(0x0d); // LCD_SendDat(0x0f); // LCD_SendDat(0x00); // LCD_SendDat(0x03); // LCD_SendDat(0x05); // LCD_SendDat(0x07); // LCD_SendDat(0x09); // LCD_SendDat(0x0b); // LCD_SendDat(0x0d); // LCD_SendDat(0x0f); // LCD_SendDat(0x00); // LCD_SendDat(0x05); // LCD_SendDat(0x0b); // LCD_SendDat(0x0f); // LCD_SendCmd(0x3a); //interface pixel format LCD_SendDat(0x03); // 0x02 for 8-bit 0x03 for 12bit __delay_cycles(1600000); LCD_SendCmd(0x29); //Display ON }
私たちの住所は家や通りではなく、私たちの住所は...
ディスプレイをオンにすると、ゴミ箱または白/黒の画面が表示されます。 これは、コントローラが内部メモリに関連してマトリックスの状態を変更し、それをオンにすると、それが「記憶」しているすべてを表示するためです。 情報を表示(または変更)するには、メモリを変更するだけで十分です。コントローラーはディスプレイを更新します(初期設定で設定された特定の周波数でディスプレイを常に更新します。デフォルトの更新周波数は85Hzです)。 たとえば、ピクセルの色を変更するには、新しい値をメモリに書き込むだけです。 ただし、最初に新しい値を書き込むアドレスを指定する必要があります。 コンピュータがメモリセルのアドレスを設定して新しい値を書き込むだけの場合、データを連続して送信できるメモリ範囲を指定する必要があります。
たとえば、画面全体に表示するには、記録された領域の開始(x0、y0)と終了(x101、y80)を選択する必要があります。 また、1ピクセルのみの色を変更する必要がある場合は、それぞれ領域[x、y] [x + 1、y + 1]を設定します。
エリアを選択すると、データを送信するだけで、メモリに順次書き込まれます(左から右、上から下、またはその逆)。初期設定によって異なります)。 たとえば、40x40pxの領域を選択すると、1600個の値を連続して送信する必要があります(これは完全に正しいわけではありませんが、順番にそれ以上です)。 値を送信し続けると、更新は次のピクセル(この場合は最初のピクセル)から続行されます。
エリアコマンド
LCD_SendCmd(0x2A); // X (x0 - , x1 - ) LCD_SendDat(x0); LCD_SendDat(x1); LCD_SendCmd(0x2B); // Y (y0 - , y1 - ) LCD_SendDat(y0+1); // Y 1, 0 LCD_SendDat(y1+1); LCD_SendCmd(0x2C); //
あなたへの手紙! 中国語で真...
ディスプレイをオンにする方法と、描画する領域を選択する方法さえすでにわかっていますが、どのように色を送信しますか? ディスプレイは2つのカラーパレットで動作します。
- 8ビット(256色)
- 12ビット(4096色)
8ビットカラーの場合、すべてが単純です-各カラーに8ビットを送信するだけです(つまり、R2R1R0G2G1G0B1B0、ここでR2R1R0は赤の3ビットなど。赤と緑に3ビット、青に2ビット)。
しかし、12ビットカラーの場合、すべてが少し複雑です。 シェードごとにすでに4ビットがあります。 データシートから写真を提供します。
ご覧のとおり、1バイトを使用して1つの色を送信します。 1ピクセルのみを変更する必要がある場合、2バイトの情報が送信されますが、2番目のバイトではD3-D0は使用されません。 2ピクセルを変更する必要がある場合は、3バイトを送信します(2番目のバイトのD3-D0が始まり、3番目のバイトのD7-D0が2番目のピクセルの色の続きになります)。
再びコードの一部
画面全体の塗りつぶし関数の例。
void LCD_Flush(unsigned char R, unsigned char G, unsigned char B) { volatile int i = 4040; volatile char B0, B1, B2; B0 = ((R << 4) & 0xF0) + (G & 0x0F); B1 = ((B << 4) & 0xF0) + (R & 0x0F); B2 = ((G << 4) & 0xF0) + (B & 0x0F); LCD_SendCmd(0x2A); LCD_SendDat(0); LCD_SendDat(100); LCD_SendCmd(0x2B); LCD_SendDat(1); LCD_SendDat(80); LCD_SendCmd(0x2C); while (i--) { LCD_CD_OUT |= LCD_CD; SPI = B0; SPI = B1; SPI = B2; } }
そして、約束の時計はどこですか?
そして今、最も難しいのは時計を描くことです。 ご覧のとおり、それらはセグメントインジケータとして様式化されているため、時計を表示するには、異なる場所に2種類のセグメント(垂直および水平)を描画するだけです。
まず、設計を決定する必要があります。 MS-Paintのすばらしいプログラムのおかげで、これは本当に助かりました;)。
それは私が得たものです。 各セグメントのサイズは12x4pxです(逆に、垂直セグメントは4x12pxです)。
そして、描画する領域の選択について思い出しましょう。 適切な場所に12x4の領域を指定して、画面全体を再描画せずにセグメントを描画することもできます。 セグメントを詳しく見ると、角を除いて、ほぼ完全に1色で塗りつぶされていることがわかります。 したがって、セグメントを描画するアルゴリズムは非常に簡単です:メモリを空の色で塗りつぶし始め(残念ながら透明度がないため、背景色で塗りつぶします)、右上隅と左下隅のチェックを追加し、最後のピクセルも背景色で塗りつぶします。 同様に縦型の場合。 そして、ドットの描画方法については教えません。
理解できない言語でのわいせつな言葉のセット
水平セグメントを表示するための関数の例。 greenBrightは(アクティブなセグメントの)「明るい」色定数、greenDimは非アクティブなセグメントの色定数です。 BG0、BG1、BG2-背景をスケッチするためのビット定数。
__delay_cyclesに気づいた場合-これは不可解な魔法であり、それなしでは動作しません(ただし、ハードウェアSPIは1クロックサイクルでは送信されないため、データを送信する時間がありません(ただし、独自に送信する場合とは異なり、はるかに高速です))。
__delay_cyclesに気づいた場合-これは不可解な魔法であり、それなしでは動作しません(ただし、ハードウェアSPIは1クロックサイクルでは送信されないため、データを送信する時間がありません(ただし、独自に送信する場合とは異なり、はるかに高速です))。
void drawHorizontal(char type, unsigned char x, unsigned char y) { volatile unsigned char i = 22, B2, B1, B0; if (type) { B0 = greenBright; B1 = 0; B2 = (greenBright << 4) & 0xF0; } else { B0 = greenDim; B1 = 0; B2 = (greenDim << 4) & 0xF0; } LCD_SendCmd(0x2A); LCD_SendDat(x); LCD_SendDat(x+11); LCD_SendCmd(0x2B); LCD_SendDat(y+1); LCD_SendDat(y+4); LCD_SendCmd(0x2C); __delay_cycles(4); LCD_CD_OUT |= LCD_CD; SPI = BG0; SPI = (BG1 << 4) & 0xF0; __delay_cycles(2); SPI = B2; while(i--) { if (i == 17) { SPI = B0; __delay_cycles(2); SPI = (BG0 >> 4) & 0x0F; __delay_cycles(2); SPI = (BG0 << 4) & 0xF0 + (BG1 << 4) & 0x0F; continue; } if (i == 4) { SPI = BG0; SPI = (BG1 << 4) & 0xF0; __delay_cycles(2); SPI = B2; continue; } SPI = B0; SPI = B1; SPI = B2; } SPI = B0; __delay_cycles(2); SPI = (BG0 >> 4) & 0x0F; __delay_cycles(2); SPI = (BG0 << 4) & 0xF0 + (BG1 << 4) & 0x0F; }
次に、数値を一連のセグメントに変換する必要があります(たとえば、1を表示するには、適切な垂直セグメントのみを描画する必要があります)。 これを非常に簡単に決定しました-さまざまな数字(0〜9)のセグメント値の配列を作成しました。 数字を代入すると、セグメントのレンダリングを制御する1/0値の配列が得られます。 たとえば、1はセグメントを描画する必要があることを意味し、0はそれが不要であることを意味します(または「非アクティブ」として描画します)。 関数を作成するために、何をどこで描くかを知ることは難しくありません。
単純な配列
/******************************************************************************************** * Array for Clock * ____ * _|__1_|_ * |6| |2| * |_|____|_| * _|__7_|_ * |5| |3| * |_|____|_| * |__4_| * ********************************************************************************************/ static const char HH[10][7] = { {1,1,1,1,1,1,0}, // 0 {0,1,1,0,0,0,0}, // 1 {1,1,0,1,1,0,1}, // 2 {1,1,1,1,0,0,1}, // 3 {0,1,1,0,0,1,1}, // 4 {1,0,1,1,0,1,1}, // 5 {1,0,1,1,1,1,1}, // 6 {1,1,1,0,0,0,0}, // 7 {1,1,1,1,1,1,1}, // 8 {1,1,1,1,0,1,1} // 9 };
クロック描画コード
void drawClock(char hh, char mm, char dots) { volatile char h0, h1, m0, m1; h0 = hh / 10; h1 = hh - (h0 * 10); m0 = mm / 10; m1 = mm - (m0 * 10); drawHorizontal(HH[h0][0], 9, 25); drawHorizontal(HH[h1][0], 31, 25); drawHorizontal(HH[m0][0], 58, 25); drawHorizontal(HH[m1][0], 80, 25); drawVertical(HH[h0][5], 6, 29); drawVertical(HH[h0][1], 20, 29); drawVertical(HH[h1][5], 28, 29); drawVertical(HH[h1][1], 42, 29); drawVertical(HH[m0][5], 55, 29); drawVertical(HH[m0][1], 69, 29); drawVertical(HH[m1][5], 77, 29); drawVertical(HH[m1][1], 91, 29); drawHorizontal(HH[h0][6], 9, 38); drawHorizontal(HH[h1][6], 31, 38); drawHorizontal(HH[m0][6], 58, 38); drawHorizontal(HH[m1][6], 80, 38); drawVertical(HH[h0][4], 6, 42); drawVertical(HH[h0][2], 20, 42); drawVertical(HH[h1][4], 28, 42); drawVertical(HH[h1][2], 42, 42); drawVertical(HH[m0][4], 55, 42); drawVertical(HH[m0][2], 69, 42); drawVertical(HH[m1][4], 77, 42); drawVertical(HH[m1][2], 91, 42); drawHorizontal(HH[h0][3], 9, 51); drawHorizontal(HH[h1][3], 31, 51); drawHorizontal(HH[m0][3], 58, 51); drawHorizontal(HH[m1][3], 80, 51); drawDots(dots); }
そして、私たちは記事の終わりに来ます。 私は彼らの仕事の原理を詳細に説明できたと思います:)そして、おやつのために彼らがどのように働き、「点」を点滅させるかについての短いビデオを。
PS
おそらくこの記事では、スペル、文法、句読点の間違いがあります。 それらを見つけたら、コメントを書かないで、PMでメッセージを送ってください。
記事は完成していないため、時計の実装については書いていません:)最初は、ディスプレイ、外部フラッシュメモリ、外部時計カレンダーを使用する小さなガジェットを作成する予定でしたが、間違って時計を購入したため、すべてが起きました:)別の理由-私は、より大きなディスプレイを使用したかったが、例えば中国のノクラn95 8GBから適切なディスプレイを購入できなかった。 誰かがどこで買うか教えてもらえますか?
誰かがソースコードを必要とする場合-連絡先、私は共有することができます:)画面上の文字の出力の実装について質問がある場合(テキストを印刷する)-ソースコードも共有することができます(私はそれについて書いていません、ここでは時計に関する記事のようです: )、また、別のポストを引っ張らない)。 また、Siemens CX75(SSD-1286コントローラーには、データシートさえあります)の画面を操作するためのライブラリーを共有できます。私は自分で書きましたが、誤って書き込みました。