JSON

インターネット上のデータ交換フォーマットとして、最近広く使われているJSON (JavaScript Object Notation)は、名前にJavaScriptが入っている事からも分かる通り、JavaScriptの文法をベースに作られたフォーマットだが、もちろんそれ以外の言語でも使用する事ができる。

PythonにもJSONを簡単に取り扱うためのモジュールが存在しているので、Raspberry PiでJSONのデータを取得し、SPI接続の液晶モジュールに表示させてみた。

ライブドアのお天気WebサービスがJSON形式で天気情報データを配布していて、データの構造については「お天気Webサービス仕様」に詳しく書かれている。

基本的には

import json

でモジュールを読み込んでおいてから、loadsメソッドで

http://weather.livedoor.com/forecast/webservice/json/v1?city=’地域ID’

を読み込むと指定した地域IDに対応した天気情報データが取得できる。

取得したデータには地域名、天気、気温などが含まれていて、天気に対応したアイコン画像のURLもその中に含まれている。

テストとして札幌、仙台、東京、名古屋、大阪、福岡、那覇、の天気データを取得して、順番に表示するプログラムを作ってみた。

2013-12-13 15.07.08

日本語の表示で特定のフォントサイズの時に表示が崩れるという現象が発生して、ちょっとばかり悩んでしまった。どうもTrueTypeフォントでサイズ指定が小さい時に、ビットマップフォントが代替で使用される仕組みと関係しているのでは無いかと思われるが、原因は今のところ不明。

日本語フォントは「ttf-kochi-gothic」を使用したので、フォントがインストールされていない場合は

$ sudo apt-get install ttf-kochi-gothic

で予めインストールしておく必要がある。

テストプロプログラムのソースコードは、「続きを読む」をクリック。
続きを読む »

広告

Color LCD(5)

SPI接続カラーLCDのコントーラST7735Rにはインテリジェントな描画機能が無いので、画面上に直線を引いたり塗りつぶしたりといった処理はすべて自前で行わなくてはならない。

任意の位置にドットを表示する事ができれば、あとはプログラムによって色々な図形を描画することは可能で、実際にブレゼンハムのアルゴリズムで直線描画のメソッドを作ったりはしてみたのだが、必要とされる描画機能をすべて自前で実装するのはかなり大変。

画像ファイルを読み込むために使用したPillowには各種描画を行うためのモジュールも用意されているので、それを使ってメモリ上の仮想画面に描画を行ってからLCDに転送してはどうか?と思って試してみた。

具体的には

from PIL import ImageDraw

でImageDrawモジュールをロードしておいて

im = Image.new(‘RGBA’, (128, 160), (0, 0, 0))

で新規の画像イメージを作成する。
あとはline、rectangle等のメソッドを使用してその画像に描画を行ってから、LCDにまとめて転送すれば良い。

試してみると一画面まるごとの転送でも0.5秒以下だったので、決して速いとは言えないが用途によっては十分かも知れない。
一画面まるごとではなく、必要な範囲だけ転送するようにすれば当然もっと速くなる。そのための範囲指定転送も試してみた。

from PIL import ImageFont

でフォントモジュールをロードすれば文字表示も可能で、試してはいないがフォントを適切に選択すれば漢字表示もできるはず。

全画面転送、部分転送、テキスト表示を行うテストプログラムは、長くなるので最後の「続きを読む」に以降に置いた。

プログラムを実行すると最初に以下のように図形描画と文字列描画を行い

2013-12-12 12.13.34

四角形をランダムな位置と色で表示する。最初の図形はメモリ上の仮想画面に描画してからまるごと転送、四角形の描画は更新範囲だけの転送を行うようにした。

2013-12-12 12.13.42

あたりまえだが転送する範囲が狭ければ素早く転送されるので、最初に画面全体を転送しておき、あとは更新部分だけを転送するような形で利用すれば、かなり実用性が高まるのではないかと思う。
それ以上のレスポンスが必要な場合はSPI転送を諦めて、パラレル接続にすべきだろう。

