SPI制御のデジタルポテンショが使えるようになったので、使い方をまとめます。
テスト回路のセットアップ
- デジタルポテンショ: MCP4261-502
- 抵抗: 5 kΩ
- 分解能: 8 bit(0 Ω-5 kΩを257step)
- チャンネル数: 2
- 通信法: SPI
- 制御マイコン: KB2040
- 使用コード: CircuitPython
- 使用エディタ: MuEditor
MCP4261
- Vss, Vdd
- Vss (GND), Vdd (2.7 V-5.5 V)
- \(\overline{\text{CS}}\)
- Chip Select
- Lにするとシリアルコマンド有効
- SCK, SDI, SDO
- シリアルクロック、シリアルデータ入力、シリアルデータ出力
- \(\overline{\text{WP}}\)
- 不揮発性メモリーを強制的に書き込み禁止にする
- \(\overline{\text{SHDN}}\)
- レジスタネットワーク端子をハードウェアシャットダウン状態にする
KB2040
接続
KB2040 | MCP4261 | 備考 |
---|---|---|
RAW | Vdd | 正電源(+5 V) |
GND | Vss | グラウンド |
SCK | SCK | クロック |
MISO | SDO | 読み込み |
MOSI | SDI | 書き込み |
D10 | \(\overline{\text{CS}}\) | チップセレクト |
SPIコマンド
とりあえず、抵抗の値の書き込みと読み込みだけを目指します。
SPI mode
(8ビット・バイトの)1番目のクロック・ビットの立ち上がりエッジにおけるSDIピンの状態によって、以下の2つのモードのどちらかに決定される。
- MODE 0,0
- 待機状態のSCK: L
- SCKの立ち上がりエッジでSDIがクロック入力
- SCKの立ち下がりエッジでSDOがクロック入力
- MODE 1,1
- 待機状態のSCK: H
- SCKの立ち上がりエッジでSDIがクロック入力
- SCKの立ち下がりエッジでSDOがクロック入力
Command byte
MCP4261のSPIコマンドの概要は、データシートの下図を参照します。
- Memory Address (4 bit): アクセスする機能を指定
0b0000
: ワイパー0(揮発性)0b0001
: ワイパー1(揮発性)0b0010
: ワイパー0(不揮発性)0b0011
: ワイパー1(不揮発性)
- Command Bits (2 bit): 機能に送るコマンドを指定
0b11
: データ読み込み0b00
: データ書き込み0b01
: インクリメント(+1)0b10
: デクリメント(-1)
- Data Bits
- 書き込み/読み込みの場合は10 bit(D9:D0)
- D9は書き込み時は未使用で
0
にしておく、読み込み時はコマンドエラービットに対応(1: valid, 0: invalid) - [D8:D0]は、フルスケール:
0b1_0000_0000
、最小値:0b0_0000_0000
- D9は書き込み時は未使用で
- インクリメント/デクリメントの場合は2 bit(D9:D8)、ただし使用しない
- 書き込み/読み込みの場合は10 bit(D9:D0)
CircuitPythonでのSPI通信
本節はAdafruit SPI Devices ドキュメントの要約です。 実機での動作を確認せずにドキュメントを和訳しただけの部分もあるのはご容赦ください。
SPIプロトコル
CircuitPythonでSPI通信を行うには、board
, busio
, digitalio
モジュールをインストールする必要があります。
import board
import busio
import digitalio
CSラインは単なるデジタルI/Oなので、digitalio
モジュールを使って制御します。
= digitalio.DigitalInOut(board.D10)
cs = digitalio.Direction.OUTPUT
cs.direction = True cs.value
次に、SPIハードウェアバスへのインターフェイスを作成します。まず、busio.SPI
クラスのインスタンスを作成します。
= busio.SPI(board.SCK, MISO=board.MISO, MOSI=board.MOSI) spi
SPIバスに対して呼び出しを行う前に、「ロック」してコードがSPIに排他的にアクセスできるようにします。busio.SPI.try_lock()
関数を待ち、終了したらbusio.SPI.unlock()
関数を呼び出すなど、バスをロックする方法はいくつかあります。バスをロックすることで、CircuitPythonにこのコードがSPIを使う必要があること、そしてSPIを使う他のコードはこのコードが終わるまで待つ必要があることを伝えます。これは、異なるコードが同じハードウェアペリフェラルを同時に使おうとしたり、お互いに割り込んだりしないようにするためです。
ここでは、SPIバスをロックするのにbusio.SPI.try_lock()
関数が成功するのを待つループ構文を使います。
while not spi.try_lock():
pass
このループは、SPIバスがロックされてtry_lock()
関数がtrueを返すまで、try_lock()
関数を呼び出し続けます。他のコードがバスを使用しているかもしれないので、このループはバスをロックし、バスが使用可能であることを確認し続けます。バスがロックされたら、アクセスするための関数を呼び出し始めることができます。
=5000000, phase=0, polarity=0)
spi.configure(baudrate= False # CS端子をLにして通信開始
cs.value = bytearray(4) # 4バイトの変数を初期化
result # SPIで読み出した内容をresultに保存
spi.readinto(result) = True # CS端子をHにして通信終了 cs.value
busio.SPI.readinto()
関数は、4バイトのデータを読み出すために呼び出されます。渡されたバッファのサイズによって、何バイトのデータが読み込まれるかが決まります。
MOSIライン上でデータを送信するためには、busio.SPI.write()
関数を使用します。例えば、0x01, 0xFFのバイトを送信するには次のように実行します。
=5000000, phase=0, polarity=0)
spi.configure(baudrate= False # CS端子をLにして通信開始
cs.value bytes([0x01, 0xFF])) # 2バイトの書き込み
spi.write(= True # CS端子をHにして通信終了 cs.value
最後にbusio.SPI.unlock()
を実行してSPIバスのロックを解除し、他のコードがSPIバスを使えるようにします。
busio.SPI.unlock()
try-finallyブロックの中に記述して、たとえ何かが失敗したとしてもunlockが常に呼び出されるようにするとよいです。
while not spi.try_lock():
pass
try:
=5000000, phase=0, polarity=0)
spi.configure(baudrate= False # CS端子をLにして通信開始
cs.value = bytearray(4) # 4バイトの変数を初期化
result # SPIで読み出した内容をresultに保存
spi.readinto(result) = True # CS端子をHにして通信終了
cs.value finally:
spi.unlock()
result
SPIDeviceライブラリ
組み込みAPIを使うには、SPIバスにアクセスするためのロックとアンロック関数を注意深く管理し、デバイスのチップセレクトラインを明示的に操作する必要があります。SPIデバイスと通信するコードを書くなら、CircuitPythonのバスデバイスライブラリを使う方が、ロックとアンロック、チップセレクトラインを自動で制御してくれるので管理が少し楽になります。
バスデバイスライブラリを使うには、ボードにライブラリをインストールする必要があります。最新版のダウンロードページに行き、 adafruit-circuitpython-bundle-9.x-mpy-yyyymmdd.zip
をダウンロードします。展開した中のlib
フォルダにあるadafruit_bus_deviceフォルダをボードのCIRCUITPY
ドライブのlib
フォルダにコピーします。
バスデバイスライブラリをインストールしたら、SPIDeviceクラスを使ってSPIバス上のデバイスへのアクセスを簡素化できます。まず、SPIバスとCSラインを前項と同じようにセットアップします
import board
import busio
import digitalio
= busio.SPI(board.SCK, MISO=board.MISO, MOSI=board.MOSI)
spi = digitalio.DigitalInOut(board.D10) cs
次にバスデバイスモジュールをインポートし、SPIDeviceクラスのインスタンスを作成します。SPIDeviceクラスには、SPIバス、チップセレクトライン、ボーレート、極性、SPI接続の位相を知らせる必要があります。
from adafruit_bus_device.spi_device import SPIDevice
= SPIDevice(spi, cs, baudrate=5000000, polarity=0, phase=0) device
これで、SPIデバイス・インスタンスと対話する準備が整いました。今回はコードをwith構文に置くことで、自動的にバスをロックし、CSラインを確認し、SPIバスを設定します。完了したらバスをアンロックします。
with device:
= bytearray(4) # 4バイトの変数を初期化
result # SPIで読み出した内容をresultに保存
spi.readinto(result)
result
configureを呼び出したり、CSラインをHighからLowに変更したり戻したりする必要はなく、SPIDeviceクラスがすべて自動的に処理してくれます。
CircuitPythonによるMCP4261の制御
SPIDeviceライブラリを使ってMCP4261を制御する関数を作ります。とりあえず、抵抗の値の書き込みと読み込みだけを目指します。
書き込み
ポテンショのポジションを揮発性メモリに書き込む関数です。
def pod_write(device, wiper, val):
if val > 256:
= 256
val if val < 0:
= 0
val = (wiper << 12 | 0b00 << 10 | val) # D12にwiper, [D11:D10]に書き込みコマンド, [D8:D0]にval
buf with device:
2, 'big')) # バッファのデータを書き込む spi.write(buf.to_bytes(
- device: SPIデバイス・インスタンス
- wiper: ポテンショの番号(
0
or1
) - val: ポテンショの位置(
0
-256
)
読み込み
ポテンショのポジションを揮発性メモリから読み込む関数です。
def pod_read(device, wiper):
= (wipre << 12 | 0b11 << 10) # D12にwiper, [D11:D10]に読み込みコマンド
buf with device:
= bytearray(2)
result 2, 'big'), result) # 書き込みと読み込みを同時に行う
spi.write_readinto(buf.to_bytes(return (result[0] & 0b0000_0001) << 8 | result[1] # 2バイト[D15:D0]から[D8:D0]を取り出してデコード
- device: SPIデバイス・インスタンス
- wiper: ポテンショの番号(
0
or1
)
テストコード
以下のコードをKB2040に書き込んでテストしてみました。 SPDeviceライブラリでの説明通り、ライブラリからadafruit_bus_deviceフォルダをボードのCIRCUITPY
ドライブのlib
フォルダにコピーしておきます。
# === パッケージ読み込み ===
import board
import busio
import digitalio
from adafruit_bus_device.spi_device import SPIDevice
import time
# === SPIバスとCSのセットアップ ===
= busio.SPI(board.SCK, MISO=board.MISO, MOSI=board.MOSI)
spi = digitalio.DigitalInOut(board.D10)
cs
# === SPIDeviceインスタンスの作成 ===
= SPIDevice(spi, cs, baudrate=1000000, polarity=0, phase=0)
device
# === 関数の作成 ===
def pod_write(device, wiper, val):
if val > 256:
= 256
val if val < 0:
= 0
val = (wiper << 12 | 0b00 << 10 | val) # D12にwiper, [D11:D10]に書き込みコマンド, [D8:D0]にval
buf with device:
2,'big'))
spi.write(buf.to_bytes(
def pod_read(device, wiper):
= (wiper << 12 | 0b11 << 10) # D12にwiper, [D11:D10]に読み込みコマンド
buf with device:
= bytearray(2)
result 2,'big'), result)
spi.write_readinto(buf.to_bytes(return (result[0] & 0b0000_0001) << 8 | result[1] # 2バイト[D15:D0]から[D8:D0]を取り出してデコード
# === メインループ ===
= 0
p = 1
step while True:
if p > 256:
= -1
direction if p < 0:
= 1
direction = p + direction * step
p
0, p) # ポテンショポジションの書き込み
pod_write(device, print(pod_read(device, 0)) # ポジションの読み込み
0.1) time.sleep(
実行すると、抵抗値が三角波状に増減を繰り返すはずです。 テスタで確認する場合には変化が速すぎるので、最終行をtime.sleep(1)
などに調整してください。