AVR0シリーズでOLEDディスプレイを使う方法(2)

AVR0シリーズでSSD1306を使う マイコン/Arduino

電子工作でよく使われる0.96インチ(128×64)のOLEDをAVR0(attiny)シリーズで使う方法を紹介します。

前回の記事で紹介したプログラムをブラッシュアップしましたので再度紹介します。

前提

AVR0(attiny)シリーズではAdafruiteのライブラリが使用できません。そこで、機能を最低限まで絞って文字を表示することに特化した最小限のライブラリを作成しました。できることは以下だけです。

  • 文字表示開始行を指定できる
  • その場所から文字をOLEDディスプレイに表示できる
  • 対象はI2C接続された128×64のOLEDディスプレイ(SSD1306コントローラー)

前回紹介したプログラムからの変更点は

  • 文字表示を行う関数を、Stringを引数に取るprintlnSと、char[]型を引数にとるprintlnの2つ用意しました。(printlnSでchar[]に変換してprintlnを呼び出しています)
  • 文字サイズ拡張を1倍から4倍まで対応(整数倍のみ。現状、5倍以上はバグります。)
  • お行儀よく.hと.cppを分離

存在しない行や列へのデータ出力等は制御していません。はみ出さないように運用側でカバーしてください

なお、外部のライブラリとしてはWire.hしか使用していませんので他のアーキテクチャーのマイコンでも動作すると思います。

が、AVR0/1/2系よりもリッチな環境なら素直にAdafruiteのライブラリを使う方が良いと思います。また、AVR0シリーズでもプログラム容量が4KBしかないattiny404や402などでは容量的に厳しいです。attiny1604以上を推奨します。

VKLSVAN 2個 0.96

VKLSVAN 2個 0.96

999円(12/05 10:04時点)
Amazonの情報を掲載しています
本ライブラリだけで3~4KBほど使用しますので、attiny404だと文字表示するだけで終わります。他は何もできません。(下手すると入らない)
attiny404/202は秋月でも2024年10月ごろから取り扱いが始まりましたが、それ以上のAVRtinyマイコンはDigikey(もしくはDigikey代行してくれるマルツ)がおすすめです。
手軽なところではflash容量が8KBのattiny804か16KBのattiny1604当たりが使えますが、価格差がほとんどないのでattiny1604がおススメです。

ライブラリ

以下の3ファイルで構成しています。fontデータについてはTiny4Kライブラリから流用させていただいています。以下3ファイルを作成し、メインプログラムと同じフォルダに置いてください。

  • tiny1306.h
  • tiny1306.cpp
  • font6x8.h

tiny1306.h

#ifndef tiny1306_h
#define tiny1306_h

class TINY1306 {
 private:
  void printOneChar6x8(char chr, uint8_t row);
  uint8_t currentPage;
  uint8_t charScale;
  void setPageGo(uint8_t p);
  uint8_t oledAddr;
  uint8_t oledWidth; // OLEDの幅(ピクセル数)
  uint8_t oledPages; // OLEDのページ数
 public:
  TINY1306(uint8_t addr, uint8_t width, uint8_t page);
  void init();
  void display();
  void clear();
  void printlnS(String str);
  void println(char *chr);
  void setScale(uint8_t scl);
  void setPage(uint8_t p);
};

#endif

tiny1306.cpp

#include "Wire.h"
#include "font6x8.h"
#include "tiny1306.h"

//スケールアップのために使う関数
uint8_t extractBlock(uint8_t value, int blockIndex, int factor) {
 uint8_t result = 0b00000000;
 for (int i = 0; i < 8; i++)
 {
  uint8_t shift = (8 * blockIndex + i) / factor;
  uint8_t temp = (value >> shift) & 0b01;
  result = result | temp << i;
 }
 return result;
}

TINY1306::TINY1306(uint8_t addr, uint8_t width, uint8_t page)
{
 oledAddr = addr;//0x3C; //重要なのはこれだけ。
 oledWidth = width;//実は使わないけど入れてある [128]
 oledPages = page;//これも現状では使わない。[8] 128x64ディスプレイの場合
 charScale = 1;
}