テストプロプログラムのソースコードは、「続きを読む」をクリック。

続きを読む »

Color LCD(4)

前回試してみたSPI接続カラーLCDへの画像ファイル表示。イメージ処理モジュールを利用して簡単に行う事ができたが、表示速度が思ったよりもかなり遅い。

これは1ピクセル単位でSPI通信を行っているため、オーバーヘッドが大きいのだろうと推測して、複数バイトをまとめて転送するように書き換えてみた。

spidevのxfer2は4096バイトまでまとめて転送できるようなので、画面の塗りつぶしは2048ピクセルの転送を10回、イメージの表示は128ピクセルの1ライン分を160回転送するようにした。プログラムは以下の通り。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO
import spidev           # sudo pip install spidev
import time
import sys
from PIL import Image   # sudo pip install pillow

class ST7735:
    def __init__(self):
        self.spi = spidev.SpiDev()
        self.spi.open(0, 0)
        self.spi.max_speed_hz = 16000000
        GPIO.setmode(GPIO.BCM)
        GPIO.setwarnings(False)
        GPIO.setup(24, GPIO.OUT)
        GPIO.setup(25, GPIO.OUT)
        self.reset()
        self.write_cmd(0x11)
        time.sleep(0.12)
        self.write((0xB1, 0x01, 0x2C, 0x2D))
        self.write((0xB2, 0x01, 0x2C, 0x2D))
        self.write((0xB3, 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D))
        self.write((0xB4, 0x07))
        self.write((0xC0, 0xA2, 0x02, 0x84))
        self.write((0xC1, 0xC5))
        self.write((0xC2, 0x0A, 0x00))
        self.write((0xC3, 0x8A, 0x2A))
        self.write((0xC4, 0x8A, 0xEE))
        self.write((0xC5, 0x0E))
        self.write((0x36, 0xC8))
        self.write((0xE0, 0x02, 0x1C, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2D, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10))
        self.write((0xE1, 0x03, 0x1D, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10))
        self.write((0x2A, 0x00, 0x02, 0x00, 0x81))
        self.write((0x2B, 0x00, 0x01, 0x00, 0xA0))
        self.write((0x3A, 0x05))
        self.write_cmd(0x29)
    def reset(self):
        GPIO.output(25, False)
        time.sleep(0.1)
        GPIO.output(25, True)
        time.sleep(0.1)
    def write_cmd(self, cmd):
        GPIO.output(24, False)  # RS=0
        self.spi.xfer2([cmd])
    def write_data(self, data):
        GPIO.output(24, True)   # RS=1
        self.spi.xfer2([data])
    def write(self, cmd):
        if len(cmd) == 0:
            return
        GPIO.output(24, False)  # RS=0
        self.spi.xfer2([cmd[0]])
        GPIO.output(24, True)   # RS=1
        self.spi.xfer2(list(cmd[1:]))
    def write_rgb(self, r, g, b):
        self.write_data(r & 0xF8 | g >> 5)
        self.write_data(g & 0xFC << 3 | b >> 3)
    def fill(self, r, g, b):
        self.write((0x2A, 0x00, 0x02, 0x00, 0x81))
        self.write((0x2B, 0x00, 0x01, 0x00, 0xA0))
        self.write_cmd(0x2C)
        GPIO.output(24, True)   # RS=1
        hi = r & 0xF8 | g >> 5
        lo = g & 0xFC << 3 | b >> 3
        pixline = []
        for n in range(2048):
            pixline.append(hi)        
            pixline.append(lo)
        for n in range(10):
            self.spi.xfer2(pixline[0:])
    def image(self, file):
        try:
            im = Image.open(file).convert("RGB")
            im.thumbnail((128, 160))
            pix = im.load()
            self.write((0x2A, 0x00, 0x02, 0x00, 0x81))
            self.write((0x2B, 0x00, 0x01, 0x00, 0xA0))
            self.write_cmd(0x2C)
            GPIO.output(24, True)   # RS=1
            pixline = []
            for i in range(160):
                for n in range(128):
                    pixel = pix[n, i]
                    hi = pixel[0] & 0xF8 | pixel[1] >> 5
                    lo = pixel[1] & 0xFC << 3 | pixel[2] >> 3
                    pixline.append(hi)
                    pixline.append(lo)
                self.spi.xfer2(pixline)
                del pixline[:]
        except:
            pass

