3.26. CAN bus ในโค้ด¶
machine.CAN ครอบ hardware CAN controller เปิดใช้งานด้วย bus id และ bit rate:
from machine import CAN
can = CAN(1, 500_000)
can.set_filters(None) # accept all incoming IDs
bit rate ต้องตรงกับทุกโหนดอื่นบนบัสอย่างแม่นยำ -- 125_000, 250_000, 500_000 และ 1_000_000 เป็นค่าทั่วไป set_filters() กำหนดค่า ID ที่ controller จะยอมรับ None หมายถึงรับทุก ID (มีประโยชน์ขณะทดสอบลิงก์ แต่ไม่เหมาะเมื่อบัสมีความยุ่งยาก)
3.26.1. ภายใน controller¶
ฮาร์ดแวร์สามชิ้นอยู่ระหว่างโค้ด Python ของกล้องและบัส -- TX mailboxes, acceptance filter และ RX FIFO แต่ละชิ้นแมปกับส่วนหนึ่งของ CAN API ที่ใช้ด้านล่าง
TX mailboxes, acceptance filter และ RX FIFO ของ controller ระหว่างซอฟต์แวร์และบัส¶
TX mailboxes ชุดช่องฮาร์ดแวร์ขนาดเล็ก (โดยทั่วไปสามช่อง) ที่เก็บเฟรมขาออกที่กล้องส่งมาผ่าน
send()แต่ยังไม่ถึงบัส เมื่อบัสว่าง controller จะเลือก mailbox ที่มี ID ตัวเลขต่ำที่สุด (ความสำคัญสูงที่สุด) และ arbitrate กับบัสแทน กล้องไม่ได้เลือก mailbox controller จะกำหนดหนึ่งช่องและส่งคืน index จากsend()Acceptance filter ฮาร์ดแวร์ที่กำหนดค่าได้ซึ่งเปรียบเทียบ ID ของเฟรมขาเข้าแต่ละเฟรมกับรายการรูปแบบและทิ้งสิ่งที่ไม่ตรงกัน เฟรมที่ผ่านจะดำเนินต่อไปยัง RX FIFO เฟรมที่ถูกปฏิเสธจะถูกทิ้งโดย controller โดยไม่ถึง Python เลย
set_filters()กำหนดค่ารูปแบบเหล่านี้RX FIFO คิว first-in, first-out -- เฟรมแรกที่เข้ามาจะออกไปเป็นเฟรมแรกเช่นกัน เหมือนแถวที่ตู้ขายตั๋ว controller จะเพิ่มเฟรมที่รับมาต่อท้ายคิวเมื่อมาถึง และ
recv()จะดึงออกจากหน้าคิวตามลำดับเดียวกัน คิวมีความสำคัญเพราะบัสรับเฟรมในพื้นหลังขณะที่ Python กำลังยุ่งอยู่ที่อื่น กล้องจึงดึงทีละเฟรมโดยไม่สูญหาย ตราบใดที่ FIFO ไม่ล้น
3.26.2. การส่งเฟรม¶
send() จัดคิวเฟรมสำหรับการส่ง:
can.send(0x123, b"\x01\x02\x03\x04")
อาร์กิวเมนต์แรกคือ ID (จำนวนเต็ม 11 บิตสำหรับ standard frame) อาร์กิวเมนต์ที่สองคือ payload (0 ถึง 8 ไบต์สำหรับ CAN Classic) การเรียกส่งคืนเลข index ขนาดเล็กที่ระบุ hardware mailbox ที่เฟรมถูกส่งเข้าไป controller จะ arbitrate กับผู้ส่งอื่น ๆ บนบัสและส่งใหม่ตามต้องการโดยไม่ต้องการความช่วยเหลือจากซอฟต์แวร์
สำหรับ ID แบบ extended (29 บิต) ให้ OR แฟล็ก CAN.FLAG_EXT_ID เข้ากับอาร์กิวเมนต์ที่สาม:
can.send(0x18FF1234, b"hello", CAN.FLAG_EXT_ID)
3.26.3. การรับเฟรม¶
controller เริ่มต้นโดยไม่มี filter ติดตั้งและทิ้งทุกเฟรมขาเข้า ก่อนที่ recv() จะส่งคืนสิ่งใด ให้เรียก set_filters() ครั้งหนึ่ง -- รูปแบบที่ง่ายที่สุดคือ None ซึ่งรับทุก ID:
can.set_filters(None) # accept every frame
recv() จะส่งคืนเฟรมถัดไปใน receive FIFO หรือ None หากไม่มีเฟรมรอ:
msg = can.recv()
if msg is not None:
can_id, data, flags, errs = msg
print("got", hex(can_id), bytes(data))
บัสจะเติม RX FIFO ในพื้นหลัง ดังนั้น main loop แค่ดึงข้อมูลออกให้เร็วที่สุดเท่าที่ iteration ไป ตราบใดที่ FIFO ลึกกว่าช่วงว่างที่นานที่สุดระหว่างการดึง ไม่มีเฟรมใดสูญหาย
3.26.4. Filters¶
บัสจริงมักยุ่งด้วยเฟรมที่กล้องไม่สนใจ Hardware filter ให้ controller ทิ้ง ID ที่ไม่ต้องการก่อนที่จะถึง FIFO set_filters() รับรายการ tuple (id, mask, flags) เฟรมผ่าน filter หาก ID ของมัน masked ด้วย mask ตรงกับ id ที่กำหนดค่า:
# Accept only IDs 0x100 - 0x10F (mask off the bottom 4 bits)
can.set_filters(((0x100, 0x7F0, 0),))
# Accept IDs 0x300 and 0x700 exactly
can.set_filters(((0x300, 0x7FF, 0),
(0x700, 0x7FF, 0)))
เฟรมที่ไม่ตรงกันจะถูกทิ้งโดย controller และไม่ปรากฏที่ recv() เลย ซึ่งประหยัดทั้งพื้นที่ buffer และเวลา CPU
3.26.5. สถานะข้อผิดพลาดและการกู้คืน¶
CAN bus จริงพบกับข้อผิดพลาดการส่ง -- short circuit ลงกราวด์ โหนดหายไป สัญญาณรบกวนทางไฟฟ้าทำให้บิตเสียหาย controller เก็บตัวนับสองตัวที่ติดตามพฤติกรรมนี้: Transmit Error Counter (TEC) และ Receive Error Counter (REC) แต่ละตัวจะเพิ่มขึ้นเมื่อ controller ตรวจพบข้อผิดพลาดและลดลงหลังจากการถ่ายโอนสำเร็จ ค่าตัวนับทำให้ controller อยู่ในสถานะหนึ่งในสี่สถานะ:
Error Active (TEC และ REC ทั้งคู่ต่ำกว่า 96) การทำงานปกติ เมื่อโหนดตรวจพบข้อผิดพลาดบัสมันส่ง dominant active error frame ซึ่งบังคับให้ทุกโหนดอื่นทิ้งเฟรมที่กำลังดำเนินอยู่เพื่อให้ผู้ส่งลองใหม่
Error Warning (ตัวนับใดตัวหนึ่งถึง 96) ยังคงทำงานเต็มรูปแบบบนบัส -- สถานะ warning เป็นสัญญาณซอฟต์แวร์ว่าข้อผิดพลาดกำลังสะสม ไม่ใช่การเปลี่ยนแปลงพฤติกรรม
Error Passive (ตัวนับใดตัวหนึ่งถึง 128) โหนดยังคงอยู่บนบัสแต่หยุดส่ง dominant error frame ข้อผิดพลาดตอนนี้ถูกส่งสัญญาณด้วย passive (recessive) error frame เพื่อไม่ให้โหนดที่มีข้อบกพร่องทำลายบัสสำหรับทุกคน
Bus Off (TEC ถึง 256) controller ตัดสินใจแล้วว่าโหนดนี้ไม่น่าเชื่อถือพอที่จะเข้าร่วม มันตัดการเชื่อมต่อจากบัส หยุดส่งและ acknowledge และอยู่นิ่งจนกว่าซอฟต์แวร์จะเริ่มต้นใหม่อย่างชัดเจน
การเปลี่ยนแปลงสามแรกเป็นอัตโนมัติทั้งหมด -- เมื่อตัวนับลดลงหลังจากเฟรมสำเร็จ controller จะย้ายตัวเองกลับสู่ Error Active โดยไม่ต้องแทรกแซง
Bus Off เป็นสถานะเดียวที่ต้องการการกระทำจากซอฟต์แวร์ restart() รีเซ็ต controller และส่งคืนไปยัง Error Active รูปแบบทั่วไปคือตรวจสอบสถานะจาก main loop และ restart หลังจากหน่วงเวลาสั้น ๆ เพื่อให้บัสมีเวลาคงที่:
import time
from machine import CAN
can = CAN(1, 500_000)
can.set_filters(None)
while True:
if can.state() == CAN.STATE_BUS_OFF:
time.sleep_ms(100)
can.restart()
# ... rest of the loop
ค่าตัวนับปัจจุบันสามารถรับได้จาก get_counters() สำหรับการวินิจฉัย -- TEC ที่ขึ้นอย่างต่อเนื่องบนบัสที่เงียบโดยทั่วไปมักชี้ไปที่การเดินสาย termination หรือ bit rate ที่กำหนดค่าไม่ถูกต้อง