STM32 GPIO เบื้องต้น: ไฟกระพริบและปุ่มกด (Hello World ฉบับฮาร์ดแวร์)
บทนำ: Hello World แห่งโลกฮาร์ดแวร์
ยินดีต้อนรับสู่บทเรียนแรกของการก้าวสู่โลก Embedded Systems ครับ! ในฐานะ Instructor ของ 123Microcontroller ผมขอบอกเลยว่า “การทำไฟกระพริบ (Blinky)” สำหรับคนทำฮาร์ดแวร์นั้น ศักดิ์สิทธิ์เทียบเท่ากับ “Hello World” ของฝั่งซอฟต์แวร์เลยทีเดียว
มันไม่ใช่แค่เรื่องไฟติด-ดับ แต่มันคือการยืนยัน 3 สิ่งสำคัญ:
- Toolchain ของเราติดตั้งสมบูรณ์
- เราคุยกับชิป (Flash Program) รู้เรื่อง
- หัวใจของชิป (Clock) กำลังเต้นอยู่
มาเริ่มกันเลยครับ!
ระดับความยาก: ⭐ เริ่มต้น เวลาที่ใช้: ~20-30 นาที
1. อุปกรณ์และการเชื่อมต่อ (Hardware Mapping)
GPIO (General Purpose Input/Output) เปรียบเสมือน “แขนขา” ของไมโครคอนโทรลเลอร์ครับ บนบอร์ด STM32F4 Discovery นี้ มีอุปกรณ์มาให้เราเล่นครบโดยไม่ต้องต่อวงจรเพิ่ม:

- LED 4 สี (Output):
- 🟢 Green: ต่อขา PD12
- 🟠 Orange: ต่อขา PD13
- 🔴 Red: ต่อขา PD14
- 🔵 Blue: ต่อขา PD15
- ปุ่มกด (Input):
- 🔵 User Button: ต่อขา PA0 (กด = 1/High, ปล่อย = 0/Low)
2. การตั้งค่าใน STM32CubeMX
เปิดโปรเจกต์ .ioc ใน STM32CubeIDE แล้วทำตามนี้ครับ:
- เปิดใช้ขา LED (Output):
- ไปที่หมุดขา PD12, PD13, PD14, PD15 บนรูปชิป
- คลิกซ้ายแล้วเลือก GPIO_Output
- Tip: คลิกขวาที่ขา เลือก Enter User Label ตั้งชื่อเป็น
LED_Green,LED_Orangeฯลฯ เวลาเขียนโค้ดจะเรียกใช่ง่ายขึ้นมากครับ
- เปิดใช้ขาปุ่มกด (Input):
- ไปที่ขา PA0 เลือก GPIO_Input
- เจาะลึกการตั้งค่า (System Core > GPIO):
- สำหรับ LED: ตั้ง
GPIO Output Levelเป็น Low (ให้ดับก่อนตอนเริ่ม) และGPIO Modeเป็น Output Push Pull - สำหรับปุ่มกด (PA0): ตั้ง
GPIO Pull-up/Pull-downเป็น No pull-up and no pull-down (เพราะบนบอร์ดมี R Pull-down ภายนอกให้อยู่แล้ว)
- สำหรับ LED: ตั้ง
- Generate Code: กด Save (
Ctrl+S) เพื่อสร้างโค้ดเริ่มต้น
3. เจาะลึกโค้ด (Code Walkthrough)
เปิดไฟล์ main.c แล้วเลื่อนลงมาที่ while(1) ครับ เราจะเขียนโค้ดในพื้นที่ USER CODE BEGIN 3 เท่านั้น (เพื่อป้องกันโค้ดหายเมื่อ Gen ใหม่)
โจทย์ที่ 1: ไฟกระพริบ (Blinky)
เราจะใช้ฟังก์ชัน HAL_GPIO_TogglePin เพื่อสลับสถานะ และ HAL_Delay เพื่อหน่วงเวลา
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// สลับสถานะไฟเขียว (PD12) ทุกๆ 500ms
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
// หน่วงเวลา 500 มิลลิวินาที (blocking delay)
HAL_Delay(500);
}
/* USER CODE END 3 */
โจทย์ที่ 2: กดปุ่มไฟติด ปล่อยปุ่มไฟดับ
เราจะใช้ HAL_GPIO_ReadPin เพื่ออ่านค่าปุ่ม และ HAL_GPIO_WritePin เพื่อสั่งไฟ
/* USER CODE BEGIN 3 */
// อ่านค่าจากปุ่ม PA0 (User Button)
// ถ้ากดปุ่ม (สถานะเป็น SET หรือ 1)
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET)
{
// สั่งให้ไฟสีส้ม (PD13) ติด (Write Pin SET)
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);
}
else
{
// ถ้าปล่อยปุ่ม ให้ไฟดับ (Write Pin RESET)
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_RESET);
}
HAL_Delay(10); // หน่วงเวลาเล็กน้อยเพื่อลดสัญญาณรบกวน (Simple Debounce)
/* USER CODE END 3 */
4. เกร็ดความรู้แบบมือโปร (Pro Tips)
Push-Pull vs Open-Drain ต่างกันยังไง?
- Push-Pull: เหมือนก๊อกน้ำที่มีปั๊ม อัดไฟออกไป (1) หรือดูดลงกราวด์ (0) ได้แรง เหมาะกับขับ LED
- Open-Drain: เหมือนสวิตช์ต่อลงดิน ทำได้แค่ดึงลงกราวด์ (0) หรือปล่อยลอย (Hi-Z) มักใช้กับระบบสื่อสารเช่น I2C
Atomic Operation (ความลับของ BSRR)
สังเกตไหมครับว่า HAL_GPIO_WritePin ข้างในมันไปเขียนที่รีจิสเตอร์ BSRR (Bit Set/Reset Register) ไม่ใช่ ODR (Output Data Register) โดยตรง?
เหตุผลคือ BSRR ยอมให้เราเปลี่ยนค่า “ขาใดขาหนึ่ง” ได้ โดยไม่กระทบขาอื่น และที่สำคัญคือ ไม่โดน Interrupt แทรกกลาง (Atomic) ซึ่งปลอดภัยกว่ามากในงาน Real-time ครับ
Clock is Key
มือใหม่ที่เขียนแบบ Register เองมักลืมเปิด Clock ให้ GPIO แต่ถ้าใช้ HAL Library ฟังก์ชัน MX_GPIO_Init() จะเรียก __HAL_RCC_GPIOD_CLK_ENABLE() ให้เราอัตโนมัติครับ จำไว้เสมอว่า “ถ้าไม่มี Clock ต่อให้โค้ดถูก ขาก็ไม่ทำงานครับ”
การบ้าน: ลองเขียนโปรแกรมให้ไฟวิ่งวน 4 ดวง (เขียว -> ส้ม -> แดง -> ฟ้า) แล้ววนกลับ และเมื่อกดปุ่มค้างไว้ ให้ไฟวิ่งเร็วขึ้นดูครับ! ขอให้สนุกกับการโค้ดครับ 🚀