ข้ามไปยังเนื้อหา

GPIO และอุปกรณ์ต่อพ่วงผ่าน Driver API

Course 1 · Module 3
Suggested time: ประมาณ 3 ชั่วโมง (อ่าน + lab บนบอร์ด)
Format: บทเรียนเชิงปฏิบัติ — เรียก Driver API ของ TESA Firmware SDK บนโปรเจกต์ที่ build/flash ได้จาก M02

Lab · Cheatsheet · ← Table of Contents · ← M02 · M04 →

หมายเหตุ: โค้ดในบทนี้เขียนสำหรับเฟิร์มแวร์ชุดใด (ตรวจสอบเมื่อ 26 ก.ย. 2026)

โค้ด C ในบทนี้เรียก API ของเฟิร์มแวร์ TESAIoT Bitstream ที่ต้นฉบับเรียกว่า “TESA Firmware SDK” ซึ่งเผยแพร่เป็นไฟล์ HEX สำเร็จรูป (tesaiot-bitstream-<version>.hex) คู่กับ Bitstream Studio ในแพ็กแล็บ TESAIoT_Hackathon ซอร์สโค้ดของเฟิร์มแวร์ชุดนี้ยังไม่เปิดเผยต่อสาธารณะ ฟังก์ชันอย่าง led_controller_*, cm55_button_*, cm55_uart_send, sensor_sht40_*, cm55_i2c_manager_i2c_lock, bitstream_led_pwm_*, cm55_adc_* จึงยังไม่มี header ให้เปิดดูหรือนำไป build เอง ให้อ่าน snippet เป็นแนวคิดและลำดับการเรียกใช้ ส่วนการเรียก FreeRTOS และ Infineon PDL (เช่น xTaskCreate, vTaskDelay, Cy_GPIO_*) เป็น API สาธารณะตามปกติ

ถ้าต้องการโค้ดที่อ่านและ build ได้จากซอร์สเปิด ให้ดู tesaiot-pse84-devkit-sdk (Apache-2.0) ซึ่งเป็นคนละโค้ดเบสและตั้งชื่อ API ต่างกัน ตัวอย่างที่ตรวจแล้วว่าทำงานเรื่องเดียวกับบทนี้ (commit ef72c1b):

ยังไม่พบตัวเทียบใน SDK สาธารณะ: PWM หรี่ไฟ (bitstream_led_pwm_*) และ cm55_uart_send


เมื่อเรียนจบ คุณควรทำได้ดังนี้:

  1. อธิบายพื้นฐาน GPIO และทดลองบนบอร์ดด้วย API ของหลักสูตร (led_controller_*, cm55_button_*, หรือ Cy_GPIO_*)
  2. อธิบายบทบาทของ peripheral หลัก: Timer, UART, I²C, SPI, PWM, ADC
  3. ใช้ Driver API ของ TESA Firmware SDK เพื่อควบคุมอุปกรณ์ต่อพ่วงอย่างเป็นระบบ
  4. ทำแบบฝึกแยกบล็อกแล้วรวมเป็นวงจรเล็ก ๆ พร้อมจดชื่อฟังก์ชันจริงที่เรียกใช้

โมดูลนี้พาคุณจาก “โปรเจกต์รันได้” ใน M02 ไปสู่การ คุยกับฮาร์ดแวร์ผ่านชั้น Driver ตามแผนที่ใน M01

เกี่ยวกับ snippet ในบทนี้
ตัวอย่างภาษา C ด้านล่างดึงจาก TESA Firmware SDK (wrapper บน CM55 + FreeRTOS)
ในแล็บให้เรียกชื่อฟังก์ชันเหล่านี้ตามโปรเจกต์ที่คุณล็อกเวอร์ชันไว้ — ดูตัวอย่างเพิ่มบน TESAIoT Developer Hub (Domain: GPIO / Sensors / Embedded)

