common-lisp
電子工作
RaspberryPi

CommonLispでRaspberryPi電子工作 ~有機ELディスプレイ~

More than 1 year has passed since last update.

はじめに

今回はMARY-OB基板(OLED Board)【MARMEX-OB】を制御していきたいと思います。
載ってるディスプレイコントローラはSSD1351でした。

回路図

まず最初にRaspberryPiとLIS3DHを以下のように繋ぎます。

パッケージ作成

cffiをQuicklispでロードし、パッケージ定義。
いつものです。

packages.lisp
;; cffiをQuicklispでロード
(ql:quickload "cffi")

;; cl-cffiパッケージを定義
(defpackage :cl-cffi
    (:use :common-lisp :cffi))

APIラッパー作成

今回は特に何も追加していません。

libwiringPi.lisp
(define-foreign-library libwiringPi
  (:unix "libwiringPi.so"))

(use-foreign-library libwiringPi)

;;;; Core function

;; Initialization of the wiringPi
(defcfun "wiringPiSetupGpio" :int)

;; Set the mode of the GPIO pin
(defcfun "pinMode" :void (pin :int) (mode :int))

;; GPIO pin output control
(defcfun "digitalWrite" :void (pin :int) (value :int))

;; Waiting process
(defcfun "delay" :void (howlong :uint))

;; Set the state when nothing is connected to the terminal
(defcfun "pullUpDnControl" :void (pin :int) (pud :int))

;; Read the status of the GPIO pin
(defcfun "digitalRead" :int (pin :int))

;;;; I2C Library

;; Initialization of the I2C systems.
(defcfun "wiringPiI2CSetup" :int (fd :int))

;; Writes 8-bit data to the instructed device register.
(defcfun "wiringPiI2CWriteReg8" :int (fd :int) (reg :int) (data :int))

;; It reads the 16-bit value from the indicated device register.
(defcfun "wiringPiI2CReadReg16" :int (fd :int) (reg :int))

;;;; SPI library

;; SPI初期化
(defcfun "wiringPiSPISetup" :int (channel :int) (speed :int))

;; 選択されたSPIバス上での同時書込み/読出しトランザクションを実行
(defcfun "wiringPiSPIDataRW" :int (channel :int) (data :pointer) (len :int))

定数定義

今回は定数定義が多いため、別ファイルに分割しました。
以下の書籍とWebサイトを参考にしました。

・書籍
2枚入り 組み合わせ自在 超小型armマイコン基板 (CQ出版)

・Webサイト
SSD1351データシート
有機ELディスプレイコントローラ SSD1351について

definition-file.lisp
;; SPI settings
(defconstant +spi-cs+ 0)
(defconstant +spi-speed+ 10000000)

