LoginSignup
21
6

More than 3 years have passed since last update.

obnizOSをOLED付ESP32に入れてみた

Last updated at Posted at 2019-08-25

先日、obnizOSがリリースされたので、さっそく手元にあったESP32に入れてみました。

obnizOS
 https://obniz.io/ja/doc/obnizos/os_overview

ちょうどそのESP32には、OLEDディスプレイがついているので、それを制御してみようと思います。
(本当はM5Stick-Cでやろうとしたのですが、CoreDumpを吐いて動きませんでした。。。)

私が買っていたのはこれです。
 ESP32 OLED開発ボードWiFi Bluetoothデュアルコアディスプレイモジュール、ケーブルESP WROOM 32 Wemos Lolin for Arduino

obnizOSのインストールは、他の方の記事を参考にさせていただきました。例えば、
 obnizOSをM5Stack(M5StickC)にインストールする手順
 obniz OSをESP32(NefryBT)に入れて動かそう!

ESP32 OLED開発ボードとOLEDディスプレイについて

ESP32 OLED開発ボードには、WeMosという刻印があり、それをもとに調べてみると、以下の参考ページがありました。
 Wemos Lolin board (ESP32 with 128x64 SSD1306 I2C OLED display)

ESP32 OLED開発ボードについているディスプレイは以下だそうです。
 SSD1306

128x64ドットのモノクロです。
接続I/Fはいろいろあるのですが、その中のI2Cで接続されているようです。
スレーブアドレスは0x3Cでした。

PINの関係がいまいちわからないのですが、以下にしたら動きました。
・SDA:5
・SCL:4

SSD1306ドライバの作成

以下を参考にさせていただき、obnizに移植しました。

oled-js-pi
 https://github.com/juddflamm/oled-js-pi/blob/master/oled.js

以下、ソースコードです。

wemos.js
'use strict';

class WeMos{
    constructor(obniz){
        this.display = new WeMos_display(obniz);
    }
}

class WeMos_display{
    constructor(obniz, addr, sda, scl){
        this.obniz = obniz;

    this.ADDRESS = addr || 0x3C;
    this.SDA = sda || 5;
    this.SCL = scl || 4;

    this.obniz.i2c0.start({mode: "master", sda: this.SDA, scl: this.SCL, clock: 400000}); 

    this.WIDTH = 128;
    this.HEIGHT = 64;

    this.width = this.WIDTH;
    this.height = this.HEIGHT;

        this.mode = false;
        this.buffer = [];
        this.clear();

        this.TRANSFER_SIZE = 16;

        this.DISPLAY_OFF = 0xAE;
        this.DISPLAY_ON = 0xAF;
        this.SET_DISPLAY_CLOCK_DIV = 0xD5;
        this.SET_MULTIPLEX = 0xA8;
        this.SET_DISPLAY_OFFSET = 0xD3;
        this.SET_START_LINE = 0x00;
        this.CHARGE_PUMP = 0x8D;
        this.EXTERNAL_VCC = false;
        this.MEMORY_MODE = 0x20;
        this.SEG_REMAP = 0xA1; // using 0xA0 will flip screen
        this.COM_SCAN_DEC = 0xC8;
        this.COM_SCAN_INC = 0xC0;
        this.SET_COM_PINS = 0xDA;
        this.SET_CONTRAST = 0x81;
        this.SET_PRECHARGE = 0xd9;
        this.SET_VCOM_DETECT = 0xDB;
        this.DISPLAY_ALL_ON_RESUME = 0xA4;
        this.NORMAL_DISPLAY = 0xA6;
        this.COLUMN_ADDR = 0x21;
        this.PAGE_ADDR = 0x22;
        this.INVERT_DISPLAY = 0xA7;
        this.ACTIVATE_SCROLL = 0x2F;
        this.DEACTIVATE_SCROLL = 0x2E;
        this.SET_VERTICAL_SCROLL_AREA = 0xA3;
        this.RIGHT_HORIZONTAL_SCROLL = 0x26;
        this.LEFT_HORIZONTAL_SCROLL = 0x27;
        this.VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29;
        this.VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A;

    /* initialze */
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.DISPLAY_OFF]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.SET_DISPLAY_CLOCK_DIV, 0x80]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.SET_MULTIPLEX, 0x3F]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.SET_DISPLAY_OFFSET, 0x00]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.SET_START_LINE]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.CHARGE_PUMP, 0x14]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.MEMORY_MODE, 0x00]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.SEG_REMAP]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.COM_SCAN_DEC]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.SET_COM_PINS, 0x12]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.SET_CONTRAST, 0x8F]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.SET_PRECHARGE, 0xF1]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.SET_VCOM_DETECT, 0x40]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.DISPLAY_ALL_ON_RESUME]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.NORMAL_DISPLAY]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.DISPLAY_ON]);

    this.mode = true;
  }

  async clear(){
    for( var y = 0 ; y < this.fl(this.HEIGHT / 8) ; y++ ){
      for( var x = 0 ; x < this.WIDTH ; x++ ){
        this.buffer[ y * this.WIDTH + x ] = 0x00;
      }
    }

    if( this.mode ){
      return this.update();
    }else{
      return Promise.resolve();
    }
  }

  async drawing(mode){
    this.mode = mode;
    if( this.mode ){
      return this.update();
    }else{
      return Promise.resolve();
    }
  }

  async raw(ary){
    for( var y = 0 ; y < this.HEIGHT ; y++ ){
      for( var x = 0 ; x < this.WIDTH ; x += 8 ){
        var val = ary[y * this.fl(this.WIDTH / 8) + this.fl(x / 8)];
        for( var i = 0 ; i < 8 ; i++ )
          this.put_pixel(x + i, y, (val & (0x01 << i)) != 0x00);
      }
    }

    if( this.mode ){
      return this.update();
    }else{
      return Promise.resolve();
    }
  }

  async draw(ctx){
      var img = ctx.getImageData(0, 0, this.WIDTH, this.HEIGHT);

    for (var y = 0; y < this.HEIGHT; y++ ) {
      for (var x = 0; x < this.WIDTH; x++) {
        var val = this.to_mono(img.data[(x + y * this.WIDTH) * 4], img.data[(x + y * this.WIDTH) * 4 + 1], img.data[(x + y * this.WIDTH) * 4 + 2], img.data[(x + y * this.WIDTH) * 4 + 3]);
        this.put_pixel(x, y, val);
      }
    }

    if( this.mode ){
      return this.update();
    }else{
      return Promise.resolve();
    }
  }

  put_pixel(x, y, val){
    var temp = this.buffer[this.fl(y / 8) * this.WIDTH + x];
    var index = y % 8;
    if(val)
      temp |= 0x01 << index;
    else
      temp &= (~(0x01 << index)) & 0xff;
    this.buffer[this.fl(y / 8) * this.WIDTH + x] = temp;
  }

  async update(){
    var ret = await this.obniz.i2c0.readWait(this.ADDRESS, 1);
    if( (ret[0] >> 7) & 0x01 != 0x00 ){
      console.log('busy');
      return;
    }

    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.COLUMN_ADDR, 0, this.WIDTH - 1]);
    this.obniz.i2c0.write(this.ADDRESS, [0x00, this.PAGE_ADDR, 0, this.fl(this.HEIGHT / 8) - 1]);
    for( var y = 0 ; y < this.fl(this.HEIGHT / 8) ; y++ ){
      for( var x = 0 ; x < this.WIDTH ; x += this.TRANSFER_SIZE ){
        var data = this.buffer.slice( y * this.WIDTH + x, y * this.WIDTH + x + this.TRANSFER_SIZE);
        data.unshift(0x40);
        this.obniz.i2c0.write(this.ADDRESS, data);
      }
    }
  }

  fl(f){
    return Math.floor(f);
  }

  to_mono(r, g, b, a){
    var grey = r * 0.299 + g * 0.587 + b * 0.114;
    if( a > 127 || grey > 127.5)
      return 1;
    else
      return 0;
  }
}

obniz.displayの仕様に似せて作ってみました。
線の描画や文字列の描画機能はなく、canvasを使うことを前提にしています。
使うときには、以下のような流れになります。

var wemos = new WeMos(obniz); /* これで初期化 */
wemos.display.clear(); /* 必要に応じて画面クリア */
const ctx = obniz.util.createCanvasContext(wemos.display.width, wemos.display.height); /* canvasを作成 */
/* ここでいろいろcanvasの描画 */
wemos.display.draw(ctx); /* OLEDに描画 */

HTMLサンプル

ついでに、ブラウザから操作するHTMLページを載せておきます。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com 'unsafe-eval' 'unsafe-inline'; style-src * 'unsafe-inline'; media-src *; img-src * data: content: blob:;">
  <meta name="format-detection" content="telephone=no">
  <meta name="msapplication-tap-highlight" content="no">
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">

  <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <!-- Optional theme -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
  <!-- Latest compiled and minified JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

  <title>obniz + WeMos</title>

  <script src="js/methods_utils.js"></script>
  <script src="js/vue_utils.js"></script>

  <script src="https://unpkg.com/obniz@2.2.0/obniz.js" crossorigin="anonymous"></script>
  <script src="js/wemos.js"></script>
  <script src="https://unpkg.com/vue"></script>
</head>
<body>
    <div id="top" class="container">
        <h1>obniz + WeMos</h1>
        <label>connected</label> {{connected}}<br>
        <label>string</label> <input type="text" class="form-control" v-model="string">
        <button class="btn btn-primary" v-on:click="print_string()">print_string</button>
        <button class="btn btn-primary" v-on:click="clear_screen()">clear_screen</button>

        <div class="modal fade" id="progress">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h4 class="modal-title">{{progress_title}}</h4>
                    </div>
                    <div class="modal-body">
                        <center><progress max="100" /></center>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script src="js/start.js"></script>
</body>
js/start.js
'use strict';

var obniz = new Obniz("【obnizのid】");
var wemos = null;

var vue_options = {
    el: "#top",
    data: {
        progress_title: '',

        connected: false,
        string: ''
    },
    computed: {
    },
    methods: {
        initialize: function(){
            obniz.onconnect = async () => {
                console.log('obniz.onconnect');

                this.connected = true;
                wemos = new WeMos(obniz);
                wemos.display.clear();
            };
            obniz.onclose = () =>{
                console.log('obniz.onclose');
                wemos = null;
                this.connected = false;
            };
        },
        print_string: function(){
            const ctx = obniz.util.createCanvasContext(wemos.display.width, wemos.display.height);

            ctx.fillStyle = "white";
            ctx.font = "20px Avenir";
            ctx.fillText(this.string, 0, 40);

            wemos.display.draw(ctx);
        },
        clear_screen: function(){
            wemos.display.clear();
        }
    },
    created: function(){
    },
    mounted: function(){
        proc_load();

        this.initialize();
    }
};
vue_add_methods(vue_options, methods_utils);
var vue = new Vue( vue_options );

【obnizのid】の部分は、お手持ちのobnizデバイスに合わせてください。
その他、足りないソースは こちら です。

ブラウザから参照すると、obnizデバイスと接続されて、connectedがtrueになります。
stringのテキストボックスに適当な文字を入れて、「print_string」ボタンを押下すると、obnizのディスプレイにその文字が表示されます。
「clear_screen」ボタンを押下すると、ディスプレイがクリアされます。

image.png

以上

21
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
6