if __name__ == "__main__":
    lcd = ST7735()
    param = sys.argv
    t1 = time.clock()
    for n in range(100):
        lcd.fill(0, 0, 0)
        lcd.image(param[1])
    t2 = time.clock()
    print 'processing time : ', str(t2 - t1)
    try:
        while True:
            pass
    except KeyboardInterrupt:
        print '\nbreak'
    GPIO.cleanup()

画面のクリアとイメージ表示を100回くり返すようにして、所要時間を旧バージョンと比べてみると、以下のようになった。

$ sudo python spilcd2.py Lenna.png
processing time :  36.28
$ sudo python spilcd.py Lenna.png
processing time :  283.74

結果は一目瞭然で、8倍近く高速化できた。
このぐらいの速度で表示できるのなら、Pythonだけでも何か実用性のある使い方ができるかも知れない。

Color LCD(3)

カラー液晶モジュールをRaspberry Piに接続して、最も低レベルな描画を行う所まではできたが、そこから一歩進んで画像ファイルを表示させようと思うと、画像を扱うための処理がかなり必要になってくる。

Pythonの画像処理ライブラリについて調べてみると、PILと呼ばれるモジュールが有名なようだが、かなり前から開発が止まっていて現在はPILからforkされたpillowプロジェクトがそれを引き継いでいるようだ。

pillowはパッケージ管理ツールpipでインストールできる。

$ sudo pip install pillow

使い方をざっと読んでみると、openメソッドで指定したファイルを開き、loadでその画像のピクセル情報を取得できるようだ。

pillowを使って画像ファイルを読み込み、液晶に表示させるプログラムは以下のようになった。読み込んだファイルを128×160のサムネイルに変換してから、1ピクセルずつ読み出して液晶に表示させている。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO
import spidev           # sudo pip install spidev
import time
import sys
from PIL import Image   # sudo pip install pillow

class ST7735:
    def __init__(self):
        self.spi = spidev.SpiDev()
        self.spi.open(0, 0)
        self.spi.max_speed_hz = 16000000
        GPIO.setmode(GPIO.BCM)
        GPIO.setwarnings(False)
        GPIO.setup(24, GPIO.OUT)
        GPIO.setup(25, GPIO.OUT)
        self.reset()
        self.write_cmd(0x11)
        time.sleep(0.12)
        self.write((0xB1, 0x01, 0x2C, 0x2D))
        self.write((0xB2, 0x01, 0x2C, 0x2D))
        self.write((0xB3, 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D))
        self.write((0xB4, 0x07))
        self.write((0xC0, 0xA2, 0x02, 0x84))
        self.write((0xC1, 0xC5))
        self.write((0xC2, 0x0A, 0x00))
        self.write((0xC3, 0x8A, 0x2A))
        self.write((0xC4, 0x8A, 0xEE))
        self.write((0xC5, 0x0E))
        self.write((0x36, 0xC8))
        self.write((0xE0, 0x02, 0x1C, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2D, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10))
        self.write((0xE1, 0x03, 0x1D, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10))
        self.write((0x2A, 0x00, 0x02, 0x00, 0x81))
        self.write((0x2B, 0x00, 0x01, 0x00, 0xA0))
        self.write((0x3A, 0x05))
        self.write_cmd(0x29)
    def reset(self):
        GPIO.output(25, False)
        time.sleep(0.1)
        GPIO.output(25, True)
        time.sleep(0.1)
    def write_cmd(self, cmd):
        GPIO.output(24, False)  # RS=0
        self.spi.xfer2([cmd])
    def write_data(self, data):
        GPIO.output(24, True)   # RS=1
        self.spi.xfer2([data])
    def write(self, cmd):
        if len(cmd) == 0:
            return
        GPIO.output(24, False)  # RS=0
        self.spi.xfer2([cmd[0]])
        GPIO.output(24, True)   # RS=1
        self.spi.xfer2(list(cmd[1:]))
    def write_rgb(self, r, g, b):
        self.write_data(r & 0xF8 | g >> 5)
        self.write_data(g & 0xFC << 3 | b >> 3)
    def fill(self, r, g, b):
        self.write((0x2A, 0x00, 0x02, 0x00, 0x81))
        self.write((0x2B, 0x00, 0x01, 0x00, 0xA0))
        self.write_cmd(0x2C)
        hi = r & 0xF8 | g >> 5
        lo = g & 0xFC << 3 | b >> 3
        GPIO.output(24, True)   # RS=1
        for i in range(20480):
            self.spi.xfer2([hi, lo])
    def image(self, file):
        try:
            im = Image.open(file).convert("RGB")
            im.thumbnail((128, 160))
            pix = im.load()
            self.write((0x2A, 0x00, 0x02, 0x00, 0x81))
            self.write((0x2B, 0x00, 0x01, 0x00, 0xA0))
            self.write_cmd(0x2C)
            GPIO.output(24, True)   # RS=1
            for i in range(160):
                for n in range(128):
                    pixel = pix[n, i]
                    hi = pixel[0] & 0xF8 | pixel[1] >> 5
                    lo = pixel[1] & 0xFC << 3 | pixel[2] >> 3
                    self.spi.xfer2([hi, lo])
        except:
            pass

