Edited at

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

ラズパイに液晶をくっつけて色々表示したら面白いんじゃないかと唐突に思いついたので作ってみました。

※最終的には魔改造の結果、ラズパイ起動後に自動実行され日付、時刻、温度、湿度、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


⑧動作の様子

こんな感じで動いてます