;; SSD1351 Commands
(defconstant +cmd-setcolumn+      #X15)  ; Column開始アドレスと終了アドレスを設定
(defconstant +cmd-setrow+         #X75)  ; Rowの開始アドレスと終了アドレスを設定
(defconstant +cmd-writeram+       #X5C)  ; RAMにデータを書き込み、他のコマンドを送るまで自動インクリメントでデータを書き込む
(defconstant +cmd-readram+        #X5D)  ; RAMのデータを読み込み、他のコマンドを送るまで自動インクリメントでデータを読み込む
(defconstant +cmd-setremap+       #XA0)  ; アドレスのインクリメントのモード 方向(垂直/水平)、色(RGB/RBG)、色モード(262k/65k/256)を設定
(defconstant +cmd-startline+      #XA1)  ; RAMのどこから描画対象にするかを設定
(defconstant +cmd-displayoffset+  #XA2)  ; 
(defconstant +cmd-displayalloff+  #XA4)  ; 全て黒 (GDRAMは書き換えない。)
(defconstant +cmd-displayallon+   #XA5)  ; 全て白 (GDRAMは書き換えない。)
(defconstant +cmd-normaldisplay+  #XA6)  ; 上の二つのコマンドを無効にする。
(defconstant +cmd-invertdisplay+  #XA7)  ; 色を反転
(defconstant +cmd-functionselect+ #XAB)  ; VDD regulatorの設定
(defconstant +cmd-displayoff+     #XAE)  ; スリープモードの設定 (OFF)
(defconstant +cmd-displayon+      #XAF)  ; スリープモードの設定 (ON)
(defconstant +cmd-precharge+      #XB1)  ; Phase1 と Phase2の設定
(defconstant +cmd-displayenhance+ #XB2)  ; 
(defconstant +cmd-clockdiv+       #XB3)  ; CLK、DCLKの設定
(defconstant +cmd-setvsl+         #XB4)  ; 
(defconstant +cmd-setgpio+        #XB5)  ; GPIO0とGPIO1の設定
(defconstant +cmd-precharge2+     #XB6)  ; phase 3 second pre-charge periodの設定
(defconstant +cmd-setgray+        #XB8)  ; グレイスケールのルックアップテーブル(GS1-GS63)を設定
(defconstant +cmd-uselut+         #XB9)  ; ビルトインのグレイスケールのルックアップテーブルを使う
(defconstant +cmd-prechargelevel+ #XBB)  ; Pre-charge voltageを設定
(defconstant +cmd-vcomh+          #XBE)  ; VCOMH Voltageを設定
(defconstant +cmd-contrastabc+    #XC1)  ; R,G,B毎のコントラスト設定
(defconstant +cmd-contrastmaster+ #XC7)  ; 全体のコントラスト設定
(defconstant +cmd-muxratio+       #XCA)  ; Multiplex Ratioの設定
(defconstant +cmd-commandlock+    #XFD)  ; コマンドを受けつけないモードにする

;; Test command
(defconstant +cmd-horizscroll+    #X96)  ; スクロールのテストコマンド スクロール設定
(defconstant +cmd-stopscroll+     #X9E)  ; スクロールのテストコマンド スクロール停止
(defconstant +cmd-startscroll+    #X9F)  ; スクロールのテストコマンド スクロール開始

;; GPIO settings
(defconstant +pin-oled-cs+    8)
(defconstant +pin-oled-vcc+   18)
(defconstant +pin-oled-reset+ 23)
(defconstant +pin-oled-mosi+  10)
(defconstant +pin-oled-sclk+  11)

;; GPIO pin mode
(defconstant +output+ 1)
(defconstant +input+  0)

;; GPIO level
(defconstant +high+   1)
(defconstant +low+    0)

;; OLED
(defconstant +oled-cmd+    0)
(defconstant +oled-data+   1)
(defconstant +oled-width+  128)
(defconstant +oled-height+ 128)

;; Color
(defconstant +max-red+   #X1F)
(defconstant +max-green+ #X3F)
(defconstant +max-blue+  #X1F)

OLED SSD1351プログラム本体

8色のラインを表示するだけのプログラムです。

oled-ssd1351.lisp
;; Load packages
(load "packages.lisp" :external-format :utf-8)

(in-package :cl-cffi)

;; Load wrapper API
(load "libwiringPi.lisp" :external-format :utf-8)

;; 定数定義をロード
(load "definition-file.lisp" :external-format :utf-8)

;; OLED Vcc Power ON
(defun oled-power-on ()
  (digitalWrite +pin-oled-vcc+ +high+))

;; OLED Vcc Power OFF
(defun oled-power-off ()
  (digitalWrite +pin-oled-vcc+ +low+))

;; SPI書込み処理
;; アクセスタイミングに合わせて+pin-oled-mosi+をHigh/Lowします。
(defun spi-write (data)
  (let ((count 7))
    (loop
       (if (< count 0) (return))
       (digitalWrite +pin-oled-sclk+ +low+)
       (if (equal (ldb (byte 1 count) data) 1)
           (digitalWrite +pin-oled-mosi+ +high+)
           (digitalWrite +pin-oled-mosi+ +low+))
       (digitalWrite +pin-oled-sclk+ +high+)
       (decf count))))

;; コマンド/データ書込み処理
;; コマンドであれば、9ビット目は"0"
;; データであれば、9ビット目は"1"
(defun oled-cd-write (mode data)
  (digitalWrite +pin-oled-cs+ +low+)
  (digitalWrite +pin-oled-sclk+ +low+)
  (if (equal mode 0)
      (digitalWrite +pin-oled-mosi+ +oled-cmd+)
      (digitalWrite +pin-oled-mosi+ +oled-data+))
  (digitalWrite +pin-oled-sclk+ +high+)
  (spi-write data)
  (digitalWrite +pin-oled-cs+ +high+))

;; 全スクリーンブラックアウト処理
(defun black-out ()
  ;; Column (#X15)
  (oled-cd-write +oled-cmd+ +cmd-setcolumn+)
  (dolist (data `(#X00 #X7F))
    (oled-cd-write +oled-data+ data))

  ;; Row (#X75)
  (oled-cd-write +oled-cmd+ +cmd-setrow+)
  (dolist (data `(#X00 #X7F))
    (oled-cd-write +oled-data+ data))

  ;; Start write to ram (#XAF)
  (dotimes (count (* 128 128))
    (oled-cd-write +oled-cmd+ +cmd-writeram+)
    (dolist (data `(#X00 #X00))
      (oled-cd-write +oled-data+ data))))

;; 四角形描画処理
(defun create-rectangle (x y width height rgb-bit1 rgb-bit2)
  ;; Column (#X15)
  (oled-cd-write +oled-cmd+ +cmd-setcolumn+)
  (oled-cd-write +oled-data+ x)
  (oled-cd-write +oled-data+ (- (+ x width) 1))

  ;; Row (#X75)
  (oled-cd-write +oled-cmd+ +cmd-setrow+)
  (oled-cd-write +oled-data+ y)
  (oled-cd-write +oled-data+ (- (+ y height) 1))

  ;; Start write to ram (#XAF)
  (dotimes (count (* width height))
    (oled-cd-write +oled-cmd+ +cmd-writeram+)
    (oled-cd-write +oled-data+ rgb-bit1)
    (oled-cd-write +oled-data+ rgb-bit2)))

;; 色を作成する処理
;; OLEDモジュールのピクセルフォーマットは
;; 赤色データ:11~15ビットの5ビット
;; 緑色データ:5~10ビットの6ビット
;; 青色データ:0~4ビットの5ビット
(defun create-rgb-bits (count)
  (let (red green blue rgb-bit1 rgb-bit2)
    (if (equal (ldb (byte 1 2) count) 1) (setq red +max-red+) (setq red #X00))
    (if (equal (ldb (byte 1 1) count) 1) (setq green +max-green+) (setq green #X00))
    (if (equal (ldb (byte 1 0) count) 1) (setq blue +max-blue+) (setq blue #X00))
    (setq rgb-bit1 (logior (ash red 3) (ash green -3)))
    (setq rgb-bit2 (logior (ash green -5) blue))
    (list rgb-bit1 rgb-bit2)))

;; 8色のラインを描画する処理
(defun create-color-bar ()
  (let (colorbar-width colorbar-height rgb-bits)
    (setq colorbar-width (/ +oled-width+ 8))
    (setq colorbar-height +oled-height+)
    (dotimes (count 8)
      (setq rgb-bits (create-rgb-bits count))
      (create-rectangle (* colorbar-width count) 0 colorbar-width colorbar-height (car rgb-bits) (cadr rgb-bits)))))

;; SSD1351 OLED 初期化処理
;; 書籍「2枚入り 組み合わせ自在 超小型armマイコン基板 (CQ出版)」を参考に初期化
(defun oled_init ()
  ;; #XA4
  (oled-cd-write +oled-cmd+ +cmd-displayalloff+)

  ;; #XFD (Unlock)
  (oled-cd-write +oled-cmd+ +cmd-commandlock+)
  (oled-cd-write +oled-data+ #X12)

  ;; #XFD (Unlock)
  (oled-cd-write +oled-cmd+ +cmd-commandlock+)
  (oled-cd-write +oled-data+ #XB1)

  ;; #XAE
  (oled-cd-write +oled-cmd+ +cmd-displayoff+)

  ;; #XB3
  (oled-cd-write +oled-cmd+ +cmd-clockdiv+)
  (oled-cd-write +oled-data+ #XF1)

  ;; #XCA
  (oled-cd-write +oled-cmd+ +cmd-muxratio+)
  (oled-cd-write +oled-data+ #X7F)

  ;; #XA2
  (oled-cd-write +oled-cmd+ +cmd-displayoffset+)
  (oled-cd-write +oled-data+ #X00)

  ;; #XA1
  (oled-cd-write +oled-cmd+ +cmd-startline+)
  (oled-cd-write +oled-data+ #X00)

  ;; #XA0 (#X74:65k カラー)
  (oled-cd-write +oled-cmd+ +cmd-setremap+)
  (oled-cd-write +oled-data+ #X74)

  ;; #XB5
  (oled-cd-write +oled-cmd+ +cmd-setgpio+)
  (oled-cd-write +oled-data+ #X00)

  ;; #XAB
  (oled-cd-write +oled-cmd+ +cmd-functionselect+)
  (oled-cd-write +oled-data+ #X01)

  ;; #XB4
  (oled-cd-write +oled-cmd+ +cmd-setvsl+)
  (dolist (data `(#XA0 #XB5 #X55))
    (oled-cd-write +oled-data+ data))

  ;; #XC1
  (oled-cd-write +oled-cmd+ +cmd-contrastabc+)
  (dolist (data `(#XC8 #X80 #XC8))
    (oled-cd-write +oled-data+ data))

  ;; #XC7
  (oled-cd-write +oled-cmd+ +cmd-contrastmaster+)
  (oled-cd-write +oled-data+ #X0F)

  ;; #XB8
  (oled-cd-write +oled-cmd+ +cmd-setgray+)
  (dolist (data `(#X02 #X03 #X04 #X05 #X06 #X07 #X08 #X09
                  #X0A #X0B #X0C #X0D #X0E #X0F #X10 #X11
                  #X12 #X13 #X15 #X17 #X19 #X1B #X1D #X1F
                  #X21 #X23 #X25 #X27 #X2A #X2D #X30 #X33
                  #X36 #X39 #X3C #X3F #X42 #X45 #X48 #X4C
                  #X50 #X54 #X58 #X5C #X60 #X64 #X68 #X6C
                  #X70 #X74 #X78 #X7D #X82 #X87 #X8C #X91
                  #X96 #X9B #XA0 #XA5 #XAA #XAF #XB4))
    (oled-cd-write +oled-data+ data))

  ;; #XB1
  (oled-cd-write +oled-cmd+ +cmd-precharge+)
  (oled-cd-write +oled-data+ #X32)

  ;; #XB2
  (oled-cd-write +oled-cmd+ +cmd-displayenhance+)
  (dolist (data `(#XA4 #X00 #X00))
    (oled-cd-write +oled-data+ data))

  ;; #XBB
  (oled-cd-write +oled-cmd+ +cmd-prechargelevel+)
  (oled-cd-write +oled-data+ #X17)

  ;; #XB6
  (oled-cd-write +oled-cmd+ +cmd-precharge2+)
  (oled-cd-write +oled-data+ #X01)

  ;; #XBE
  (oled-cd-write +oled-cmd+ +cmd-vcomh+)
  (oled-cd-write +oled-data+ #X05)

  ;; #XA6
  (oled-cd-write +oled-cmd+ +cmd-normaldisplay+)

  ;; OLED Black out
  (black-out)

  ;; OLED Vcc Power ON
  (oled-power-on)

  ;; #XAF
  (oled-cd-write +oled-cmd+ +cmd-displayon+))

;; メイン関数
(defun main ()
  ;; SPI初期化
  (wiringPiSPISetup +spi-cs+ +spi-speed+)

  ;; GPIO初期化
  (wiringPiSetupGpio)

  ;; 各種GPIOモード設定 (全部OUTPUT)
  (pinMode +pin-oled-cs+    +output+)
  (pinMode +pin-oled-vcc+   +output+)
  (pinMode +pin-oled-reset+ +output+)
  (pinMode +pin-oled-mosi+  +output+)
  (pinMode +pin-oled-sclk+  +output+)

  ;; CS High
  (digitalWrite +pin-oled-cs+ +high+)

  ;; OLEDリセット処理
  (digitalWrite +pin-oled-reset+ +high+)
  (delay 500)
  (digitalWrite +pin-oled-reset+ +low+)
  (delay 500)
  (digitalWrite +pin-oled-reset+ +high+)
  (delay 500)

  ;; OLED初期化
  (oled_init)

  ;; 8色のライン描画
  (create-color-bar)

  ;; 待機
  (delay 10000)

  ;; ブラックアウト
  (black-out)

  ;; OLED Vcc Power OFF
  (oled-power-off))

;; 実行!!!
(main)

今回は9ビットワードでデータを書き込む必要があったため、wiringPiSPIDataRW関数が使えませんでした。
なので、自分で9ビットワードで書き込むために、oled-cd-write関数とspi-write関数を作成しました。
今回使用したOLEDモジュールのアクセスタイミングは以下のようになっています。

よって、それに合わせてditigalWrite関数でHighとLowを出力してあげれば9ビットワードで送信できるという仕組みです。
まさか、こんなものを自分で作ることになるとは思いませんでした・・・。

実行

以下のコマンドで実行します。

sbcl --load oled-ssd1351.lisp

以下実行中の様子

まだ少し不安定なところはありますが、一応表示することができました。
何回か実行しているとたまにちゃんと表示されてくれないときがあったりするのでまだまだ改良余地ありですね~。

最後に

何と言いますが、ものすごく難しかったです。
OLEDに8色のラインが表示されたときは思わずガッツポーズしてしまいました。(笑)

他にも文字や画像、動画なども表示できればいいですね。

今回のソースコードはGithubで公開しています。
よろしければ以下からどうぞ~。
Github