Serial Buses (SPI, I2C, UART): เส้นทางสื่อสารระดับฮาร์ดแวร์ หัวใจของการเชื่อมต่อในโลก Embedded C
บทนำ: เส้นเลือดใหญ่แห่งการเชื่อมต่อสถาปัตยกรรมสมองกล
สวัสดีครับน้องๆ วิศวกรและเพื่อนนักพัฒนาชาว www.123microcontroller.com ทุกคน! กลับมาเข้าประจำสถานีรบกับวิศวกรขอบตาดำๆ กันอีกครั้ง วันนี้เราจะมาคุยขยี้กันถึงเรื่องที่เป็นเสมือน “ระบบเส้นเลือดใหญ่” ของอุตสาหกรรมการเขียนโปรแกรมระดับฮาร์ดแวร์ (Hardware Interaction) ระบบขนส่งข้อมูลที่ชี้เป็นชี้ตายความเร็วของบอร์ด นั่นก็คือเรื่องของ Buses (บัส) ครับ
ในโลกอันโดดเดี่ยวของระบบสมองกลฝังตัว (Embedded Systems) แกนไมโครคอนโทรลเลอร์ของเราไม่สามารถทำตัวหยิ่งผยองทำงานอย่างโดดเดี่ยวได้ มันต้องคอยอ้าปากรับส่งข้อมูลจากเซ็นเซอร์ดมกลิ่น บันทึกข้อมูลประวัติลึกลงหน่วยความจำ หรือผลักข้อมูลภาพกระแทกไปแสดงผลที่หน้าจอ การที่ชิปซิลิคอนต่างชาติต่างภาษาเหล่านี้จะหันหน้าคุยกันรู้เรื่องได้นั้น ต้องอาศัย “โปรโตคอลการสื่อสาร” ที่มีล่ามแปลภาษามาตรฐาน ซึ่งแหล่งข้อมูลระดับตำนานวิศวกรได้จับกลุ่มจัดอันดับบัสยอดฮิตตลอดกาลไว้ 3 ทหารเสือ ได้แก่ UART, SPI และ I2C ครับ วันนี้เราจะมาเจาะลึกชำแหละกันว่า บัสอุตสาหกรรมแต่ละตัวมีจุดเด่นจุดดับอย่างไร และเราในฐานะโปรแกรมเมอร์ผู้ออกคำสั่ง C จะมีศิลปะในการโต้ตอบกับมันระดับ Bare-metal ได้อย่างไรให้เสถียรและทรงประสิทธิภาพทะลุพิกัดสูงสุด!
ทะลวงโครงสร้าง: ทำความรู้จัก 3 ทหารเสือบัสสื่อสารแห่งสหัสวรรษ
การป้อนสายสื่อสารระหว่างฮาร์ดแวร์ในปัจจุบันมักจะถูกบีบให้ใช้รูปแบบการส่งข้อมูลแบบ “อนุกรม (Serial)” หรือการเข้าแถวเรียงหนึ่งส่งข้อมูลทีละบิต เหตุผลหลักคือเพื่อประหยัดจำนวนสายไฟทองแดงและหวงแหนพื้นที่อันมีค่าบนแผ่นวงจร PCB, แม้ว่าคนซาดิสต์บางคนจะสามารถเขียนซอฟต์แวร์จำลองพลิกตรรกะสัญญาณนาฬิกาและข้อมูลขึ้นมาใช้เองได้ดื้อๆ (เทคนิคนี้ในวงการนักแฮกเกอร์เรียกว่า Bit-banging) แต่มันก็ทำตัวเป็นปรสิตกินทรัพยากร CPU มหาศาลและจัดการจูนรอบเวลาได้ยากมากจนแทบจะร้องไห้, แหล่งข้อมูลระดับซีเนียร์จึงสั่งการแกมบังคับให้เราหนีมาใช้งานผ่านโมดูล “Hardware Peripherals” ที่โรงงานฝังมาให้ในตัวแพ็กเกจไมโครคอนโทรลเลอร์โดยตรง, ซึ่งแบ่งก๊กออกเป็น 3 ชนิดหลักๆ ที่ครองค่ายอยู่ดังนี้ครับ:
- 1. UART (Universal Asynchronous Receiver/Transmitter): การสื่อสารแบบอาร์ตตัวแม่ ไร้จังหวะควบคุม
- หลักการทำงาน: UART เป็นกบฏการสื่อสารแบบอะซิงโครนัส (Asynchronous) คือคำนยามของ “ฉันไม่มีสายสัญญาณนาฬิกา (Clock) คุมจังหวะ” อุปกรณ์ทั้งสองฝั่ง (ตัวส่งและตัวรับ) จะต้องทำสัญญาใจตกลงความเร็วในการรับส่ง (Baud rate) ให้เร็วก้าวให้ตรงกันเป๊ะๆ ไว้ล่วงหน้า,
- สายสัญญาณ: ใช้เส้นด้ายสายไฟหลักๆ เพียงแค่ 2 เส้น คือพอร์ตส่ง TX (Transmit) และพอร์ตรับ RX (Receive) โดยกฎเหล็กคือต้องต่อไขว้กัน คือเอาปาก TX ของฝั่งหนึ่งไปแหย่เข้าหู RX ของอีกฝั่ง,
- จุดเด่น: สันดานการทำงานเป็นแบบ Full-duplex (ตีคู่รับและส่งข้อมูลสวนทางชนกันได้พร้อมกันแบบถนน 2 เลน) นิยมนำหน้าถูกดึงไปใช้บ่อยสุดในการสื่อสารพ่น Text ดีบักระหว่างบอร์ดกับคอมพิวเตอร์ (PC) โมดูล GPS หรือตัวเชื่อม Bluetooth สุดคลาสสิก
- 2. SPI (Serial Peripheral Interface): เจ้าความเร็วแสงทะลวงนรกแห่งแผ่นบอร์ด
- หลักการทำงาน: SPI เป็นโปรโตคอลลูกผู้ชายแบบซิงโครนัส (Synchronous) สถาปัตยกรรมอำนาจนิยมแบบ Master-Slave โดยบอร์ด Master จะตั้งตัวเป็นนายทาสผู้ยึดสิทธิ์สร้างเต้นพัลส์สัญญาณนาฬิกาเพื่อควบคุมเคาะจังหวะการรับส่งข้อมูลทั้งหมดแบบเบ็ดเสร็จ,,
- สายสัญญาณ: เลนถนนกินที่ใช้สายไฟ 4 เส้น ได้แก่ ขา MOSI (ส่งข้อมูลกระแทกจาก Master), ขา MISO (ทะลักรับข้อมูลเข้าคืน Master), ขา SCK (สายเต้นนาฬิกา), และขา CS/SS (Chip Select สายนิ้วชี้ระบุตัวรับ),
- จุดเด่น: เป็นขุนพลทะลวงฟันแบบ Full-duplex เช่นกัน ข้อมูลจะถูกเลื่อนมุดเข้าและเลื่อนผลักออกพร้อมกันในทุกๆ ไซเคิลชีพจรนาฬิกาเดาะ (Shift register) วงจรกลไกนี้ทำให้มัน “สามารถปั่นทำความเร็วได้ระดับปีศาจสูงสุด” (วิ่งชนหลักหลายสิบ MHz ได้สบายๆ) เหมาะอย่างยิ่งกับงานกระหายข้อมูลที่ต้องการแบนด์วิดท์สูบฉีดสูงๆ เช่น การสาดพิกเซลลงจอ LCD, การอ่านเขียน SD Card รัวๆ หรือดึงค่าเซ็นเซอร์ความเร็วทะลุเพดาน,, แต่ข้อเสียจุดตายคือยิ่งคุณมีเสาอุปกรณ์งอกเยอะ ก็ยิ่งต้องใช้พอร์ตเปลืองขา CS ชี้เป้าบานเบอะเต็มบอร์ด
- 3. I2C (Inter-Integrated Circuit): จิ๋วแจ๋วประหยัดสาย เครือข่ายปัญญาประดิษฐ์
- หลักการทำงาน: I2C เป็นโปรโตคอลซิงโครนัสทรงปัญญาที่อนุญาตให้มีผู้นำ Master หลายตัวฟาดฟันกันได้ (Multi-master architecture),
- สายสัญญาณ: เขียมสายไฟใช้ทำมาหากินแค่ 2 เส้น คือเส้นเลือด SDA (สายวิ่งข้อมูล) และ SCL (สายชีพจรนาฬิกา) โดยมีข้อบังคับคือต้องดึงขึงตัวต้านทาน Pull-up ไฟเลี้ยงไว้ตลอดเส้นทางด้วย,,
- จุดเด่น: ธรรมชาติทำงานแบบ Half-duplex (มีมารยาทผลัดกันรับส่งทีละฝั่ง ถนนเลนเดียว) และใช้ระบบโครงข่าย “การแจกที่อยู่ (Addressing)” (ไม่ว่าจะเป็น 7-bit หรือ 10-bit) ด้วยลูกเล่นนี้ทำให้สามารถเกาะชิปอุปกรณ์ลูกข่ายขนานกันเป็นสิบๆ ตัวบนสายไฟแค่ 2 เส้นบางๆ นี้ได้อย่างน่าอัศจรรย์ ไม่ต้องมานั่งโยงเปลืองขา CS ให้รุงรังแบบเผ่า SPI, นอกจากนี้ยังมีระบบฉลาดตรวจสอบว่าข้อมูลถูกส่งถึงมือผู้รับหรือไม่ด้วยการเคาะบิตตอบรับ (ACK/NACK) เหมาะเจาะสุดหล่อลงประทับสำหรับเซ็นเซอร์ความเร็วต่ำขนาดเล็ก หรือแผ่นบันทึกข้อความ EEPROM