งานในแล็บ API ที่แนะนำให้เรียก ชั้นด้านล่าง (รู้ไว้พอ)
LED on/off / toggle led_controller_set / led_controller_toggle Cy_GPIO_* + BSP pin
ปุ่ม + event cm55_button_init / cm55_button_on_pressed GPIO IRQ + FreeRTOS task
Log ข้อความ printf / LOG_INFO หลัง bring-up init_retarget_io · cm55_uart_*
เซ็นเซอร์ I²C sensor_sht40_* (หรือเซ็นเซอร์อื่นใน SDK) cm55_i2c_manager_* · mtb_hal_i2c_*
อ่าน POT (Eval) cm55_adc_read_pot_mv Autonomous Analog SAR
หรี่ไฟ PWM bitstream_led_pwm_set_brightness Cy_TCPWM_PWM_*
หน่วงเวลาใน task vTaskDelay(pdMS_TO_TICKS(...)) FreeRTOS (ลงลึกใน M04)
SPI ทั่วไปใน lab แนวคิด + ตัวอย่าง Infineon / Hub ใน SDK ปัจจุบัน path SPI หลักอยู่ที่ radar (Cy_SCB_SPI_*)

อย่าใช้ cyhal_gpio_* เป็นเส้นทางหลักของหลักสูตรนี้ — สแต็ก TESA ใช้ wrapper + PDL / MTB HAL

เอกสาร ใช้เมื่อ
TESAIoT Developer Hub ตัวอย่างโค้ดหลักสูตร + API Reference
AN241775 — HAL on PSOC™ Edge (PDF) PDL / HAL / Device Configurator
AN235935 — Getting started on ModusToolbox™ (PDF) build / program / UART terminal
mtb-example-psoc-edge-hello-world LED + UART (Infineon เสริม)
mtb-example-psoc-edge-gpio-interrupt GPIO interrupt (Infineon เสริม)
mtb-example-psoc-edge-spi-dma SPI CE เมื่อต้องการแล็บ SPI แยก
retarget-io แนวคิด printf → UART
TESAIoT_Hackathon HEX / Flasher / web-app
Bitstream Studio telemetry บนโฮสต์ (เสริม)

Application → TESA Driver API (led_controller_*, cm55_*, sensor_*)
→ MTB HAL / Infineon PDL → Hardware pins / blocks
หลักการในหลักสูตร ทำไมสำคัญ
เรียก wrapper ของ SDK ก่อน โค้ดแล็บตรงกับผลิตภัณฑ์ / โปรเจกต์ตัวอย่าง
Init ให้ครบก่อน read/write บัสและพินพร้อม
ตรวจค่าคืน / timeout แยกบั๊กซอฟต์แวร์กับฮาร์ดแวร์
ใช้ mutex รอบบัสร่วม (I²C) หลาย task แชร์บัสเดียวกันได้อย่างปลอดภัย

Key phrase
Application ตัดสินใจ — Driver คุยกับฮาร์ดแวร์ — อย่ากระโดดไปแตะ register ในแบบฝึกมาตรฐาน


GPIO คือขาที่ตั้งเป็นอินพุตหรือเอาต์พุตดิจิทัลได้

โหมด ใช้ทำอะไร ใน SDK หลักสูตร
Output ขับ LED led_controller_* หรือ Cy_GPIO_Write / Cy_GPIO_Inv
Input / event อ่านปุ่ม cm55_button_*
Interrupt ขอบขา → callback ภายใน cm55_button (GPIO IRQ + task)

แนวคิด: active-high/low, pull-up/down, debounce (ในปุ่มของ SDK มักจัดการในโมดูลปุ่ม)

#include "led_controller.h"
void lab_led_demo(void)
{
(void)led_controller_init(); /* optional; BSP มัก init พินไว้แล้ว */
led_controller_set(LED_RED, true);
led_controller_toggle(LED_GREEN);
led_controller_off_all();
}

led_id_t: LED_RED, LED_GREEN, LED_BLUE

#include "cybsp.h"
#include "cy_gpio.h"
/* ตัวอย่าง: สลับ USER LED1 ด้วย PDL */
Cy_GPIO_Inv(CYBSP_USER_LED1_PORT, CYBSP_USER_LED1_PIN);
Cy_GPIO_Write(CYBSP_USER_LED1_PORT, CYBSP_USER_LED1_PIN, 1U);
#include "sensor_button.h" /* public API: cm55_button_* */
static void on_btn_pressed(cm55_button_t handle, const button_event_t *evt)
{
(void)handle;
led_controller_toggle(LED_BLUE);
printf("button %lu pressed (count=%lu)\r\n",
(unsigned long)evt->button_id,
(unsigned long)evt->press_count);
}
void lab_button_setup(void)
{
(void)cm55_button_init();
(void)cm55_button_on_pressed(BUTTON_ID_0, on_btn_pressed);
}

