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 ที่กำหนดค่าไม่ถูกต้อง