Picoは素のままではWi-FiもBluetoothも赤外線も使えません。制御するのにシリアル通信を使ってみます。
ラズパイ4Bをマスター、PicoをスレーブにしてI2Cインターフェースを使います。
こんな感じで、ヘッダーピンをジャンパーワイヤで結線します。
Lチカとサーボモータ制御をやってみます。
注:
ZeroからPicoにMicroPythonの環境を作ることはできないようです(BOOTSEL + USB接続するとZeroがハードリセットされてしまいます)。ZeroからPicoを制御する場合は以下に記述したようにラズパイ3以上かWindowsやMacでMicroPython環境を作っておきます。最初にZeroとPicoを接続してZeroからPicoに給電するとリセットがかかりますが、起動後はPicoはI2Cデバイスとして認識されています。また、USBで接続すればThonnyからもPicoが見えます。ただ何かが走っているとBusyなので、事前にラズパイ4などでPicoはクリーンにしておく必要があります。
参:
ZeroからPicoをコーディングするのに以下のようなヘンタイ的なやり方もあるようです。
ご参考までに。
スマホとRaspberry Pi Zero Wを使ってRaspberry Pi Picoを屋外でもコーディング
環境作り
ラズパイ4B側ではI2Cインターフェースを有効にしておきます。
Pico側ではMicroPythonをセットアップしておきます。
このページ参照
Pi PicoにMicroPythonをセットアップして実行してみる(メモ)
Picoをスレーブとして動作させましょう。
このコードを使わせていただきます。
【i2cSlave.py】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
from machine import mem32,mem8,Pin class i2c_slave: I2C0_BASE = 0x40044000 I2C1_BASE = 0x40048000 IO_BANK0_BASE = 0x40014000 mem_rw = 0x0000 mem_xor = 0x1000 mem_set = 0x2000 mem_clr = 0x3000 IC_CON = 0 #I2C Control Register IC_TAR = 4 #I2C Target Address Register IC_SAR = 8 #I2C Slave Address Register IC_DATA_CMD = 0x10 #I2C Rx/Tx Data Buffer and Command Register IC_RAW_INTR_STAT = 0x34 #I2C Raw Interrupt Status Register IC_RX_TL = 0x38 #I2C Receive FIFO Threshold Register IC_TX_TL = 0x3C #I2C Transmit FIFO Threshold Register IC_CLR_INTR = 0x40 #Clear Combined and Individual Interrupt Register IC_CLR_RD_REQ = 0x50 IC_CLR_TX_ABRT = 0x54 IC_ENABLE = 0x6c #I2C ENABLE Register IC_STATUS = 0x70 #I2C STATUS Register def write_reg(self,reg,data,method=0): mem32[self.i2c_base|method|reg] = data def set_reg(self,reg,data): self.write_reg(reg,data,method=self.mem_set) def clr_reg(self,reg,data): self.write_reg(reg,data,method=self.mem_clr) def __init__(self,i2cID = 0,sda=0,scl=1,slaveAddress=0x41): self.scl = scl self.sda = sda self.slaveAddress = slaveAddress self.i2c_ID = i2cID if self.i2c_ID == 0: self.i2c_base = self.I2C0_BASE else: self.i2c_base = self.I2C1_BASE #1 Disable DW_apb_i2c self.clr_reg(self.IC_ENABLE,1) #2 set slave address #clr bit 0 to 9 #set slave address self.clr_reg(self.IC_SAR,0x1ff) self.set_reg(self.IC_SAR,self.slaveAddress &0x1ff) #3 write IC_CON 7bit,enable in slave-only self.clr_reg(self.IC_CON,0b01001001) #set SDA PIN mem32[ self.IO_BANK0_BASE|self.mem_clr|(4 + 8 * self.sda)] = 0x1f mem32[ self.IO_BANK0_BASE|self.mem_set|(4 + 8 * self.sda)] = 3 #set SLA PIN mem32[ self.IO_BANK0_BASE|self.mem_clr|(4 + 8 * self.scl)] = 0x1f mem32[ self.IO_BANK0_BASE|self.mem_set|(4 + 8 * self.scl)] = 3 #4 enable i2c self.set_reg(self.IC_ENABLE,1) def anyRead(self): status = mem32[self.i2c_base|self.IC_RAW_INTR_STAT] & 0x20 if status: return True return False def put(self,data): #reset flag self.clr_reg(self.IC_CLR_TX_ABRT,1) status = mem32[ self.i2c_base|self.IC_CLR_RD_REQ] mem32[ self.i2c_base|self.IC_DATA_CMD] = data & 0xff def any(self): #get IC_STATUS status = mem32[ self.i2c_base|self.IC_STATUS] #check RFNE receive fifo not empty if (status & 8): return True return False def get(self): while not self.any(): pass return mem32[ self.i2c_base|self.IC_DATA_CMD] & 0xff if __name__ == "__main__": import utime from machine import mem32 from i2cSlave import i2c_slave s_i2c = i2c_slave(0,sda=0,scl=1,slaveAddress=0x41) counter = 1 try: while True: if s_i2c.any(): print(s_i2c.get()) if s_i2c.anyRead(): counter = counter + 1 s_i2c.put(counter & 0xff) except KeyboardInterrupt: pass |
Lチカさせるコード
【main.py】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import utime from machine import Pin from i2cSlave import i2c_slave i2c = i2c_slave(0,sda=0,scl=1,slaveAddress=0x41) led = Pin(25,Pin.OUT) def led_chika2(c): for i in range(c): led.value(1) utime.sleep(0.5) led.value(0) utime.sleep(0.5) #起動時に3回チカチカさせる led_chika2(3) #信号を待ち受け try: while True: c = int(i2c.get()) led_chika2(c) except KeyboardInterrupt: pass |
セットアップ
上記の2つのコードをPicoに書き込んで、ラズパイ4BとPicoをジャンパーワイヤで接続してPicoを再起動させます。
以前に何か作業をしていた場合は、まず、PicoのFlashメモリーをリセットしてスッピンにしておきます。
ラズパイ4BでThonny Python IDEを開いて、上記 i2cSlave.py をPicoに書き込みます。
どこにSaveするか聞いてくるので、Pico側に保存
Newをクリックしてmain.pyも同様に保存しておきます。
こんな感じでコードはルートに保存されます。
ラズパイ4BとPicoをジャンパーワイヤで結線してPicoを起動させます
まず、MicroUSBケーブルは外しておきます。
結線はこんな感じ。
順番はまぁどうでもいいと思いますが、
GPIOのRx、Txをクロスで結線します(青と緑)。
黒をGNDに結線して、最後に赤を5Vに結線します。
これでPicoに電力が供給されて起動します。起動後に自動でmain.pyが走りますので、うまくいってれば、LEDが3度点滅して信号待ちの状態になっているはずです。
ラズパイ4BからPicoのLED点滅を制御
main.pyのコードでI2Cアドレスを0x41と設定しています。
見てみましょう。
ラズパイ4Bでターミナルを起動して以下のコマンドを発行します。
1 |
sudo i2cdetect -y 1 |
Picoの main.pyが起動していればこうなります。
Python3を起動してox41からLチカの回数を送信してみます。
こんなコードです。
1 2 3 4 5 6 |
import smbus bus = smbus.SMBus(1) #4回点滅させる bus.write_byte(ox41,4) |
4回チカチカしてくれればOK。
現状のコードで送信できるのは 0~255 です。
256以上の数値は再び0からセットされます。
サーボモータを制御
MG90S マイクロ・サーボモータを使ってみます。
Picoとの結線 data送信には28番のpin(GP28)を使っています。
結線はI2Cとかぶらないようにしているだけなので以下のようなのでもOKです。この場合data線はGP2です。赤と黒の電力供給用は上のでもいいです。
こうすると、単純に2つのサーボを使えますね。ただ、2個のサーボの電力をPicoだけから取るのは厳しいかも…。
基本コード
1 2 3 4 5 6 7 8 9 10 11 |
from machine import PWM, Pin import time servo1 = PWM(Pin(28)) servo1.freq(50) while True: servo1.duty_u16(1000) time.sleep(1) servo1.duty_u16(7500) time.sleep(1) |
ラズパイ4Bをマスター、Picoをスレーブにしてやってみます。
スレーブ用のコードは上記と同じ
【i2cSlave.py】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
from machine import mem32,mem8,Pin class i2c_slave: I2C0_BASE = 0x40044000 I2C1_BASE = 0x40048000 IO_BANK0_BASE = 0x40014000 mem_rw = 0x0000 mem_xor = 0x1000 mem_set = 0x2000 mem_clr = 0x3000 IC_CON = 0 #I2C Control Register IC_TAR = 4 #I2C Target Address Register IC_SAR = 8 #I2C Slave Address Register IC_DATA_CMD = 0x10 #I2C Rx/Tx Data Buffer and Command Register IC_RAW_INTR_STAT = 0x34 #I2C Raw Interrupt Status Register IC_RX_TL = 0x38 #I2C Receive FIFO Threshold Register IC_TX_TL = 0x3C #I2C Transmit FIFO Threshold Register IC_CLR_INTR = 0x40 #Clear Combined and Individual Interrupt Register IC_CLR_RD_REQ = 0x50 IC_CLR_TX_ABRT = 0x54 IC_ENABLE = 0x6c #I2C ENABLE Register IC_STATUS = 0x70 #I2C STATUS Register def write_reg(self,reg,data,method=0): mem32[self.i2c_base|method|reg] = data def set_reg(self,reg,data): self.write_reg(reg,data,method=self.mem_set) def clr_reg(self,reg,data): self.write_reg(reg,data,method=self.mem_clr) def __init__(self,i2cID = 0,sda=0,scl=1,slaveAddress=0x41): self.scl = scl self.sda = sda self.slaveAddress = slaveAddress self.i2c_ID = i2cID if self.i2c_ID == 0: self.i2c_base = self.I2C0_BASE else: self.i2c_base = self.I2C1_BASE #1 Disable DW_apb_i2c self.clr_reg(self.IC_ENABLE,1) #2 set slave address #clr bit 0 to 9 #set slave address self.clr_reg(self.IC_SAR,0x1ff) self.set_reg(self.IC_SAR,self.slaveAddress &0x1ff) #3 write IC_CON 7bit,enable in slave-only self.clr_reg(self.IC_CON,0b01001001) #set SDA PIN mem32[ self.IO_BANK0_BASE|self.mem_clr|(4 + 8 * self.sda)] = 0x1f mem32[ self.IO_BANK0_BASE|self.mem_set|(4 + 8 * self.sda)] = 3 #set SLA PIN mem32[ self.IO_BANK0_BASE|self.mem_clr|(4 + 8 * self.scl)] = 0x1f mem32[ self.IO_BANK0_BASE|self.mem_set|(4 + 8 * self.scl)] = 3 #4 enable i2c self.set_reg(self.IC_ENABLE,1) def anyRead(self): status = mem32[self.i2c_base|self.IC_RAW_INTR_STAT] & 0x20 if status: return True return False def put(self,data): #reset flag self.clr_reg(self.IC_CLR_TX_ABRT,1) status = mem32[ self.i2c_base|self.IC_CLR_RD_REQ] mem32[ self.i2c_base|self.IC_DATA_CMD] = data & 0xff def any(self): #get IC_STATUS status = mem32[ self.i2c_base|self.IC_STATUS] #check RFNE receive fifo not empty if (status & 8): return True return False def get(self): while not self.any(): pass return mem32[ self.i2c_base|self.IC_DATA_CMD] & 0xff if __name__ == "__main__": import utime from machine import mem32 from i2cSlave import i2c_slave s_i2c = i2c_slave(0,sda=0,scl=1,slaveAddress=0x41) counter = 1 try: while True: if s_i2c.any(): print(s_i2c.get()) if s_i2c.anyRead(): counter = counter + 1 s_i2c.put(counter & 0xff) except KeyboardInterrupt: pass |
サーボモータを制御するコード
【main.py】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
from machine import PWM, Pin from i2cSlave import i2c_slave import time i2c = i2c_slave(0,sda=0,scl=1,slaveAddress=0x41) servo1 = PWM(Pin(28)) servo1.freq(50) def servo_rotate(v): if v >= 10 and v <= 75: w = v * 100 servo1.duty_u16(w) else: pass #初期動作 servo1.duty_u16(1000) time.sleep(0.5) servo1.duty_u16(7500) time.sleep(0.5) servo1.duty_u16(4300) time.sleep(0.5) #信号を待ち受け try: while True: v = int(i2c.get()) servo_rotate(v) except KeyboardInterrupt: pass |
Lチカの場合と同様にThonnyでPicoに書き込みます。
USBケーブルは外しておき、ラズパイ4Bとの結線は上記と同様で、これでPicoに電力供給します。main.pyが起動すれば、サーボモータが初期動作をするはずです。
注:
サーボモータのような外部に電力供給する場合、十分な電力が供給できない場合があります。
ジャンパーワイヤによっては内部に抵抗が残っていることがあり、これが原因のようです。
そういう場合は、PicoはUSB接続して、ここから電力を供給する必要があります。
あるいは、Picoをバッテリに接続するか、モータの電源はPCA9685のような別ルートから供給するようにします。
シリアル通信用に結線されているので、ラズパイ4BのターミナルからPicoのI2Cアドレスが確認できます。
別ナーミナルからPythonを起動して信号を送ればモーターが回転します。
duty_u16の変数の範囲は1000ー4300ー7500ですので、変数は10~75の範囲で整数を送信します。
こんなコード
1 2 3 4 5 6 7 8 |
import smbus bus = smbus.SMBus(1) #回転 bus.write_byte(0x41,10) bus.write_byte(0x41,43) bus.write_byte(0x41,75) |
0から180度の範囲でジージー、ジージーと回転します。
注
bus.write_byteを連続して発行しなければならないことがあります。
bus.write_byte(ox41,43);bus.write_byte(ox41,43)
注
BOOTSELボタンを押しながらUSB接続してPicoをストレージデバイスとして認識させる場合、
ラズパイ4Bから5Vで電力供給している時はジャンパーワイヤを外しておきましょう。
そうでなければ認識してくれません。
注
PicoがBUSY(つまりmain.pyが実行中)で、Thonnyから実行ファイルが見れなくなった場合
Ctrl + C と書いてますが、Ctrl+D だそうです。
で、USBケーブルを抜き差し
STOPボタンを押すと回復します。
Appendix
Picoにカメラを接続して、ラズパイから物体検出なんかしてみようかな….とお考えなら、この方のブログを参照
【Raspberry Pi Pico】カメラを接続して機械学習プログラムを動かす【Tensorflow Lite】
Leave a Reply