void TINY1306::init()
{
 Wire.beginTransmission(oledAddr);
 Wire.write(0x00); // 複数バイトコマンドタグ
 Wire.write(0xC8); // [C0]/C8:上下の描画方向
 Wire.write(0xA1); // [A0]/A1:左右の描画方向
 Wire.write(0xA8); Wire.write(0x3F); // 画面の解像度 [3F]:64Line / 1F:32Line
 Wire.write(0xDA); Wire.write(0x12); // [12]:Sequential / 02:Alternative
 Wire.write(0xD3); Wire.write(0x00); // 縦方向のオフセット
 Wire.write(0x40); // 縦方向の描画開始位置
 Wire.write(0xA6); // 表示方法 - A7にすると画面反転
 Wire.write(0x8D); Wire.write(0x14);
 Wire.endTransmission();

 /*参考
       ゆるく楽しむ プログラミング&電子工作 様
       !http://try3dcg.world.coocan.jp/note/i2c/ssd1306.html
*/
}

void TINY1306::setPageGo(uint8_t p)
{
 Wire.beginTransmission(oledAddr);
 Wire.write(0x00);
 Wire.write(0xB0 | (p & 0x07));
 Wire.write(0x00);
 Wire.write(0x10);
 Wire.endTransmission();
}

void TINY1306::setPage(uint8_t p)
{
 currentPage = p;
}

void TINY1306::setScale(uint8_t scale)
{
 charScale = scale;
}

void TINY1306::display()
{
 Wire.beginTransmission(oledAddr);
 Wire.write(0x80);
 Wire.write(0xAF);
 Wire.endTransmission();
}

void TINY1306::clear()
{
 for (uint8_t m = 0; m < oledPages; m++) {
  setPageGo(m);
  Wire.beginTransmission(oledAddr);
  Wire.write(0x40);
  for (uint8_t x = 0; x < oledWidth; x++) {
   if (Wire.write(0x00) == 0) {
    Wire.endTransmission();
    Wire.beginTransmission(oledAddr);
    Wire.write(0x40);
    Wire.write(0x00);
   }
  }
  Wire.endTransmission();
 }
}

void TINY1306::printOneChar6x8(char chr, uint8_t row)
{
 int i = ((int)chr - 32) * 6; //ASCIIコードから変換

 Wire.beginTransmission(oledAddr);
 Wire.write(0x40);

 for (int j = 0; j < 6 * charScale; j++)
 {
  uint8_t font = extractBlock(font6x8[i + j / charScale], row, charScale);
  Wire.write(font);
 }
 Wire.endTransmission();
}

void TINY1306::println(char *chr)
{
 for (uint8_t j = 0; j < charScale; j++)
 {
  setPageGo(currentPage + j);
  for (uint8_t i = 0; i < strlen(chr); i++)
  {
   printOneChar6x8(chr[i], j);
  }
 }
}

void TINY1306::printlnS(String str)
{
  uint8_t len = str.length() + 1; //この+1がないと末尾が消える
  char chr[len];
  str.toCharArray(chr, len);
  println(chr);

/*参考
  HatenaBlog 戯言日記 様
  !https://doubtpad.hatenablog.com/entry/2020/09/28/015028
*/
}

font6x8.h

/*
* This fonts is 6x8size font for SSD1306 display controlled by AVR Tiny0 Series.
*
* Based on Tiny4kOLED - Drivers for SSD1306 controlled dot matrix OLED/PLED 128x32 displays
* from 2017-04-25 at https://github.com/datacute/Tiny4kOLED
*
*/

