現時点の情報です。最新情報はM5StickC非公式日本語リファレンスを確認してみてください。
概要
M5StickCの画面制御で使われているST7735関連のソースコードを読んでみました。
ST7735とは?
ST7735とST7735Sがあるようですが、M5Stack社が公開しているデータシートからM5StickCで使われているのはSがついている方だと思います。(違いはわかりません、、、最近はS付きばかり売られている?)
ST7735は132×162ドットのフレームバッファを内蔵しており、フレームバッファに16ビットの色情報を書き込むことで、いい感じに液晶に表示してくれるコントローラーです。
液晶とコントローラーは個別に選択することができますが、個人で買えるモジュールはセットで販売されていると思います。
ST7735_Defines.h
使っている液晶などの設定をするファイルです。
液晶のサイズ指定
// Change the width and height if required (defined in portrait mode) // or use the constructor to over-ride defaults #ifndef TFT_WIDTH #define TFT_WIDTH 80 #endif #ifndef TFT_HEIGHT #define TFT_HEIGHT 160 #endif
M5StickCの液晶は縦長なので80×160と定義されています。この値が画面用のクラスであるTFT_eSPIのコンストラクタの引数として指定されています。
他のサイズの液晶を使う場合には、宣言を書き換えるかコンストラクタの引数の引数で与えろとコメントがありますね。
class TFT_eSPI : public Print { public: TFT_eSPI(int16_t _W = TFT_WIDTH, int16_t _H = TFT_HEIGHT);
利用している液晶選択
//#define ST7735_INITB // #define ST7735_INITB // #define ST7735_GREENTAB // #define ST7735_GREENTAB2 //#define ST7735_GREENTAB3 // #define ST7735_GREENTAB128 // For 128 x 128 display #define ST7735_GREENTAB160x80 // For 160 x 80 display (BGR, inverted, 26 offset) //#define ST7735_REDTAB //#define ST7735_BLACKTAB //#define ST7735_REDTAB160x80 // Enumerate the different configurations
ここではM5StickCで利用しているST7735_GREENTAB160x80だけコメントが外されており、それ以外はコメントアウトされています。
グリーンタブというのが、わかりにくいのですが新品の液晶を購入した場合の液晶保護シートについているタブの色です。上記が緑で80×160なのでM5StickCと同じものだと思います。
いまは緑ばかりですが、上記のようにREDTABもあります。
液晶の列挙
#define INITR_GREENTAB 0x0 #define INITR_REDTAB 0x1 #define INITR_BLACKTAB 0x2 // Display with no offsets #define INITR_GREENTAB2 0x3 // Use if you get random pixels on two edges of green tab display #define INITR_GREENTAB3 0x4 // Use if you get random pixels on edge(s) of 128x128 screen #define INITR_GREENTAB128 0x5 // Use if you only get part of 128x128 screen in rotation 0 & 1 #define INITR_GREENTAB160x80 0x6 // Use if you only get part of 128x128 screen in rotation 0 & 1 #define INITR_REDTAB160x80 0x7 // Added for https://www.aliexpress.com/item/ShengYang-1pcs-IPS-0-96-inch-7P-SPI-HD-65K-Full-Color-OLED-Module-ST7735-Drive/32918394604.html #define INITB 0xB
上記の選択に近いですが、ここは定義されている液晶が列挙されています。INITR_REDTAB160x80とかのURLを見ると、中身は緑に変わっていたりと商品はどんどん入れ替わるので気をつけましょう!
ここに無いものは自分で定義することになると思います。
液晶の確定
#if defined (ST7735_INITB) #define TAB_COLOUR INITB ...(略) #elif defined (ST7735_GREENTAB160x80) #define TAB_COLOUR INITR_GREENTAB160x80 #define CGRAM_OFFSET #elif defined (ST7735_REDTAB160x80) #define TAB_COLOUR INITR_REDTAB160x80 #define CGRAM_OFFSET
ST7735_GREENTAB160x80を指定したので、内部で使う設定はINITR_GREENTAB160x80になります。また、CGRAM_OFFSETという宣言が追加されています。
この宣言がある液晶と、ない液晶があるので、ST7735_*で選択したものをここの#IFで切り分けています。
M5StickCの液晶は80×160ですので、フレームバッファには収まっています。

上記はちょっと大げさに書いてありますが、フレームバッファのどこを画面に表示するのかは、液晶によって違います。上記の場合液晶は右上に寄っています。この場合左側には使われない領域があります。(説明用の図なので実際とは違います!)
初期化を間違えてしまうと左上から描画してしまうので、一番端の列は変な色になってしまいます。obnizの初期ライブラリはこの状態なので、全面塗りつぶしても、よく見ると端っこの色が変です。
上記のように、描画する座標を調整する必要があるって宣言がCGRAM_OFFSETみたいです。
色の宣言
// Color definitions for backwards compatibility with old sketches // use colour definitions like TFT_BLACK to make sketches more portable #define ST7735_BLACK 0x0000 /* 0, 0, 0 */ #define ST7735_NAVY 0x000F /* 0, 0, 128 */ #define ST7735_DARKGREEN 0x03E0 /* 0, 128, 0 */ #define ST7735_DARKCYAN 0x03EF /* 0, 128, 128 */ #define ST7735_MAROON 0x7800 /* 128, 0, 0 */ #define ST7735_PURPLE 0x780F /* 128, 0, 128 */ #define ST7735_OLIVE 0x7BE0 /* 128, 128, 0 */ #define ST7735_LIGHTGREY 0xC618 /* 192, 192, 192 */ #define ST7735_DARKGREY 0x7BEF /* 128, 128, 128 */ #define ST7735_BLUE 0x001F /* 0, 0, 255 */ #define ST7735_GREEN 0x07E0 /* 0, 255, 0 */ #define ST7735_CYAN 0x07FF /* 0, 255, 255 */ #define ST7735_RED 0xF800 /* 255, 0, 0 */ #define ST7735_MAGENTA 0xF81F /* 255, 0, 255 */ #define ST7735_YELLOW 0xFFE0 /* 255, 255, 0 */ #define ST7735_WHITE 0xFFFF /* 255, 255, 255 */ #define ST7735_ORANGE 0xFD20 /* 255, 165, 0 */ #define ST7735_GREENYELLOW 0xAFE5 /* 173, 255, 47 */ #define ST7735_PINK 0xF81F #define BLACK 0x0000 /* 0, 0, 0 */ #define NAVY 0x000F /* 0, 0, 128 */ #define DARKGREEN 0x03E0 /* 0, 128, 0 */ #define DARKCYAN 0x03EF /* 0, 128, 128 */ #define MAROON 0x7800 /* 128, 0, 0 */ #define PURPLE 0x780F /* 128, 0, 128 */ #define OLIVE 0x7BE0 /* 128, 128, 0 */ #define LIGHTGREY 0xC618 /* 192, 192, 192 */ #define DARKGREY 0x7BEF /* 128, 128, 128 */ #define BLUE 0x001F /* 0, 0, 255 */ #define GREEN 0x07E0 /* 0, 255, 0 */ #define CYAN 0x07FF /* 0, 255, 255 */ #define RED 0xF800 /* 255, 0, 0 */ #define MAGENTA 0xF81F /* 255, 0, 255 */ #define YELLOW 0xFFE0 /* 255, 255, 0 */ #define WHITE 0xFFFF /* 255, 255, 255 */ #define ORANGE 0xFD20 /* 255, 165, 0 */ #define GREENYELLOW 0xAFE5 /* 173, 255, 47 */ #define PINK 0xF81F
色を宣言しています。ST7735は16ビットで色を制御しています。RGB順に並んでいますがRが5ビット、Gが6ビット、Bが5ビットで制御しています。
01234567 01234567 RRRRRGGG GGGBBBBB
16ビットの場合にはこの他に透明色のAを追加してRGB各5ビットの場合もあります。
コマンドと設定値
// Delay between some initialisation commands #define TFT_INIT_DELAY 0x80 // Generic commands used by TFT_eSPI.cpp #define TFT_NOP 0x00 #define TFT_SWRST 0x01 #define TFT_CASET 0x2A #define TFT_PASET 0x2B #define TFT_RAMWR 0x2C #define TFT_RAMRD 0x2E #define TFT_IDXRD 0x00 //0xDD // ILI9341 only, indexed control register read #define TFT_MADCTL 0x36 #define TFT_MAD_MY 0x80 #define TFT_MAD_MX 0x40 #define TFT_MAD_MV 0x20 #define TFT_MAD_ML 0x10 #define TFT_MAD_BGR 0x08 #define TFT_MAD_MH 0x04 #define TFT_MAD_RGB 0x00 #define TFT_INVOFF 0x20 #define TFT_INVON 0x21 // ST7735 specific commands used in init #define ST7735_NOP 0x00 #define ST7735_SWRESET 0x01 #define ST7735_RDDID 0x04 #define ST7735_RDDST 0x09 #define ST7735_SLPIN 0x10 #define ST7735_SLPOUT 0x11 #define ST7735_PTLON 0x12 #define ST7735_NORON 0x13 #define ST7735_INVOFF 0x20 #define ST7735_INVON 0x21 #define ST7735_DISPOFF 0x28 #define ST7735_DISPON 0x29 #define ST7735_CASET 0x2A #define ST7735_RASET 0x2B // PASET #define ST7735_RAMWR 0x2C #define ST7735_RAMRD 0x2E #define ST7735_PTLAR 0x30 //Add #define ST7735_VSCRDEF 0x33 #define ST7735_COLMOD 0x3A #define ST7735_MADCTL 0x36 #define ST7735_VSCRSADD 0x37 #define ST7735_FRMCTR1 0xB1 #define ST7735_FRMCTR2 0xB2 #define ST7735_FRMCTR3 0xB3 #define ST7735_INVCTR 0xB4 #define ST7735_DISSET5 0xB6 #define ST7735_PWCTR1 0xC0 #define ST7735_PWCTR2 0xC1 #define ST7735_PWCTR3 0xC2 #define ST7735_PWCTR4 0xC3 #define ST7735_PWCTR5 0xC4 #define ST7735_VMCTR1 0xC5 #define ST7735_RDID1 0xDA #define ST7735_RDID2 0xDB #define ST7735_RDID3 0xDC #define ST7735_RDID4 0xDD #define ST7735_PWCTR6 0xFC #define ST7735_GMCTRP1 0xE0 #define ST7735_GMCTRN1 0xE1
いろいろコマンドとかが列挙されていますが、ここだけみてもわからないので実際のコードのところで理解したいと思います。
ST7735_Init.h
液晶の初期化をしているファイルです。
初期化コマンド
// Initialization commands for ST7735 screens static const uint8_t PROGMEM Bcmd[] = { // Initialization commands for 7735B screens 18, // 18 commands in list: ST7735_SWRESET, TFT_INIT_DELAY, // 1: Software reset, no args, w/delay 50, // 50 ms delay ST7735_SLPOUT , TFT_INIT_DELAY, // 2: Out of sleep mode, no args, w/delay 255, // 255 = 500 ms delay ST7735_COLMOD , 1+TFT_INIT_DELAY, // 3: Set color mode, 1 arg + delay: 0x05, // 16-bit color 10, // 10 ms delay ...(略)
この辺は定形の決り文句みたいなので、無視します。データシートをみると何をやっているかはわかると思いますが、定形なので順番に必要なパラメータをセットしているだけのようです。
液晶初期化
if (tabcolor == INITB) { commandList(Bcmd); } else { commandList(Rcmd1); if (tabcolor == INITR_GREENTAB) { commandList(Rcmd2green); colstart = 2; rowstart = 1; } else if (tabcolor == INITR_GREENTAB2) { commandList(Rcmd2green); writecommand(ST7735_MADCTL); writedata(0xC0); colstart = 2; rowstart = 1; } else if (tabcolor == INITR_GREENTAB3) { commandList(Rcmd2green); colstart = 2; rowstart = 3; } else if (tabcolor == INITR_GREENTAB128) { commandList(Rcmd2green); colstart = 0; rowstart = 32; } else if (tabcolor == INITR_GREENTAB160x80) { commandList(Rcmd2green); writecommand(TFT_INVON); colstart = 26; rowstart = 1; } else if (tabcolor == INITR_REDTAB160x80) { commandList(Rcmd2green); colstart = 24; rowstart = 0; } else if (tabcolor == INITR_REDTAB) { commandList(Rcmd2red); } else if (tabcolor == INITR_BLACKTAB) { writecommand(ST7735_MADCTL); writedata(0xC0); } commandList(Rcmd3); }
上記で初期化しています。M5StickCはINITR_GREENTAB160x80なので、ハイライトされている場所が実行されます。
commandList(Rcmd1)
ブラックタブ以外の共通初期化処理みたいです。
commandList(Rcmd2green)
グリーンタブの共通初期化処理。
writecommand(TFT_INVON)
他で利用していないコマンドがでてきました!
宣言を調べると、INVのONとOFFがあります。
#define TFT_INVOFF 0x20 #define TFT_INVON 0x21
ここからは20とか21を参考にデータシートを調べます。

INVOFFはデフォルトの状態で、フレームバッファのメモリをそのまま表示させるモードみたいです。

INVONはフレームバッファのメモリの輝度を反転させて転送するモードかな?
液晶画面によって0が一番暗い状態が一般的ですが、たまに反転していて0が一番明るい状態の物があります。
M5Stackだと途中で使っている液晶モジュールが変わって、古いライブラリだと色がおかしくなった事象があったと思います。液晶の色が反転してみる場合には、この設定も疑いましょう。
colstart = 26, rowstart = 1
この設定がフレームバッファと液晶の表示座標のズレを指定しています。横1ドット、縦26ドットずれた場所から描画されているみたいです。
commandList(Rcmd3)
こちらもブラックタブ以外の共通初期化処理みたいです。
ST7735_Rotation.h
画面の回転をした場合の処理です。
入力値チェック
rotation = m % 4; // Limit the range of values to 0-3
画面の方向は0から3までなので、変な値が来ても大丈夫なように4のあまりで0-3の値に変更しています。
writecommand(TFT_MADCTL)
コマンドを投げていますね。データシートを調べます。

上記が定義です。ちょっとこれだけだと、よくわからないですね。
ビット | 概要 |
MY | Y軸反転 |
MX | X軸反転 |
MV | X軸とY軸を入れ替える |
ML | 横方向の描画方向 |
MH | 縦方向の描画方向 |
RGB | RGBかBGR |
上記がどのように使われているのかを確認します。
case 0: if (tabcolor == INITR_BLACKTAB) { writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_RGB); } else if(tabcolor == INITR_GREENTAB2) { writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_RGB); colstart = 2; rowstart = 1; } else if(tabcolor == INITR_GREENTAB3) { writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_BGR); colstart = 2; rowstart = 3; } else if(tabcolor == INITR_GREENTAB128) { writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_MH | TFT_MAD_BGR); colstart = 0; rowstart = 32; } else if(tabcolor == INITR_GREENTAB160x80) { writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_MH | TFT_MAD_BGR); colstart = 26; rowstart = 1; } else if(tabcolor == INITR_REDTAB160x80) { writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_MH | TFT_MAD_BGR); colstart = 24; rowstart = 0; } else if(tabcolor == INITB) { writedata(TFT_MAD_MX | TFT_MAD_RGB); } else { writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_BGR); } _width = _init_width; _height = _init_height; break;
方向が0なので、M5StickCだと縦においた場合の画面方向です。
M5StickCはINITR_GREENTAB160x80なので、TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_MH | TFT_MAD_BGRが指定されていました。
MXとMYはだいたい入っているので、よくわからないですね。M5StickCのだけ抜粋したコードを見てみます。
switch (rotation) { case 0: writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_MH | TFT_MAD_BGR); colstart = 26; rowstart = 1; _width = _init_width; _height = _init_height; break; case 1: writedata(TFT_MAD_MV | TFT_MAD_MY | TFT_MAD_BGR); colstart = 1; rowstart = 26; _width = _init_height; _height = _init_width; break; case 2: writedata(TFT_MAD_BGR); colstart = 26; rowstart = 1; _width = _init_width; _height = _init_height; break; case 3: writedata(TFT_MAD_MX | TFT_MAD_MV | TFT_MAD_BGR); colstart = 1; rowstart = 26; _width = _init_height; _height = _init_width; break; }
えーっと、画面の回転は以下の図です。

