01076051Lecture 03

GPIO — General Purpose Input/Output

บทที่ 3: การใช้งานขาเข้า/ออกดิจิทัล (GPIO)

GPIO คือสะพานพื้นฐานที่เชื่อม MCU กับโลกจริง — ขับ LED, อ่านปุ่มกด, ส่ง/รับสัญญาณดิจิทัลกับ IC อื่น. บทนี้เจาะว่าขาแต่ละขาถูกตั้งโหมดด้วย register อะไร (MODER/OTYPER/OSPEEDR/PUPDR), ความต่าง push-pull vs open-drain, การใช้ pull-up/pull-down, การเขียนบิตแบบ atomic ด้วย BSRR, Schmitt trigger กับ debounce และการเข้าถึง register ผ่านภาษา C ซึ่งเป็นหัวใจของ Lab01/Lab02.

ผลการเรียนรู้ (Learning Outcomes)

  • อธิบาย digital output (ขับ LED/IC) และ digital input (ปุ่ม/สวิตช์) พร้อมปัญหา contact bounce และการ debounce
  • กำหนดโหมดขา GPIO (input/output/alternate function/analog) และระบุ register ที่เกี่ยวข้อง
  • เปรียบเทียบ output push-pull vs open-drain และการใช้ pull-up/pull-down
  • ใช้ BSRR เขียนบิตแบบ atomic โดยไม่ต้อง disable interrupt
  • อธิบาย Schmitt trigger (hysteresis) และความเชื่อมโยงกับ debounce
  • อ่าน memory map (8 บล็อก × 512MB, little-endian) และการเข้าถึง register ของพอร์ตผ่านภาษา C
  • ขับโหลด DC ที่กินกระแสเกินกำลังขาผ่านวงจร interface (ทรานซิสเตอร์สวิตช์ + freewheeling diode)

§3.1 Digital Output และ Digital Input

Digital Output คือการใช้ขา GPIO ส่งค่าออกไป เช่น ขับ LED ให้ติด/ดับ หรือเขียนสัญญาณดิจิทัลไปยัง IC ภายนอก. Digital Input คือการใช้ขา GPIO อ่านค่าเข้ามา เช่น อ่านสถานะปุ่มกด (push button) หรือสวิตช์ หรือรับสัญญาณดิจิทัลจาก IC ภายนอก. GPIO จึงเป็นการเชื่อมต่อสัญญาณดิจิทัลขั้นพื้นฐานระหว่าง MCU กับโลกจริง.

ปัญหาสำคัญของสวิตช์เชิงกลคือ contact bounce. เมื่อหน้าสัมผัสของสวิตช์แตะกัน มันไม่ได้แตะนิ่งทันที แต่จะกระเพื่อมสลับ 0/1 หลายครั้งในช่วงสั้น ๆ ก่อนที่จะนิ่ง ทำให้ MCU นับการกดผิดพลาดหนึ่งครั้งเป็นหลายครั้ง.

การแก้ contact bounce เรียกว่า debounce ทำได้สองแบบ. แบบฮาร์ดแวร์ใช้วงจร RC ร่วมกับ Schmitt trigger เพื่อกรองสัญญาณให้นิ่ง. แบบซอฟต์แวร์ใช้วิธีหน่วงเวลาสั้น ๆ แล้วอ่านค่าซ้ำเพื่อยืนยันสถานะ.

VIZ-17Schmitt Trigger / Debounce
ขอบที่เกิด (transitions): 2
Input ที่ขา (analog, มี noise)
V_HIV_LO
Output ดิจิทัล (ค่าที่เข้า IDR)

สัญญาณจากสวิตช์ที่ bounce (เส้นวน) ถูกแปลงเป็นดิจิทัล. Schmitt trigger มี 2 threshold (hysteresis) จึงกรอง noise ได้ ให้ขอบสะอาด ต่างจากการใช้ threshold เดียวที่สั่นตาม noise.

§3.2 GPIO ของ STM32F767ZI

STM32F767ZI มีพอร์ต GPIO ได้แก่ A, B, C, D, E, F, G, H, I, J, K. แต่ละพอร์ตมี 16 ขา เช่น PA[0]–PA[15] ไปจนถึง PJ[0]–PJ[15] ส่วนพอร์ต K มี PK[0]–PK[7]. ตัวชิปบรรจุในแพ็กเกจแบบ LQFP144.

แต่ละขารับกระแสได้สูงสุด ±25 mA (ค่า absolute max). MCU จึงขับโหลด DC เล็ก ๆ ได้โดยตรง แต่ขับมอเตอร์ที่กินกระแสมากไม่ได้ ต้องมีวงจร interface ที่ดึงกระแสจากแหล่งแรงดันสูงกว่าแทน.

