ぷろじぇくと、みすじら。

ILI9340な液晶をNetduinoで使う

Created at:

液晶+Netduino plus 2

aitendoで2.2インチのQVGAなSPI接続のTFT液晶を買いまして、
Arduinoで使おうと思っていろいろやったついでにNetduino plus 2で使えるかどうか試してみました。
Adafruitの2.2インチ液晶と同じコントローラ(ILI9340)を持っているものっぽいです。

ILI9340の利用例はRaspberry PiとかArduinoではちょこちょこあるしArduinoにはライブラリがあるのですが、
Netduinoでの利用例は見当たらなかったので直接コントロールして表示しています。

ちなみにこの液晶はロジックの電圧に3.3Vを要求していて5Vでつなぐとダメで、
Arduino Unoに直結すると動かないのですが(なのでTrinket ProやArduino M0 Proで先に動かした)、
Netduinoは3.3Vなので動かせそうとだなと思ってつないでコードを書いたところちゃんと動作しました。

接続はArduinoのSPIと同じで以下のような感じ。




















































LCD側ピンNetduino側ピン備考
VCC 3.3V
GND GND
CS D10 SPI / チップセレクト(SS)
RESET D8
D/C D9 コマンド用
SDI/MOSI D11 SPI
SCK D13 SPI / クロック(SCLK)
LED 3.3V
SDO/MISO D12 SPI

実装にはaitendoのデモコードとImageWriter: Raspberry Pi (9) LCD表示 1Adafruitのライブラリを参考にして、
SPIでひたすらコマンドとデータを転送する形になっています。実際のコードは以下の通りです。

なお、Netduino plus 2はRAMが100KBちょっとということもあり、QVGAのピクセルデータを全部保持することもできないので、
線や図形を適宜描画するか、画像はSDカードから読み込んで表示するといったことが必要となります。

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
using System.IO;

namespace NetduinoApplication2
{
    public class Program
    {
        // DC はコマンドを発行するときに操作する用の線
        static OutputPort _dcPort;
        // RST はリセット用の線
        static OutputPort _resetPort;
        // SPI
        static SPI _spi;

        public static void Main()
        {
            _dcPort = new OutputPort(Pins.GPIO_PIN_D9, true);
            _resetPort = new OutputPort(Pins.GPIO_PIN_D8, false);

            // SPIをセットアップ
            _spi = new SPI(new SPI.Configuration(Pins.GPIO_PIN_D10, false, 0, 0, true, true, 8000, SPI_Devices.SPI1));

            // リセットをかける
            _resetPort.Write(false); // LOW (Reset)
            Thread.Sleep(100);
            _resetPort.Write(true); // HIGH (Reset off)
            Thread.Sleep(100);

            // ILI9340を初期化する
            InitializeLcd();

            // ちなみに初期化後は白でも黒でもない中途半端な状態になるので塗りつぶしをしないとしましました風に見える

            if (false)
            { 
                // パターン1: SDカードのファイルから読み込んで画像を表示する
                // ファイルの中身は240x320の RRGGGBB のバイト列
                
                // まず描画先のアドレスを指定する: (0, 0) -> (239, 319)
                SetAddress(0, 0, 239, 319);

                // ファイルを読んで流し込む
                var path = @"SD\nanikabitmapdata.bin";
                var buffer = new byte[16 * 1024]; // 16KB ごとに読み出す
                using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
                {
                    int readLen = 0;

                    while (true)
                    {
                        readLen = stream.Read(buffer, 0, buffer.Length);

                        if (readLen == 0) break;

                        // byte列で送信しないととても遅いので注意
                        if (readLen != buffer.Length)
                        {
                            var tmp = new byte[readLen];
                            Array.Copy(buffer, tmp, readLen);

                            WriteData(tmp);
                        }
                        else
                        {
                            WriteData(buffer);
                        }
                    }
                }
            }
            else
            {
                // パターン2: ピクセルを直接操作して矩形を書く
                ushort x = 100;
                ushort y = 100;
                ushort width = 40;
                ushort height = 40;
                ushort color = 0x07E0; // 緑

                // まず描画先のアドレスを指定する: (100, 100) -> (139, 139)
                SetAddress(x, y, (ushort)(x + width - 1), (ushort)(y + height - 1));
                // ピクセルデータを流し込む
                for (var i = 0; i < width * height; i++)
                {
                    WriteData(color);
                }
            }
        }

