PC Linuxで電子工作するのに便利なICとして、当ブログでも何度か取り上げているMCP2221。
直近ではカーネルドライバから使う方法を記事にしたが、カーネルドライバの出来がいまいちのようで思うようにチップが動いてくれない事が判明。
代わりに、Easy MCP2221というPythonモジュールを使うとチップを使いこなせる事が分かったので記事にまとめる。
MCP2221のLinuxカーネルドライバは不完全?
つい先日、コマンドラインツールを用いてMCP2221をカーネルドライバから使えそうな事を確認した。
が、i2cの個別デバイスをカーネルに認識させようと以下のようにsysfs経由でコマンドを送ると…
echo ssd1306 0x3c | sudo tee /sys/class/i2c-adapter/i2c-5/new_device
echo gpio-pcf857x 0x27 | sudo tee /sys/class/i2c-adapter/i2c-5/new_device
エラーを起こしてカーネルが腐り、以降USBデバイスが認識出来なくなってシステムの再起動が必要な状態に(上記コマンドは実行しない方が無難)。
i2cデバイスを動かすためのPythonコードを書いてみても、不具合が多発して使い物にならない事が発覚。
Easy MCP2221のインストールと設定
折角カーネルが認識するのに使い物にならないと途方に暮れていたところ、Easy MCP2221という比較的新しいPythonモジュールを発見。
カーネルによるネイティブ対応と同等ではないので多少の工夫は必要だが、既存のI2Cデバイス用Pythonモジュールを使って幾つかのデバイスを動かす事が出来る。
Pythonモジュールのインストール
インストール作業そのものは、pipで一発。
pip install EasyMCP2221
この他にも必要な作業があり、下記ページの指示に従った。
次節以降に具体的な作業をメモしておく。
hid_mcp2221のロード抑制
hid_mcp2221というカーネルモジュールがEasy MCP2221とコンフリクトを起こすので、このモジュールが自動ロードされないようmodprobeのブラックリストファイルを作成する。具体的には、/etc/modprobe.d/blacklist-mcp2221.confを作成して以下を記述する。
blacklist hid_mcp2221
既にデバイスを挿している場合は、ブラックリストファイルを保存後、デバイスを一旦抜いて以下を実行しておく。
sudo rmmod hid_mcp2221
udevルールの追加
Easy MCP2221はキャラクタデバイス/dev/hidraw*を介してMCP2221にアクセスする。一般ユーザ権限でEasy MCP2221を使いたい場合は、以下のudevルールを/etc/udev/rules.d/99-mcp2221.rulesなどのファイルを作成して記述しておく。
SUBSYSTEM=="hidraw", ATTRS{idProduct}=="00dd", ATTRS{idVendor}=="04d8", GROUP="plugdev", MODE="0666"
サンプルコードの実行
以下の簡単なコードを実行してみる。
import EasyMCP2221
mcp = EasyMCP2221.Device()
print(mcp)
うまく行けば、システムによるチップの検出状況が表示される。
"Chip settings": {
"ADC reference value": "4.096V",
"CDC Serial Number Enumeration": "enabled",
"Clock output duty cycle": 50,
"Clock output frequency": "12MHz",
"DAC output value": 8,
"DAC reference value": "VDD",
"Interrupt detection edge": "both",
:
I2Cデバイスを動かす
Luma.OLEDを使ったSSD1306描画
Luma.OLEDは小型OLEDディスプレイのPython描画ライブラリ。以下でインストール出来る。
pip install luma.oled
ライブラリ内部でPython標準のsmbusライブラリを使っているものの、
独自のラッパークラスでi2cバスのオブジェクトを生成し描画オブジェクトに渡しているので、
同等の動作をするラッパークラスを定義することでMCP2221のI2Cでも描画出来る。
以下がそのコード。MCP2221のI2Cクラスもsmbusと互換性があるため、自作のi2c_serialクラスを挟むことで、Lumaのラッパークラスを代替する事が出来た。
from luma.core.render import canvas
from luma.oled.device import ssd1306
from PIL import ImageFont
import EasyMCP2221
import time
class i2c_serial(object):
def __init__(self, bus, address):
self._bus = bus
self._addr = address
self._cmd_mode = 0x00
self._data_mode = 0x40
self._max_length = 255
def command(self, *cmd):
self._bus.write_i2c_block_data(self._addr, self._cmd_mode, list(cmd))
def data(self, data):
for i in range(0, len(data), self._max_length):
chunk = data[i:i + self._max_length]
self._bus.write_i2c_block_data(self._addr, self._data_mode, chunk)
if __name__ == "__main__":
# I2Cの初期化
bus = EasyMCP2221.SMBus()
serial = i2c_serial(bus, 0x3c)
device = ssd1306(serial, width=128, height=64)
# 描画処理
with canvas(device) as draw:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 15)
# テキストを描画
draw.text((0, 0), "Hello, SSD1306!", font=font, fill="white")
time.sleep(10)
上記コードを実行して、SSD1306に文字列を表示させたところ。
PCF8574接続の1602LCD
以前SBC Linuxの記事で取り上げた1602LCD。
この時に使ったGitHub公開のラズパイ用LCDライブラリも内部でPython標準smbusを使っているが、コードの作り的にLCDライブラリの改造が必要。ライブラリの使い方などはSBCの場合と特に変わらないので割愛し、ライブラリのコードを改造した部分だけ以下に抜粋。
import platform
import time
USE_MCP2221 = platform.machine() == 'x86_64'
if USE_MCP2221:
from EasyMCP2221 import SMBus
else:
import smbus
class LCD:
def __init__(self, pi_rev = 2, i2c_addr = 0x3F, backlight = True):
# device constants
self.I2C_ADDR = i2c_addr
:(中略)
if USE_MCP2221:
self.bus = SMBus()
else:
# Open I2C interface
if pi_rev == 2:
# Rev 2 Pi uses 1
self.bus = smbus.SMBus(1)
elif pi_rev == 1:
# Rev 1 Pi uses 0
self.bus = smbus.SMBus(0)
:(以下略)
こんな感じで書けば、SBCとPCのコードを共存させる事が出来る。このファイルをLCD_mcp2221.pyとして保存。
写真は、テストとして以下のコードを実行した時。
from LCD_mcp2221 import LCD
lcd = LCD(None, 0x27)
lcd.message("Hello 1602LCD!", 1)
GPピンを使う
MCP2221のGPピンの機能は別記事に書いた通りWindowsツールでデフォルト値を設定出来る。
ピン機能の設定
EasyMCP2221では、チップ内蔵のEEPROMに保存された(=ツールで設定した)内容を上書きする形で、GPピンの機能をPythonコードで設定出来る。
例えば以下はgp0,gp1をGPIO入力、gp2,gp3をGPIO出力にする例。
mcp.set_pin_function(gp0 = "GPIO_IN", gp1 = "GPIO_IN", gp2 = "GPIO_OUT", gp3 = "GPIO_OUT")
ADCとDACを動かす
GP2とGP3を電線で直結させ、それぞれADCとDACに設定して動かしてみた。コードは以下。
import EasyMCP2221
mcp = EasyMCP2221.Device()
# ピンを設定
mcp.set_pin_function(gp2 = "ADC", gp3 = "DAC")
mcp.ADC_config(ref="4.096V")
mcp.DAC_config(ref="4.096V")
# DACで指定した電圧を出力
outvolt = 3.35
step = int(2**5*(outvolt/4.096))
real_outvolt = 4.096*step/2**5
print("実際のDAC出力電圧: %f" % real_outvolt)
mcp.DAC_write(step)
# ADCの読み値を出力
while True:
vals = mcp.ADC_read()
volt = vals[1]*4.096/1024.0 # GP2(ADC1)なので値はvals[1]で取り出せる
print('ADC測定値: %.02fV\r' % volt, end="")
DACは5bitしか解像度が無いため指定通りの電圧が出せない場合もあり、「実際のDAC出力電圧」を計算して表示するようにしている。これを実行すると、以下のような出力が得られる。
実際のDAC出力電圧: 3.328000
ADC測定値: 3.344000V
DAC出力にテスターを当てると概ね同じ電圧値が得られるので、ADCとDACどちらも電子工作には十分な精度が有りそうだ。
まとめ
EasyMCP2221を使うことで、MCP2221と接続したI2Cデバイスや内蔵ADC/DACがPythonコードからバッチリ利用可能な事を確認した。記事では端折ったが、GPIOやUARTも普通に使える。
一点残念なのは、GPIOを入力に設定した際にPythonのポーリングオブジェクトによる待ちが出来ない事。カーネルドライバ経由で使えば以前記事にした方法が使えるのだが…どうしてもGPIOの入力待ちを使いたい場合は、MCP2221を敢えてカーネルドライバから使うというのも一手かもしれない。
コメント