บทนำ: Hello World แห่งโลกฮาร์ดแวร์

ยินดีต้อนรับสู่บทเรียนแรกของการก้าวสู่โลก Embedded Systems ครับ! ในฐานะ Instructor ของ 123Microcontroller ผมขอบอกเลยว่า “การทำไฟกระพริบ (Blinky)” สำหรับคนทำฮาร์ดแวร์นั้น ศักดิ์สิทธิ์เทียบเท่ากับ “Hello World” ของฝั่งซอฟต์แวร์เลยทีเดียว

มันไม่ใช่แค่เรื่องไฟติด-ดับ แต่มันคือการยืนยัน 3 สิ่งสำคัญ:

  1. Toolchain ของเราติดตั้งสมบูรณ์
  2. เราคุยกับชิป (Flash Program) รู้เรื่อง
  3. หัวใจของชิป (Clock) กำลังเต้นอยู่

มาเริ่มกันเลยครับ!

ระดับความยาก: ⭐ เริ่มต้น เวลาที่ใช้: ~20-30 นาที

1. อุปกรณ์และการเชื่อมต่อ (Hardware Mapping)

GPIO (General Purpose Input/Output) เปรียบเสมือน “แขนขา” ของไมโครคอนโทรลเลอร์ครับ บนบอร์ด STM32F4 Discovery นี้ มีอุปกรณ์มาให้เราเล่นครบโดยไม่ต้องต่อวงจรเพิ่ม:

แผนภาพแสดงตำแหน่งขา GPIO สำหรับ LED และปุ่มกดบนบอร์ด STM32

  • LED 4 สี (Output):
    • 🟢 Green: ต่อขา PD12
    • 🟠 Orange: ต่อขา PD13
    • 🔴 Red: ต่อขา PD14
    • 🔵 Blue: ต่อขา PD15
  • ปุ่มกด (Input):
    • 🔵 User Button: ต่อขา PA0 (กด = 1/High, ปล่อย = 0/Low)

2. การตั้งค่าใน STM32CubeMX

เปิดโปรเจกต์ .ioc ใน STM32CubeIDE แล้วทำตามนี้ครับ:

  1. เปิดใช้ขา LED (Output):
    • ไปที่หมุดขา PD12, PD13, PD14, PD15 บนรูปชิป
    • คลิกซ้ายแล้วเลือก GPIO_Output
    • Tip: คลิกขวาที่ขา เลือก Enter User Label ตั้งชื่อเป็น LED_Green, LED_Orange ฯลฯ เวลาเขียนโค้ดจะเรียกใช่ง่ายขึ้นมากครับ
  2. เปิดใช้ขาปุ่มกด (Input):
    • ไปที่ขา PA0 เลือก GPIO_Input
  3. เจาะลึกการตั้งค่า (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 ภายนอกให้อยู่แล้ว)
  4. 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 ดวง (เขียว -> ส้ม -> แดง -> ฟ้า) แล้ววนกลับ และเมื่อกดปุ่มค้างไว้ ให้ไฟวิ่งเร็วขึ้นดูครับ! ขอให้สนุกกับการโค้ดครับ 🚀