/* Standard ASCII 6x8 font */
//PROGMEMをつけると何故かうまく動作しない
//AVR0/1/2シリーズではconstと書けばflashにしか保存されないらしい
//他アーキテクチャーのマイコンで使う場合はPROGMEMをつけた方が良いかも
const uint8_t font6x8 [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0 
0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, // ! 1 
0x00, 0x00, 0x07, 0x00, 0x07, 0x00, // " 2 
0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14, // # 3 
0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12, // $ 4 
0x00, 0x23, 0x13, 0x08, 0x64, 0x62, // % 5 
0x00, 0x36, 0x49, 0x55, 0x22, 0x50, // & 6 
0x00, 0x00, 0x05, 0x03, 0x00, 0x00, // ' 7 
0x00, 0x00, 0x1c, 0x22, 0x41, 0x00, // ( 8 
0x00, 0x00, 0x41, 0x22, 0x1c, 0x00, // ) 9 
0x00, 0x14, 0x08, 0x3E, 0x08, 0x14, // * 10
0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, // + 11
0x00, 0x00, 0x00, 0xA0, 0x60, 0x00, // , 12
0x00, 0x08, 0x08, 0x08, 0x08, 0x08, // - 13
0x00, 0x00, 0x60, 0x60, 0x00, 0x00, // . 14
0x00, 0x20, 0x10, 0x08, 0x04, 0x02, // / 15
0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E, // 0 16
0x00, 0x00, 0x42, 0x7F, 0x40, 0x00, // 1 17
0x00, 0x42, 0x61, 0x51, 0x49, 0x46, // 2 18
0x00, 0x21, 0x41, 0x45, 0x4B, 0x31, // 3 19
0x00, 0x18, 0x14, 0x12, 0x7F, 0x10, // 4 20
0x00, 0x27, 0x45, 0x45, 0x45, 0x39, // 5 21
0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30, // 6 22
0x00, 0x01, 0x71, 0x09, 0x05, 0x03, // 7 23
0x00, 0x36, 0x49, 0x49, 0x49, 0x36, // 8 24
0x00, 0x06, 0x49, 0x49, 0x29, 0x1E, // 9 25
0x00, 0x00, 0x36, 0x36, 0x00, 0x00, // : 26
0x00, 0x00, 0x56, 0x36, 0x00, 0x00, // ; 27
0x00, 0x08, 0x14, 0x22, 0x41, 0x00, // < 28
0x00, 0x14, 0x14, 0x14, 0x14, 0x14, // = 29
0x00, 0x00, 0x41, 0x22, 0x14, 0x08, // > 30
0x00, 0x02, 0x01, 0x51, 0x09, 0x06, // ? 31
0x00, 0x32, 0x49, 0x59, 0x51, 0x3E, // @ 32
0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C, // A 33
0x00, 0x7F, 0x49, 0x49, 0x49, 0x36, // B 34
0x00, 0x3E, 0x41, 0x41, 0x41, 0x22, // C 35
0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C, // D 36
0x00, 0x7F, 0x49, 0x49, 0x49, 0x41, // E 37
0x00, 0x7F, 0x09, 0x09, 0x09, 0x01, // F 38
0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A, // G 39
0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F, // H 40
0x00, 0x00, 0x41, 0x7F, 0x41, 0x00, // I 41
0x00, 0x20, 0x40, 0x41, 0x3F, 0x01, // J 42
0x00, 0x7F, 0x08, 0x14, 0x22, 0x41, // K 43
0x00, 0x7F, 0x40, 0x40, 0x40, 0x40, // L 44
0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F, // M 45
0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F, // N 46
0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E, // O 47
0x00, 0x7F, 0x09, 0x09, 0x09, 0x06, // P 48
0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E, // Q 49
0x00, 0x7F, 0x09, 0x19, 0x29, 0x46, // R 50
0x00, 0x46, 0x49, 0x49, 0x49, 0x31, // S 51
0x00, 0x01, 0x01, 0x7F, 0x01, 0x01, // T 52
0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F, // U 53
0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F, // V 54
0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F, // W 55
0x00, 0x63, 0x14, 0x08, 0x14, 0x63, // X 56
0x00, 0x07, 0x08, 0x70, 0x08, 0x07, // Y 57
0x00, 0x61, 0x51, 0x49, 0x45, 0x43, // Z 58
0x00, 0x00, 0x7F, 0x41, 0x41, 0x00, // [ 59
0x00, 0x02, 0x04, 0x08, 0x10, 0x20, // \ 60
0x00, 0x00, 0x41, 0x41, 0x7F, 0x00, // ] 61
0x00, 0x04, 0x02, 0x01, 0x02, 0x04, // ^ 62
0x00, 0x40, 0x40, 0x40, 0x40, 0x40, // _ 63
0x00, 0x00, 0x01, 0x02, 0x04, 0x00, // ' 64
0x00, 0x20, 0x54, 0x54, 0x54, 0x78, // a 65
0x00, 0x7F, 0x48, 0x44, 0x44, 0x38, // b 66
0x00, 0x38, 0x44, 0x44, 0x44, 0x20, // c 67
0x00, 0x38, 0x44, 0x44, 0x48, 0x7F, // d 68
0x00, 0x38, 0x54, 0x54, 0x54, 0x18, // e 69
0x00, 0x08, 0x7E, 0x09, 0x01, 0x02, // f 70
0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C, // g 71
0x00, 0x7F, 0x08, 0x04, 0x04, 0x78, // h 72
0x00, 0x00, 0x44, 0x7D, 0x40, 0x00, // i 73
0x00, 0x40, 0x80, 0x84, 0x7D, 0x00, // j 74
0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, // k 75
0x00, 0x00, 0x41, 0x7F, 0x40, 0x00, // l 76
0x00, 0x7C, 0x04, 0x18, 0x04, 0x78, // m 77
0x00, 0x7C, 0x08, 0x04, 0x04, 0x78, // n 78
0x00, 0x38, 0x44, 0x44, 0x44, 0x38, // o 79
0x00, 0xFC, 0x24, 0x24, 0x24, 0x18, // p 80
0x00, 0x18, 0x24, 0x24, 0x18, 0xFC, // q 81
0x00, 0x7C, 0x08, 0x04, 0x04, 0x08, // r 82
0x00, 0x48, 0x54, 0x54, 0x54, 0x20, // s 83
0x00, 0x04, 0x3F, 0x44, 0x40, 0x20, // t 84
0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C, // u 85
0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C, // v 86
0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C, // w 87
0x00, 0x44, 0x28, 0x10, 0x28, 0x44, // x 88
0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C, // y 89
0x00, 0x44, 0x64, 0x54, 0x4C, 0x44, // z 90
0x00, 0x08, 0x36, 0x41, 0x41, 0x00, // { 91
0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, // | 92
0x00, 0x00, 0x41, 0x41, 0x36, 0x08, // } 93
0x00, 0x08, 0x04, 0x08, 0x10, 0x08, // ~ 94
};