บนคิตบางรุ่นมีปุ่มเดียว (BUTTON_ID_0); Eval อาจมี BUTTON_ID_1 ตาม BSP


สำหรับแล็บหลาย task / คาบเวลา ให้ใช้ FreeRTOS (ลงรายละเอียดใน M04):

#include "FreeRTOS.h"
#include "task.h"
void blink_task(void *arg)
{
(void)arg;
for (;;) {
led_controller_toggle(LED_RED);
vTaskDelay(pdMS_TO_TICKS(500));
}
}

TCPWM ระดับต่ำ (Cy_TCPWM_Counter_* / mtb_hal_timer_*) มีในสแต็ก แต่แล็บพื้นฐานไม่จำเป็นต้องเรียกตรง

หลัง bring-up ของแอป (cm55_initialize ในโปรเจกต์มาตรฐาน) มักมี retarget IO แล้ว — ใช้:

#include <stdio.h>
#include "app_log.h"
void lab_uart_log(void)
{
printf("hello from CM55\r\n");
LOG_INFO("LAB", "btn toggled");
}

ส่งบัฟเฟอร์ดิบเมื่อต้องการ:

#include "cm55_uart.h"
const char msg[] = "raw uart\r\n";
cm55_uart_send((const uint8_t *)msg, sizeof(msg) - 1U);

Terminal มักเป็นพอร์ต KitProg3 — baud ตามโปรเจกต์/คู่มือ (ตัวอย่าง Infineon มัก 115200 8N1)

#include "sensor_sht40.h"
void lab_i2c_sht40(void)
{
if (sensor_sht40_startup() != CY_RSLT_SUCCESS) {
printf("SHT40 startup failed\r\n");
return;
}
sht40_sample_t sample;
if (sensor_sht40_read(&sample)) {
printf("T=%.2f C RH=%.2f %%\r\n",
(double)sample.temperature,
(double)sample.humidity);
}
}

เมื่อเขียนไดรเวอร์ระดับต่ำเองบนบัสร่วม ให้ล็อก:

cm55_i2c_manager_i2c_lock();
/* mtb_hal_i2c_controller_write / read ... */
cm55_i2c_manager_i2c_unlock();

เซ็นเซอร์อื่นใน SDK ตามแนวเดียวกัน: sensor_bmi270_*, sensor_dps368_*, sensor_bmm350_* (รายละเอียดใน M05)

ในผลิตภัณฑ์ปัจจุบัน path SPI ที่เห็นชัดในไลบรารีอยู่ที่โมดูล radar (Cy_SCB_SPI_Init / Cy_SCB_SPI_Enable / Cy_SCB_SPI_Transfer)
สำหรับแล็บ SPI ทั่วไป: ใช้ตัวอย่างบน Developer Hub หรือ mtb-example-psoc-edge-spi-dma

แนวคิดที่ต้องรู้: CS, CPOL/CPHA, MOSI/MISO/SCK

#include "bitstream_led_pwm.h"
void lab_pwm_brightness(void)
{
if (bitstream_led_pwm_init() != 0) {
return;
}
(void)bitstream_led_pwm_set_brightness(0 /* led_id */, 20); /* ~20% */
(void)bitstream_led_pwm_set_brightness(0, 80); /* ~80% */
}

LED เปิด/ปิดแบบดิจิทัลยังใช้ led_controller_* ได้ — PWM ใช้เมื่อต้องการความสว่างต่อเนื่อง

#include "sensor_adc.h"
void lab_adc_pot(void)
{
if (!cm55_adc_init()) {
printf("ADC init failed or POT N/A on this kit\r\n");
return;
}
int32_t counts = cm55_adc_read_pot_counts();
int16_t mv = cm55_adc_read_pot_mv();
printf("POT counts=%ld mV=%d\r\n", (long)counts, (int)mv);
}

บนคิตที่ไม่มี POT (เช่น AI kit บางคอนฟิก) ฟังก์ชันอาจเป็น no-op / คืน 0 — ให้ยึดคิตที่ใช้