一番シンプルなのが2の逆さですね。TFT_MAD_BGRのみの指定です。おそらくこの向きが液晶の正しい向きな気がします。
このBGRは液晶の色の並び方を示しています。他の液晶はRGBの指定もありましたが、この液晶はBGRの順に色が並んでいます。デフォルトはRGBなのでBGRを指定しないと色が変になってしまいます。
obnizの初期ライブラリは未指定だったみたいで、デフォルトのRGBモードで動いていたので、色がおかしかったです。
rotation | 方向 | MX | MY | MV | MH | BGR |
0 | 縦 | ○ | ○ | ○ | ○ | |
2 | 縦 | ○ | ||||
1 | 横 | ○ | ○ | ○ | ||
3 | 横 | ○ | ○ | ○ |
各指定をまとめたのが上の図です。画面を横にしているときには、XとY軸が逆になるのでMVを指定する必要があります。
MXとMYで反転させて、回転方向を決めます。0は逆さに回転するので、MXとMYの両方をしていします。
MHは画面の転送順番を指定するのですが、実際のところ指定しなくても見た目はほとんど変わりません。画面の上から描画されていくか、下から描画されていくかの違いです。同じくMLで右からか左からかも指定できますが、転送が非常に遅い場合か、スローカメラで撮影しないと差はわからないと思います。
1と3はどっちの方向に回転するのかでMXとMYを選択しています。
左右反転の鏡面表示や、上下反転表示をしたい場合には、上記のデフォルト値から反転したい方向のフラグを逆に設定します。
// 縦向きのデフォルト値 M5.Lcd.setRotation(2); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_BGR); // 縦向きの左右反転(TFT_MAD_MXを追加) M5.Lcd.setRotation(2); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MX | TFT_MAD_BGR); // 縦向きの上限反転(TFT_MAD_MYを追加) M5.Lcd.setRotation(2); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_MV | TFT_MAD_BGR); // 横向きのデフォルト値 M5.Lcd.setRotation(3); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MX | TFT_MAD_MV | TFT_MAD_BGR); // 横向きの左右反転(TFT_MAD_MYを追加) M5.Lcd.setRotation(3); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_MV | TFT_MAD_BGR); // 横向きの上限反転(TFT_MAD_MXを取る) M5.Lcd.setRotation(3); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MV | TFT_MAD_BGR);
上記の関係になります。横になった場合はMVによってXとYが逆になっているので、反転方向も逆になります。
#include <M5StickC.h> int mode = -1; void setup() { M5.begin(); } void loop() { M5.update(); if ( M5.BtnA.wasPressed() ) { mode++; mode = mode % 10; if ( mode == 0 ) { M5.Lcd.setRotation(0); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_MH | TFT_MAD_BGR); } else if ( mode == 1 ) { M5.Lcd.setRotation(0); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MY | TFT_MAD_MH | TFT_MAD_BGR); } else if ( mode == 2 ) { M5.Lcd.setRotation(0); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MX | TFT_MAD_MH | TFT_MAD_BGR); } else if ( mode == 3 ) { M5.Lcd.setRotation(0); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MH | TFT_MAD_BGR); } else if ( mode == 4 ) { M5.Lcd.setRotation(0); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_BGR); } else if ( mode == 5 ) { M5.Lcd.setRotation(0); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_ML | TFT_MAD_BGR); } else if ( mode == 6 ) { M5.Lcd.setRotation(3); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MX | TFT_MAD_MV | TFT_MAD_BGR); } else if ( mode == 7 ) { M5.Lcd.setRotation(3); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MV | TFT_MAD_BGR); } else if ( mode == 8 ) { M5.Lcd.setRotation(3); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_MV | TFT_MAD_BGR); } else if ( mode == 9 ) { M5.Lcd.setRotation(3); M5.Lcd.writecommand(TFT_MADCTL); M5.Lcd.writedata(TFT_MAD_MY | TFT_MAD_MV | TFT_MAD_BGR); } M5.Lcd.fillScreen(BLACK); M5.Lcd.setCursor(5, 10); M5.Lcd.print("mode:"); M5.Lcd.print(mode); } }
上記サンプルスケッチを動かして関係性を調べてみてください。
その他のコマンド
Sleepなど
M5StickCだとAXP192で制御するので、あまり使わないはずですが液晶のON、OFFやスリープ設定があります。
GAMSET (26h): Gamma Set
画像などで、ガンマ値を変更して表示したい場合に使えそうです。
VSCSAD: Vertical Scroll Start Address of RAM (37h)
特定エリアの画像などを縦スクロールする機能。かなり難易度が高そうなので普通は使わないと思います。
その他
フレームバッファへのアクセス方法がいろいろありますが、通常はTFT_eSPIクラスやTFT_eSpriteクラス経由でアクセスしたほうがよいと思います。
まとめ
結構長くなってしまいましたが、ST7735を使う上でのアウトラインは理解できたきがします。TFT_eSPIクラスが実際の描画まわりのラッパークラスになります。ただTFT_eSPIクラスは操作単位でST7735につど転送しているので、処理がかなり重いです。
画面のチラツキも出てくるので、高速描画ではダブルバッファリングという処理を行います。これは描画途中のフレームバッファを用意して、その画面にすべての描画が終了してから、実際のフレームバッファに転送する方法です。
おそらくTFT_eSpriteがダブルバッファリングをおこなっている描画クラスな気がするのですが、らびやんさんが最新版のTFT_eSpriteに入れ替えるプルリクエストを出しているので、取り込まれたバージョンがでたら、もう少し検証を進めたいと思います。
コメント