はじめに
これまで、デジタル出力からPWM出力、アナログ入力までのデバイス周りの内容は一通り解説してきました。また、直近の2回ではRaspberry Piを使い易くするための環境について紹介しました。
今回からは本編に戻り、通信やシステム化などのテーマで解説していきます。今回は、主に有線での通信について解説します。
有線通信と無線通信との違い
通信の種類は有線と無線に大別されます。最近では無線LANやBluetoothなどが普及しているので、無線通信についても身近になってきていると思います。
まず、有線と無線の違いについて触れていきます。有線は直接つなぐことができため通信が安定し、大容量のデータにも対応可能で、セキュリティも比較的安全です。しかし、接続数に限界があり、通信の接続数が増えると物理的な問題が生じ易くなります。
一方、無線は通信の種類により異なる接続距離を考慮できれば、どこでも接続できるようになるため便利です。一方で、通信の不安定さやデータ転送、セキュリティなど、有線と比較して注意すべき点が多くあります。
このように、有線と無線それぞれの特徴を捉えることは、状況に応じた検討を行う際に非常に重要です。そのため、今回と次回で有線と無線を分けて解説することにします。
今回は、Raspberry PiとPCをシリアル通信で接続するI2C通信を行う方法を解説します。
用意するもの
今回はRaspberry PiとPC以外にも、様々なパーツを使用します。少し準備が大変かも知れませんが、頑張って集めましょう。また、第2回で作成した回路1も使用します(詳細は第2回を参照)。
- Raspberry Pi 3B
- microSDカード(OSインストール済みのもの。第1回を参照)
- PC
- USBシリアル変換モジュール(FT-232RQ)
- 温湿度・気圧センサモジュール(AE-BME280)
- キャラクタ液晶ディスプレイモジュール(AE-AQM0802+PCA9515)
- はんだこて
- はんだ
- こて台
- ブレッドボード
- ジャンパーワイヤ オス-メス
- ジャンパーワイヤ オス-オス
なお、第2回で作成した回路1は、下記のパーツを使用しています(詳細は第2回を参照)。
- LED
- 押しスイッチ
- ブレッドボード
- ジャンパーワイヤ オス-メス
- 抵抗(330Ω×2:LED側/10kΩ:スイッチ側)
Raspberry PiとPCをシリアル通信で接続しよう
シリアル通信の準備
シリアル通信とはコンピュータを接続する方法の1つで、相互接続して1度に1ビットずつ逐次的にデータを送ることをいいます。1ビットは2進数(0と1)の1桁分の情報量です。
まず、下図のようにRaspberry PiとUSBシリアル変換モジュール(FT-232RQ)をつなぎます。
接続後、Raspberry Piに電源ケーブルをつなぎ、Raspberry Piを起動させます。
PC側で接続が認識されると、デバイスマネージャー上でポート(COM No.)が追加されます。
ポートを確認後(図の場合ではCOM5)、PC側でTera Termを立ち上げます。これまでは接続先としてTCP/IPを選択していましたが、今回はシリアルを選択します。先ほどのポートを選択し、Raspberry Piのシリアル通信のボー・レートは115200と設定されているので、「設定」→「シリアルポート」でTera Termのボー・レートの設定を変更します。
Tera Termの画面上には何も表示されていませんが、[Enter]キーを1回押すとログインプロンプトが表示されます。
raspberrypi loginに「pi」、パスワードに「raspberry」を入力して[Enter]キーを押します。下の画面が表示されると接続成功です。
なお、シリアル通信の接続は「exit」コマンドを入力することで終了できます。Raspberry Piも終了する場合は「sudo shutdown –h now」コマンドを入力します。
Lチカを試してみよう
それでは、シリアル通信で恒例のLチカテストを実行してみましょう。nanoエディタを立ち上げ、「light1.py」というファイルに以下のプログラムを入力します(第2回でも使用)。
from gpiozero import LED
from time import sleep
led = LED(17)
while True:
led.on()
sleep(1)
led.off()
sleep(1)
保存した後、プログラムを実行すると、2つのLEDが点滅します。このプログラムの実行結果は、下記の動画を参照してください。
Raspberry PiでI2C通信を使ってみよう
モジュールの準備
I2C通信とは、フィリップス社で開発されたシリアル通信の1種です。Raspberry Piとモジュールをシリアル通信でつなぐことができます。I2C通信はデータ線とクロック線の2本の信号線で接続しますが、この信号線にモジュールを並列に接続していくことで複数のモジュールをつなぐことも可能になります。
今回はモジュールとして、温湿度・気圧センサ(BME280)とキャラクタ液晶ディスプレイ(AQM0802)を使用しますが、各モジュールとも使用前にはんだ付けが必要です。なお、本稿でははんだ付け方法の解説は割愛しますので、各自インターネットや書籍などを参考に作業を行ってください。
はんだ付け後の各モジュールは以下のようになります。
AQM0802を動かしてみよう
AE-AQM0802+PCA9515は、I2C接続小型キャラクタLCD(AQM0802A)をRaspberry Piで使用するために専用IC(PCA9515)を実装したRaspberry Pi用変換モジュールです。Raspberry Piから発信された情報やセンサから取得した情報などをLCDに表示できます。
まずは、このモジュールを使って「Hello World!」をLCD上に表示させます。以下の表と図を参考に配線しましょう。
次に、PC上でRealVNCを立ち上げ、Raspberry Piのデスクトップ上にあるRaspberry Piのアイコンから「Preferences」→「Raspberry Pi Configuration」→「Interfaces」の画面を立ち上げ、I2Cが「enabled」になっているかを確認します。
確認後、以下のコマンドでI2Cライブラリを導入します。
$ sudo apt-get install i2c-tools
$ sudo apt-get python-smbus
ここで、AQM0802がRaspberry PiでI2C通信として認識されているかを確認します。以下のコマンドを入力します。
$ sudo i2cdetect -y 1
下図のように、「3e」(0x3eのアドレス)の表示があれば認識されています。
続いて、LCD上に文字を表示させます。Thonny Python IDEを立ち上げ、以下のプログラムを作成します。
#!/usr/bin/python
# -*- coding: utf-8 -*-
import smbus
import time
class i2clcd:
i2c = smbus.SMBus(1)
addr = 0x3e
contrast = 42 # 0~63
def __init__(self):
self.i2c.write_byte_data(self.addr, 0, 0x38) # function set(IS=0)
self.i2c.write_byte_data(self.addr, 0, 0x39) # function set(IS=1)
self.i2c.write_byte_data(self.addr, 0, 0x14) # internal osc
self.i2c.write_byte_data(self.addr, 0,
(0x70 | (self.contrast & 0x0f))) # contrast
self.i2c.write_byte_data(self.addr, 0,
(0x54 | ((self.contrast >> 4) & 0x03))) # contrast/icon/power
self.i2c.write_byte_data(self.addr, 0, 0x6B) # follower control
# self.i2c.write_byte_data(self.addr, 0, 0x6c) # follower control
time.sleep(0.2)
def clear(self):
self.i2c.write_byte_data(self.addr, 0, 0x38) # function set(IS=0)
self.i2c.write_byte_data(self.addr, 0, 0x70) # Constract
self.i2c.write_byte_data(self.addr, 0, 0x0C) # Display On
self.i2c.write_byte_data(self.addr, 0, 0x01) # Clear Display
self.i2c.write_byte_data(self.addr, 0, 0x06) # Entry Mode Set
time.sleep(0.2)
def puts(self, msg):
self.i2c.write_byte_data(self.addr, 0, 0x38) # function set(IS=0)
[self.i2c.write_byte_data(self.addr, 0x40, ord(c)) for c in msg]
def setaddress(self, line, col):
self.i2c.write_byte_data(self.addr, 0, 0x38) # function set(IS=0)
self.i2c.write_byte_data(self.addr, 0, 0x80 | (0x40 if line > 0 else 0) | col)
def setcg(self, no, cg):
self.i2c.write_byte_data(self.addr, 0, 0x38) # function set(IS=0)
self.i2c.write_byte_data(self.addr, 0, 0x40 | (no 3))
[self.i2c.write_byte_data(self.addr, 0x40, c) for c in cg]
def putcg(self, line, col, no):
self.setaddress(line, col)
self.i2c.write_byte_data(self.addr, 0x40, no)
if __name__ == '__main__':
try:
lcd = i2clcd()
lcd.clear()
word1 = 'Hello'
word2 = 'World!'
cgchar0 = (0b00000,
0b00001,
0b01100,
0b10010,
0b10000,
0b10010,
0b01100)
lcd.setcg(0, cgchar0)
while True:
lcd.setaddress(0, 3)
lcd.puts(word1)
lcd.putcg(0, 7, 0)
lcd.setaddress(1, 2)
lcd.puts(word2)
except KeyboardInterrupt:
pass
プログラムは「LCD.py」の名前で保存します。プログラムを実行すると、LCD上に「Hello World!」が表示されます。
BME280を動かしてみよう
AE-BME280は、ボッシュ社のBME280を搭載したセンサモジュールで、温度、湿度、気圧の3つの環境情報を同時に測定できます。Raspberry Piとの通信方式はSPIとI2Cを選択でき、今回はI2Cを選択します。
まずは、このモジュールを使ってshell上に温湿度と気温を表示させてみます。
以下の表と図を参考に、配線しましょう。
次に、I2C通信でRaspberry Piに認識されているか確認します。以下のコマンドを入力します。
$ sudo i2cdetect -y 1
下図のように、「76」(0x76のアドレス)の表示があれば認識されています。
shell上にデータを表示させます。以下のプログラムを作成します。#!/usr/bin/env python
# -*- coding: utf-8 -*-
class Bme280():
def __init__(self, i2c_address= 0x76, bus_number=1):
import smbus
self.digT = []
self.digP = []
self.digH = []
self.t_fine = 0
self.bus_number = bus_number
self.bus = smbus.SMBus(self.bus_number)
self.i2c_address = i2c_address
self.setup()
self.get_calib_param()
self.data = []
for i in range (0xF7, 0xF7+8):
self.data.append(self.bus.read_byte_data(self.i2c_address,i))
self.press_raw = (self.data[0] 12) | (self.data[1] 4) | (self.data[2] >> 4)
self.temp_raw = (self.data[3] 12) | (self.data[4] 4) | (self.data[5] >> 4)
self.humid_raw = (self.data[6] 8) | self.data[7]
def get_calib_param(self):
calib = []
for i in range (0x88,0x88+24):
calib.append(self.bus.read_byte_data(self.i2c_address,i))
calib.append(self.bus.read_byte_data(self.i2c_address,0xA1))
for i in range (0xE1,0xE1+7):
calib.append(self.bus.read_byte_data(self.i2c_address,i))
self.digT.append((calib[1] 8) | calib[0])
self.digT.append((calib[3] 8) | calib[2])
self.digT.append((calib[5] 8) | calib[4])
self.digP.append((calib[7] 8) | calib[6])
self.digP.append((calib[9] 8) | calib[8])
self.digP.append((calib[11] 8) | calib[10])
self.digP.append((calib[13] 8) | calib[12])
self.digP.append((calib[15] 8) | calib[14])
self.digP.append((calib[17] 8) | calib[16])
self.digP.append((calib[19] 8) | calib[18])
self.digP.append((calib[21] 8) | calib[20])
self.digP.append((calib[23] 8) | calib[22])
self.digH.append( calib[24] )
self.digH.append((calib[26] 8) | calib[25])
self.digH.append( calib[27] )
self.digH.append((calib[28] 4) | (0x0F & calib[29]))
self.digH.append((calib[30] 4) | ((calib[29] >> 4) & 0x0F))
self.digH.append( calib[31] )
for i in range(1,2):
if self.digT[i] & 0x8000: self.digT[i] = (-self.digT[i] ^ 0xFFFF) + 1
for i in range(1,8):
if self.digP[i] & 0x8000: self.digP[i] = (-self.digP[i] ^ 0xFFFF) + 1
for i in range(0,6):
if self.digH[i] & 0x8000: self.digH[i] = (-self.digH[i] ^ 0xFFFF) + 1
def get_data(self):
temp = self.get_temp()
pressure = self.get_pressure()
humid = self.get_humid()
return temp, humid, pressure
def get_pressure(self):
pressure = 0.0
v1 = (self.t_fine / 2.0) - 64000.0
v2 = (((v1 / 4.0) * (v1 / 4.0)) / 2048) * self.digP[5]
v2 = v2 + ((v1 * self.digP[4]) * 2.0)
v2 = (v2 / 4.0) + (self.digP[3] * 65536.0)
v1 = (((self.digP[2] * (((v1 / 4.0) * (v1 / 4.0)) / 8192)) / 8) + ((self.digP[1] * v1) / 2.0)) / 262144
v1 = ((32768 + v1) * self.digP[0]) / 32768
if v1 == 0: return 0
pressure = ((1048576 - self.press_raw) - (v2 / 4096)) * 3125
if pressure 0x80000000: pressure = (pressure * 2.0) / v1
else: pressure = (pressure / v1) * 2
v1 = (self.digP[8] * (((pressure / 8.0) * (pressure / 8.0)) / 8192.0)) / 4096
v2 = ((pressure / 4.0) * self.digP[7]) / 8192.0
pressure = pressure + ((v1 + v2 + self.digP[6]) / 16.0)
return pressure/100
def get_temp(self):
v1 = (self.temp_raw/ 16384.0 - self.digT[0] / 1024.0) * self.digT[1]
v2 = (self.temp_raw/ 131072.0 - self.digT[0] / 8192.0) * (self.temp_raw/ 131072.0 - self.digT[0] / 8192.0) * self.digT[2]
self.t_fine = v1 + v2
return self.t_fine / 5120.0
def get_humid(self):
var_h = self.t_fine - 76800.0
if var_h != 0:
var_h = (self.humid_raw - (self.digH[3] * 64.0 + self.digH[4]/16384.0 * var_h)) * (self.digH[1] / 65536.0 * (1.0 + self.digH[5] / 67108864.0 * var_h * (1.0 + self.digH[2] / 67108864.0 * var_h)))
else:
return 0
var_h = var_h * (1.0 - self.digH[0] * var_h / 524288.0)
if var_h > 100.0:
var_h = 100.0
elif var_h 0.0:
var_h = 0.0
return var_h
def setup(self):
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
self.bus.write_byte_data(self.i2c_address, 0xF2, ctrl_hum_reg)
self.bus.write_byte_data(self.i2c_address, 0xF4, ctrl_meas_reg)
self.bus.write_byte_data(self.i2c_address, 0xF5, config_reg)
if __name__ == '__main__':
bme280 = Bme280(0x76, 1)
temp, humid, press = bme280.get_data()
print("Temp:", round(temp,1), "C")
print("Humid:", round(humid,1), "%")
print("Pressure:", round(press,1), "hPa")
プログラムは「BME280.py」の名前で保存します。プログラムを実行すると、shell上に温湿度と気圧が表示されます。
BME280で取得したデータをAQM0802に表示させよう
ここでは、BME280が取得した温湿度情報をLCD上に表示します。I2C通信を使うと、複数のモジュールを並列に接続できます。BME280を配線しているブレッドボードに、先ほどと同様にAQM0802を配線しましょう。
配線が完了したら、以下のコマンドを入力してI2C通信でRaspberry Piに認識されているかを確認します。
$ sudo i2cdetect -y 1
下図のように、「3e」(AQM0802のアドレス)、「76」(BME280のアドレス)の表示があれば認識されています。
LCD上に温湿度を表示させます。以下のプログラムを作成します。
#!/usr/bin/python
# -*- coding: utf-8 -*-
import smbus
import time
from BME280 import Bme280
from LCD import i2clcd
bme280 = Bme280()
temp, humid, press = bme280.get_data()
lcd = i2clcd()
lcd.clear()
cgchar0 = (0b00000,
0b00001,
0b01100,
0b10010,
0b10000,
0b10010,
0b01100)
lcd.setcg(0, cgchar0)
while True:
lcd.setaddress(0, 4)
lcd.puts(str(round(temp,1)))
lcd.setaddress(1, 4)
lcd.puts(str(round(humid,1)))
プログラムは「LCD_BME280.py」の名前で保存します。プログラムを実行すると、LCD上に温湿度が表示されます。
ここで、ずっとプログラムを動かしたままにしておくと、以下のエラーが表示されてしまいます。
このエラー内容については、次回で解析していきたいと思います。
おわりに
いかがでしたか? 今回は有線通信をテーマに、シリアル通信とI2C通信を解説しました。特に、I2C通信で複数のモジュール(BME280とAQM0802)がRaspberry Piの2本の信号線(SCLとSDA)で並列につながっていることを確認いただけたでしょうか。これを応用すれば、さらに複雑な回路も設計できるので、活用の幅が大きく広がることが期待できます。
一方、BME280で読み取ったデータがAQM0802にうまく表示されたものの、エラーが起こりました。この内容は次回で解析していきますが、このような「動く」のに「エラー」が出るというのはある意味、I2C通信の特徴かもしれません。
また、今回のプログラムではBME280とAQM0802の基本プログラム(BME280.pyとLCD.py)を作成しておき、基本プログラムを呼び出しながら命令を実行(LCD_BME280.py)しています(今回のプログラムファイルはこちらからダウンロードできます(RaspberryPi07_program)。この考え方(クラス)は、PythonやI2Cにおいて非常に重要になるので、次回のエラー解析が終わった後に、詳細を解説したいと思います。お楽しみに!
- この記事のキーワード