Application
├── led_controller_* / Cy_GPIO_* → LED
├── cm55_button_* → button events
├── printf / LOG_* / cm55_uart_* → UART log
├── sensor_* + cm55_i2c_manager_* → I²C sensors
├── bitstream_led_pwm_* → PWM brightness
├── cm55_adc_* → POT (Eval)
└── vTaskDelay / xTaskCreate → timing & tasks (M04)

ลำดับมาตรฐาน:

  1. Init / configure (*_init, *_startup)
  2. Enable / start (ถ้าแยก)
  3. Transfer (read/write/set)
  4. ตรวจค่าคืน
  5. Deinit เมื่อเลิกใช้ (แอปจริง)

แผ่นจดชื่อ API: peripheral-api-map.md

แหล่ง วิธีใช้
TESAIoT Developer Hub Domain GPIO / Sensors / Embedded
โปรเจกต์ตัวอย่างที่คุณใช้ header ของโมดูลเดียวกันกับ snippet ในบทนี้
Infineon CE เปรียบเทียบแนวทางผู้ผลิต (เสริม)

static void on_pressed(cm55_button_t h, const button_event_t *evt)
{
(void)h;
(void)evt;
led_controller_toggle(LED_GREEN);
printf("btn toggled\r\n");
}
void app_lab_ab(void)
{
(void)led_controller_init();
(void)cm55_button_init();
(void)cm55_button_on_pressed(BUTTON_ID_0, on_pressed);
}
void app_lab_adc_pwm(void)
{
(void)cm55_adc_init();
(void)bitstream_led_pwm_init();
for (;;) {
int16_t mv = cm55_adc_read_pot_mv();
/* map 0..1800 mV → 0..100% (ปรับตาม Vref จริงของคิต) */
uint8_t duty = (uint8_t)((mv * 100) / 1800);
if (duty > 100U) {
duty = 100U;
}
(void)bitstream_led_pwm_set_brightness(0, duty);
vTaskDelay(pdMS_TO_TICKS(50));
}
}

ใช้ sensor_sht40_read + printf ตาม §3.3 — ต่อไปจัดหน้าต่างข้อมูลใน M05 / ดูบน Bitstream Studio เมื่อเฟิร์มแวร์ส่ง telemetry


  • ตรวจ alias LED / ปุ่มจาก BSP ของคิตในมือ
  • I²C บนบอร์ดส่วนใหญ่มี pull-up แล้ว — ล็อกบัสเมื่อหลาย task
  • ADC POT ใช้ได้บน Eval ตามที่ SDK รองรับ
  • แพ็ก HEX / Flasher: TESAIoT_Hackathon

Lab หลักวัดผลที่ LED / UART / ค่าที่อ่านได้
Host เสริม: Bitstream Studio


  1. เรียก TESA Driver API (led_controller_*, cm55_*, sensor_*) เป็นหลัก
  2. Timer ในแล็บพื้นฐาน = vTaskDelay (FreeRTOS) — M04 ขยาย multi-task
  3. UART = printf / LOG_* / cm55_uart_send
  4. I²C / ADC / PWM มี wrapper พร้อมใช้ใน SDK
  5. SPI เป็นแนวคิด + ตัวอย่าง Hub/Infineon
  1. ทำแบบฝึก: Lab
  2. จดชื่อ API จริง: Cheatsheet
  3. เมื่อพร้อม ไปต่อ M04 — RTOS Programming (บทเรียน M04)

  1. TESAIoT Developer Hub
  2. Bitstream Studio (Marketplace)
  3. TESAIoT_Hackathon
  1. AN241775 (PDF)
  2. AN235935 (PDF)
  3. mtb-example-psoc-edge-hello-world
  4. mtb-example-psoc-edge-gpio-interrupt
  5. mtb-example-psoc-edge-spi-dma
  6. retarget-io
  7. KIT_PSE84_EVAL
  1. FreeRTOS task control — vTaskDelay
  2. FreeRTOS xTaskCreate

คำถามสั้นสามข้อใน quiz.yaml ผูกกับเป้าหมายของบทเรียนนี้ข้อละหนึ่งคำถาม ลองตอบเองก่อน แล้วค่อยเทียบกับเฉลยและคำอธิบายในไฟล์

ลงมือต่อที่ แล็บ: GPIO และอุปกรณ์ต่อพ่วงบนฮาร์ดแวร์จริง

Lab · Cheatsheet · ← Table of Contents · ← M02 · M04 →