if __name__ == "__main__":
    lcd = ST7735()
    lcd.fill(0, 0, 0)
    param = sys.argv
    lcd.image(param[1])
    try:
        while True:
            pass
    except KeyboardInterrupt:
        print '\nbreak'
    GPIO.cleanup()

起動時の引数として与えられたファイルを読み込むので、画像ファイルjack.jpgが存在している時

$ sudo python spilcd.py jack.jpg

と指定すれば、以下のように表示される。

2013-12-07 23.47.49

このプログラムに更に手を加えて、URLを処理するモジュールとテキストバッファモジュールを追加すれば、URLを指定してネット上の画像を表示させるようにもできる。
変更箇所だけ以下に記載しておく。

import urllib, cStringIO # 追加

if __name__ == "__main__":
    lcd = ST7735()
    lcd.fill(0, 0, 0)
    param = sys.argv
    file = cStringIO.StringIO(urllib.urlopen(param[1]).read())
    lcd.image(file)
    try:
        while True:
            pass
    except KeyboardInterrupt:
        print '\nbreak'
    GPIO.cleanup()

このプログラムに

$ sudo python spilcd.py http://upload.wikimedia.org/wikipedia/en/2/24/Lenna.png

のように画像のURLを引数として起動すれば

2013-12-07 23.48.36このように表示される。

SPI接続のLCDに描画するだけなら、ワンチップマイコンでも容易くできてしまうが、画像ファイルを読み込んだりネットワーク上の画像を表示させたりが簡単にできてしまう所は、Linuxマイコンボードならではだろう。

Color LCD(2)

aitendo1.8インチ液晶withキャリーボードをPythonで制御する場合は、既存のSPI用拡張モジュールを使用するのが手っ取り早い。

Raspberry Pi用のモジュールは何種類か存在しているようだが、今回は spidev をインストールして使ってみた。例によってパッケージ管理ツールpipを使って、以下のようにインストールする。

% sudo pip install spidev

ちなみに既にインストールされているパッケージを表示する場合は、以下のようにfreezeオプションを付けて実行する。

% sudo pip freeze

spidevモジュールを使ったSPI接続液晶のテストプログラムは、以下のようになる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO
import spidev           # sudo pip install spidev
import time

