3.22. SPI ในโค้ด¶
machine.SPI ห่อหุ้มตัวควบคุม SPI ของฮาร์ดแวร์ โดยสาย CS เป็นเอาต์พุต Pin ปกติที่จัดการโดยสคริปต์ สร้างอินสแตนซ์ SPI ด้วย id บัส อัตราสัญญาณนาฬิกาที่ต้องการ และ (หากจำเป็น) โหมด:
from machine import SPI, Pin
spi = SPI(0, baudrate=1_000_000, polarity=0, phase=0)
cs = Pin("P3", Pin.OUT, value=1) # CS idle high
id เลือกบล็อกฮาร์ดแวร์ SPI ที่จะใช้ หมายเลขที่ใช้ได้และพิน SCK/MOSI/MISO ที่แมปไปนั้นขึ้นอยู่กับบอร์ด (ดู บอร์ด OpenMV) baudrate คือความถี่ SCK เป็นเฮิรตซ์ -- อัตราจริงที่ฮาร์ดแวร์ทำได้อาจต่ำกว่าเล็กน้อยเนื่องจากการหารสัญญาณนาฬิกา ซึ่งค่าที่พิมพ์ออกมาของออบเจ็กต์ SPI จะแสดงให้เห็น
พิน CS ถูกสร้างด้วย value=1 เพื่อให้ idle ในสถานะไม่ถูกเลือก ทุกการรับส่งข้อมูลจะเลือก CS (ดึงลงต่ำ), ย้ายไบต์, และยกเลิกการเลือก CS (ดึงขึ้นสูง) อีกครั้ง
3.22.1. การอ่าน การเขียน การแลกเปลี่ยน¶
สามเมธอดครอบคลุมกรณีใช้งานทั่วไป:
cs.value(0)
spi.write(b"\x10\x20\x30") # send 3 bytes, ignore what comes back
cs.value(1)
cs.value(0)
data = spi.read(4) # read 4 bytes; sends 0x00 while reading
cs.value(1)
rx = bytearray(2)
cs.value(0)
spi.write_readinto(b"\x9F\x00", rx) # send 0x9F, 0x00; receive 2 bytes
cs.value(1)
write() เป็นเส้นทางเร็วแบบเขียนอย่างเดียว ตัวควบคุมผลักไบต์และทิ้งสิ่งที่อุปกรณ์ต่อพ่วงส่งกลับบน MISO read() เป็นภาพสะท้อน -- มันส่งสัญญาณนาฬิกา N พัลส์ SCK ขณะส่งไบต์ดัมมี่คงที่ (0 โดยค่าเริ่มต้น) บน MOSI และเก็บไบต์ MISO write_readinto() เป็นรูปแบบฟูลดูเพล็กซ์: ส่งไบต์จากบัฟเฟอร์หนึ่งและเก็บไบต์ MISO พร้อมกันไว้ในอีกบัฟเฟอร์หนึ่ง อุปกรณ์ต่อพ่วงหลายตัวใช้รูปแบบนี้ -- "ส่งไบต์คำสั่ง แล้วอ่านการตอบสนองในการถ่ายโอนครั้งถัดไป" -- ดังนั้นสองการดำเนินการจึงพอดีกับการเรียก write_readinto หนึ่งครั้ง
อุปกรณ์ต่อพ่วงส่วนใหญ่คาดหวังให้สาย CS คงถูกเลือกตลอดการรับส่งข้อมูลทั้งหมด (ตั้งแต่ไบต์คำสั่งจนถึงไบต์การตอบสนอง) ดังนั้นให้วงเล็บ cs.value(0) / cs.value(1) ล้อมรอบ ลำดับทั้งหมด ไม่ใช่รอบแต่ละการเรียกเมธอด
3.22.2. การอ่าน sensor ทั่วไป¶
sensor SPI หลายตัวจัดระเบียบสถานะของตนเป็นชุดรีจิสเตอร์ภายในและใช้รูปแบบการแลกเปลี่ยนเดียวกัน: ส่งที่อยู่รีจิสเตอร์ (พร้อมแฟล็กอ่าน/เขียนในบิตบนสุด) แล้วอ่านหรือเขียนไบต์ของรีจิสเตอร์นั้น การอ่านรีจิสเตอร์ 0x0F บนอุปกรณ์ดังกล่าว:
rx = bytearray(2)
cs.value(0)
spi.write_readinto(b"\x8F\x00", rx) # 0x80 = "read" flag, then reg 0x0F
cs.value(1)
register_value = rx[1]
ไบต์ MISO แรกเป็นขยะ (อุปกรณ์ยังรับคำสั่งอยู่ตอนนั้น) ไบต์ MISO ที่สองเก็บเนื้อหาของรีจิสเตอร์ รูปแบบไบต์คำสั่งที่แน่นอน -- บิตใดเป็นแฟล็กอ่าน/เขียน, ที่อยู่จะเพิ่มอัตโนมัติในการอ่านหลายไบต์หรือไม่ -- อยู่ในแผ่นข้อมูลของอุปกรณ์
3.22.3. Bit-banging¶
อินสแตนซ์ SPI ข้างต้นใช้บล็อกฮาร์ดแวร์ SPI: อุปกรณ์ต่อพ่วงเฉพาะภายใน MCU ที่มีรีจิสเตอร์เลื่อนและตัวสร้างสัญญาณนาฬิกาของตัวเองซึ่งสร้างรูปคลื่น SCK / MOSI / MISO บนซิลิกอน ซอฟต์แวร์เพียงส่งไบต์ให้มัน บิตจะเคลื่อนที่บนสายโดยไม่ต้องการความช่วยเหลือจาก CPU เพิ่มเติม ปล่อยให้ CPU ทำงานอื่นได้พร้อมกัน
ทางเลือกอื่นคือ bit-banging: ซอฟต์แวร์วนลูปบนแต่ละบิตและสลับพิน GPIO โดยตรงเพื่อสร้างรูปคลื่นเดียวกัน ไม่มีอุปกรณ์ต่อพ่วงฮาร์ดแวร์เกี่ยวข้อง -- CPU ขับ SCK ต่ำ, ตั้งค่า MOSI, ขับ SCK สูง, สุ่มตัวอย่าง MISO และอื่นๆ สำหรับทุกบิตของทุกไบต์ นี่ทำให้ CPU ยุ่งตลอดการรับส่งข้อมูลและทำงานช้ากว่าบล็อกฮาร์ดแวร์ แต่ใช้งานได้กับพินใดๆ และไม่ต้องการบล็อกฮาร์ดแวร์ว่างอยู่
machine.SoftSPI เป็นการนำ SPI API เดียวกันมาใช้แบบ bit-bang:
from machine import SoftSPI, Pin
spi = SoftSPI(baudrate=500_000, polarity=0, phase=0,
sck=Pin("P2"), mosi=Pin("P0"), miso=Pin("P1"))
ใช้เมื่ออุปกรณ์ต้องอยู่บนพินที่ไม่ได้เดินสายไปยังบล็อกฮาร์ดแวร์ SPI หรือเมื่อบล็อกฮาร์ดแวร์ทั้งหมดถูกใช้งานอยู่ 500 kHz เป็นเพดานที่สบายสำหรับ cam ส่วนใหญ่ โดย CPU จะยุ่งตลอดการรับส่งข้อมูล