สำหรับโหลด DC เล็ก ๆ นิยมใช้ทรานซิสเตอร์เป็นสวิตช์ เช่น MOSFET เบอร์ ZVN4206A ที่มีแรงดัน VGS threshold สูงสุดราว 3V จึงถูกเปิดได้ด้วย logic 1 ที่ 3.3V ของ MCU. และเมื่อขับมอเตอร์ DC ต้องมี flying diode หรือ freewheeling diode คร่อมมอเตอร์เพื่อกันแรงดันย้อนกลับที่เกิดตอนตัดกระแส.

VIZ-16Output Stage: Push-Pull vs Open-Drain
OTYPER
เขียน ODR
  • P-MOS: ปิด
  • N-MOS: ทำงาน (ดึงลง GND)
  • สถานะขา: GND (0)
VDDGNDP-MOSN-MOSGND (0)pin

เลือกชนิด output และค่า ODR แล้วดูว่า P-MOS / N-MOS ตัวใดทำงาน. push-pull ขับทั้ง 0 และ 1; open-drain ขับได้แค่ 0 ส่วน 1 เป็น Hi-Z (ต้องมี pull-up ภายนอกถึงได้ logic 1).

§3.3 Memory Organization และ Memory Map

ใน STM32 ทั้ง program memory, data memory, register และ I/O port ถูกวางอยู่ใน address space เชิงเส้นเดียวกันขนาด 4 GB. การเรียงไบต์เป็นแบบ little-endian คือไบต์ที่ตำแหน่งต่ำสุดในหนึ่ง word คือ LSB (บิตน้ำหนักน้อยสุด). address space ทั้ง 4 GB ถูกแบ่งเป็น 8 บล็อกหลัก บล็อกละ 512 MB.

ตำแหน่งสำคัญของ F767 มีดังนี้. Block0 เป็นพื้นที่ code/Flash โดย Flash บนบัส AXIM อยู่ที่ 0x0800_0000–0x081F_FFFF. Block1 เป็น SRAM เริ่มที่ 0x2000_0000 (DTCM 128KB ที่ 0x2000_0000, SRAM1 368KB ที่ 0x2002_0000, SRAM2 16KB ที่ 0x2007_C000). Block2 เป็น Peripherals เริ่มที่ 0x4000_0000 (รวมบัส APB1/APB2/AHB1/AHB2). ส่วน Cortex-M7 internal peripherals เช่น NVIC และ SysTick อยู่ที่ 0xE000_0000.

Blockหน้าที่เริ่มที่
Block0Code / Flash0x0000_0000
Block1SRAM0x2000_0000
Block2Peripherals (APB/AHB)0x4000_0000
Block3FMC bank 1-20x6000_0000
Block4QuadSPI / FMC0x8000_0000
Block5FMC0xA000_0000
Block6FMC0xC000_0000
Block7Cortex-M7 internal0xE000_0000
การแบ่ง address space เป็น 8 บล็อก (บล็อกละ 512 MB)
VIZ-18Memory Map — 8 บล็อก × 512 MB
Block 2
0x4000_0000

Peripherals: APB1/APB2/AHB1/AHB2 (GPIO อยู่ที่นี่)

Little-endian: word 0x11223344
44+033+122+211+3
byte +0 = LSB (0x44)

address space 4 GB แบ่งเป็น 8 บล็อก บล็อกละ 512 MB. คลิกบล็อกเพื่อดู base address และหน้าที่. byte เรียงแบบ little-endian (byte ต่ำสุดใน word คือ LSB).

§3.4 GPIO Registers (ต่อพอร์ต)

แต่ละพอร์ตมี config register ขนาด 32 บิต สี่ตัวสำหรับตั้งค่าขา. GPIOx_MODER ใช้เลือกโหมดของขา (input/output/AF/analog). GPIOx_OTYPER ใช้เลือก output type ระหว่าง push-pull กับ open-drain. GPIOx_OSPEEDR ใช้ตั้งความเร็ว slew ของขา. GPIOx_PUPDR ใช้เปิด/ปิด pull-up หรือ pull-down ภายใน.

นอกจาก config register ยังมี data register ขนาด 32 บิต สองตัว คือ GPIOx_IDR (input data, อ่านได้อย่างเดียว) และ GPIOx_ODR (output data, อ่าน/เขียนได้). และมี register เสริมอีกคือ GPIOx_BSRR (ตั้ง/ล้างบิตแบบ atomic), GPIOx_LCKR (ล็อกค่า config), และ GPIOx_AFRL/AFRH (เลือก alternate function ของขากลุ่ม low/high).

