LoginSignup
3
6

More than 3 years have passed since last update.

Raspberry Pi ZERO WH + ACM1602NI LCD液晶 + BME280温湿度気圧センサーで色々表示してみる

Last updated at Posted at 2019-09-09

ラズパイに液晶をくっつけて色々表示したら面白いんじゃないかと唐突に思いついたので作ってみました。
※最終的には魔改造の結果、ラズパイ起動後に自動実行され日付、時刻、温度、湿度、CPU使用率、メモリー使用率、IPアドレスが表示されるようになりました。
※pythonプログラミングは超初心者です。美しいコードとか一切意識してません。また作りたいから作っただけで実用性は無視です。基本的には自分用の覚書です。

用意したもの

Raspberry Pi ZERO WH
http://akizukidenshi.com/catalog/g/gM-12958/
LCD液晶 ACM1602NI
http://akizukidenshi.com/catalog/g/gP-05693/
100kΩ半固定抵抗(↑のLCDを使うならこれが必要)
http://akizukidenshi.com/catalog/g/gP-08014/
温湿度気圧センサー BME280 
http://akizukidenshi.com/catalog/g/gK-09421/
その他、ハンダゴテ、ブレッドボード、ラズパイ接続環境などの電子工作及びRaspberry Pi関連一般機器

①I2C接続を有効にしてLCDに文字を出してみる

ラズパイではデフォルトではI2C接続が無効になっているはずです。
設定を行いI2Cを有効化、サンプルプログラムを使いなにか文字を投影しLCDの動作確認まで行います。
ポイントは、dtparam=i2c_baudrate=50000を設定すること、i2cdetectをしないことです。
私の場合はボーレートを設定するconfig.txt内でi2cに関する設定文のコメントアウトをはずしてやらないと動きませんでした。
その後https://github.com/yuma-m/raspi_lcd_acm1602ni.gitのサンプルプログラムで文字が表示されればOK
詳細な説明は参考ページをがわかりやすいです。↓
https://www.denshi.club/pc/raspi/i2caqmlcdarduinode1-aqm0802-2.html
http://yura2.hateblo.jp/entry/2016/02/13/Raspberry_Pi%E3%81%AB%E6%8E%A5%E7%B6%9A%E3%81%97%E3%81%9FLCD%28ACM1602NI%29%E3%82%92Python%E3%81%A7%E5%8B%95%E3%81%8B%E3%81%99
https://ameblo.jp/raspberrypi-iot/entry-12345308004.html

②BME280を接続して温度、湿度、気圧を取得してみる

次にBME280センサーを接続してデータの取得をしてみます。
先にLCD液晶の回路があるはずなのでそこに追加する形でいきました。
I2C接続は線を共用できるので配線が少なく楽ですね。
配線後にhttps://github.com/SWITCHSCIENCE/BME280/blob/master/Python27/bme280_sample.pyのサンプルプログラムを使って各種データが取得できればOK
ポイントは、今回のようにACM1602NIをすでに接続した状態でテストをするならやっぱりi2cdetectはやってはいけません。
これをやると液晶が止まります・・・
詳細な説明は参考ページをがわかりやすいです。↓
https://deviceplus.jp/hobby/raspberrypi_entry_039/

ここまでの配線状況
DSC_0631.JPG

③BME280のデータをLCDに投影する

LCD表示のサンプルプログラムを見るとmain関数の中は次のようになっていました。

raspi_lcd.py
前略
def main():
    if not 2 <= len(sys.argv) <= 3:
        print('Usage: python raspi_lcd.py "message for line 1" ["message for line 2"]')
        return
    else:
        lcd_controller = LCDController()
        lcd_controller.display_messages(sys.argv[1:3])
後略

