เหตุการณ์ส่งแพ็กเก็ตอีเทอร์เน็ต
(github.com/francisrstokes)- ในฐานะก้าวแรกของการสร้าง TCP/IP stack เองบนไมโครคอนโทรลเลอร์ ได้ลองเชื่อมต่อ STM32F401 Nucleo กับ shield Wiznet W5100 เพื่อส่ง Ethernet frame
- ไม่ได้ใช้ความสามารถ TCP/IP แบบฮาร์ดแวร์ของ W5100 แต่ใช้เฉพาะ MAC Raw mode ให้ชิปทำเฉพาะการประมวลผลระดับต่ำที่จำเป็นต่อการส่ง frame
- อุปสรรคแรกเริ่มจากการเดินสาย SPI ของ Arduino Ethernet shield ไม่ตรงกับ Nucleo และแก้ได้ด้วยการตรวจสอบการ route ของ ICSP header แล้วดัดแปลงการเดินสายบนบอร์ด
- ต่อมาพบการตอบสนองผิดปกติบน MISO ที่ดูเหมือนเกิดจากปัญหา timing ของ chip select และ packet ขยะใน Wireshark โดยมี logic analyser กับการเทียบกับ implementation อ้างอิงเป็นเครื่องมือดีบักหลัก
- สาเหตุสุดท้ายคือบั๊กใน
w5100_write16()ที่เขียน byte ที่สองซ้ำไปยัง address เดิม และด้วยการสร้าง เครื่องมือวิเคราะห์ SPI capture CSV จึงไปถึงการส่ง packet ได้สำเร็จ
จุดเริ่มต้นของการสร้าง TCP/IP stack เอง
- เป้าหมายคือการเริ่มซีรีส์ “Networking from scratch” ที่จะ implement TCP/IP stack ตั้งแต่พื้นฐานบนไมโครคอนโทรลเลอร์
- ผลลัพธ์ที่เห็นชัดในขั้นนี้คือการส่ง Ethernet packet แรก แต่จุดสนใจจริงคือกระบวนการไล่หาบั๊กที่เกิดขึ้นระหว่างฮาร์ดแวร์กับไดรเวอร์
- บอร์ดที่ใช้คือบอร์ดพัฒนา Nucleo ที่ใช้ STM32F401
- ARM Cortex-M4
- ทำงานได้สูงสุด 84MHz
- RAM 96KiB
- ประเมินว่ามีหน่วยความจำเพียงพอสำหรับเก็บ packet หลายชุด
บทบาทของ Ethernet และ W5100
- Ethernet ไม่ใช่แค่พอร์ตหรือรูปแบบ frame แบบง่าย ๆ แต่เป็น ตระกูลเทคโนโลยีและมาตรฐาน ที่รวมถึงฮาร์ดแวร์ชั้นกายภาพ วิธีส่งสัญญาณ การจัดการ bus collision และการวางโครงสร้าง frame
- การประมวลผลสัญญาณ Ethernet มีความซับซ้อน จึงมักใช้ ASIC เฉพาะทางที่รับข้อมูลระดับ frame แล้วรับผิดชอบการจัดการสัญญาณไฟฟ้าบนสายเคเบิล
- โปรเจกต์นี้ใช้ Arduino Ethernet shield ที่มีชิป Wiznet W5100
- บอร์ดที่ใช้เป็นของเลียนแบบราคาถูก จึงต้องมีการแก้ไขเพื่อให้ทำงานได้ถูกต้อง
- W5100 เป็นชิป Ethernet ASIC ที่ฝัง hardware TCP/IP stack ไว้ภายใน
- มี “socket” ให้ 4 ช่อง
- ตั้งค่าได้ในระดับ TCP, UDP, IP และ “MAC Raw”
- เนื่องจากเป้าหมายคือการ implement TCP/IP stack เอง จึงไม่ใช้ความสามารถ TCP/IP ของ W5100 และใช้ socket หนึ่งช่องใน MAC Raw mode เท่านั้น
- ผู้ใช้ส่ง Ethernet frame เข้าไป
- W5100 ทำการส่งจริง
- preamble และ start-of-frame marker เป็นองค์ประกอบระดับไฟฟ้า จึงให้ชิปจัดการ
- 32-bit CRC ก็ให้ W5100 คำนวณ
ปัญหา 1: สัญญาณ SPI ไปไม่ถึง W5100
- การแลกเปลี่ยนข้อมูลกับ W5100 ทำผ่าน SPI
- MOSI: เอาต์พุตจากไมโครคอนโทรลเลอร์ซึ่งเป็นชิปหลัก
- MISO: เอาต์พุตจาก W5100
- clock: clock อ้างอิงของข้อมูล
- chip select: สัญญาณที่บอกว่ากำลังสื่อสารกับชิป subordinate
- datasheet ของ W5100 กำหนด protocol คำสั่ง 4 byte บน SPI
- operation 1 byte
- address แบบ 16-bit big-endian 2 byte
- value 1 byte
- operation คือ write
0xf0หรือ read0x0f- ค่าอื่นไม่ถูกต้องและควรถูก ignore
- ทุกครั้งที่ W5100 clock out byte จะส่งค่าที่รู้ล่วงหน้ากลับทาง MISO จึงตรวจสอบปัญหาการสื่อสารได้ง่าย
- สำหรับคำสั่ง read byte ที่ 4 จะเป็นค่าที่อ่านจาก address ที่ระบุ
- ตอนแรกแม้ส่งคำสั่งทาง MOSI ก็เห็นค่า garbage บน MISO
- สาเหตุคือ Arduino Ethernet shield ถูกออกแบบให้ route สัญญาณ SPI ผ่าน ICSP 6-pin header ไม่ใช่ header มาตรฐานของ Arduino
- บอร์ด Arduino ทางการเชื่อมสัญญาณ SPI ระหว่าง ICSP กับ header มาตรฐานไว้ภายใน จึงไม่มีปัญหา
- บอร์ด Nucleo ไม่มี ICSP header ทำให้สัญญาณ SPI ที่ส่งไปไม่ถึง W5100
- ใช้มัลติมิเตอร์วัดความต้านทานระหว่าง power rail กับสัญญาณ SPI เพื่อยืนยันปัญหาการเชื่อมต่อ
- ถ้าความต้านทานเป็นอนันต์ ก็รู้ได้ว่าสายขาดหรือไม่ได้ต่อถึงกัน
- แก้โดยบัดกรีและใช้ลวดทองแดงเคลือบอีนาเมลเชื่อมสัญญาณระหว่าง Nucleo กับ W5100 โดยตรง
ปัญหา 2: timing ของ chip select และการตอบสนองผิดปกติบน MISO
- หลังจากสัญญาณ SPI เชื่อมต่อจริงแล้ว ก็สื่อสารกับ W5100 ได้ และ implement ขั้นต่อไป
- ตั้งค่า W5100 สำหรับ raw Ethernet transmission
- ตั้งค่า MAC address
- ตั้งค่า segment ของหน่วยความจำ TX/RX
- เขียน Ethernet frame สำหรับทดสอบลงในหน่วยความจำ TX แล้ว trigger การส่ง
- เชื่อม Nucleo กับ shield เข้ากับแล็ปท็อปด้วยสาย CAT5 และเปิด Wireshark แต่ไม่เห็น packet
- ปัญหาระดับ low-level ไม่มี error message หรือ stack trace และอยู่ในลักษณะ “เขย่าอิเล็กตรอนแล้วสิ่งที่คาดไว้ไม่เกิดขึ้น” จึงไล่หาสาเหตุได้ยาก
- ในการดีบักใช้ logic analyser
- สุ่มตัวอย่างการเปลี่ยน high/low ของสัญญาณดิจิทัลจากหลาย channel
- ซอฟต์แวร์ตีความกลุ่มสัญญาณอย่าง SPI bus แล้วแสดง transaction byte
- export เป็นรูปแบบโครงสร้างอย่าง CSV ได้
- อุปกรณ์ที่ใช้คือ Saleae Logic 8
- analyser ราคาถูกระดับประมาณ €10 ก็อาจใช้ร่วมกับ Saleae Logic2 ได้ แต่มี tradeoff เรื่องการทำงานให้ถูกต้องและสม่ำเสมอ
- คำสั่ง SPI แรกดูปกติ
0xf0 0x00 0x00 0x80- ตั้ง bit สูงสุดของ Mode Register ที่ address
0x0000เพื่อ trigger software reset - MISO ตอบกลับปกติตั้งแต่
0x00ถึง0x03
- คำสั่ง read ถัดไปมีการตอบสนองผิดปกติ
- MOSI:
0x0f 0x00 0x00 0x00 - MISO:
0x03 0xff 0xff 0xff
- MOSI:
- เพราะ
0x03คือค่าท้ายสุดของ transaction ก่อนหน้า จึงสงสัยว่าเป็นปัญหา state ภายในหรือ timing ของ W5100 - SPI clock ที่ใช้อยู่ต่ำกว่าค่าสูงสุดตาม datasheet ประมาณ 14MHz มาก จึงไม่มองว่าเป็นสาเหตุ
- แทนที่จะเป็นเช่นนั้น สันนิษฐานว่า chip select เปลี่ยนเป็น high เร็วเกินไปจนชิปเข้าสู่ state ที่ไม่ดี จึงใส่ delay ไม่กี่ไมโครวินาทีก่อนเปลี่ยน state
- หลังจากนั้นการตอบกลับบน MISO กลับมาปกติ
- อ่านค่าที่ตั้งไว้กลับมาก็ได้ค่าที่สมเหตุสมผล
- ยังไม่เข้าใจสาเหตุที่แน่ชัดของปัญหานี้ทั้งหมด
- ข้อจำกัดเกี่ยวกับ chip select ใน SPI timing diagram หน้า 66 ของ datasheet นั้นถูกปฏิบัติตามแล้ว
- สามารถกลับไปตรวจสอบ boundary condition ที่แน่นอนได้ แต่ตอนนี้ให้ความสำคัญกับการเดินหน้าโปรเจกต์ก่อน
ปัญหา 3: packet ขยะที่ปรากฏใน Wireshark
- เมื่อเปิด Wireshark อีกครั้ง packet ปรากฏแล้ว แต่ไม่ใช่ packet ที่ตั้งใจส่ง
- ทันทีที่ไมโครคอนโทรลเลอร์ส่งคำสั่ง transmit ก็ปรากฏ raw Ethernet packet ที่ใหญ่กว่าที่ใส่ไว้มากและเต็มไปด้วยข้อมูลขยะ
- จึงพับแผนที่จะ implement โดยอาศัยแค่ datasheet กับ spec แล้วในขั้นนี้เปลี่ยนไปเทียบกับ implementation ที่ยืนยันว่าทำงานได้
- Arduino มีประโยชน์สำหรับสร้าง implementation อ้างอิงอย่างรวดเร็ว
- ใช้ Arduino กับ library และโค้ดประมาณ 5 บรรทัดก็ตรวจสอบพฤติกรรมซับซ้อนได้
- แต่การหา library สำหรับส่ง raw Ethernet packet ใช้เวลามากกว่า
- ผู้ใช้ Arduino ส่วนใหญ่ไม่ได้พยายาม implement ความสามารถเครือข่ายตั้งแต่ต้น
- library ทางการได้เอาการรองรับการส่ง raw packet ออกจาก public API แล้ว
- พบโปรเจกต์ GitHub W5100MacRaw ที่ทำการส่งรับ packet ด้วยขั้นตอนขั้นต่ำ
- เปรียบเทียบ source นั้นกับ implementation ของตัวเองแล้ว แต่ความต่างที่เห็นทันทีไม่ชี้ขาด
- ลำดับการ read/write register ต่างกัน
- มี read/write บางตัวที่มีอยู่เฉพาะฝั่งเดียว
- โดยปกติถ้ามี dependency กับลำดับ ก็คาดว่า datasheet จะระบุไว้
- แม้ปรับลำดับ read/write ให้เหมือน implementation อ้างอิงแล้ว Wireshark ก็ยังแสดง packet ขยะอยู่
บั๊กจริงที่เผยออกมาด้วยเครื่องมือเล็ก ๆ
- กลยุทธ์ถัดไปคือเขียนเครื่องมือ parse SPI capture CSV จาก Saleae Logic2 แล้วแสดงกลับเป็นรายการ register read/write
- เครื่องมือนี้เขียนด้วย Python และมีความยาวทั้งหมดมากกว่า 200 บรรทัดเล็กน้อย
- ส่วนใหญ่เป็นชื่อ register และ address ที่คัดลอกจาก datasheet
- ใช้เวลาเขียนประมาณ 1 ชั่วโมง
- ใส่ argument parsing ด้วยเพื่อให้ input และ output ชัดเจน
- capture SPI จาก implementation อ้างอิงบน Arduino และ implementation ของตัวเอง แล้ว export เป็น CSV จากนั้นประมวลผลด้วยเครื่องมือและทำ diff
- ปัญหาอยู่ที่ฟังก์ชันอำนวยความสะดวก
w5100_write16(u16 address, u16 value)- register จำนวนมากของ W5100 เป็นค่า 16-bit
- รูปแบบคำสั่งเขียนได้ครั้งละ 8-bit เท่านั้น จึงต้องแยกเป็น high/low byte
- ฟังก์ชันนี้เขียน byte ที่สองซ้ำที่ address เดิม แทนที่จะเขียนไปยัง
address + 1
- log อ้างอิงจาก Arduino เขียนไปที่
S0_TX_WR0และS0_TX_WR1ตามลำดับS0_TX_WR0 [0x0424] 0x00S0_TX_WR1 [0x0425] 0x3c
- log ของตัวเองเขียนทั้งสองครั้งไปที่
S0_TX_WR0 [0x0424]S0_TX_WR0 [0x0424] 0x00S0_TX_WR0 [0x0424] 0x3c
- register นี้คือ Socket 0 transmit write pointer
- W5100 คาดว่าจะอ่าน register 16-bit นี้ตามลำดับ
- เขียน byte ลงในหน่วยความจำ TX
- เขียน write pointer ใหม่กลับเข้าไป
- แล้วส่ง socket send command
- เมื่อสั่ง send โดยยังไม่ได้เขียน byte ที่สอง ชิปจึงเข้าสู่สถานะผิดปกติที่ไม่ได้กำหนดไว้ และ packet ที่ดูเหมือนผลลัพธ์สุ่มก็ปรากฏใน Wireshark
- หลังแก้ฟังก์ชันแล้ว packet ทดสอบก็แสดงใน Wireshark ได้ถูกต้อง
คุณค่าของเวลาที่ใช้สร้างเครื่องมือดีบัก
- การส่ง Ethernet packet แรกเองไม่ใช่ความสำเร็จขนาดใหญ่ แต่ในโปรเจกต์นี้เป็นช่วงเวลาที่ถือเป็นความสำเร็จอย่างชัดเจน
- การย้อนดูบั๊กเป็นเรื่องสนุกในโปรเจกต์ส่วนตัว และการใช้เวลากับ การเขียนเครื่องมือและสำรวจพื้นที่การดีบัก แทบจะคุ้มค่าเสมอ
- ในบางสภาพแวดล้อมระดับมืออาชีพ มีแนวโน้มที่จะมองงานที่ไม่ได้สร้าง deliverable โดยตรงว่าเป็นความสูญเปล่า
- เมื่อกระบวนการพัฒนาถูกแบ่งย่อยแบบ JIRA งานที่ไม่ได้เติม checkbox โดยตรงมักถูกประเมินค่าต่ำ
- ในสภาพที่ยังเข้าใจระบบไม่เพียงพอ การเขียนเครื่องมืออาจสำคัญพอ ๆ กับการเขียน test หรือบางครั้งสำคัญกว่า
- การดีบักใกล้เคียงกับการนำวิธีการทางวิทยาศาสตร์ไปใช้
- รวบรวมข้อมูล
- ตั้งการคาดการณ์
- ตรวจสอบการคาดการณ์ด้วยการทดลอง
- ปรับปรุงการคาดการณ์ด้วยข้อมูลใหม่
- หลังจากนี้โปรเจกต์จะย้ายไปสู่ปัญหาในระดับ abstraction ที่สูงขึ้น
- ความเข้าใจผิดเกี่ยวกับ RFC
- การเขียน multitasking code
- และเดินหน้าพร้อมบั๊กใหม่ ๆ
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ความสามารถในการสร้าง เครื่องมือเล็ก ๆ เองได้ถือเป็นพลังพิเศษ และมักเป็นแก่นสำคัญของสิ่งที่เรียกกันว่าโปรแกรมเมอร์ 10x
น่าเสียดายที่ทักษะแบบนี้มักถูกใช้เงียบ ๆ ในที่ที่ไม่ค่อยมีใครมองเห็น
แต่สำหรับคนที่มีความอยากรู้อยากเห็น และคิดถึงผลกระทบลำดับที่สองหรือฟีเจอร์ที่กำลังจะต้องใช้ในไม่ช้า การทำงานกับนักพัฒนาที่ไม่คิดแบบนั้นค่อนข้างเจ็บปวด
คนที่เพิ่งทำงานด้วยกันล่าสุดไม่ใช่คนไม่ดี แต่ตอนนี้ผมกำลังเก็บกวาดโปรเจกต์ที่เขาทำตามตั๋วงานสั่งมา 100% เท่านั้น โปรเจกต์ใหม่ที่ตั้งใจจะมาแทนปัญหาของซอฟต์แวร์เดิม กลับยังเหลือปัญหาเดิมทั้งหมดไว้ด้วยเหตุผลเดิม ๆ
อย่างไรก็ดี คำว่า นักพัฒนา 10x ฟังดูเป็น buzzword อยู่บ้าง เพราะทำให้นึกถึงนักพัฒนาบ้าบิ่นที่ “ทำงานให้เสร็จ” โดยไม่ต้องมีคนกำกับ แต่ทิ้งต้นทุนมหาศาลไว้ ทุกอย่างถูกทำเป็นไซโล พอภายหลังมีคนต้องไปแตะ หรือคนนั้นลาออก ก็พังลงเหมือนบ้านไพ่
ตามอุดมคติ ทุกคนควรมี เวลาสำรวจ แต่ก็ต้องมีผู้จัดการที่คอยปกป้องเวลานี้อย่างเข้มงวด แม้ผู้บริหารระดับสูงจะบอกว่า “ต้องทำให้เสร็จเร็วขึ้น” ก็ตาม เรื่องจะยากขึ้นอีกถ้าผู้จัดการทีมอื่นพูดว่า “ทีมนั้นได้จ้างคนเพิ่มเพื่อชดเชยเวลาที่เสียไปไหม? เราก็ต้องการกำลังคนนั้นเหมือนกัน”
สุดท้ายจึงต้องมีระบบในระดับองค์กร และแม้แต่ Google ก็ยังเลิกใช้ เวลา 20% ไปแล้ว
วิธีแก้แบบ “ไม่ค่อยมีจริยธรรม” คือแอบใส่เวลาประเภทนี้ลงไปเล็กน้อยในการประมาณงานพัฒนา
การทำเครื่องมือของตัวเองมีประโยชน์ในสถานการณ์ที่ใช้ได้ แต่ผมก็มองว่านักพัฒนาที่มีประสิทธิผลคือคนที่เขียนโค้ดให้น้อยที่สุดและใช้สิ่งที่มีอยู่แล้วให้เป็นประโยชน์ ถ้าพูดตามตัวอย่างในบทความนี้ ก็ไม่มีใครจำเป็นต้องสร้าง TCP stack ใหม่เอง เพราะมี implementation ที่ดีอยู่แล้ว
ถึงอย่างนั้น การทำเองอาจเป็นวิธีที่ดีที่สุดในการได้ความเข้าใจเชิงลึก และความเข้าใจเชิงลึกก็เป็นองค์ประกอบหนึ่งของนักพัฒนา 10x ในตำนาน เพียงแต่ไม่ควรคาดหวังว่านายจ้างจะยอมจ่ายเงินให้กับกระบวนการนั้น
โดยปกติถ้าเป็นงานที่เสร็จได้ภายในหนึ่งวัน ผมก็ทำเลย ไม่เคยมีครั้งไหนที่ผิดพลาดหรือกลายเป็นการเสียเปล่า กรณีแย่ที่สุด สุดท้ายก็ได้คัดลอกโค้ดนั้นไปแปะใช้ที่อื่นในภายหลัง
จากนั้นเขาก็เริ่มขอให้ทำให้คนที่ไม่ใช่โปรแกรมเมอร์ใช้งานได้ด้วย แล้วจู่ ๆ เครื่องมือเหล่านั้นก็กลายเป็นงานที่ใหญ่กว่าที่คาดไว้มาก
ชื่อเรื่องค่อนข้างกำกวม แต่บทความนี้เป็นจุดเริ่มต้นของซีรีส์ที่สร้าง TCP/IP และสแตกเฟรมมิงอีเทอร์เน็ต สำหรับไมโครคอนโทรลเลอร์ตั้งแต่ศูนย์
ผู้เขียนใช้ชิป W5100 ซึ่งจัดการ TCP/IP เองได้ แต่ก็รองรับการส่งเฟรมอีเทอร์เน็ตที่เตรียมไว้ล่วงหน้าเข้าไปให้ด้วย อย่างไรก็ดี ชิปจะจัดการ preamble และการคำนวณ CRC เอง
เนื้อหาส่วนใหญ่ของบทความเกี่ยวกับการสื่อสารกับตัวชิปและการส่งแพ็กเก็ตทดสอบ ดูเหมือนจะเป็นแพ็กเก็ตที่ hardcode ไว้ แต่ในบทความไม่ได้ระบุชัดเจน
ส่วนตัวผมคาดหวังว่าจะเป็นเรื่องการ bit banging อีเทอร์เน็ตบนฮาร์ดแวร์สุดพิสดาร
เคล็ดลับอย่างหนึ่งคือดัดแปลงบอร์ด PHY + MagJack ที่พบได้ทั่วไป ให้ RP2040 เป็นตัวสร้างสัญญาณนาฬิกาเอง จากนั้นสัญญาณจะซิงค์กันและไม่จำเป็นต้อง oversample RMII
ถ้าต้องการแค่ 10Mb/s ก็ทำด้วยวิธีที่ยุ่งเหยิงกว่านี้ได้ ผมยังรอเทอร์มินัลกระจกแบบ “สมัยใหม่” ที่รวมเอาเอาต์พุตวิดีโอ DVI/HDMI กับอีเทอร์เน็ตบน RP2040 อยู่ แค่ telnet ก็น่าจะพอ ส่วน SSH คงฝืนเกินไป
ช่วงหลังผมย้ายสายงานไปทาง วิศวกรรม FPGA ที่เน้นอีเทอร์เน็ต ซึ่งค่อนข้างไม่ธรรมดา
เป็นเส้นทางที่สนุก และสุดท้ายก็ไปถึงขั้นออกแบบ Hard MAC IP เองและส่งแพ็กเก็ตผ่าน PHY IP แบบคัสตอม
ขอแนะนำอย่างยิ่งสำหรับคนที่อยากลองความท้าทายนี้ใน “โหมดยาก” โลกเน็ตเวิร์กถูกทำให้เป็นนามธรรมจากผู้ใช้มากเกินไป การได้เข้าใจว่าการ์ดอีเทอร์เน็ต โมเด็ม และสวิตช์ประกอบและแยกส่วนต่าง ๆ ของแพ็กเก็ตอย่างไร รวมถึง PHY/PCS กู้คืนสัญญาณจากลิงก์อย่างไรนั้นมีคุณค่ามากจริง ๆ
เพื่อการดีบักและตรวจสอบความถูกต้อง เราทำให้เวอร์ชันจำลองที่สร้างด้วย Verilator เชื่อมต่อกับ อุปกรณ์ TUN/TAP ของ Linux ได้ และสามารถเชื่อมต่อจากเครื่องของนักพัฒนาได้โดยตรงโดยไม่ต้องยึดฮาร์ดแวร์จริงไว้ใช้งาน สิ่งนี้มีประโยชน์มากเป็นพิเศษเพราะมีฮาร์ดแวร์อยู่เพียงตัวเดียว
ความรู้ด้านเน็ตเวิร์กของผมมีแค่ความเข้าใจพื้นฐานมาก ๆ เกี่ยวกับโมเดล OSI แต่ผมอยากขุดลึกเข้าไปในโลกของเน็ตเวิร์ก
เพิ่งเคยเห็นการตีความ MOSI/MISO แบบใหม่เป็นครั้งแรก คือเรียกว่า main out/subordinate in แทน master out/slave in
แบบนี้ยังเรียกชื่อพินว่า MOSI/MISO ต่อไปได้ ผมก็น่าจะใช้วิธีนี้เหมือนกัน ทางเลือก COPI/CIPO(controller out/peripheral in) จำไม่ค่อยติดหัวเท่าไร
ผมสงสัยว่าทำไมผู้เขียนถึงใช้ W5100 Ethernet shield กับ STM32F401 ถ้าใช้บอร์ด STM32F407 ที่ง่ายพอ ๆ กัน ก็จะมี Ethernet MAC ในตัว และสามารถพัฒนาควบคู่กับบอร์ด Ethernet PHY ราคาถูกได้
มีโปรเจกต์ตัวอย่าง Ethernet อยู่เยอะ และพัฒนาได้ง่ายพอ ๆ กับ STM32F401
อีกอย่าง คำอธิบายที่ว่า “เพราะความซับซ้อนของการประมวลผลสัญญาณ Ethernet จึงมักใช้ ASIC เฉพาะทาง” ผมคิดว่าโดยรวมไม่ค่อยถูกในบริบทของไมโครคอนโทรลเลอร์ ในหลายกรณี ฟังก์ชัน Ethernet ถูกใส่มาเป็น peripheral ในตัวไมโครคอนโทรลเลอร์อยู่แล้ว เช่น STM32F407 หรือ ESP32
แน่นอนว่าการใช้ชิปที่มี peripheral Ethernet ในตัวย่อมสมเหตุสมผลกว่า เพียงแต่ก็เหมือนเป็นการแลกความซับซ้อนของการตั้งค่า W5100 ไปเป็นความซับซ้อนของการตั้งค่า peripheral ของ ST
โค้ด networking ได้ abstract ชิปจริงไว้หลังอินเทอร์เฟซไดรเวอร์อยู่แล้ว (ลักษณะอย่าง read/write/ioctl) ดังนั้นการพอร์ตน่าจะค่อนข้างง่าย
ในซีรีส์นี้จะลองพิจารณา STM32F407 ดู
Ethernet ไม่ได้จัดการกับ packet แต่จัดการกับ frame
packet เป็นแนวคิดของ IP
แต่ RFC 791 เป็นเอกสารจากยุคที่เครือข่าย L2 ข้างใต้น่าจะเป็น ARPAnet ที่ใช้ packet ขนาด 128 ไบต์
ทุกวันนี้ความแตกต่างนี้แทบควรจะไม่มีความหมายแล้ว ถ้าคุณหวังพึ่ง IP fragmentation เพื่อส่ง IP datagram ขนาดใหญ่เป็น L2 packet หลายชิ้น แปลว่ามีบางอย่างผิดแล้ว IPv6 ไม่รองรับแม้แต่ fragmentation ภายในเครือข่าย
ในการทำงานจริง เมื่อเวลาผ่านไป ความแตกต่างนี้แทบหายไปแล้ว เพราะโดยปกติ Ethernet frame หนึ่งเฟรมบรรจุ IP datagram หนึ่งอัน และทุกคนก็เรียกมันว่า packet กันหมด
https://en.wikipedia.org/wiki/Ethernet_frame
ถ้าอยากลองใช้ Ethernet แบบมีสายในไมโครคอนโทรลเลอร์ บอร์ด STM32 Nucleo รุ่นใหญ่บางรุ่นมี Ethernet 100Mbps ในตัว และราคาประมาณ 25 ดอลลาร์ ถือว่าค่อนข้างถูก
ความเห็นต่อซอฟต์แวร์ STM32Cube มีทั้งดีและไม่ดี แต่ก็สร้างตัวอย่างการสื่อสาร Ethernet ที่ใช้งานได้ให้
https://www.st.com/en/evaluation-tools/nucleo-f439zi.html
มี MQTT, HTTP client และ server ฯลฯ มาให้
ครั้งสุดท้ายที่ใช้ Cube-MX ประสบการณ์โดยรวมแย่มาก ถ้าจะกลับไปใช้ STM32 อีก ผมน่าจะเลือก stm32-hal หรือ libopencm3 มากกว่า ทั้งตัวเครื่องมือและโค้ดที่ generate ออกมามีบั๊กแย่ ๆ กับ edge case เต็มไปหมด จนต้องเสียเวลาหลายวันในการ debug ตอนนี้อาจดีขึ้นแล้วก็ได้
https://github.com/egnor/wt32-eth01
https://liliputing.com/waveshare-esp32-p4-nano-is-a-tiny-ris...
เพิ่งเห็นว่ามีโมดูลที่ถูกกว่านี้ด้วย ของอย่าง WT32-ETH01 อาจมีประสิทธิภาพน้อยกว่า
ถ้าอยากลองเขียน network stack เองบน Linux สามารถใช้
socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))เพื่อทำงานในระดับ abstraction ที่ไม่ต่างจากบทความนี้มากนักpacket แบบ
SOCK_RAWจะรับส่งข้อมูล packet กับ device driver โดยไม่แก้ไขข้อมูล เมื่อรับเข้ามา address จะถูก parse แล้วส่งต่อมาในโครงสร้าง address มาตรฐานsockaddr_llตอนส่ง buffer ที่ผู้ใช้ให้มาต้องมี header ของ physical layer อยู่ด้วย และ packet นั้นจะถูกใส่ลงในคิวของ network driver ของ interface ที่ address ปลายทางระบุไว้โดยไม่ถูกเปลี่ยนแปลง
https://man7.org/linux/man-pages/man7/packet.7.html
สิ่งนี้จะเพิ่ม interface เสมือนเข้าไปใน network stack เหมือน interface ของ VPN ส่วน packet socket ทำกลับกัน คือให้สื่อสารโดยตรงกับ interface จริง
ถ้าพูดให้ถูก น่าจะต้องเรียกว่า Ethernet frame
ทำได้ดี ผมเพิ่งใช้เวลางาน 16 ชั่วโมงที่ผ่านมาไปกับงานฝั่งตรงข้าม: implement การ parse Ethernet II (รวม VLAN), IPv4+6, UDP ไปจนถึงโปรโตคอล IP สำหรับยานยนต์
จุดประสงค์คือทำความเข้าใจสตรีม capture ของบัสแบบ proprietary ซึ่งสตรีมนั้นส่ง Ethernet frame ที่ซ้อน Ethernet frame ไว้ข้างในอีกที และผมรับมันผ่าน raw socket
งานแบบนี้ Wireshark กับ ChatGPT มีค่ามากจริง ๆ