使い方

例1:文字の表示

適当な文字をサイズを変えながら表示する例です。

少々わかりにくいのですが、ここでいう”PAGE”とは行のことです。1行8ピクセルで、縦64ピクセルのOLEDディスプレイの場合は8ページまであります。

#include "Wire.h"
#include "tiny1306.h"

TINY1306 oled = TINY1306(0x3C, 128, 8);

void setup() {
 Wire.begin();
 oled.init();
 oled.clear();
 oled.display();
}

void loop()
{
 oled.setScale(1);
 oled.setPage(0);
 oled.printlnS("TSUZUREYA");
 
 oled.setScale(2);
 oled.setPage(2);
 oled.printlnS("TSUZUREYA");
 
 oled.setScale(3);
 oled.setPage(5);
 oled.printlnS("TSUZURE");
 
 delay(100);
}

attinyでSSD1306をつかう

 

例2:数値の表示

文字と適当な数値(float)を表示させる手順です。

#include "Wire.h"
#include "tiny1306.h"

TINY1306 oled = TINY1306(0x3C, 128, 8);

void setup() {
 Wire.begin();
 oled.init();
 oled.clear();
 oled.display();
}

void loop()
{
 oled.setScale(3);
 oled.setPage(2);
 String strA;
 float dataA = 12.45;

 for (int i = 0; i < 1000; i++)
 {
  dataA += 0.01;
  strA = "A:" + String(dataA, 2);
  oled.printlnS(strA);
  delay(10);
 }
}

コメント