ここでコマンドラインから引数としてうけとった文字列の数を判定し、受け取った引数が1または2ならlcd_controller.display_messages(sys.argv[1:3])に渡してLCDに投影されるようです。
※ちなみに引数はsys.argvに格納されますがsys.argv[0]はプログラム名が入るらしいです。
・・・適当にリストを作ってそこに文字列を格納しlcd_controller.display_messagesに渡せば投影できるのでは?(単純
というわけでBME280のサンプルプログラムをLCD表示サンプルプログラムに無理やり追記しLCD表示に関するmain関数部分のみ次のように書き換えました
var_h,temperature,pressurear_h,temperature,pressureの各変数をグローバル変数化し参照できるように変えているので注意
また、このLCDは仕様上「℃」の文字が使えません。BME280のサンプルプログラムをそのまま使うと「℃」が表示できずエラーを起こしますのでなにかしら対策が必要です。私は代わりに「’C」を表示するようにしました。

lcd.py
前略
def main():
    while 1:
        lcddisp = [] #LCD表示文字列格納リスト
        readData() #BME280のデータ取得関数
        lcdhum = ("HUM:%6.2f %" % (var_h)) #取得した湿度に表示用の文字列を追加
        lcdtemp = ("TEMP: %-6.2f'C" % (temperature)) #取得した気温に表示用の文字列を追加 
        lcdpressure = ("pressure:%7.2fhPa" % (pressure/100))  #取得した気圧に表示用の文字列を追加      
        lcddisp.append(lcdtemp) #LCD表示文字列格納リストに気温を追加
        lcddisp.append(lcdhum) #LCD表示文字列格納リストに湿度を追加
        lcd_controller = LCDController()
        lcd_controller.display_messages(lcddisp[0:2]) #LCD表示文字列格納リストを表示関数に渡す
        time.sleep(2) #2秒待機後にLCD表示アップデート
後略

④日付と時刻を表示する

せっかくなので日付と時刻ぐらい表示したくね?(いきあたりばったりの思いつき)
main関数が以下のようになりました。(import datetime忘れないでね)

lcd.py
前略
import datetime
中略
def main():
    while 1:
        lcddisp = [] #LCD表示文字列格納リスト
        readData() #BME280のデータ取得関数
        lcdhum = ("HUM:%6.2f %" % (var_h)) #取得した湿度に表示用の文字列を追加
        lcdtemp = ("TEMP: %-6.2f'C" % (temperature)) #取得した気温に表示用の文字列を追加 
        lcdpressure = ("pressure:%7.2fhPa" % (pressure/100))  #取得した気圧に表示用の文字列を追加      
        lcddisp.append(lcdtemp) #LCD表示文字列格納リストに気温を追加
        lcddisp.append(lcdhum) #LCD表示文字列格納リストに湿度を追加
        lcd_controller = LCDController()
        lcd_controller.display_messages(lcddisp[0:2]) #LCD表示文字列格納リストを表示関数に渡す
        time.sleep(2) #2秒待機後にLCD表示アップデート

        lcddisp = [] #LCD表示文字列格納リストリセット
        dt_now = datetime.datetime.now() #現在時刻の取得
        lcddisp.append(dt_now.strftime('DATE: %Y/%m/%d')) #年月日を格納
        lcddisp.append(dt_now.strftime('TIME: %H:%M:%S')) #時分秒を格納
        lcd_controller = LCDController()
        lcd_controller.display_messages(lcddisp[0:2]) #LCD表示文字列格納リストを表示関数に渡す
        time.sleep(2) #2秒待機後にLCD表示アップデート
後略

⑤CPU使用率とメモリー使用率を表示する

将来のヘッドレス運用に備えてCPU使用率とメモリー使用率も監視したいよね?
どうやらpsutilモジュールなるものを使うとできるらしい
参考↓
https://algorithm.joho.info/programming/python/psutil-cpu-memory-usage/
そして度重なるmain関数の魔改造

lcd.py
前略
import psutil
中略
def main():
    while 1:
        lcddisp = [] #LCD表示文字列格納リスト
        readData() #BME280のデータ取得関数
        lcdhum = ("HUM:%6.2f %" % (var_h)) #取得した湿度に表示用の文字列を追加
        lcdtemp = ("TEMP: %-6.2f'C" % (temperature)) #取得した気温に表示用の文字列を追加 
        lcdpressure = ("pressure:%7.2fhPa" % (pressure/100))  #取得した気圧に表示用の文字列を追加      
        lcddisp.append(lcdtemp) #LCD表示文字列格納リストに気温を追加
        lcddisp.append(lcdhum) #LCD表示文字列格納リストに湿度を追加
        lcd_controller = LCDController()
        lcd_controller.display_messages(lcddisp[0:2]) #LCD表示文字列格納リストを表示関数に渡す
        time.sleep(2) #2秒待機後にLCD表示アップデート

        lcddisp = [] #LCD表示文字列格納リストリセット
        dt_now = datetime.datetime.now() #現在時刻の取得
        lcddisp.append(dt_now.strftime('DATE: %Y/%m/%d')) #年月日を格納
        lcddisp.append(dt_now.strftime('TIME: %H:%M:%S')) #時分秒を格納
        lcd_controller = LCDController()
        lcd_controller.display_messages(lcddisp[0:2]) #LCD表示文字列格納リストを表示関数に渡す
        time.sleep(2) #2秒待機後にLCD表示アップデート

        lcddisp = [] #LCD表示文字列格納リストリセット
        memory = psutil.virtual_memory() #メモリー使用率取得
        cpu_percent = psutil.cpu_percent(interval=1) #CPU使用率取得
        lcdmem = ('MEM: ' + str(memory.percent) + " %") #メモリ使用率に表示用の文字列を追加
        lcdcpu = ('CPU: ' + str(cpu_percent) + " %") #CPU使用率に表示用の文字列を追加
        lcddisp.append(lcdcpu) #CPU使用率を格納
        lcddisp.append(lcdmem) #メモリー使用率を格納
        lcd_controller = LCDController()
        lcd_controller.display_messages(lcddisp[0:2]) #LCD表示文字列格納リストを表示関数に渡す
        time.sleep(2) #2秒待機後にLCD表示アップデート
後略

⑥IPアドレスを表示する

ラズパイ起動後にIPアドレス表示してくれたらSSHとかRDPとかするとき便利だよね、と思いついたので・・・
どうやらgethostnameを使うとできるとあるが127.0.0.1しか取得できない・・・
調べると以下のページに答えがあったので参考に↓
https://qiita.com/kjunichi/items/8e4967d04c3a1f6af35e
そしてmain関数を含めた最終的なコードの全容は次の通り

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

from __future__ import print_function
from smbus2 import SMBus
import sys
import time
import smbus
import unicodedata
import datetime
import psutil
import socket

from config import BUS_NUMBER, LCD_ADDR, SLEEP_TIME, DELAY_TIME
from character_table import INITIALIZE_CODES, LINEBREAK_CODE, CHAR_TABLE

COMMAND_ADDR = 0x00
DATA_ADDR = 0x80
bus_number  = 1
i2c_address = 0x76

bus = SMBus(bus_number)

digT = []
digP = []
digH = []

t_fine = 0.0

pressure = 0.0
temperature = 0.0
var_h = 0.0
lcddisp = ["0","0"]

def writeReg(reg_address, data):
    bus.write_byte_data(i2c_address,reg_address,data)

def get_calib_param():
    calib = []

    for i in range (0x88,0x88+24):
        calib.append(bus.read_byte_data(i2c_address,i))
    calib.append(bus.read_byte_data(i2c_address,0xA1))
    for i in range (0xE1,0xE1+7):
        calib.append(bus.read_byte_data(i2c_address,i))

    digT.append((calib[1] << 8) | calib[0])
    digT.append((calib[3] << 8) | calib[2])
    digT.append((calib[5] << 8) | calib[4])
    digP.append((calib[7] << 8) | calib[6])
    digP.append((calib[9] << 8) | calib[8])
    digP.append((calib[11]<< 8) | calib[10])
    digP.append((calib[13]<< 8) | calib[12])
    digP.append((calib[15]<< 8) | calib[14])
    digP.append((calib[17]<< 8) | calib[16])
    digP.append((calib[19]<< 8) | calib[18])
    digP.append((calib[21]<< 8) | calib[20])
    digP.append((calib[23]<< 8) | calib[22])
    digH.append( calib[24] )
    digH.append((calib[26]<< 8) | calib[25])
    digH.append( calib[27] )
    digH.append((calib[28]<< 4) | (0x0F & calib[29]))
    digH.append((calib[30]<< 4) | ((calib[29] >> 4) & 0x0F))
    digH.append( calib[31] )

    for i in range(1,2):
        if digT[i] & 0x8000:
            digT[i] = (-digT[i] ^ 0xFFFF) + 1

    for i in range(1,8):
        if digP[i] & 0x8000:
            digP[i] = (-digP[i] ^ 0xFFFF) + 1

    for i in range(0,6):
        if digH[i] & 0x8000:
            digH[i] = (-digH[i] ^ 0xFFFF) + 1  

def readData():
    data = []
    for i in range (0xF7, 0xF7+8):
        data.append(bus.read_byte_data(i2c_address,i))
    pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
    temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
    hum_raw  = (data[6] << 8)  |  data[7]
    compensate_T(temp_raw)
    compensate_P(pres_raw)
    compensate_H(hum_raw)

def compensate_P(adc_P):
    global  t_fine
    global  pressure
    v1 = (t_fine / 2.0) - 64000.0
    v2 = (((v1 / 4.0) * (v1 / 4.0)) / 2048) * digP[5]
    v2 = v2 + ((v1 * digP[4]) * 2.0)
    v2 = (v2 / 4.0) + (digP[3] * 65536.0)
    v1 = (((digP[2] * (((v1 / 4.0) * (v1 / 4.0)) / 8192)) / 8)  + ((digP[1] * v1) / 2.0)) / 262144
    v1 = ((32768 + v1) * digP[0]) / 32768
    if v1 == 0:
        return 0
    pressure = ((1048576 - adc_P) - (v2 / 4096)) * 3125
    if pressure < 0x80000000:
        pressure = (pressure * 2.0) / v1
    else:
        pressure = (pressure / v1) * 2
    v1 = (digP[8] * (((pressure / 8.0) * (pressure / 8.0)) / 8192.0)) / 4096
    v2 = ((pressure / 4.0) * digP[7]) / 8192.0
    pressure = pressure + ((v1 + v2 + digP[6]) / 16.0)  

def compensate_T(adc_T):
    global t_fine
        global temperature
    v1 = (adc_T / 16384.0 - digT[0] / 1024.0) * digT[1]
    v2 = (adc_T / 131072.0 - digT[0] / 8192.0) * (adc_T / 131072.0 - digT[0] / 8192.0) * digT[2]
    t_fine = v1 + v2
    temperature = t_fine / 5120.0

def compensate_H(adc_H):
    global t_fine
    global var_h
        var_h = t_fine - 76800.0
    if var_h != 0:
        var_h = (adc_H - (digH[3] * 64.0 + digH[4]/16384.0 * var_h)) * (digH[1] / 65536.0 * (1.0 + digH[5] / 67108864.0 * var_h * (1.0 + digH[2] / 67108864.0 * var_h)))
    else:
        return 0
    var_h = var_h * (1.0 - digH[0] * var_h / 524288.0)
    if var_h > 100.0:
        var_h = 100.0
    elif var_h < 0.0:
        var_h = 0.0

def setup():
    osrs_t = 1          #Temperature oversampling x 1
    osrs_p = 1          #Pressure oversampling x 1
    osrs_h = 1          #Humidity oversampling x 1
    mode   = 3          #Normal mode
    t_sb   = 5          #Tstandby 1000ms
    filter = 0          #Filter off
    spi3w_en = 0            #3-wire SPI Disable

    ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode
    config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en
    ctrl_hum_reg  = osrs_h

    writeReg(0xF2,ctrl_hum_reg)
    writeReg(0xF4,ctrl_meas_reg)
    writeReg(0xF5,config_reg)


setup()
get_calib_param()

class LCDController:
    def __init__(self):
        self.bus = smbus.SMBus(BUS_NUMBER)
        pass

    def send_command(self, command, is_data=True):
        if is_data:
            self.bus.write_i2c_block_data(LCD_ADDR, DATA_ADDR, [command])
        else:
            self.bus.write_i2c_block_data(LCD_ADDR, COMMAND_ADDR, [command])
        time.sleep(DELAY_TIME)

    def initialize_display(self):
        for code in INITIALIZE_CODES:
            self.send_command(code, is_data=False)

    def send_linebreak(self):
        for code in LINEBREAK_CODE:
            self.send_command(code, is_data=False)

    def normalize_message(self, message):
        if isinstance(message, str):
            message = message.decode('utf-8')
        return unicodedata.normalize('NFKC', message)

    def convert_message(self, message):
        char_code_list = []
        for char in message:
            if char not in CHAR_TABLE:
                error_message = 'undefined character: %s' % (char.encode('utf-8'))
                raise ValueError(error_message)
            char_code_list += CHAR_TABLE[char]
        if len(char_code_list) > 16:
            raise ValueError('Exceeds maximum length of characters for each line: 16')
        return char_code_list

    def display_one_line(self, line_no, message):
        message = self.normalize_message(message)
        char_code_list = self.convert_message(message)
        for code in char_code_list:
            self.send_command(code)

    def display_messages(self, message_list):
        self.initialize_display()
        time.sleep(SLEEP_TIME)
        for line_no, message in enumerate(message_list):
            if line_no == 1:
                self.send_linebreak()
            self.display_one_line(line_no, message)


def main():
    while 1:
        lcddisp = [] 
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(("8.8.8.8",80))
        ip = s.getsockname()[0]
        lcddisp.append("IP address:")
        lcddisp.append(ip)
        lcd_controller = LCDController()
        lcd_controller.display_messages(lcddisp[0:2])
        time.sleep(2)

        lcddisp = []
        dt_now = datetime.datetime.now()
        lcddisp.append(dt_now.strftime('DATE: %Y/%m/%d'))
        lcddisp.append(dt_now.strftime('TIME: %H:%M:%S'))
        lcd_controller = LCDController()
        lcd_controller.display_messages(lcddisp[0:2])
        time.sleep(2)

        lcddisp = []
        readData()
        lcdhum = ("HUM:%6.2f %" % (var_h)) 
        lcdtemp = ("TEMP: %-6.2f'C" % (temperature)) 
        lcdpressure = ("pressure:%7.2fhPa" % (pressure/100))        
        lcddisp.append(lcdtemp)
        lcddisp.append(lcdhum)
        lcd_controller = LCDController()
        lcd_controller.display_messages(lcddisp[0:2])
        time.sleep(2)

        lcddisp = []
        memory = psutil.virtual_memory()
        cpu_percent = psutil.cpu_percent(interval=1)
        lcdmem = ('MEM: ' + str(memory.percent) + " %")
        lcdcpu = ('CPU: ' + str(cpu_percent) + " %")
        lcddisp.append(lcdcpu)
        lcddisp.append(lcdmem)
        lcd_controller = LCDController()
        lcd_controller.display_messages(lcddisp[0:2])
        time.sleep(2)

if __name__ == '__main__':
    main()

クソコード臭が拭えませんが普通に動くので気にしません。

⑦ラズパイ起動後に自動起動させる

ヘッドレス運用時にラズパイ起動後に自動で起動してくれればIPアドレスとかとれるし正常起動したかわかるし便利そうなので設定してみました。
どうやらsystemdを設定すればいけるそうなので参考ページのとおりに設定したらあっさりいけました。
参考↓
https://qiita.com/molchiro/items/ee32a11b81fa1dc2fd8d
https://qiita.com/ikemura23/items/6f9adce99a3db555a0e4

⑧動作の様子

こんな感じで動いてます

3
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
3
6