class ST7735:
    def __init__(self):
        self.spi = spidev.SpiDev()
        self.spi.open(0, 0)
        self.spi.max_speed_hz = 16000000
        GPIO.setmode(GPIO.BCM)
        GPIO.setwarnings(False)
        GPIO.setup(24, GPIO.OUT)
        GPIO.setup(25, GPIO.OUT)
        self.reset()
        self.write_cmd(0x11)
        time.sleep(0.12)
        self.write((0xB1, 0x01, 0x2C, 0x2D))
        self.write((0xB2, 0x01, 0x2C, 0x2D))
        self.write((0xB3, 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D))
        self.write((0xB4, 0x07))
        self.write((0xC0, 0xA2, 0x02, 0x84))
        self.write((0xC1, 0xC5))
        self.write((0xC2, 0x0A, 0x00))
        self.write((0xC3, 0x8A, 0x2A))
        self.write((0xC4, 0x8A, 0xEE))
        self.write((0xC5, 0x0E))
        self.write((0x36, 0xC8))
        self.write((0xE0, 0x02, 0x1C, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2D, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10))
        self.write((0xE1, 0x03, 0x1D, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10))
        self.write((0x2A, 0x00, 0x02, 0x00, 0x81))
        self.write((0x2B, 0x00, 0x01, 0x00, 0xA0))
        self.write((0x3A, 0x05))
        self.write_cmd(0x29)
    def reset(self):
        GPIO.output(25, False)
        time.sleep(0.1)
        GPIO.output(25, True)
        time.sleep(0.1)
    def write_cmd(self, cmd):
        GPIO.output(24, False)  # RS=0
        self.spi.xfer2([cmd])
    def write_data(self, data):
        GPIO.output(24, True)   # RS=1
        self.spi.xfer2([data])
    def write(self, cmd):
        if len(cmd) == 0:
            return
        GPIO.output(24, False)  # RS=0
        self.spi.xfer2([cmd[0]])
        GPIO.output(24, True)   # RS=1
        self.spi.xfer2(list(cmd[1:]))
    def write_rgb(self, r, g, b):
        self.write_data(r & 0xF8 | g >> 5)
        self.write_data(g & 0xFC << 3 | b >> 3)
    def fill(self, r, g, b):
        self.write((0x2A, 0x00, 0x02, 0x00, 0x81))
        self.write((0x2B, 0x00, 0x01, 0x00, 0xA0))
        self.write_cmd(0x2C)
        for i in range(160):
            for n in range(128):
                self.write_rgb(r, g, b)
        
if __name__ == "__main__":
    lcd = ST7735()
    lcd.write_cmd(0x2C)
    try:
        while True:
            lcd.fill(0, 0, 0)   # Fill Black
            for i in range(160):
                for n in range(128):
                    if i < 22:
                        lcd.write_rgb(0xFF, 0xFF, 0xFF) # White
                    elif i < 44:
                        lcd.write_rgb(0xFF, 0xFF, 0x00) # Yellow
                    elif i < 66:
                        lcd.write_rgb(0x00, 0xFF, 0xFF) # Cyan
                    elif i < 88:
                        lcd.write_rgb(0x00, 0xFF, 0x00) # Green
                    elif i < 110:
                        lcd.write_rgb(0xFF, 0x00, 0xFF) # Magenta
                    elif i < 132:
                        lcd.write_rgb(0xFF, 0x00, 0x00) # Red
                    else:
                        lcd.write_rgb(0x00, 0x00, 0xFF) # Blue
            time.sleep(3)
    except KeyboardInterrupt:
        print '\nbreak'
    GPIO.cleanup()

実行すると黒一色で画面が塗りつぶされて、そのあと白、黄色、シアン、緑、マゼンタ、赤、青のカラーバーが表示されるはず。

2013-12-04 14.46.53

液晶モジュールで使用されているST7735コントローラチップは、1バイトのコマンドの後に複数バイトのデータを続けて送信する方式になっていて、コマンドとデータはRSピンによって切り替える。RSピンがL(0)の時はコマンド、H(1)の時はデータとして認識される。

