この画面には明確なドキュメントが見つかりませんでしたので、実際に対処して実験する必要がありました。 制御デバイスとして、Raspberry PIを使用しました。 この画面をミニモニターに変えるプログラムも作成されました。
説明
このディスプレイの解像度は132 x 176ピクセルで、16(5-6-5)、12(4-4-4)、8(3-3-2)ビットの3つのカラーパレットで作業できます。
ピン配置と接続
ここではすべてが簡単です。画面は2.9ボルトの電圧で駆動され、バックライト(LED±)は約12ボルトの電圧で個別に駆動されます(510オームの抵抗を介してバックライトに接続されたバッテリーを使用しました)。
ピンの説明 | ||
---|---|---|
# | お名前 | 機能 |
1 | RS | 低= CMD、高=データ |
2 | 〜Rst | リセット入力アクティブアクティブ |
3 | 〜Cs | チップセレクト、アクティブロー |
4 | 同期 | 外部フレーム同期入力、デフォルトでは未使用 |
5 | CLK | SPIクロックイン信号(高から低) |
6 | データ | SPIデータ入力信号(MSBファースト) |
7 | Vcc | 電源、通常2.9V(3.3Vでテスト済み) |
8 | GND | 地上 |
9 | LED + | バックライト電圧、約 12V(必要な電流に依存) |
10 | LED | バックライト共通ピン |
また、画面制御に直接進む前に、この画面を何かに接続する必要があります。 私の場合は、Raspberry Piになります。 画面のSPI接点は、それぞれGPIO_17およびGPIO_27の対応するラズベリー、RS、およびRST SPI接点に接続されます。 この接続はRPI Revision-2に関連しています。異なるモデルを使用している場合、GPIOの名前と連絡先番号は異なる場合があります。
私はスクリーン接続コネクタを気にせず、MGTFワイヤで結論にはんだ付けしました。 この接続の画面は、説明のように2.9ではなく3.3Vから給電されます。
これは、回路全体がどのように見えるかです
画面制御コマンド
画面は非常に簡単に制御されます-コマンドとデータをSPI経由で送信します。 RSピンの状態は、1つの画面を別の画面と区別するのに役立ちます。高レベル(ログ1)はデータ転送を意味し、低(ログ0)コマンドは送信を意味します。 転送では、ビッグエンディングのバイト順が使用されます。
コマンドのリスト:
- CMD_RESET 0x01-ソフトリセット
- CMD_MEMORY_ACCESS_CONTROL 0x36-表示領域を塗りつぶす方向を設定します。シングルバイト引数0bVHRXXXXXがあります。
V-垂直充填(0-トップダウン、1-ボトムアップ)、
H-水平方向の塗りつぶし(0-左から右、1-右から左)、
R-行と列が入れ替わります(塗りつぶしは上から下、左から右のままです) - CMD_WAKEUP 0x11-ウェイクアップ
- CMD_PALETTE 0x3A-カラーパレットを8(0x02)、12(0x03)、および16(0x05)ビットに設定します
- CMD_ENABLE 0x29-ディスプレイをオンにします
- CMD_SET_X 0x2A-Xで描画領域を設定します
- CMD_SET_Y 0x2B-描画領域をYに設定します
- CMD_START_WRITE 0x2C-ビデオメモリへの記録を開始
コード
画面は3つのカラーモードすべてでテストされましたが、ソースを乱雑にしないために、16ビットのみを検討します。 他のすべてのモードでは、12ビットモードを除き、画面に違いはありません-2ピクセルあたり3バイト、1ポイントのみを出力する必要がある場合、2バイトが送信されます(最後の画面の最下位4ビットは無視されます)。
bcm2835ライブラリは、ラズベリーのGPIOにアクセスするために使用されました。
初期GPIO初期化
int init_gpio() { if (!bcm2835_init()) return 0; bcm2835_spi_begin(); bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST); // CPOL = 0, CPHA = 0, Clock idle low, data is clocked in on rising edge, output data (change) on falling edge bcm2835_spi_setDataMode(BCM2835_SPI_MODE0); // SPI 13 // // (30 ) bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_16); ///< 16 = 64ns = 15.625MHz bcm2835_spi_chipSelect(BCM2835_SPI_CS0); /// Select Our Device bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, LOW); // bcm2835_gpio_fsel(LCD_RS, BCM2835_GPIO_FSEL_OUTP); bcm2835_gpio_fsel(LCD_RESET, BCM2835_GPIO_FSEL_OUTP); return 1; }
いくつかのヘルパー関数
void send_cmd(char cmd) { bcm2835_gpio_write(LCD_RS, RS_CMD); // - bcm2835_spi_transfer(cmd); } void send_data(char data) { bcm2835_gpio_write(LCD_RS, RS_DATA); // - bcm2835_spi_transfer(data); }
描画領域を設定する
void set_draw_area(char x1, char y1, char x2, char y2) { send_cmd(CMD_SET_X); send_data(x1); send_data(x2); send_cmd(CMD_SET_Y); send_data(y1); send_data(y2); }
画面での実験の結果、各フレームの出力の前に領域を設定する必要はなく、一度だけ設定して記録を開始するコマンドを送信するだけで十分であり、単純に連続ストリームでSPIによってフレームのシーケンスを駆動することがわかりました。
出力とデータ転送の準備
void draw_start() { send_cmd(CMD_START_WRITE); bcm2835_gpio_write(LCD_RS, RS_DATA); } void send_draw_data(char* data, int size) { bcm2835_spi_transfern(data, size); }
画面を初期化する過程で、面倒なことが発見されました-起動するには画面のリセットを歪める必要がありますが、この出力でのその後の操作は画面をthe迷に導き、外部の影響への応答を停止します。 私たちは栄養にそれを捨てなければなりません。 ハードウェアリセット手順が1回だけ実行されるように、プログラムを開発する際にこれを考慮する必要があります。
画面の初期化
void reset_LCD() { // bcm2835_gpio_write(LCD_RESET, LOW); bcm2835_delay(50); bcm2835_gpio_write(LCD_RESET, HIGH); bcm2835_delay(50); // send_cmd(CMD_RESET); } void init_LCD() { reset_LCD(); send_cmd(CMD_MEMORY_ACCESS_CONTROL); send_data(0b00000000); send_cmd(CMD_WAKEUP); bcm2835_delay(20); send_cmd(CMD_PALETTE); send_data(_16_BIT_COLOR); bcm2835_delay(20); send_cmd(CMD_ENABLE); }
これは準備でしたが、今では最も美味しいです-16ビットの画像を表示してみましょう。 ご存知のように、最初のパンケーキはゴツゴツしていて、プログラムを開始した後、私はかなり奇妙なイメージを得ました-修正後にすべてがうまくいった-それは間違ったバイト順です。
コード
int main(int argc, char **argv) { if (!init_gpio()) return 1; init_LCD(); set_draw_area(0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1); draw_start(); uint16_t screen[SCREEN_HEIGHT][SCREEN_WIDTH]; FILE* f_scr = fopen("test.bmp", "r"); fseek(f_scr, 0x42, SEEK_SET); // skip bmp header fread(&screen, 1, SCREEN_HEIGHT * SCREEN_WIDTH * 2/*16bit*/, f_scr); fclose(f_scr); // change byte order for(int x = 0; x < SCREEN_WIDTH; x++) for(int y = 0; y < SCREEN_HEIGHT; y++) screen[y][x] = (screen[y][x] >> 8) | (screen[y][x] << 8); send_draw_data((char*)&screen[0][0], SCREEN_WIDTH*SCREEN_HEIGHT*2/*16 bit*/); close_gpio(); return 0; }
編集前後の画像
モニターとしてのLCD
実験の最初から、画面を「ラズベリー」のモニターとして使用するという考えから離れることはありませんでした。
アイデアは単純です-画像は/ dev / fb0から取得され、16ビットでサイズ変更され、画面に表示されます。
1024x768 => 176x132の画像の圧縮結果はあまり有益ではないため、フレームバッファは320x240に設定されました。これは、「ラズベリー」フラッシュドライブのFATセクションのconfig.txtを編集することで実行できます。
framebuffer_width=320 framebuffer_height=240
その後、画像はまだプリミティブ補間を使用して圧縮されますが、結果はすでに許容可能と呼ばれる可能性があります。 CPUを節約するために、同じフレームをスキップすることも追加されました。
ソースLPH9157-2_RPI.c
#include <bcm2835.h> #include <stdio.h> #include <sys/mman.h> #include <fcntl.h> #include <time.h> #include <string.h> #include <unistd.h> // GPIO LCD #define LCD_RS RPI_V2_GPIO_P1_11 #define LCD_RESET RPI_V2_GPIO_P1_13 #define RS_CMD 0 #define RS_DATA 1 #define CMD_RESET 0x01 #define CMD_MEMORY_ACCESS_CONTROL 0x36 // Memory Access Control #define CMD_WAKEUP 0x11 // #define CMD_PALETTE 0x3A // #define CMD_ENABLE 0x29 // #define CMD_SET_X 0x2A // X #define CMD_SET_Y 0x2B // Y #define CMD_START_WRITE 0x2C // #define _8_BIT_COLOR 0x02 #define _12_BIT_COLOR 0x03 #define _16_BIT_COLOR 0x05 #define SCREEN_WIDTH 132 #define SCREEN_HEIGHT 176 int init_gpio() { if (!bcm2835_init()) return 0; bcm2835_spi_begin(); bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST); bcm2835_spi_setDataMode(BCM2835_SPI_MODE0); /// CPOL = 0, CPHA = 0, Clock idle low, /// data is clocked in on rising edge, /// output data (change) on falling edge bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_16); ///< 16 = 64ns = 15.625MHz bcm2835_spi_chipSelect(BCM2835_SPI_CS0); /// Select Our Device bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, LOW); // bcm2835_gpio_fsel(LCD_RS, BCM2835_GPIO_FSEL_OUTP); bcm2835_gpio_fsel(LCD_RESET, BCM2835_GPIO_FSEL_OUTP); return 1; } int close_gpio() { bcm2835_spi_end(); bcm2835_close(); } void send_cmd(char cmd) { bcm2835_gpio_write(LCD_RS, RS_CMD); bcm2835_spi_transfer(cmd); } void send_data(char data) { bcm2835_gpio_write(LCD_RS, RS_DATA); bcm2835_spi_transfer(data); } void send_data_array(char* data, int size) { bcm2835_gpio_write(LCD_RS, RS_DATA); bcm2835_spi_transfern(data, size); } void set_draw_area(char x1, char y1, char x2, char y2) { send_cmd(CMD_SET_X); send_data(x1); send_data(x2); send_cmd(CMD_SET_Y); send_data(y1); send_data(y2); } void draw_start() { send_cmd(CMD_START_WRITE); bcm2835_gpio_write(LCD_RS, RS_DATA); } void send_draw_data(char* data, int size) { bcm2835_spi_transfern(data, size); } void reset_LCD() { bcm2835_gpio_write(LCD_RESET, LOW); bcm2835_delay(50); bcm2835_gpio_write(LCD_RESET, HIGH); bcm2835_delay(50); send_cmd(CMD_RESET); } void init_LCD() { reset_LCD(); send_cmd(CMD_MEMORY_ACCESS_CONTROL); send_data(0b00000000); send_cmd(CMD_WAKEUP); bcm2835_delay(20); send_cmd(CMD_PALETTE); send_data(_16_BIT_COLOR); bcm2835_delay(20); send_cmd(CMD_ENABLE); } #define FB_WIDTH 320// 176 #define FB_HEIGHT 240// 144 int main(int argc, char **argv) { if (!init_gpio()) return 1; int smooth = 0; int dynamic_fps = 0; int argn = 1; while(argn < argc) { if(argv[argn][0] == '-') switch(argv[argn][1]) { case 'i': init_LCD(); close_gpio(); printf("lcd initialized\n"); return 0; break; case 's': smooth = 1; break; case 'd': dynamic_fps = 1; break; default: printf("Usage: lcd [options]\n"); printf("Options:\n"); printf(" -i initialize lcd (hardware reset)\n"); printf(" -d dynamic FPS (skip same frames)\n"); printf(" -s smooth image (enable basic intrpolation)\n"); return 0; break; } argn++; } ///------------------------------------------------ /// draw screen set_draw_area(0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1); draw_start(); uint16_t screen[SCREEN_HEIGHT][SCREEN_WIDTH]; uint16_t old_fb[FB_HEIGHT * FB_WIDTH]; int fd_scr = open("/dev/fb0", O_RDONLY); int scr_sz = FB_HEIGHT * FB_WIDTH * 2/*16bit*/; uint16_t* fb_screenshot = mmap(0, scr_sz, PROT_READ, MAP_PRIVATE, fd_scr, 0); // scaling float scale_X = FB_HEIGHT / (float)SCREEN_WIDTH; float scale_Y = FB_WIDTH / (float)SCREEN_HEIGHT; int frame_cnt = 0; struct timespec ts1, ts2; clock_gettime(CLOCK_MONOTONIC, &ts1); for(;;) // forever { if(dynamic_fps) if(memcmp(&old_fb, fb_screenshot, sizeof(old_fb)) == 0) { usleep(10000); continue; } else { memcpy(&old_fb, fb_screenshot, sizeof(old_fb)); } for(int x = 0; x < SCREEN_WIDTH; x++) for(int y = 0; y < SCREEN_HEIGHT; y++) { int fb_x = y * scale_X; int fb_y = x * scale_Y; uint16_t px = fb_screenshot[fb_x + fb_y * FB_WIDTH]; if(smooth) { // look around if((fb_x - 1 >= 0) && (fb_x + 1 < FB_WIDTH) && (fb_y - 1 >= 0) && (fb_y + 1 < FB_HEIGHT)) { for(int dx = -1; dx <= 1; dx++) for(int dy = -1; dy <= 1; dy++) if((dx == 0) ^ (dy == 0)) { uint16_t add_px = fb_screenshot[(fb_x + dx) + (fb_y + dy) * FB_WIDTH]; px = ((px & 0xf7de) >> 1) + ((add_px & 0xf7de) >> 1); /// ^thank you habr => http://habr.ru/p/128773/ } } } screen[y][SCREEN_WIDTH - 1 - x] = (px << 8) | (px >> 8); } send_draw_data((char*)&screen[0][0], sizeof(screen)); /// calc fps frame_cnt++; if(frame_cnt >= 100) { clock_gettime(CLOCK_MONOTONIC, &ts2); float allsec = (ts2.tv_sec - ts1.tv_sec) + (ts2.tv_nsec - ts1.tv_nsec) / 1000000000.0; float fps = frame_cnt / allsec; printf("%f FPS\n", fps); frame_cnt = 0; clock_gettime(CLOCK_MONOTONIC, &ts1); } usleep(1000); } munmap(fb_screenshot, scr_sz); close(fd_scr); close_gpio(); printf("fin\n"); return 0; }
組立:
pi@raspberrypi ~ $ gcc -o lcd LPH9157-2_RPI.c -lbcm2835 -lrt -std=gnu99
打ち上げ:
pi@raspberrypi ~ $ sudo ./lcd -i pi@raspberrypi ~ $ sudo ./lcd -d -s
パラメータ:
-i-プライマリ初期化(ハードリセットのプル)
-s-アンチエイリアスを有効にします
-d-ダイナミックfps(同一のフレームはスキップされます-CPUを節約します)