ผลการเรียนรู้ (Learning Outcomes)
- อธิบายที่มาและสายสัญญาณของ I2C (SDA = ข้อมูล, SCL = clock) พร้อมความเร็วมาตรฐาน
- อธิบายบทบาท Master กับ Slave และระบบ address 7-bit บนบัสเดียว
- อธิบายว่าทำไมสาย I2C ต้องเป็น open-drain และต้องต่อ pull-up resistor
- อ่าน frame ของ I2C ได้: start condition → address+R/W → ACK → data → stop condition
- แมปขา I2C ของ STM32F767ZI ให้ตรงกับตำแหน่ง Arduino header บนบอร์ด Nucleo
- เขียนลำดับขั้นการอ่านเซนเซอร์ AM2320 ด้วย HAL (Master transmit → delay → Master receive → ตรวจ CRC)
§9.1 I2C คืออะไร และสายสัญญาณ
I2C ย่อมาจาก Inter-Integrated Circuit. คำว่า Inter แปลว่า “ระหว่าง” สื่อว่ามันทำให้ IC (ชิป) หลายตัวคุยกันได้. ที่มาคือบริษัท Philips สร้างโปรโตคอลนี้ขึ้นเพื่อให้ IC ภายในเครื่องเล่นวิดีโอเทปสื่อสารกันเอง.
I2C ใช้สายสัญญาณเพียง 2 เส้น. เส้นแรกคือ SDA (Serial Data) ใช้ส่งข้อมูลทีละบิต. เส้นที่สองคือ SCL (Serial Clock) ใช้ส่งสัญญาณ clock เพื่อกำหนดจังหวะการอ่านแต่ละบิต.
ความเร็วมาตรฐาน (Standard Mode) อยู่ที่ 100 kbit/s. ถ้าเป็น Fast Mode จะขยับขึ้นเป็น 400 kbit/s. บางยี่ห้อยังทำได้เร็วกว่านี้ถึงระดับ MHz แต่ค่ามาตรฐานทั่วไปคือ 100 kbit/s.
เพราะ I2C มี clock แยกต่างหาก (SCL) จึงจัดเป็นการสื่อสารแบบ synchronous คือมีสัญญาณนาฬิกากำกับจังหวะ ต่างจาก UART ที่เป็น asynchronous (ไม่มีสาย clock ร่วม).
| หัวข้อ | รายละเอียด |
|---|---|
| จำนวนสาย | 2 เส้น (SDA + SCL) |
| SDA | Serial Data — ส่งข้อมูลทีละบิต |
| SCL | Serial Clock — จังหวะ clock |
| Standard Mode | 100 kbit/s |
| Fast Mode | 400 kbit/s |
| ชนิด | synchronous (มี clock), แบบบัส (bus) |
วางเมาส์ (หรือโฟกัส) บนอุปกรณ์เพื่อดูจุดที่มันแตะบัส
§9.2 Master / Slave และระบบ address
บน I2C จะมีบทบาทสองแบบ. Master คือฝ่ายที่เริ่มการสื่อสาร (initiate) และควบคุมสัญญาณ clock เป็นคนเปิดบทสนทนา. Slave คือฝ่ายที่คอยตอบเมื่อถูกเรียก. โดยทั่วไป MCU ทำหน้าที่เป็น Master และเซนเซอร์เป็น Slave.
I2C รองรับ multi-master คือมี Master ได้หลายตัวบนบัสเดียว. STM32 ตั้งค่าโมดูล I2C ให้ทำงานได้ทั้ง Master mode และ Slave mode.
เพราะเป็นบัส อุปกรณ์ทุกตัวใช้สายสัญญาณชุดเดียวกัน จึงต้องมีวิธีแยกว่า Master กำลังคุยกับใคร. วิธีคือให้อุปกรณ์แต่ละตัวมี address เหมือนบ้านเลขที่. address มาตรฐานมีขนาด 7-bit บางตัวที่ซับซ้อนกว่าใช้ 10-bit แต่ส่วนใหญ่ที่ซื้อกันใช้ 7-bit.
เมื่อ Master ส่ง address ออกไป อุปกรณ์ทุกตัวบนบัสจะคอยเทียบว่าตรงกับ address ของตัวเองหรือไม่. ถ้าตรง ตัวนั้นจะตอบกลับด้วยสัญญาณ ACK (acknowledge) และเตรียมรับ/ส่งข้อมูลต่อไป ถ้าไม่ตรงก็เพิกเฉย.
ปัญหาที่พบบ่อยคือซื้อเซนเซอร์เบอร์เดียวกันมาสองตัว address จะชนกัน. ผู้ผลิตมักออกแบบให้ปรับ bit ท้ายของ address ได้ (ส่วนใหญ่ 3 bit ท้าย) เพื่อหลบการชน. บน STM32F767ZI มีโมดูล I2C ให้ 4 ตัว ชื่อ I2C1 ถึง I2C4 ความสามารถเท่ากันทุกตัว.
| หัวข้อ | Master | Slave |
|---|---|---|
| ใครเริ่ม | เริ่มการสื่อสาร (initiate) | คอยตอบเมื่อถูกเรียก |
| คุม clock (SCL) | คุม | ตามจังหวะ clock |
| มักเป็นใคร | MCU (STM32) | เซนเซอร์/IC |
| จำนวนบนบัส | ได้หลายตัว (multi-master) | ได้หลายตัว |
| ใช้ address | ส่ง address ไปเรียก | มี address ของตัวเอง |
Master เรียก 0x44 → มีเพียงตัวที่ตรงตอบ ACK ตัวอื่นเพิกเฉย
§9.3 Open-drain และ pull-up resistor
สาย SDA และ SCL ออกแบบให้เป็นแบบ open-drain. หมายความว่าปลายทรานซิสเตอร์ของขานี้เปิดลอยอยู่ ไม่มีวงจรจ่ายไฟให้สายเป็น 1 ด้วยตัวเอง. อุปกรณ์แต่ละตัวทำได้แค่ “ดึงสายลงเป็น 0” เท่านั้น ดึงขึ้นเป็น 1 เองไม่ได้.
ด้วยเหตุนี้จึงต้องต่อ pull-up resistor จากไฟเลี้ยง (VDD) เข้าที่สาย SDA และ SCL. ตัวต้านทานนี้ทำหน้าที่ดึงสายให้เป็น 1 (ระดับ VDD) เมื่อไม่มีใครดึงลง. ค่าที่ใช้บ่อยคือ 4.7 kΩ แต่ปรับลงได้เล็กน้อยถ้าต้องการ.
ต้องเลือกว่าจะ pull-up ขึ้นไฟ 5V หรือ 3.3V ให้ตรงกับระดับ logic ของอุปกรณ์. บอร์ด Nucleo ใช้ไฟ 3.3V ดังนั้นเซนเซอร์หรือโมดูลที่นำมาต่อควรมีสวิตช์เลือกได้ว่าใช้ 3.3V หรือ 5V. ถ้าโมดูลต่อ 5V ไว้ตายตัว logic จะกลายเป็น 5V และใช้กับบอร์ด 3.3V ไม่ได้.
ประโยชน์ของ open-drain คือเมื่อ Master ดึงสายลง (จาก 1 เป็น 0) จะเกิดขอบขาลง อุปกรณ์ทุกตัวบนบัสเห็นพร้อมกัน ใช้เป็นจังหวะ start condition ได้. อุปกรณ์มักมีวงจรตรวจจับขอบขาลงนี้เพื่อปลุกตัวประมวลผลภายในให้เตรียมพร้อม.
I2C ต่างจากอีเทอร์เน็ตแบบ CSMA/CD ตรงที่ไม่มีการตรวจชน (collision detection) ที่ซับซ้อนขนาดนั้น มีเพียงเงื่อนไข start/stop และกติกาเรื่อง ACK ที่เรียบง่ายกว่า.
ไม่มีใครดึง → pull-up ดึงเป็น 1. นี่คือเหตุผลที่ NACK = สายคงอยู่ที่ 1
§9.4 Frame Format ของ I2C
การสื่อสาร I2C เริ่มด้วย start condition. นิยามคือ ขณะที่ SCL เป็น 1 แล้วสาย SDA เปลี่ยนจาก 1 เป็น 0 (ขอบขาลง) ถือเป็น start condition. และปิดท้ายด้วย stop condition คือ ขณะที่ SCL เป็น 1 แล้ว SDA เปลี่ยนจาก 0 เป็น 1.
หลัง start condition ไบต์แรกที่ Master ส่งคือ address ของ Slave (7-bit) บวกกับ 1 bit บอกทิศทางคือ R/W bit (Read = 1, Write = 0) รวมเป็น 8-bit พอดี 1 ไบต์. I2C ส่ง MSB (บิตสูงสุด) ก่อน.
หลังจากนั้น Slave ที่ address ตรงจะดึงสายลงเป็น 0 เพื่อตอบ ACK ในจังหวะ clock ที่ 9. ถ้าไม่มีใครตอบ สายจะคงเป็น 1 (เพราะ pull-up) นับเป็น NACK (not-acknowledge) แปลว่าไม่มีใครรับ. ใน HAL จะได้ค่า HAL error กลับมา.
I2C ไม่ได้กำหนดตายตัวว่าต้องส่งกี่ไบต์ กำหนดแค่ว่าไบต์แรกคือ address ส่วนที่เหลือแล้วแต่ Device กำหนด. ดังนั้นผู้เขียนโปรแกรมต้องรู้ว่าอุปกรณ์แต่ละตัวต้องส่ง/รับกี่ไบต์.
ตัวอย่าง frame ของเซนเซอร์ AM2320: ไบต์ที่ 1 = address + R/W bit, ไบต์ที่ 2 = function code, ไบต์ที่ 3 = payload (ความยาวขึ้นกับ function code) และปิดท้ายด้วย CRC 16-bit เพื่อตรวจความผิดพลาดของข้อมูล.
| ลำดับ | สิ่งที่ส่ง | หมายเหตุ |
|---|---|---|
| 1 | Start condition | SCL=1, SDA ตกจาก 1→0 |
| 2 | Address (7-bit) + R/W bit | รวม 8-bit, ส่ง MSB ก่อน |
| 3 | ACK / NACK | Slave ดึงลง 0 = ACK; คงเป็น 1 = NACK |
| 4 | Data byte(s) | กี่ไบต์ขึ้นกับ Device |
| 5 | Stop condition | SCL=1, SDA ขึ้นจาก 0→1 |
START: SDA ตกขณะ SCL สูง
slave ดึง SDA = 0 ที่ clock 9 → master รู้ว่ามีคนตอบ (ACK)
§9.5 ตัวอย่างจริง: อ่านเซนเซอร์ AM2320 ด้วย I2C
AM2320 เป็น Digital Temperature and Humidity Sensor วัดอุณหภูมิและความชื้นสัมพัทธ์ แล้วส่งออกมาเป็นข้อมูล digital ผ่าน I2C ได้เลย (มาตรฐาน) โดยไม่ต้องใช้ ADC. ใช้ไฟ 3.3V ได้ ความละเอียด 0.1 (ทศนิยม 1 ตำแหน่ง) ทั้งอุณหภูมิและความชื้น.
ค่าที่วัดได้: ความชื้นช่วง 0–99.9% และอุณหภูมิช่วง -40 ถึง 80 °C. ความแม่นยำที่ 25 °C ราว ±3% สำหรับความชื้น และ ±0.5 °C สำหรับอุณหภูมิ. ควรเลือกใช้เซนเซอร์ให้ช่วงการวัดเหมาะกับงานจริง เพราะที่ช่วงสุดขอบ (ความชื้นสูงมากหรือต่ำมาก, อุณหภูมิต่ำหรือสูงมาก) ค่าความคลาดเคลื่อนจะเพิ่มขึ้น.
ข้อมูลภายในเก็บใน register ของเซนเซอร์. อุณหภูมิและความชื้นแต่ละค่ามีขนาด 16-bit แต่ register แต่ละตัวเก็บได้ 8-bit จึงใช้ 2 register ต่อค่า. register address 0 = high byte ความชื้น, address 1 = low byte ความชื้น, address 2 = high byte อุณหภูมิ, address 3 = low byte อุณหภูมิ.
ขั้นตอนอ่าน: ใช้ function code 0x03 (อ่าน multiple register) โดยระบุ address เริ่มต้น = 0 และจำนวน register ที่อ่าน = 4 ตัว (register 0,1,2,3). Host (MCU) ส่ง start → address+Write → function code 0x03 → start register 0x00 → จำนวน 0x04.
ค่า 16-bit ที่ส่งกลับมาเป็นจำนวนเต็มที่คูณ 10 ไว้แล้ว เพื่อเก็บทศนิยม 1 ตำแหน่งโดยไม่ต้องใช้รูปแบบ floating point. ตอนใช้งานต้องหารด้วย 10 เอง. เช่นความชื้นส่งมา 0x01F4 = 500 หารด้วย 10 ได้ 50.0%; อุณหภูมิส่งมา 0x00FA = 250 หารด้วย 10 ได้ 25.0 °C.
สำคัญ: เซนเซอร์จะเข้าสู่ sleep ถ้าไม่ถูกใช้งานเกินราว 5 วินาที. ก่อนอ่านต้อง wake up ก่อน โดยส่ง start + address แล้วรออย่างน้อย 800 ไมโครวินาที จบด้วย stop แล้วค่อยส่ง command อ่านจริง. ถ้าช่วงเวลาอ่านถี่กว่า 5 วินาที ก็ไม่ต้อง wake up ซ้ำ.
เมื่อรับ 8 ไบต์กลับมา (index 0 = function code, 1 = จำนวนไบต์, 2–3 = ความชื้น, 4–5 = อุณหภูมิ, 6–7 = CRC) ต้องนำ 6 ไบต์แรกไปคำนวณ CRC 16-bit แล้วเทียบกับ 2 ไบต์สุดท้าย ถ้าตรงกันแปลว่าข้อมูลถูกต้อง. สุดท้ายรวม high byte กับ low byte กลับเป็น 16-bit แล้วหาร 10 เพื่อได้ค่าจริง.
| Register address | ความหมาย |
|---|---|
| 0x00 | High byte ของความชื้น |
| 0x01 | Low byte ของความชื้น |
| 0x02 | High byte ของอุณหภูมิ |
| 0x03 | Low byte ของอุณหภูมิ |
| Function code | การทำงาน |
|---|---|
| 0x03 | อ่าน multiple register (อุณหภูมิ/ความชื้น) |
| 0x10 | เขียนหลาย register |
// address เซนเซอร์ 7-bit = 0x5C, HAL ต้องการ address ชิดซ้าย + bit R/W
// 0x5C << 1 = 0xB8 (Write)
uint8_t cmd[3] = {0x03, 0x00, 0x04}; // func 0x03, start reg 0x00, อ่าน 4 register
uint8_t data[8]; // buffer รับ 8 ไบต์
// 1) wake up เซนเซอร์: ส่ง address เปล่า แล้วหน่วง > 800us
HAL_I2C_Master_Transmit(&hi2c1, 0xB8, cmd, 1, 10);
HAL_Delay(1); // >800us
// 2) ส่ง command อ่านจริง
HAL_I2C_Master_Transmit(&hi2c1, 0xB8, cmd, 3, 10);
HAL_Delay(1); // รอ sensor ประมวลผล
// 3) รอรับผลลัพธ์ (address Read = 0xB9)
HAL_I2C_Master_Receive(&hi2c1, 0xB9, data, 8, 10);
// 4) ตรวจ CRC ของ data[0..5] เทียบ data[6],data[7]
// 5) รวม byte: humidity = (data[2]<<8 | data[3]) / 10.0f
// temperature = (data[4]<<8 | data[5]) / 10.0fเซ็นเซอร์ส่งค่าเป็นจำนวนเต็ม × 10 แยกเป็น 2 ไบต์ (hi, lo). รวมเป็น 16 บิตแล้วหาร 10. ไบต์ตอบ 6 ตัวแรกเข้า CRC, 2 ตัวท้ายคือค่า CRC.
แบบฝึกหัด
12 ข้อ — แต่ละข้อมีชนิดและระดับความยากกำกับ กดปุ่ม "ดูเฉลย" เพื่อดูคำตอบและคำอธิบาย
- 1.ปรนัยง่าย
I2C ใช้สายสัญญาณกี่เส้น และคือเส้นใด
- 2.ปรนัยง่าย
หน้าที่ของสาย SCL ใน I2C คืออะไร
- 3.ปรนัยง่าย
ความเร็ว Standard Mode ของ I2C คือเท่าใด
- 4.ถูก/ผิดง่าย
I2C รองรับการมี Master ได้หลายตัวบนบัสเดียว (multi-master)
- 5.ปรนัยกลาง
เหตุใดสาย SDA และ SCL ของ I2C จึงต้องต่อ pull-up resistor
- 6.ปรนัยกลาง
start condition ของ I2C เกิดขึ้นเมื่อใด
- 7.ปรนัยกลาง
ไบต์แรกที่ Master ส่งหลัง start condition ประกอบด้วยอะไร
- 8.ถูก/ผิดกลาง
ถ้าไม่มี Slave ตัวใดดึงสายลงตอบ สายจะคงเป็น 1 ซึ่งถือเป็น NACK
- 9.เติมคำกลาง
AM2320 เก็บค่าอุณหภูมิและความชื้นเป็น 16-bit แต่ register ละ 8-bit จึงใช้ ___ register ต่อหนึ่งค่า
- 10.เติมคำยาก
AM2320 ส่งค่าความชื้นกลับมาเป็น 0x01F4 (= 500). ค่าความชื้นจริงเป็นกี่เปอร์เซ็นต์
- 11.วิเคราะห์ยาก
ทำไมก่อนอ่านค่า AM2320 บางครั้งต้องส่ง address เปล่าแล้วหน่วงราว 800 ไมโครวินาทีก่อน แล้วค่อยส่ง command อ่านจริง
- 12.วิเคราะห์ยาก
ในโค้ด HAL ทำไมจึงใส่ address เป็น 0xB8 ทั้งที่ datasheet ระบุ address ของ AM2320 คือ 0x5C