แต่ละขาตั้งโหมดได้อิสระทีละขา มีให้เลือก 8 แบบ ได้แก่ Input floating, Input pull-up, Input pull-down, Analog, Output open-drain (เปิด/ปิด pull ได้), Output push-pull (เปิด/ปิด pull ได้), Alternate function push-pull และ Alternate function open-drain.

VIZ-14GPIO Register / Mode Configurator (PBx)
ขา PB
Pull
MODER — bit [1:0]
00
PUPDR — bit [1:0]
00
OTYPER — bit [0]
0
ใช้เมื่อเป็น output

ขาเป็น input: output buffer ปิด, Schmitt trigger เปิด, pull=ไม่มี (floating) — อ่านสถานะขาได้จาก IDR

เลือกโหมดของขา แล้วดูว่าบิตใน MODER / PUPDR / OTYPER เปลี่ยนอย่างไร และขาจะมีพฤติกรรมแบบใด. แต่ละขาใช้ 2 บิตใน MODER/PUPDR และ 1 บิตใน OTYPER.

§3.5 GPIO Operation และโหมดการทำงาน

หลังจาก reset ขา GPIO จะเริ่มที่โหมด Input Floating (MODER=00b, PUPDR=00b) และ alternate function ยังไม่ทำงาน.

ในโหมด Output ค่าที่เขียนลงใน ODR จะออกไปที่ขา. แบบ Push-Pull: เขียน 0 ทำให้ N-MOS ทำงานดึงขาลง GND, เขียน 1 ทำให้ P-MOS ทำงานดึงขาขึ้น VDD. แบบ Open-Drain: เขียน 0 ทำให้ N-MOS ทำงานดึงลง GND เหมือนกัน แต่เขียน 1 จะกลายเป็น Hi-Z เพราะไม่มี P-MOS ดึงขึ้น ต้องมี pull-up ภายนอกถึงจะได้ logic 1.

register IDR จะจับค่าที่ขาทุก ๆ AHB1 clock cycle. ทุกขามี weak pull-up/pull-down ภายในที่เปิด/ปิดได้เมื่อตั้งเป็น input. ในโหมด Input: output buffer ปิด, Schmitt trigger เปิด, pull resistor ทำงานตามที่ตั้งไว้ และค่าถูก sample เข้า IDR ทุก AHB1 clock. ในโหมด Output: output buffer เปิด, ถ้าอ่าน IDR จะได้สถานะจริงที่ขา แต่ถ้าอ่าน ODR จะได้ค่าที่เขียนลงไปล่าสุด.

โหมด Alternate function: output buffer ถูกขับด้วยสัญญาณจาก peripheral เช่น UART หรือ SPI, Schmitt trigger เปิด, pull ปิด. โหมด Analog: output buffer ปิด, Schmitt trigger ปิด (จึงกินพลังงานเป็นศูนย์ไม่ว่าแรงดัน analog จะเป็นเท่าใด), pull ปิด และถ้าอ่าน IDR จะได้ค่า 0. Schmitt trigger คือวงจรที่มี hysteresis ช่วยกรองสัญญาณรบกวนและทำให้ขอบสัญญาณที่ไม่ชัดกลายเป็นขอบที่คม ซึ่งเกี่ยวโยงกับการ debounce.

VIZ-16Output Stage: Push-Pull vs Open-Drain
OTYPER
เขียน ODR
  • P-MOS: ปิด
  • N-MOS: ทำงาน (ดึงลง GND)
  • สถานะขา: GND (0)
VDDGNDP-MOSN-MOSGND (0)pin

เลือกชนิด output และค่า ODR แล้วดูว่า P-MOS / N-MOS ตัวใดทำงาน. push-pull ขับทั้ง 0 และ 1; open-drain ขับได้แค่ 0 ส่วน 1 เป็น Hi-Z (ต้องมี pull-up ภายนอกถึงได้ logic 1).

§3.6 Atomic Bit Set/Reset ด้วย BSRR

การแก้ ODR ทีละบิตด้วยวิธี read-modify-write (เช่น ODR |= mask) มีความเสี่ยง เพราะระหว่างที่อ่านค่าออกมากับตอนเขียนกลับ อาจมี interrupt เข้ามาคั่นกลาง ทำให้ set/reset ไม่ครบ หรือไปทับค่าที่ ISR เพิ่งเปลี่ยนไป.

BSRR แก้ปัญหานี้ด้วยการเขียนแบบ atomic. เขียน '1' ลงบิตครึ่งล่าง (bit 0–15) เพื่อ set ขานั้นให้เป็น 1 หรือเขียน '1' ลงบิตครึ่งบน (bit 16–31) เพื่อ reset ขานั้นให้เป็น 0 (จะใช้ register BRR สำหรับ reset อย่างเดียวก็ได้). บิตที่เราเขียน '0' จะไม่ถูกแตะ. ดังนั้นจึงไม่ต้อง disable interrupt และยังแก้ได้หลายบิตพร้อมกันใน single AHB1 write ครั้งเดียว.