ลองของจริงบน TESAIoT Dev Kit: เปิดตัวอย่างบน Developer Hub เพื่ออ่านโค้ด ดาวน์โหลด หรือ flash เฟิร์มแวร์สำเร็จรูป

  • QWA309 — Push Button Monitor — อ่านปุ่มกด SW9 (P17.5) และ SW10 (P17.7) แบบ active-low pull-up แสดงสถานะกด/ปล่อย + นับจำนวนครั้งบน LVGL
  • QWA309 — Potentiometer Monitor — อ่าน 4 potentiometers (P15.4–P15.7) ผ่าน AUTANALOG SAR ADC 12-bit (Vref 1.8V) แสดงเป็น bar + แรงดัน + เปอร์เซ็นต์ real-time — practise แรกที
  • QWA309 — 4-Channel ADC Scope — plot ค่า pot 4 ตัว (P15.4-7, SAR 12-bit) เป็นเส้น scrolling บน LVGL chart 0-100% — analog oscilloscope

คำถามทบทวน

ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย

  1. ในสแต็กของบทเรียนนี้ ข้อใดถูกระบุว่า “อย่าใช้เป็นเส้นทางหลัก” ของหลักสูตร (เป้าหมายข้อ 1)

    1. `cyhal_gpio_*`
    2. `led_controller_*`
    3. `Cy_GPIO_Write` / `Cy_GPIO_Inv`
    4. `cm55_button_*`
    ดูเฉลย

    คำตอบ: A. `cyhal_gpio_*`

    ท้ายตาราง Naming map: สแต็กของหลักสูตรใช้ wrapper + PDL / MTB HAL ไม่ใช้ `cyhal_gpio_*` เป็นเส้นทางหลัก

  2. เรียงลำดับการใช้ Driver API มาตรฐานตามหัวข้อ 4 (เป้าหมายข้อ 2)

    1. ตรวจค่าคืน
    2. Init / configure
    3. Deinit เมื่อเลิกใช้
    4. Transfer (read / write / set)
    5. Enable / start
    ดูเฉลย

    ลำดับที่ถูก: B. Init / configure → E. Enable / start → D. Transfer (read / write / set) → A. ตรวจค่าคืน → C. Deinit เมื่อเลิกใช้

    หัวข้อ 4: Init/configure → Enable/start → Transfer → ตรวจค่าคืน → Deinit

  3. ทำไมต้องล็อกบัส I²C รอบการอ่านเขียนระดับต่ำ (เป้าหมายข้อ 3)

    1. เพราะหลาย task แชร์บัสเดียวกัน การล็อกทำให้ใช้บัสร่วมกันได้อย่างปลอดภัย
    2. เพื่อเพิ่มความเร็วของบัส
    3. เพราะบอร์ดไม่มีตัวต้านทาน pull-up
    4. เพื่อให้ PWM ทำงานพร้อม I²C ได้
    ดูเฉลย

    คำตอบ: A. เพราะหลาย task แชร์บัสเดียวกัน การล็อกทำให้ใช้บัสร่วมกันได้อย่างปลอดภัย

    ตารางหลักการในหัวข้อ 1 และหัวข้อ 3.3: ใช้ mutex รอบบัสร่วม เพื่อให้หลาย task ใช้บัสเดียวกันได้อย่างปลอดภัย

อ้างอิงบทเรียนนี้

ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน

"GPIO และอุปกรณ์ต่อพ่วงผ่าน Driver API" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

ข้อความอ้างอิงภาษาอังกฤษ: "GPIO and Peripherals through a Driver API" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0

ลิงก์บทเรียน: https://tesaiot.github.io/tesa-qualification-program/courses/firmware-sdk-edge-ai/m03-gpio-peripherals/l01-gpio-and-peripherals/

บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/drsanti/TESAIoT-Courses/blob/287c21814ba8c75f693136616dcd270349a15966/C1/M03/README.md · Original content by Asst. Prof. Dr. Santi Nuratch (ผศ.ดร.สันติ นุราช), KMUTT. Course 1 (C1/) of drsanti/TESAIoT-Courses. TESA funded the work and holds the rights; published here under CC BY-NC 4.0. The upstream repository carries no licence file. Text kept faithful; structure, front matter, quizzes and notes added by TESA Open Knowledge.

วิธีอ้างอิง TESA ฉบับเต็ม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA