I2C 接続の有機EL + Raspberry Pi + Ruby で温度計付き時計を実装してみる

 今回は Raspberry Pi と OLED(有機EL)ディスプレイを接続して表示させてみたいと思います。使用したディスプレイは aitendo さんで販売されていた下記パーツです。

www.aitendo.com

 購入時は下記写真のように、ディスプレイとピンヘッダが同封されていますが別々になっていますので、自分で半田付けして使用します。

f:id:akanuma-hiroaki:20171014221626j:plain:w450

Raspberry Pi と接続

 それでは Raspberry Pi と OLED ディスプレイを接続します。今回下記サイトを参考にさせていただきました。

Overview | SSD1306 OLED Displays with Raspberry Pi and BeagleBone Black | Adafruit Learning System

raspberry pi でI2C接続の有機ELを使って時計を作る - Qiita

 実際の配線は下記のようにシンプルです。今回使用しているディスプレイは I2C 接続なので、Raspberry Pi では 3番(SDA)と5番(SCL)のピンを使うことになります。

f:id:akanuma-hiroaki:20171015015610p:plain:w300:left

VCC: 1番ピン(3.3V Power)
GND: 6番ピン(GND)
SCL: 5番ピン(SCL)
SDA: 3番ピン(SDA)

I2C の使用設定

 Raspberry Pi の I2C 接続を使うにはいくつか設定が必要です。設定方法については下記サイトを参考にさせていただきました。

I2Cを使う(設定編) | Make.

Configuring I2C | Adafruit's Raspberry Pi Lesson 4. GPIO Setup | Adafruit Learning System

 まずは I2C 接続をするためのモジュール設定を追加します。

pi@raspberrypi:~/display_sample $ sudo vi /etc/modules

 下記内容を追記します。

i2c-bcm2708 
i2c-dev

 そして再起動します。

pi@raspberrypi:~/display_sample $ sudo reboot

 次に必要なライブラリをインストールします。 python-smbus をインストールすると i2c-tools もインストールされます。

pi@raspberrypi:~/display_sample $ sudo apt-get install python-smbus
Reading package lists... Done
Building dependency tree        
Reading state information... Done
The following package was automatically installed and is no longer required:
  libbison-dev
Use 'apt-get autoremove' to remove it.
The following extra packages will be installed:
  i2c-tools
Suggested packages:
  libi2c-dev
The following NEW packages will be installed:
  i2c-tools python-smbus
0 upgraded, 2 newly installed, 0 to remove and 53 not upgraded.
Need to get 60.8 kB of archives.
After this operation, 286 kB of additional disk space will be used.
Do you want to continue? [Y/n] Y
Get:1 http://archive.raspberrypi.org/debian/ jessie/main i2c-tools armhf 3.1.1+svn-2 [51.3 kB]
Get:2 http://archive.raspberrypi.org/debian/ jessie/main python-smbus armhf 3.1.1+svn-2 [9,462 B]
Fetched 60.8 kB in 1s (35.8 kB/s)  
Selecting previously unselected package i2c-tools.
(Reading database ... 60922 files and directories currently installed.)
Preparing to unpack .../i2c-tools_3.1.1+svn-2_armhf.deb ...
Unpacking i2c-tools (3.1.1+svn-2) ...
Selecting previously unselected package python-smbus.
Preparing to unpack .../python-smbus_3.1.1+svn-2_armhf.deb ...
Unpacking python-smbus (3.1.1+svn-2) ...
Processing triggers for man-db (2.7.0.2-5) ...
Setting up i2c-tools (3.1.1+svn-2) ...
/run/udev or .udevdb or .udev presence implies active udev.  Aborting MAKEDEV invocation.
Setting up python-smbus (3.1.1+svn-2) ...

 次にシステムの設定ファイルを編集します。

pi@raspberrypi:~/display_sample $ sudo vi /boot/config.txt

 編集前後の差分は下記のようになります。

pi@raspberrypi:~/display_sample $ diff /boot/config.txt.20171014 /boot/config.txt
46c46,47
< #dtparam=i2c_arm=on
---
> dtparam=i2c1=on
> dtparam=i2c_arm=on

 起動時に I2C のドライバが有効になっていることを確認しておきます。

pi@raspberrypi:~/display_sample $ dmesg | grep i2c
[    2.429468] i2c /dev entries driver

 ここまでで設定変更は終了です。OLEDディスプレイを接続すると、下記のように接続デバイスのアドレスが表示されます。

pi@raspberrypi:~/display_sample $ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --        

Ruby でディスプレイに表示させてみる

 ここまでで接続と設定が完了したので、ディスプレイに何か表示させてみたいと思います。今回使用しているディスプレイのコントローラは SSD1306 で、公式では Python のライブラリが公開されています。

github.com

 Ruby のライブラリを探したところ、公式ではありませんが、下記の Gem が公開されていましたので、今回は試しにこれを使ってみます。

github.com

 Gemfile には下記のように書いておきます。

pi@raspberrypi:~/display_sample $ cat Gemfile 
# frozen_string_literal: true
source "https://rubygems.org"

gem 'SSD1306'

 この Gem を使うには ImageMagick が必要なので、インストールします。

pi@raspberrypi:~/display_sample $ sudo apt-get update
pi@raspberrypi:~/display_sample $ sudo apt-get install imagemagick

 そして bundle install します。

pi@raspberrypi:~/display_sample $ bundle install
Fetching gem metadata from https://rubygems.org/...
Fetching version metadata from https://rubygems.org/..
Resolving dependencies...
Using i2c 0.4.0
Installing rmagick 2.16.0 with native extensions
Using bundler 1.14.6
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /home/pi/display_sample/vendor/bundle/gems/rmagick-2.16.0/ext/RMagick
/home/pi/.rbenv/versions/2.4.1/bin/ruby -r ./siteconf20171014-3193-1tivtgl.rb extconf.rb
checking for gcc... yes
checking for Magick-config... no
checking for pkg-config... yes
Package MagickCore was not found in the pkg-config search path.
Perhaps you should add the directory containing `MagickCore.pc'
to the PKG_CONFIG_PATH environment variable
No package 'MagickCore' found
checking for outdated ImageMagick version (<= 6.4.9)... *** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
        --with-opt-dir
        --without-opt-dir
        --with-opt-include
        --without-opt-include=${opt-dir}/include
        --with-opt-lib
        --without-opt-lib=${opt-dir}/lib
        --with-make-prog
        --without-make-prog
        --srcdir=.
        --curdir
        --ruby=/home/pi/.rbenv/versions/2.4.1/bin/$(RUBY_BASE_NAME)

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /home/pi/display_sample/vendor/bundle/extensions/armv7l-linux/2.4.0-static/rmagick-2.16.0/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in /home/pi/display_sample/vendor/bundle/gems/rmagick-2.16.0 for inspection.
Results logged to /home/pi/display_sample/vendor/bundle/extensions/armv7l-linux/2.4.0-static/rmagick-2.16.0/gem_make.out

An error occurred while installing rmagick (2.16.0), and Bundler cannot continue.
Make sure that `gem install rmagick -v '2.16.0'` succeeds before bundling.

 No package 'MagickCore' found ということでエラーになってしまったので、追加で必要なライブラリをインストールします。

pi@raspberrypi:~/display_sample $ sudo apt-get install libmagickcore-dev libmagickwand-dev

 そして再度 bundle install 実行。

pi@raspberrypi:~/display_sample $ bundle install
Fetching gem metadata from https://rubygems.org/...
Fetching version metadata from https://rubygems.org/..
Resolving dependencies...
Using i2c 0.4.0
Installing rmagick 2.16.0 with native extensions
Using bundler 1.14.6
Installing SSD1306 0.6.1
Bundle complete! 1 Gemfile dependency, 4 gems now installed.
Bundled gems are installed into ./vendor/bundle.

 今度は成功しました。

 ではまずは Hello World ということで、文字を表示してみます。サンプルコードは下記のようにしました。

require 'bundler/setup'
require 'SSD1306'

class Display
  def initialize
    @disp = SSD1306::Display.new
  end

  def execute
    @disp.println('Hello World!!')
    @disp.display!
  end
end

if $0 == __FILE__
  display = Display.new
  display.execute
end

 そして実行します。

pi@raspberrypi:~/display_sample $ bundle exec ruby display.rb

 すると下記のようにディスプレイに文字が表示されます。

f:id:akanuma-hiroaki:20171014221514j:plain:w450

 フォントを大きくしてみます。上記コードの execute メソッドに下記のようにフォントサイズの設定を追加して実行します。

  def execute
    @disp.font_size = 2 # フォントサイズの設定
    @disp.println('Hello World!!')
    @disp.display!
  end

 すると先ほどと比べて縦横が二倍になった大きさで文字が表示されます。

f:id:akanuma-hiroaki:20171014222203j:plain:w450

 次に画像を表示してみます。今回のディスプレイの解像度は 128 x 64 なので、下記のような 128 x 64 のモノクロ画像を用意し、 Raspberry Pi 上に cat.png として配置しておきます。

f:id:akanuma-hiroaki:20171014224726p:plain:w200

 そしてコードは下記のようにしました。 ImageMagick の Image クラスで画像を読み込んで、それを画像表示用メソッドに渡す形になります。

require 'bundler/setup'
require 'SSD1306'

include Magick

class Display

  def initialize
    @disp = SSD1306::Display.new
  end

  def execute
    img = Image.read('/home/pi/display_sample/cat.png').first
    @disp.image(img)
    @disp.display!
  end
end

if $0 == __FILE__
  display = Display.new
  display.execute
end

 これを実行すると下記のような感じで画像が表示されます。比較用に元の画像を並べておきます。

f:id:akanuma-hiroaki:20171014224850j:plain:w450f:id:akanuma-hiroaki:20171014224726p:plain

温度計付き時計の実装

 それではここまでの内容を踏まえて時計を表示させてみたいと思います。ついでに温度センサーを組み合わせて温度も一緒に表示します。温度センサーは以前使ったことのある DS18B20 を使います。モジュールの設定方法などについてはこちらの記事をご参照ください。配線は下記のようにしています。

f:id:akanuma-hiroaki:20171019083901p:plain:w300

 まずは温度計のクラスを下記のように実装します。 DS18B20 からの温度データの読み出し方の詳細については先ほどのリンク先の記事をご覧いただくとして、ここでは割愛します。

require 'bundler/setup'

SENSOR_FILE_PATH = '/sys/bus/w1/devices/28-*/w1_slave'.freeze

# For using thermo sensor DS18B20.
class ThermoMeter
  def initialize
    @device_file_name = Dir.glob(SENSOR_FILE_PATH).first
  end

  def read
    sensor_data = File.read(@device_file_name)
    sensor_data.match(/t=(.*$)/)[1].to_f / 1000
  end
end

 次に SSD1306 を使うためのクラスを下記のように実装します。 SSD1306::Display のメソッドをラップした形です。

require 'bundler/setup'
require 'SSD1306'

include Magick

class Display
  def initialize
    @disp = SSD1306::Display.new
  end

  def font_size(size)
    @disp.font_size = size
  end

  def show
    @disp.display!
  end

  def clear
    @disp.clear!
  end

  def println(str)
    @disp.println(str)
  end

  def print(str)
    @disp.print(str)
  end
end

 そして時計を表示するためのクラスを下記のように実装します。やっていることは単純で、ループを回して一秒ごとに前回ループ時の時刻と今回の時刻が分単位で同じかをチェックし、変わっていれば温度データも取得して画面の表示を更新しています。

require 'bundler/setup'
require './display.rb'
require './thermometer.rb'

class Clock
  TIMEZONE     = 'Asia/Tokyo'
  DATE_FORMAT  = '%Y/%m/%d'
  TIME_FORMAT  = '%H:%M'
  DATE_FONT_PX = 2
  TIME_FONT_PX = 4
  TEMP_FONT_PX = 2

  def initialize
    ENV['TZ'] = 'Asia/Tokyo'
    @display = Display.new
    @thermo_meter = ThermoMeter.new
    @time_str_buffer = ''
  end

  def redisplay_time(date_str, time_str)
    @display.clear
    @display.font_size(DATE_FONT_PX)
    @display.println(date_str)
    @display.font_size(TIME_FONT_PX)
    @display.println(time_str)
    @display.font_size(TEMP_FONT_PX)
    @display.println("#{@thermo_meter.read.round(1)} deg.C")
    @display.show
  end

  def start
    begin
      loop do
        sleep(1)

        now = Time.now.localtime

        date_str = now.strftime(DATE_FORMAT)
        time_str = now.strftime(TIME_FORMAT)

        next if @time_str_buffer == time_str

        @time_str_buffer = time_str
        redisplay_time(date_str, time_str)
      end
    rescue => e
      puts e.backtrace.join("\n")
    ensure
      @display.clear
    end
  end
end

if $PROGRAM_NAME == __FILE__
  clock = Clock.new
  clock.start
end

 これを下記のように実行すると、ディスプレイに現在時刻と気温が表示されます。

pi@raspberrypi:~/display_sample $ bundle exec ruby clock.rb

f:id:akanuma-hiroaki:20171019090020j:plain:w450

まとめ

 液晶ディスプレイが使えるようになるとできることの幅も広がりそうですし、デバッグも便利になりそうです。今回は公開されている Gem をそのまま使っただけなので細かい制御まではできていませんが、表示している内容の一部分だけを書き換えたりできるようになると、レイアウトも柔軟にできそうな気がします。

 今回のコードは下記にも公開しました。

github.com

 おまけで画像の表示サンプルに使った画像の元画像を載せておきます。

f:id:akanuma-hiroaki:20171015002714j:plain:w300