コントローラの初期化時に画面の描画領域を0x2Aと0x2Bコマンドで設定していて、液晶の画素数が128×160なのにも係わらず、横が2~129、縦が1~160で設定しているのでずれているように思えるが、これはサンプルプログラムの初期化処理をそのまま持ってきたからで、実際に試してみると画面の左上隅のピクセル座標は(0,0)ではなく、(2,1)という値になっていた。

なぜそうなっているのか、理由は良く分からない。製造上の理由?

ちょっと面白いのが、この液晶コントーラはメモリへの書き込みコマンドにアドレス指定のパラメータが無く、事前に設定されている表示領域に対して、自動的に順次書き込まれるような動きをする。設定領域の右端まで書き込んだら、自動的に次のラインの左端から書き込まれる。

ブラウン管の走査線と似たようなイメージだと思ったが、それを意識しているのかは不明。おかげで画像ファイルを表示させるのは非常に簡単だった。

画像ファイルの表示にはPythonのイメージ処理モジュールを使用してみた。そのあたりは次回に。

Color LCD

Raspberry Piにキャラクタ液晶と7セグメントLEDを接続するのは既にやったので、表示器シリーズとして次はカラーグラフィック液晶をやってみたい、と思って手軽に使えそうなものを捜してみると、aitendoで扱っている1.8インチ液晶withキャリーボードが良さそうだったので購入してみた。

20130616_57e391

インタフェースがSPI接続なので信号線3本と制御線2本で簡単に接続できる事、バックライト用の別電圧電源が不用な事、制御コマンドが単純な事、キャリーボード付きで1050円(税別)と安いことなど、良いことずくめのようだがマイナスの点もある。

使用するためにはフレキケーブルのハンダ付けが必要になるのが、最も大きなポイントだろう。それとキャリーボードの端子が2.0mmピッチなので、ブレッドボードで使う場合はそこも問題になる。ハンダ付け済みで2.45mmピッチの端子が出ていれば最適なのだが、無いものはしかたがない。
※追記:aitendoにハンダ付け済みの液晶モジュールも出ていた。コントローラがST7735BとST7735Rで型番が微妙に異なるが、同じように使えるかも知れない。

同一のLCDコントローラを搭載したArduino用の液晶モジュールがAdafruitから出ていて、国内ではスイッチサイエンスから購入可能なので、値段が高くてもかまわないのであればそちらを使うという手もある。

フレキケーブルのハンダ付けも実際にやってみると、思ったほどには難しく無いのでチャレンジしてみる価値はあると思う。ハンダ付けする前にフラックスを塗布して、ブリッジは気にせず接合面にハンダを盛るような感じでハンダ付けしてから、吸い取り線で余分なハンダを吸い取るようにすると上手く行く。
ネットに参考になるサイトが色々とあるので、見てみると良い。

あとこのキャリーボードには製品ページに書いてある通りパターンミスがあって、一箇所ショートする必要があるので注意が必要だ。

液晶とキャリーボードのハンダ付けが完了して、短絡している箇所が無い事が確認できたら、Raspberry PiのGPIOコネクタと以下のように接続する。

spilcd_ブレッドボード

Raspberry Piで今までにSPIインタフェースを使用した事が無い場合は更に準備が必要で、root権限で各種設定ファイルを編集しなければならない。

$ sudo nano /etc/modules

でファイルの最後に

spidev

を追加して上書き保存。

$ sudo nano /etc/modprobe.d/raspi-blacklist.conf

でblacklist spi-bcm2708と書かれている行の先頭に#を入れてコメントアウト

#blacklist spi-bcm2708

上書き保存して再起動すると、SPIが有効になっているはず。

SPIの権限はrootユーザになっているので、一般ユーザでも読み書き可能なように権限を変更すれば完了。

$ sudo chmod 666 /dev/spidev0.0
$ sudo chmod 666 /dev/spidev0.1

長くなるので実際のプログラムついては次回に。
画像ファイルを表示させた写真だけ、先に載せておく。

2013-11-30 22.03.40