ตัวอย่างโค้ด: งัด Bare-Metal ทะลวงเขียน Register ดื้อๆ ปลุกชีพชิป SPI
มาเพ่งดูตัวอย่างการจรดปลายคีย์บอร์ดเขียนโค้ดภาษา C (รัดกุมแบบ Clean Code) เพื่อสับสวิตช์สั่งให้ฮาร์ดแวร์ SPI กระแทกข้อมูลออกอากาศ (Transmit) ไป 1 ไบต์ ถลำลึกในระดับจุดระเบิด Register (Bare-metal ดิบๆ) กันครับ โค้ดนี้จำลองจะประยุกต์ใช้เทคนิคเฝ้ามอง Polling ในการอ่านตรวจสอบเจาะสถานะของฮาร์ดแวร์ชิปก่อนที่จะกล้าส่งข้อมูล,
#include <stdint.h>
/* นิยามเสก Pointer ชี้พิกัดเป้าหมายไปยังกลุ่ม Register ของฮาร์ดแวร์ SPI (ตัวอย่างอ้างอิงจากชิป STM32) */
typedef struct {
volatile uint32_t CR1; /* Control Register 1 ห้องปุ่มควบคุมหลัก */
volatile uint32_t CR2; /* Control Register 2 ห้องปุ่มควบคุมเสริม */
volatile uint32_t SR; /* Status Register กระจกส่องดูสเตตัส */
volatile uint32_t DR; /* Data Register บ่อพักโยนข้อมูลเข้าออก */
} SPI_TypeDef;
#define SPI1 ((SPI_TypeDef *) 0x40013000)
/* ตีพิมพ์นิยาม Bitmask สำหรับใช้งัดตรวจสอบสถานะกระดาน (Status Register) */
#define SPI_SR_TXE (1U << 1) /* เคาะบิต Transmit buffer empty (ห้องส่งว่างแล้วโว้ย) */
#define SPI_SR_BSY (1U << 7) /* เคาะบิต Busy flag (ไฟแดง กำลังยุ่งอย่าเพิ่งกวน) */
/**
* @brief ฟังก์ชันปืนใหญ่สำหรับยิงส่งข้อมูล 1 ไบต์ผ่านช่องท่อ SPI ฮาร์ดแวร์
* @param data ลูกปืนข้อมูลขนาด 8-bit ที่ต้องการลั่นไก
*/
void spi_transmit_byte(uint8_t data) {
/* 1. สั่งยืนแช่คุกเข่ารอจนกว่าฮาร์ดแวร์จะยกธงแจ้งว่า "ลาน Buffer ส่งข้อมูลว่างโล่งแล้ว" (TXE = 1)
* ข้อบังคับป้องกันการหน้ามืดเขียนข้อมูลลูกใหม่ทับลูกเก่าที่ยังวิ่งส่งออกไปไม่เสร็จ
*/
while (!(SPI1->SR & SPI_SR_TXE)) {
// ยืนคอยหายใจรดต้นคอ (Polling หน่วงเวลา CPU)
}
/* 2. เมื่อทางสะดวก Buffer ว่าง ให้จับข้อมูลยัดเขียนลงหีบ Data Register (DR) ทันที
* วินาทีต่อมากลไกฮาร์ดแวร์จะดูดดึงข้อมูลนี้ไปย่อยแปลงเป็นสัญญาณไฟฟ้ารัวส่งออกสาย MOSI ด้วยตัวเอง
*/
SPI1->DR = data;
/* 3. สั่งคุกเข่ารออีกครั้ง จนกว่าฮาร์ดแวร์จะรีดพิษประมวลผลการส่งข้อมูลออกไปทางสายสัญญาณครบถ้วนทุกบิต
* (สัญญาณ Busy flag ต้องหลุดดับกลับเป็น 0 เพื่อคอนเฟิร์ม)
*/
while (SPI1->SR & SPI_SR_BSY) {
// แช่แข็งรอจนกว่าตัวฮาร์ดแวร์หลุดพ้นจากสถานะติดพัน Busy
}
}
สกัดดาบหลุมพราง: ป้องกันบอร์ดระเบิดจากการสื่อสารล้มเหลว
ในการออกแรงควบคุมบัสข่ายสื่อสารให้สตรองเสถียรตามตำราวิถี Secure Coding สายบรรทัดฐานซอฟต์แวร์โลก หนังสือและกูรูผู้เชี่ยวชาญระดับหัวกะทิได้เอ่ยถ้อยคำแนะนำเทคนิคสกัดบั๊กที่ต้องระวังสั่นเทาไว้รุนแรงดังนี้ครับ:
- อย่าตายน้ำตื้นตกม้าตายเรื่องสายไขว้ TX/RX: โคตรบั๊กยอดฮิตสลัดไม่หลุดอันดับ 1 ตลอดกาลของการโยงสาย UART คือการหลับตาต่อสายผิด! จงจารึกฝังหัวจำไว้เสมอว่าปากส่ง TX ของฝั่งไมโครคอนโทรลเลอร์เรา ต้องยื่นโยงไปต่อเสียบกับหูรับ RX ของอุปกรณ์ฝั่งปลายทางเท่านั้น (ครอสสลับสายกัน) หากคุณสติหลุดเอา TX ชนเข้าทิ่มหน้า TX ด้วยกัน ระบบจะมองหน้ากันงงๆ และสื่อสารกันไม่ได้เลยแม้แต่หยดเดียว,
- หลบหลีกการเบิร์น CPU ด้วย Polling จอมหน่วงในงานชุดข้อมูลยักษ์: ในชิ้นโค้ดตัวอย่างมินิมัลด้านบนเราจงใจใช้ลูปนรก
whileยืนคอยรอให้ฮาร์ดแวร์ดันข้อมูลส่งให้เสร็จ (สถาปัตยกรรมแบบ Polling) ซึ่งตัวกระทำนี้รีดทำให้แกน CPU สูญเสียจังหวะเวลาทองที่จะแว่บไปประมวลผลงานอย่างอื่นไปเสียเปล่าๆ หากระบบงานของคุณจำเป็นต้องส่งทะลักข้อมูลผ่านก๊อก SPI หรือ UART เป็นจำนวนมหาศาล แนะนำเด็ดขาดให้หนีไปกอดอัญเชิญใช้กลไก Interrupts ฉุกเฉิน หรือพระเอกขี่ม้าขาว DMA (Direct Memory Access) ซึ่งสองตัวนี้จะสั่งให้ฮาร์ดแวร์ก้มหน้าจัดการรับส่งข้อมูลวิ่งเข้าออกหลังฉากเบื้องหลังสมรภูมิให้เองเกลี้ยง และจะเป่าแตรปลุกเรียก CPU ให้ตื่นขึ้นมาดูผลงานเมื่อรบรับส่งข้อมุลเสร็จสิ้นแล้วเท่านั้นฮะ,, - ระวังภัยเงียบชนประสานงา Race Conditions บนรอยต่อ I2C: สาเหตุเพราะเผ่า I2C ดันหวงของใช้งานสายข้อมูล (SDA) เป็นถนนเลนเดียวร่วมกันแบบทั้งรอรับและวิ่งส่ง (สลับ Half-duplex) โครงสร้างโค้ดเมนที่ทำหน้าที่เสกเขียนและอ่านควรจะมีการออกแบบผังตรรกะจัดการ State Machine คุมคิวที่โคตรดี และต้องตื่นตัวระวังการถูกแทรกคิวขัดจังหวะจากกบฏ Interrupts ตัวอื่นๆ (ช่วงหัวเลี้ยวหัวต่อ Critical Section) ที่อาจจะลงค้อนระฆังส่งผลทุบทำให้ลำดับการคุยสื่อสาร (เช่น สเต็ปส่ง Address -> ตามต่อด้วย Data -> ถอนสมอส่ง Stop) ถูกแทรกทำลายลอจิกผิดเพี้ยนไปจนพังทั้งยวง,
- อย่ารั้นฝืนโชว์สกิลเขียน Bit-banging ขย่มขาพอร์ต ถ้าคอไม่แข็งพอ: หากคุณถูกจ้างต้องโต้ตอบคุยทำสัญญาตกลงกับอุปกรณ์ I2C หรือลากแบนด์วิดท์ SPI จงหันหน้าเรียกใช้โมดูล Peripheral Module (ฮาร์ดแวร์สร้างสเต็ปฝังชิป) ของไมโครคอนโทรลเลอร์เสมอ การพยายามฝืนสังขารเขียนลอจิกโค้ด C โยกดิบดึงขาพอร์ต I/O จ่ายไฟขึ้นลงเองทื่อๆ (Bit-banging) นอกจากจะฉุดลากประสิทธิภาพทำให้โค้ดทำงานเต่าคลานช้าลงสุดอืดแล้ว มันยังสร้างความเสี่ยงสูงปรี๊ดที่คุณจะเผลอละเมิดข้อกำหนดทางวิศวกรรมเวลา (Timing requirements / Clock stretching) ของโปรโตคอลมาตรฐานโลกได้ร่วงหล่นง่ายมากๆ,
สรุป (Conclusion)
ย้อนหลับตาภาพรวมในบริบทของการเจาะทะลวงทำ Hardware Interaction แล้ว สายบัสสื่อสารเหล่านี้ก็เปรียบเสมือน “ภาษาพูดสากล” สายใยสื่อกระแสจิตที่ไมโครคอนโทรลเลอร์ทรงพลังของเราเลือกใช้เพื่อติดต่อเจรจาขยายอาณาเขตคุยกับแผงอุปกรณ์โลกภายนอกครับ การใช้ทักษะประเมินเลือกนัดใช้ UART เพื่อตีเน้นความง่ายราบรื่นในการเชื่อมต่อส่องหน้าต่างดีบัก, เปลี่ยนโหมดดึงดาบ SPI ออกมาฟาดฟันเมื่อโปรเจกต์กระหายต้องการสูบคลื่นความเร็วข้อมูลระดับสูงปรี๊ดทะลุกรอบ, หรือถอยฉากมาเลือก I2C ฉลาดๆ เมื่อไฟลต์บังคับต้องการร้อยพวงต่ออุปกรณ์เซ็นเซอร์สิบแปดมงกุฎหลายๆ ตัวขนาบกันโดยเน้นลูกเล่นประหยัดพื้นที่พอร์ตสายไฟ คือจุดสุดยอดแห่งวิจิตรศิลปะชั้นเชิงที่วิศวกรสายสมองกลฝังตัวระดับอ๋องต้องตกผลึกเลือกหยิบใช้งานให้แม่นยำฟิตพอดีกับโปรเจกต์ตรงหน้าครับ
หวังเป็นอย่างยิ่งเดือดๆ ว่าบทความผ่าตัดเส้นเลือดชิ้นนี้จะช่วยเบิกเนตรให้น้องๆ อาชีวะนักพัฒนาเห็นทิศทางภาพรวมของจักรวาลการสื่อสารข้อมูลระดับพอร์ตบัสได้ถนัดตาชัดเจนเคลียร์ขึ้นนะครับ ใครมีบาดแผลประสบการณ์ชีวิตต่อวงจรสาย I2C วนไปมาแล้วลืมลนไฟบัดกรีใส่ตัวต้านทาน Pull-up กั้นกระแสจนบอร์ดนิ่งค้างวิญญาณหลุดไปโลกหน้ามาแล้วบ้าง (พี่ขอบตาดำคนนี้เองแหละเคยมาแล้วร้องไห้หนักมาก! 😅) อย่าลืมแวะเข้ามาล้อมวงแชร์ประสบการณ์หลอนๆ และรัวแป้นพิมพ์พูดคุยขิงโค้ดกันต่อได้ที่ฐานทัพเว็บบอร์ด www.123microcontroller.com ของแก๊งพวกเราได้เลยนะครับ! แล้วเจอกันใหม่ซิ่งข้อมูลบัสในบทความเจาะชิปหน้า Happy Embedded Coding รันบัสอย่างปลอดภัยครับซามูไรทุกคน!