今回は「AtomS3」というマイコンボードのLCDディスプレイに好きな色のネコを表示するプログラムを作成したので紹介します。
👆このコードを使用してAtomS3にネコを表示している様子。変更可能な箇所は主に4つで「ネコと表示する文字の色」「背景の色」「目の形(文字で指定)」「上側に表示する文字」が変えられます。また、モード0のトラ猫のみ追加の柄を表示しています。
AtomS3のディスプレイについて
AtomS3のディスプレイは1辺0.85インチ(2.1 cmちょっと)しかなく、とても小さいです!。そのため、複雑なものや情報量が多いものを表示するのには向いていません。
そこで、今回作成したコードでは小さいディスプレイに「1単語」と「1匹のネコ」を表示することにしました。制作物の「機能」や「雰囲気」を表現できます。AtomS3の画面に表示したいものがない場合や制作物に遊び心が欲しい場合などにぜひご活用ください。
LCD(液晶ディスプレイ)に図や文字を表示するには「用意した画像を表示する方法」と「図形やテキストを表示する関数を組み合わせる方法」があります。それぞれ次のような特徴があります。
- 「用意した画像を表示する方法」
- 好きな画像を使用できる
- SDカードがないと画像の変換が大変
- SDカードがあれば楽に表示できる
- 「図形やテキストを表示する関数を組み合わせる方法」
- 狙った画像を作成するのに時間がかかる
- SDカードが不要
- 色などを簡単に変更できる
今回は「色や文字を数パターン用意したい」「SDカードを使用しない」「本体のメモリで表示しようとしたがうまくいかなかった」などの理由で後者を選びました。(次のテックシーカーハッカソンまでに、本体のメモリで画像を表示できるように勉強してきます💦)
コード全体の紹介
こちらが今回紹介するコード全体になります。
- #include <M5AtomS3.h>
- // 変数の宣言
- volatile int mode_now = 3 ; // モードの切り替え
- /* 各モードの設定
- mode 0:白背景+トラネコ,「Sleep」
- mode 1:うす緑背景+グレーネコ,「Walk」
- mode 2:黄色背景+ピンクネコ,「Fever」
- mode 3:黒背景+白ネコ,「Listen」
- */
- // ディスプレイ用の変数
- const int back_colors_nums[4][3] = {{230, 230, 250}, {152,251,152}, {255,215,0}, {40, 40, 50}};
- const int cat_colors_nums[4][3] = {{255,165,0}, {128,128,128}, {255,105,180}, {255,250,250}};
- uint16_t back_colors[4];
- uint16_t cat_colors[4];
- const char eyes[4] = {'~','^','*','-'};
- const String mode_texts[4] = {"Sleep","Walk","Fever","Listen"};
- void setup() {
- // AtomS3モジュールを初期化
- AtomS3.begin();
- AtomS3.Display.setRotation(2);
- // 色を整数値(int型)から2バイト(uint16_t型)に変換
- for (int i = 0; i < 4; i++){
- back_colors[i] = AtomS3.Display.color565(
- back_colors_nums[i][0],
- back_colors_nums[i][1],
- back_colors_nums[i][2]);
- cat_colors[i] = AtomS3.Display.color565(
- cat_colors_nums[i][0],
- cat_colors_nums[i][1],
- cat_colors_nums[i][2]);
- }
- displayCat(back_colors[mode_now], cat_colors[mode_now], eyes[mode_now], mode_texts[mode_now]); // ネコを表示
- }
- void loop(void) {
- AtomS3.update();
- if (AtomS3.BtnA.wasPressed()) {
- // 押したらモード切り替え : 0, 1, 2, 3, 0, 1, 2...
- mode_now ++;
- mode_now %= 4;
- displayCat(back_colors[mode_now], cat_colors[mode_now], eyes[mode_now], mode_texts[mode_now]); // ネコを表示
- }
- }
- void displayCat (uint16_t back_color, uint16_t cat_color, char eye, String mode_text) { // ネコを表示
- // 背景色
- AtomS3.Display.fillScreen(back_color);
- // ネコ頭
- AtomS3.Display.fillEllipse(128 / 2, 128, 90, 70, cat_color); // ネコ楕円
- AtomS3.Display.drawEllipse(128 / 2, 128, 90, 70, BLACK); // ネコ楕円, フチ
- // ネコミミ
- AtomS3.Display.fillTriangle(64 + 25, 64 - 3, 64 + 60, 64 + 12, 64 + 50, 64 - 20, cat_color); // ネコミミ(右側)
- AtomS3.Display.fillTriangle(64 - 25, 64 - 3, 64 - 60, 64 + 12, 64 - 50, 64 - 20, cat_color); // ネコミミ(左側)
- AtomS3.Display.fillTriangle(64 + 33, 64 - 3, 64 + 54, 64 + 8, 64 + 47, 64 - 13, PINK); // ネコミミ(右側)
- AtomS3.Display.fillTriangle(64 - 33, 64 - 3, 64 - 54, 64 + 8, 64 - 47, 64 - 13, PINK); // ネコミミ(左側)
- AtomS3.Display.drawLine(64 + 25, 64 - 3, 64 + 50, 64 - 20, BLACK); // ネコミミ(右耳), フチ1
- AtomS3.Display.drawLine(64 - 25, 64 - 3, 64 - 50, 64 - 20, BLACK); // ネコミミ(左耳), フチ1
- AtomS3.Display.drawLine(64 + 60, 64 + 12, 64 + 50, 64 - 20, BLACK); // ネコミミ(右耳), フチ2
- AtomS3.Display.drawLine(64 - 60, 64 + 12, 64 - 50, 64 - 20, BLACK); // ネコミミ(左耳), フチ2
- // ネコクチ (* w *)
- AtomS3.Display.fillCircle(64 + 15, 64 + 32, 18, BLACK); // ネコクチ右
- AtomS3.Display.fillCircle(64 - 15, 64 + 32, 18, BLACK); // ネコクチ左
- AtomS3.Display.fillCircle(64 + 15, 64 + 32, 15, cat_color); // ネコクチ補助1
- AtomS3.Display.fillCircle(64 - 15, 64 + 32, 15, cat_color); // ネコクチ補助2
- AtomS3.Display.fillRect(64 - 36, 64 + 32 - 20, 73, 18, cat_color); // ネコクチ補助3
- // ネコの目
- AtomS3.Display.setCursor(0, 0);
- AtomS3.Display.setTextColor(BLACK, cat_color); // 文字の色, 背景色
- AtomS3.Display.setTextSize(3); // フォントサイズ
- String face = "- -";
- face[0] = eye;
- face[2] = eye;
- AtomS3.Display.drawCentreString(face, 64, 64 + 10);
- // 文字
- AtomS3.Display.setTextColor(cat_color, back_color); // 文字の色, 背景色
- AtomS3.Display.setTextSize(3); // フォントサイズ
- AtomS3.Display.drawCentreString(mode_text, 64, 10);
- // eyeが'~'の場合はトラネコ装飾追加
- if (eye == '~'){
- uint16_t add_color = AtomS3.Display.color565(178,34,34);
- AtomS3.Display.fillRoundRect(64 - 4, 64 - 6, 8, 30, 5, add_color); // おでこ
- AtomS3.Display.fillRoundRect(128 - 25, 128 - 15, 25, 8, 5, add_color); // ほっぺ右1
- AtomS3.Display.fillRoundRect(128 - 25, 128 - 30, 25, 8, 5, add_color); // ほっぺ右2
- AtomS3.Display.fillRoundRect(0, 128 - 15, 25, 8, 5, add_color); // ほっぺ左1
- AtomS3.Display.fillRoundRect(0, 128 - 30, 25, 8, 5, add_color); // ほっぺ左2
- }
- }
プログラムでは、「ライブラリの呼び出し→変数の宣言→setup関数での初期化→loop関数でボタンの検出, モードの切り替え, 関数displayCat()の呼び出し→関数displayCat()でネコを表示」という処理を行っています。
関数displayCat()内では表示したいものの後ろ側から「背景→ネコの頭→顔のパーツ, 文字」の順で描画していきます。LCD(液晶ディスプレイ)を表示する関数は今の画面を上書きしていくため、このような順番になっています。
少しコードが長く、何が表示されるかも数値を見ただけではわからないと思います。ただ、安心してください。このdisplayCat関数を丸ごとコピーし、色や文字を変えるだけで誰でも簡単にネコを表示できます。なるべくいろんな場面でいろんな人に使ってもらえるよう作成したコードなので、ぜひ使っていただきたいです✨
ここからは、コードを1箇所ずつゆるく解説していきます。実装でつまずいた方や他のキャラを表示してみたい方、他の環境で表示してみたい方などの参考になると幸いです。
コードのゆる解説
ライブラリの読み込み
- #include <M5AtomS3.h>
"M5AtomS3.h"というAtomS3向けのライブラリを使用します。このライブラリを使用するとAtomS3のボタンやディスプレイ、内蔵されているジャイロセンサーなどを使用することができます。Arduino IDEのライブラリマネージャーから簡単にダウンロードできます。
今回のコードはAtomS3.Displayのクラスを使用していますが、より一般的なM5スタックをサポートするM5.Lcdクラスでもほとんど同様の機能があるため、M5.Lcdに書き換えていただいても問題ありません。
変数の宣言
- // 変数の宣言
- volatile int mode_now = 3 ; // モードの切り替え
- /* 各モードの設定
- mode 0:白背景+トラネコ,「Sleep」
- mode 1:うす緑背景+グレーネコ,「Walk」
- mode 2:黄色背景+ピンクネコ,「Fever」
- mode 3:黒背景+白ネコ,「Listen」
- */
- // ディスプレイ用の変数
- const int back_colors_nums[4][3] = {{230, 230, 250}, {152,251,152}, {255,215,0}, {40, 40, 50}};
- const int cat_colors_nums[4][3] = {{255,165,0}, {128,128,128}, {255,105,180}, {255,250,250}};
- uint16_t back_colors[4];
- uint16_t cat_colors[4];
- const char eyes[4] = {'~','^','*','-'};
- const String mode_texts[4] = {"Sleep","Walk","Fever","Listen"};
ここでは、変数の宣言を行っています。mode_nowの値を0, 1, 2, 3, 0, 1, 2, 3...とボタンを押すごとに変更し、呼び出す色と文字を変えます。そのため、色と文字は長さ4の配列で宣言しています。この段階ではback_colorsとcat_colorsはまだ空の配列ですが、setup()関数内でback_colors_numsとcat_colors_numsをもとに表示したい色の情報を格納する予定です。
色についてですが、RGB(赤緑青)の強さを0〜255の256段階で表現できます。色の見本を検索して、好きな色を使用してください。
「eyes」と「mode_texts」についてですがeyesが各モードでネコの目に使う文字、mode_textsは各モードで中央上部に表示するテキストになります。mode_textsは「Listen」だけでも幅いっぱいになってしまうため、文字の大きさを考えて入力する必要があります。どうしてもオーバーする場合は79行目のフォントサイズを変更してみてください。
setup()関数
- void setup() {
- // AtomS3モジュールを初期化
- AtomS3.begin();
- AtomS3.Display.setRotation(2);
- // 色を整数値(int型)から2バイト(uint16_t型)に変換
- for (int i = 0; i < 4; i++){
- back_colors[i] = AtomS3.Display.color565(
- back_colors_nums[i][0],
- back_colors_nums[i][1],
- back_colors_nums[i][2]);
- cat_colors[i] = AtomS3.Display.color565(
- cat_colors_nums[i][0],
- cat_colors_nums[i][1],
- cat_colors_nums[i][2]);
- }
- displayCat(back_colors[mode_now], cat_colors[mode_now], eyes[mode_now], mode_texts[mode_now]); // ネコを表示
- }
setup()関数では、「AtomS3の初期化」「画面の回転」「色をint型からuint16_t型に変換」「displayCat()関数の呼び出し」を行っています。
uint16_t型とは2バイト分の正数値を格納する型です。今回は「RGB565」と呼ばれる色を2バイトで表現する方法を使用するため、uint16_tを使用します。
今回紹介するdisplayCat()関数の引数は(背景の色, ネコの色, 目の文字, 表示するテキスト)となっており、それぞれ(uint16_t型, uint16_t型, char型, String型)となっています。「変数の宣言」で宣言した配列から対応する変数を取り出し、渡すことでモードごとに違うパラメーターを与えます。
loop()関数
- void loop(void) {
- AtomS3.update();
- if (AtomS3.BtnA.wasPressed()) {
- // 押したらモード切り替え : 0, 1, 2, 3, 0, 1, 2...
- mode_now ++;
- mode_now %= 4;
- displayCat(back_colors[mode_now], cat_colors[mode_now], eyes[mode_now], mode_texts[mode_now]); // ネコを表示
- }
- }
loop()関数では、「ボタンが押されているかの判定」「モードの切り替え」「displayCat()関数の呼び出し」を行っています。
AtomS3.update()は今回使用した「ボタンが押されているか」の判定や「ジャイロセンサー」の値などを更新します。AtomS3.update()がボタンが押されている間に呼び出されると、AtomS3.BtnA.wasPressed()がTrueを返し、if文の中が実行されます。
mode_nowの値を0, 1, 2, 3, 0, 1, 2, 3...と切り替えるために、1を加えて(42行目)4で割った余りにする(43行目)ことを繰り返します。mode_nowが0〜3のときは4で割ると0あまり0, 0あまり1, ... , 0あまり3となりそのままの値ですが、mode_nowが4のときは1あまり0と0に戻ります。
displayCat()関数の呼び出しはsetup()関数のものと同じ内容ですが、配列の参照箇所がmode_nowが変わることで変化します。
displayCat()関数
- void displayCat (uint16_t back_color, uint16_t cat_color, char eye, String mode_text) { // ネコを表示
- // 背景色
- AtomS3.Display.fillScreen(back_color);
- // ネコ頭
- AtomS3.Display.fillEllipse(128 / 2, 128, 90, 70, cat_color); // ネコ楕円
- AtomS3.Display.drawEllipse(128 / 2, 128, 90, 70, BLACK); // ネコ楕円, フチ
- // ネコミミ
- AtomS3.Display.fillTriangle(64 + 25, 64 - 3, 64 + 60, 64 + 12, 64 + 50, 64 - 20, cat_color); // ネコミミ(右側)
- AtomS3.Display.fillTriangle(64 - 25, 64 - 3, 64 - 60, 64 + 12, 64 - 50, 64 - 20, cat_color); // ネコミミ(左側)
- AtomS3.Display.fillTriangle(64 + 33, 64 - 3, 64 + 54, 64 + 8, 64 + 47, 64 - 13, PINK); // ネコミミ(右側)
- AtomS3.Display.fillTriangle(64 - 33, 64 - 3, 64 - 54, 64 + 8, 64 - 47, 64 - 13, PINK); // ネコミミ(左側)
- AtomS3.Display.drawLine(64 + 25, 64 - 3, 64 + 50, 64 - 20, BLACK); // ネコミミ(右耳), フチ1
- AtomS3.Display.drawLine(64 - 25, 64 - 3, 64 - 50, 64 - 20, BLACK); // ネコミミ(左耳), フチ1
- AtomS3.Display.drawLine(64 + 60, 64 + 12, 64 + 50, 64 - 20, BLACK); // ネコミミ(右耳), フチ2
- AtomS3.Display.drawLine(64 - 60, 64 + 12, 64 - 50, 64 - 20, BLACK); // ネコミミ(左耳), フチ2
- // ネコクチ (* w *)
- AtomS3.Display.fillCircle(64 + 15, 64 + 32, 18, BLACK); // ネコクチ右
- AtomS3.Display.fillCircle(64 - 15, 64 + 32, 18, BLACK); // ネコクチ左
- AtomS3.Display.fillCircle(64 + 15, 64 + 32, 15, cat_color); // ネコクチ補助1
- AtomS3.Display.fillCircle(64 - 15, 64 + 32, 15, cat_color); // ネコクチ補助2
- AtomS3.Display.fillRect(64 - 36, 64 + 32 - 20, 73, 18, cat_color); // ネコクチ補助3
- // ネコの目
- AtomS3.Display.setCursor(0, 0);
- AtomS3.Display.setTextColor(BLACK, cat_color); // 文字の色, 背景色
- AtomS3.Display.setTextSize(3); // フォントサイズ
- String face = "- -";
- face[0] = eye;
- face[2] = eye;
- AtomS3.Display.drawCentreString(face, 64, 64 + 10);
- // 文字
- AtomS3.Display.setTextColor(cat_color, back_color); // 文字の色, 背景色
- AtomS3.Display.setTextSize(3); // フォントサイズ
- AtomS3.Display.drawCentreString(mode_text, 64, 10);
- // eyeが'~'の場合はトラネコ装飾追加
- if (eye == '~'){
- uint16_t add_color = AtomS3.Display.color565(178,34,34);
- AtomS3.Display.fillRoundRect(64 - 4, 64 - 6, 8, 30, 5, add_color); // おでこ
- AtomS3.Display.fillRoundRect(128 - 25, 128 - 15, 25, 8, 5, add_color); // ほっぺ右1
- AtomS3.Display.fillRoundRect(128 - 25, 128 - 30, 25, 8, 5, add_color); // ほっぺ右2
- AtomS3.Display.fillRoundRect(0, 128 - 15, 25, 8, 5, add_color); // ほっぺ左1
- AtomS3.Display.fillRoundRect(0, 128 - 30, 25, 8, 5, add_color); // ほっぺ左2
- }
- }
displayCat()関数は引数で与えられた色、文字をもとにディスプレイにネコを表示する関数です。ここでは「座標について」「色について」「描画するための関数について」の順に説明します。
座標について
AtomS3のディスプレイは128×128の解像度です。一番左上のピクセルが(x, y) = (0, 0)、一番右下のピクセルが(x, y) = (127, 127)となっています。
今回は自分が理解しやすいように64+〇〇や64-〇〇のように画面の中央からいくら離れているかいちいち足し算で記入しています。中心からいくらプラスか、マイナスかで足し算で書くことで、左右対称の箇所は符号を変えるだけで済ませることができるというメリットがあります。ですが、特に決まりはないので自分のわかりやすいように入力するのがいいと思います。
👇この画像で使用したソースコードも一応載せておきます。テスト用にどうぞ。
- void displayTest () {
- // 背景色
- AtomS3.Display.fillScreen(BLACK);
- // ドット
- AtomS3.Display.drawPixel(0, 0, RED);
- AtomS3.Display.drawPixel(127, 127, BLUE);
- // x = 64, y = 64
- AtomS3.Display.drawLine(64, 0, 64, 127, GREEN); // x = 64
- AtomS3.Display.drawLine(0, 64, 127, 64, GREEN); // y = 64
- }
色について
色はsetup()関数で使用した、AtomS3.Display.color565()を用いてuint16_t型の変数で指定することと、BLACKのようにライブラリ内で定義済みの色を使用することができます。
「赤」や「ピンク」、「黒」などメジャーな色を使う場合はライブライ内で定義済みの色を使用するのがいいと思います。その他の色やグラデーションを作る際はcolor565()メソッドを用いて指定する方法がおすすめです。
描画するための関数について
描画するための関数は、「楕円」「三角形」「直線」「円」「正方形」などを画面に表示することができます。座標や色を引数に入れることで好きなように図形を表示できます。図形のみならず、「文字」も同様に表示することができます。
また、基本的に今あるものを上書きするように出力されるため、表示する順番に注意する必要があります。そのため、今回のコードも「背景→ネコの体→ネコのパーツや文字」といった順で出力させます。また、上書きされる特性を活かせば使える関数にない図形も出力可能です。
👆displayCat()関数にディレイをいくつかを加え、実行した様子。
今回は、ネコの口を「円を重ねる」「長方形でそれを半分隠す」ことで ω のようなネコの口の形を表現しています。
動画の撮影をして気づいたのですが、白色が明るすぎると画面が焼き付いてしまうようです。
明るさを下げる、表示する時間を短くする、色をこまめに変える...などなど、対策が必要そうです💦(パソコンやテレビなどと同じですね)。
明るさを変更するにはsetBrightness()メソッドに0〜255の値を入力し実行してください(初期値は200)。試したところ明るさ1でも綺麗に表示されました。
例:AtomS3.Display.setBrightness(100);
まとめ
今回はAtomS3の画面に好きな色のネコをを表示するコードを紹介しました。また、「用意した画像を表示する」のではなく「図形やテキストを表示する関数を組み合わせる」ことで画面にキャラクターを表示するメリットやデメリットと、実際の手順も合わせて説明しました。メリットやデメリットは次の通りです。
- メリット
- SDカードがない環境でも表示できる。
- 色やテキストを後から変更できる。
- デメリット
AtomS3を利用させる場合はぜひご活用ください!それでは良き電子工作ライフをお過ごしください!
謝辞
このコードはTechSeeker Hackathon 2024の成果物の一部です。このような学習の機会を用意してくださった方々、同じチームとして支えてくださったデジもく会のみなさんに感謝します。本当にありがとうございました。