        static void InitializeLcd()
        {
            // 診断用コード
            Debug.Print("Display Power Mode: 0x" + ReadCommand(0x0A).ToString("x"));
            Debug.Print("MADCTL Mode: 0x" + ReadCommand(0x0B).ToString("x"));
            Debug.Print("Pixel Format: 0x" + ReadCommand(0x0C).ToString("x"));
            Debug.Print("Image Format: 0x" + ReadCommand(0x0A).ToString("x"));
            Debug.Print("Self Diagnostic: 0x" + ReadCommand(0x0F).ToString("x"));

            // この辺はILI9340の初期化コード
            // コマンドとデータはAdafruitのライブラリを参考にするとてっとり早いです
            // https://github.com/adafruit/Adafruit_ILI9340/blob/master/Adafruit_ILI9340.h
            WriteCommandAndData(0xEF, new byte[] { 0x03, 0x80, 0x02 });
            WriteCommandAndData(0xCF, new byte[] { 0x00, 0xC1, 0x30 });
            WriteCommandAndData(0xED, new byte[] { 0x64, 0x03, 0x12, 0x81 });
            WriteCommandAndData(0xE8, new byte[] { 0x85, 0x00, 0x78 });
            WriteCommandAndData(0xCB, new byte[] { 0x39, 0x2C, 0x00, 0x34, 0x02 });
            WriteCommandAndData(0xF7, new byte[] { 0x20 });
            WriteCommandAndData(0xEA, new byte[] { 0x00, 0x00 });
            WriteCommandAndData(0xC0, new byte[] { 0x23 });
            //Power control
            //SAP[2:0];BT[3:0]
            WriteCommandAndData(0xC1, new byte[] { 0x10 });
            //VCM control
            WriteCommandAndData(0xC5, new byte[] { 0x3e, 0x28 });
            //VCM control2
            WriteCommandAndData(0xC7, new byte[] { 0x86 });
            // Memory Access Control
            WriteCommandAndData(0x36, new byte[] { 0x48 }); // 回転はこの辺
            WriteCommandAndData(0x3A, new byte[] { 0x55 });
            WriteCommandAndData(0xB1, new byte[] { 0x00, 0x18 });
            // Display Function Control
            WriteCommandAndData(0xB6, new byte[] { 0x08, 0x82, 0x27 });
            // 3Gamma Function Disable
            WriteCommandAndData(0xF2, new byte[] { 0x00 });
            //Gamma curve selected 
            WriteCommandAndData(0x26, new byte[] { 0x01 });
            //Set Gamma
            WriteCommandAndData(0xE0, new byte[] { 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00 });
            //Set Gamma
            WriteCommandAndData(0XE1, new byte[] { 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F });
            //Exit Sleep
            WriteCommand(0x11);

            Thread.Sleep(120);

            //Display on
            WriteCommand(0x29);
        }

        /// <summary>
        /// 描画先アドレスを設定する
        /// </summary>
        /// <param name="x">開始X座標</param>
        /// <param name="y">開始Y座標</param>
        /// <param name="x2">終了X座標</param>
        /// <param name="y2">終了Y座標</param>
        static void SetAddress(ushort x, ushort y, ushort x2, ushort y2)
        {
            WriteCommand(0x2A);
            WriteData(x);
            WriteData(x2);
            WriteCommand(0x2B);
            WriteData(y);
            WriteData(y2);
            WriteCommand(0x2C);
        }

        /// <summary>
        /// コマンドを発行して結果を取得します。
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        static byte ReadCommand(byte command)
        {
            var buffer = new byte[1];
            buffer[0] = command;

            _dcPort.Write(false);
            _spi.WriteRead(buffer, buffer);

            _dcPort.Write(true);
            buffer[0] = 0x0;
            _spi.WriteRead(buffer, buffer);

            return buffer[0];
        }

        /// <summary>
        /// コマンドを発行します。
        /// </summary>
        /// <param name="command"></param>
        static void WriteCommand(byte command)
        {
            var buffer = new byte[1];
            buffer[0] = command;

            _dcPort.Write(false);
            _spi.WriteRead(buffer, buffer);
        }

        /// <summary>
        /// 符号なし16bit整数データを書き込みます。
        /// </summary>
        /// <param name="data"></param>
        static void WriteData(ushort data)
        {
            var hi = (byte)(data >> 8);
            var lo = (byte)(data & 0xFF);

            WriteData(new[] { hi, lo });
        }

        /// <summary>
        /// バイトデータを書き込みます。
        /// </summary>
        /// <param name="data"></param>
        static void WriteData(byte data)
        {
            WriteData(new[] { data });
        }

        /// <summary>
        /// バイト列データを書き込みます
        /// </summary>
        /// <param name="data"></param>
        static void WriteData(byte[] data)
        {
            _dcPort.Write(true);
            _spi.Write(data);
        }

        /// <summary>
        /// コマンドを発行してデータを書き込みます。
        /// </summary>
        /// <param name="command"></param>
        /// <param name="data"></param>
        static void WriteCommandAndData(byte command, byte[] data)
        {
            WriteCommand(command);
            WriteData(data);
        }
    }
}

液晶で何か表示できると結構面白そうな感じなのです。