VIZ-15BSRR Atomic Set / Reset
ODR (16 บิต) = 0x00A5 — คลิกเพื่อเลือกบิต (bit 15 … 0)
เลือก 2 บิต (mask = 0x000A)

เลือกบิตที่ต้องการ แล้วกด Set หรือ Reset. BSRR เขียนเฉพาะบิตที่เลือกแบบ atomic — บิตอื่นของ ODR ไม่เปลี่ยน และไม่ต้อง disable interrupt.

§3.7 การเข้าถึง register ของ GPIO ในภาษา C

register ของ GPIO เป็นแบบ memory-mapped I/O คือถูกแมปไว้ใน address space เดียวกับหน่วยความจำ จึงเข้าถึงได้ผ่าน pointer ที่ชี้ไปยัง address ของ register block นั้น.

ชื่อ GPIOA หรือ GPIOB ที่ใช้ใน Lab จริง ๆ แล้วคือ pointer ที่ชี้ไปยัง struct GPIO_TypeDef ที่ base address ของพอร์ตนั้น เราจึงเขียน GPIOA->MODER หรือ GPIOA->IDR เพื่อเข้าถึง register แต่ละตัวได้โดยตรง.

คำนวณ base address และเข้าถึง register ของ GPIOAc
#define PERIPH_BASE        ((uint32_t)0x40000000U)
#define AHB1PERIPH_BASE    (PERIPH_BASE + 0x00020000U)
#define GPIOA_BASE         (AHB1PERIPH_BASE + 0x0000U)
#define GPIOA              ((GPIO_TypeDef *) GPIOA_BASE)  // pointer ไป register block ของพอร์ต A

// ตัวอย่าง: ตั้ง PA0 เป็น input แล้วอ่านค่า
GPIOA->MODER &= ~(0x00000003);   // PA0 = input (เคลียร์ 2 บิตล่างของ MODER)
uint16_t PAval = GPIOA->IDR;     // อ่าน 16 บิตจาก IDR ของ GPIOA
VIZ-22GPIO_TypeDef — Field → Address
พอร์ตbase = 0x4002_0400
GPIOB->ODR

output data

base = 0x4002_0400
offset = 0x14
address = 0x4002_0414

struct GPIO_TypeDef วางทับ register block จริง. เลือกพอร์ตและ field แล้วดู address = base + offset. เช่น GPIOB->ODR = GPIOB_BASE + 0x14.

แบบฝึกหัด

12 ข้อ — แต่ละข้อมีชนิดและระดับความยากกำกับ กดปุ่ม "ดูเฉลย" เพื่อดูคำตอบและคำอธิบาย

  1. 1.ปรนัยง่าย

    register ใดใช้เลือกโหมดของขา GPIO (input/output/AF/analog)

  2. 2.ปรนัยง่าย

    register ใดอ่านสถานะที่ขา (input data) และอ่านได้อย่างเดียว

  3. 3.ปรนัยกลาง

    ในโหมด output push-pull เมื่อเขียน 1 ขาจะทำงานอย่างไร

  4. 4.ปรนัยกลาง

    ในโหมด open-drain เมื่อเขียน 1 ขาจะเป็นอย่างไร

  5. 5.ถูก/ผิดง่าย

    BSRR ให้เขียนบิตแบบ atomic โดยไม่ต้อง disable interrupt

  6. 6.ถูก/ผิดกลาง

    หลัง reset ขา GPIO จะเริ่มต้นเป็น Output Push-Pull ทันที

  7. 7.เติมคำกลาง

    BSRR เขียน 1 ที่บิตครึ่งล่าง (0–15) เพื่อ ____ ขา และครึ่งบน (16–31) เพื่อ ____ ขา

  8. 8.เติมคำง่าย

    address space 4 GB แบ่งเป็น ____ บล็อก บล็อกละ ____ MB และ byte เรียงแบบ ____

  9. 9.ปรนัยง่าย

    Schmitt trigger มีประโยชน์อย่างไร

  10. 10.ปรนัยกลาง

    แต่ละขา GPIO ของ STM32F767ZI รับกระแสสูงสุดประมาณเท่าใด (absolute max)

  11. 11.วิเคราะห์กลาง

    ทำไม HAL_GPIO_WritePin() ใน Lab01/02 จึงปลอดภัยแม้ถูก interrupt คั่นกลาง

  12. 12.วิเคราะห์ยาก

    Lab02 ต่อสวิตช์ภายนอกแล้วขาลอย (floating) อ่านค่าไม่แน่นอน ควรแก้อย่างไรและเกี่ยวกับ register ใด