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

อ่านแรงดันอนาล็อกด้วย SAR ADC 12 บิต

  1. อ่าน potentiometer 4 ตัวผ่าน SAR ADC 12 บิต และแปลงเป็นแรงดันและเปอร์เซ็นต์
  2. แสดงค่าเป็นกราฟเลื่อนแบบ oscilloscope และอธิบายความละเอียดของ ADC

บอร์ดฐาน QWA309 ของ TESAIoT Dev Kit มีอุปกรณ์จริงให้ฝึก ได้แก่ ปุ่มกด potentiometer 4 ตัว CAN transceiver และ header สำหรับต่ออุปกรณ์ภายนอก บทเรียนนี้ใช้แบบฝึกของ Developer Hub ที่เขียนไว้สำหรับบอร์ดนี้โดยตรง สองแบบฝึกในบทเรียนนี้อ่าน potentiometer ทั้ง 4 ตัวเหมือนกัน (VR1–VR4 ที่ P15.4–P15.7) แต่แสดงผลต่างรูปแบบ: แบบแรกเป็นแผงตัวเลข+บาร์ แบบที่สองเป็นกราฟเลื่อนแบบออสซิลโลสโคป

AUTANALOG SAR ADC เป็นตัวแปลงสัญญาณอนาล็อกเป็นดิจิทัลแบบ successive-approximation ในตัวชิป PSoC Edge อ่านค่าออกมาเป็นเลขจำนวนเต็ม 12 บิต คือ 0–4095 (POT_ADC_FULL_SCALE = 4095) เทียบกับแรงดันอ้างอิง (POT_ADC_VREF_MV = 1800) คือ 1.8 V ไม่ใช่ 3.3 V ความละเอียดจึงอยู่ที่ 1800 mV ÷ 4095 ขั้น ≈ 0.44 mV ต่อหนึ่งขั้น ตัวเลขนี้ละเอียดกว่าความละเอียดที่ 3.3 V (≈ 0.81 mV) เพราะช่วงแรงดันที่ต้องแบ่งแคบกว่า แต่ความละเอียดสูงไม่ได้แปลว่าค่าจะนิ่ง — noise ของแหล่งจ่ายและแรงดันอ้างอิงเพียงไม่กี่ mV ก็มากกว่า 0.44 mV ต่อขั้นแล้ว จึงเห็นค่า raw กระเพื่อมขึ้นลง 1–3 ขั้นได้แม้ไม่ได้หมุน pot

ต่างจากบัส I2C ที่ master template เปิดไว้ให้แล้ว (ดูบทเรียน 1.1) การอ่าน potentiometer ต้อง init SAR ADC เองทั้งหมด pot_adc_init() ตั้งขาทั้ง 4 (P15.4–P15.7) เป็นโหมด CY_GPIO_DM_ANALOG ด้วย Cy_GPIO_Pin_FastInit() ก่อน แล้วเรียก Cy_AutAnalog_Init(&autonomous_analog_init) โดยใช้ config autonomous_analog_init ที่ BSP สร้างไว้ให้ ตามด้วย Cy_AutAnalog_Enable() และ Cy_AutAnalog_StartAutonomousControl() เพื่อให้ ADC เริ่มแปลงค่าแบบต่อเนื่องในพื้นหลัง ถ้า Cy_AutAnalog_Init() ไม่สำเร็จ (CY_AUTANALOG_SUCCESS ไม่ตรง) ทั้งสองตัวอย่างจะไม่สร้าง timer อ่านค่า และขึ้นข้อความ “ADC init failed” บนจอแทน

แปลงค่าดิบเป็นแรงดันและเปอร์เซ็นต์ด้วยเลขจำนวนเต็ม

หัวข้อที่มีชื่อว่า “แปลงค่าดิบเป็นแรงดันและเปอร์เซ็นต์ด้วยเลขจำนวนเต็ม”

update_channel() ของ Pot Monitor อ่านผลลัพธ์ด้วย Cy_AutAnalog_SAR_ReadResult(POT_ADC_INDEX, CY_AUTANALOG_SAR_INPUT_GPIO, channel->adc_channel) แล้วมาสก์บิตด้วย 0x0FFFU เผื่อบิตอื่นติดมา จากนั้นแปลงด้วยเลขจำนวนเต็มล้วน (ไม่ใช้ float): millivolts = raw * 1800 / 4095 และ percent_tenths = raw * 1000 / 4095 (ได้หน่วยเป็นสิบเท่าของเปอร์เซ็นต์ เพื่อพิมพ์ทศนิยมหนึ่งตำแหน่งโดยไม่ใช้ float) ตัวอย่าง: raw = 2048 → 2048×1800/4095 = 900 mV (0.900 V) และ 2048×1000/4095 = 500 (50.0%) การหารแบบจำนวนเต็มปัดเศษทิ้งเสมอ (truncate) ไม่ปัดขึ้น

pot_timer_cb() ตรวจ Cy_AutAnalog_SAR_GetHSchanResultStatus(POT_ADC_INDEX) เทียบกับ POT_ADC_READY_MASK (รวมมาสก์ของช่อง GPIO0–GPIO3 ทั้งสี่) ถ้าบิตพร้อมครบทั้งสี่ช่องจะขึ้น “Live” สีเขียวมิ้นต์ ถ้ายังไม่ครบขึ้น “ADC settling” สีเหลือง ข้อควรสังเกตคือฟังก์ชันนี้เรียก update_channel() อ่านและแสดงค่าทั้ง 4 ช่องไปแล้วในลูปก่อนหน้า โดยไม่รอสถานะนี้ก่อน สถานะ “Live/settling” จึงเป็นแค่ป้ายบอกว่าฮาร์ดแวร์ conversion ครบรอบหรือยัง ไม่ใช่ตัวคุมว่าจะอัปเดตตัวเลขบนจอหรือไม่

กราฟเลื่อนแบบออสซิลโลสโคป: จำนวนจุด × คาบสุ่ม = ความยาวหน้าต่างเวลา

หัวข้อที่มีชื่อว่า “กราฟเลื่อนแบบออสซิลโลสโคป: จำนวนจุด × คาบสุ่ม = ความยาวหน้าต่างเวลา”

ADC Scope แปลงค่าดิบเป็นเปอร์เซ็นต์แบบง่ายกว่า Pot Monitor คือ pct = raw * 100 / SCOPE_FULL_SCALE (จำนวนเต็ม 0–100 ไม่มีทศนิยม) แล้วส่งเข้ากราฟด้วย lv_chart_set_next_value() บน lv_chart ที่ตั้ง lv_chart_set_update_mode(s_chart, LV_CHART_UPDATE_MODE_SHIFT) — โหมดนี้เลื่อนจุดเก่าออกทางซ้ายทีละจุดเมื่อจุดใหม่เข้ามาทางขวา กราฟมี SCOPE_POINTS = 100 จุด อ่านทุก SCOPE_PERIOD_MS = 60 ms ความยาวหน้าต่างเวลาที่มองเห็นบนกราฟจึงเท่ากับจำนวนจุดคูณคาบสุ่ม คือ 100 × 60 ms = 6 วินาที ถ้าลดคาบเหลือ 30 ms จำนวนจุดเท่าเดิมจะครอบคลุมแค่ 3 วินาที เห็นรายละเอียดเวลาเพิ่มขึ้นแต่เห็นประวัติสั้นลง

Pot Monitor กำหนด pot_channels[] ให้ VR1 = ช่อง index 1 (P15.5) และ VR2 = ช่อง index 0 (P15.4) — สลับกับลำดับที่คาดตามธรรมชาติ ส่วน ADC Scope กำหนด s_ch[] = {0, 1, 2, 3} กับชื่อ s_name[] = {"VR1","VR2","VR3","VR4"} ตามลำดับ index ตรงตัว ผลคือหมุน pot ที่ขา P15.4 ตัวเดียวกัน จะเห็นการ์ด VR2 ขยับใน Pot Monitor แต่เห็นเส้น VR1 ขยับใน ADC Scope ทั้งที่เป็น SAR channel index 0 ตัวเดียวกัน ชื่อที่ผู้ใช้เห็นบนจอ ขาจริงบนบอร์ด และ index ผลลัพธ์ในโค้ด เป็นสามสิ่งที่ต้องตรวจให้ตรงกันเองเสมอ ไม่ควรเชื่อชื่อบนจอของตัวอย่างหนึ่งไปเทียบกับอีกตัวอย่างหนึ่งตรง ๆ

แบบฝึกชุด QWA309 ของ Developer Hub (อ้างอิงที่ commit e5c7722) รันบน TESAIoT Dev Kit เท่านั้น เพราะใช้อุปกรณ์บนบอร์ดฐาน

  • QWA309 — Potentiometer Monitor — อ่าน 4 potentiometers (P15.4–P15.7) ผ่าน AUTANALOG SAR ADC 12-bit (Vref 1.8V) แสดงเป็น bar + แรงดัน + เปอร์เซ็นต์ real-time — practise แรกที่ใช้ ADC จริงบน TESAIoT Dev Kit README · โค้ด · Developer Hub
  • QWA309 — 4-Channel ADC Scope — plot ค่า pot 4 ตัว (P15.4-7, SAR 12-bit) เป็นเส้น scrolling บน LVGL chart 0-100% — analog oscilloscope README · โค้ด · Developer Hub

โค้ดตัวอย่างด้านล่างคัดลอกจากไฟล์จริงที่ commit เดียวกัน (Apache-2.0, tesaiot/developer-hub)

pot_monitor_ui.c — ตั้งขาเป็น analog input แล้วเริ่ม AUTANALOG SAR ADC:

static bool pot_adc_init(void)
{
uint32_t init_status;
pot_adc_init_pin(P15_4_PORT, P15_4_PIN);
pot_adc_init_pin(P15_5_PORT, P15_5_PIN);
pot_adc_init_pin(P15_6_PORT, P15_6_PIN);
pot_adc_init_pin(P15_7_PORT, P15_7_PIN);
init_status = Cy_AutAnalog_Init(&autonomous_analog_init);
if (CY_AUTANALOG_SUCCESS != init_status)
{
return false;
}
Cy_AutAnalog_Enable();
Cy_AutAnalog_StartAutonomousControl();
return true;
}

pot_monitor_ui.c — แปลงค่าดิบเป็นแรงดันและเปอร์เซ็นต์ด้วยเลขจำนวนเต็ม:

raw = (uint16_t)Cy_AutAnalog_SAR_ReadResult(POT_ADC_INDEX,
CY_AUTANALOG_SAR_INPUT_GPIO,
channel->adc_channel);
raw &= 0x0FFFU;
millivolts = ((uint32_t)raw * POT_ADC_VREF_MV) / POT_ADC_FULL_SCALE;
percent_tenths = ((uint32_t)raw * 1000U) / POT_ADC_FULL_SCALE;
bar_value = ((uint32_t)raw * 1000U) / POT_ADC_FULL_SCALE;

pot_monitor_ui.c — การแมป VR1/VR2 สลับกับลำดับ index ที่คาดไว้:

static pot_channel_t pot_channels[POT_COUNT] =
{
{ "VR1", "P15.5 ADC5", 1U, 0x14B8A6, NULL, NULL, NULL, NULL },
{ "VR2", "P15.4 ADC4", 0U, 0x22C55E, NULL, NULL, NULL, NULL },
{ "VR3", "P15.6 ADC6", 2U, 0xF59E0B, NULL, NULL, NULL, NULL },
{ "VR4", "P15.7 ADC7", 3U, 0xF43F5E, NULL, NULL, NULL, NULL },
};

adc_scope_ui.c — อ่านทุกช่องแล้วส่งเข้ากราฟเลื่อนแบบ SHIFT:

static void scope_timer_cb(lv_timer_t *timer)
{
(void)timer;
if (!s_adc_ok) { return; }
for (uint8_t i = 0U; i < SCOPE_CH; i++) {
uint16_t raw = (uint16_t)Cy_AutAnalog_SAR_ReadResult(SCOPE_ADC_INDEX,
CY_AUTANALOG_SAR_INPUT_GPIO, s_ch[i]) & 0x0FFFU;
uint32_t pct = ((uint32_t)raw * 100U) / SCOPE_FULL_SCALE;
lv_chart_set_next_value(s_chart, s_series[i], (int32_t)pct);
lv_label_set_text_fmt(s_val[i], "%s %lu%%", s_name[i], (unsigned long)pct);
}
}
  • คิดว่าแรงดันอ้างอิงคือ 3.3 V — SAR ADC ในตัวอย่างนี้ใช้ POT_ADC_VREF_MV = 1800 (1.8 V) ถ้าคิดผิดเป็น 3.3 V การแปลงค่าดิบเป็นแรงดันจะผิดทั้งหมด
  • เข้าใจว่าค่าที่แกว่ง 1–3 ขั้นเป็นความเสียหายของ ADC — ที่ความละเอียด ≈0.44 mV/ขั้น สัญญาณรบกวนเพียงไม่กี่ mV ก็ทำให้ค่ากระเพื่อมได้ตามปกติ ลดได้ด้วยการเฉลี่ยหลายตัวอย่าง (moving average) ไม่ใช่อาการเสีย
  • เทียบชื่อช่อง VR1–VR4 ข้ามตัวอย่างโดยไม่ดู index จริง — Pot Monitor กับ ADC Scope แมปชื่อกับ SAR index ไม่ตรงกัน (VR1/VR2 สลับกัน) ต้องดู pot_channels[]/s_ch[] ในโค้ดหรือ schematic ของบอร์ดเสมอ
  • รอสถานะ “Live” ก่อนเชื่อค่าบนจอ — ป้าย Live/ADC settling เป็นแค่ตัวบอกว่า hardware conversion ครบรอบหรือยัง โค้ดอัปเดตตัวเลขบนจอทุกรอบอยู่แล้วไม่ว่าป้ายจะขึ้นว่าอะไร
Terminal window
# ในโฟลเดอร์ master template (ดูบทเรียน 1.1)
# 1) ลบไฟล์ของ episode เก่าใน proj_cm55/apps/
# 2) คัดลอกไฟล์ทั้งหมดของ episode นี้ลงใน proj_cm55/apps/
make build
make program # flash ผ่าน KitProg3
  1. ทาย ก่อนแก้: เลือกค่าหนึ่งค่าที่ README ของตัวอย่างอธิบายไว้ในส่วน How แล้วเขียนว่าจะเห็นอะไรเปลี่ยนบนจอหรือใน log
  2. แก้และรัน build + flash แล้วเทียบกับที่ทายไว้ ถ้าไม่ตรง ให้หาว่าเข้าใจส่วนไหนผิด
  3. ทำเพิ่ม ต่อยอดหนึ่งอย่างที่ตัวอย่างยังไม่มี แล้วเก็บภาพหรือวิดีโอไว้ใน portfolio
  • ADC 12 บิต ที่ Vref 1.8 V แยกแรงดันได้ละเอียดกี่มิลลิโวลต์ต่อขั้น
  • ทำไมค่าที่อ่านได้กระโดดเล็กน้อยแม้ไม่ได้หมุน pot

คำตอบอยู่ใน README ของตัวอย่างและในโค้ด ถ้าตอบข้อใดไม่ได้ ให้กลับไปอ่านส่วน Why / What / How อีกครั้ง

คำถามทบทวน

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

  1. ค่าดิบจาก ADC เป็น 2048 Pot Monitor จะแสดงแรงดันและเปอร์เซ็นต์ใด (เป้าหมายข้อ 1)

    1. 1.650 V · 50.0 %
    2. 0.900 V · 50.0 %
    3. 0.900 V · 25.0 %
    4. 2.048 V · 50.0 %
    ดูเฉลย

    คำตอบ: B. 0.900 V · 50.0 %

    update_channel() ใช้ millivolts = raw × 1800 / 4095 = 900 (หารแบบจำนวนเต็ม) และ percent_tenths = raw × 1000 / 4095 = 500 จึงแสดง 0.900 V และ 50.0 % แรงดันอ้างอิงคือ 1.8 V ไม่ใช่ 3.3 V

  2. ADC 12 บิตที่แรงดันอ้างอิง 1.8 V แยกแรงดันได้ละเอียดราวกี่มิลลิโวลต์ต่อหนึ่งขั้น (เป้าหมายข้อ 2)

    1. 0.81 mV
    2. 1.76 mV
    3. 7.0 mV
    4. 0.44 mV
    ดูเฉลย

    คำตอบ: D. 0.44 mV

    1800 mV / 4095 ≈ 0.44 mV ต่อขั้น ตัวเลือกอื่นคือความละเอียดเมื่อใช้ 3.3 V (≈ 0.81 mV) ADC 10 บิต (≈ 1.76 mV) และ 8 บิต (≈ 7 mV) ที่ 1.8 V ความละเอียดสูงไม่ได้แปลว่าค่าจะนิ่ง เพราะ noise เพียงไม่กี่ mV ก็ขยับค่าหลายขั้นแล้ว

  3. ไม่ได้หมุน pot แต่ค่า raw ขยับขึ้นลงหนึ่งถึงสามขั้นตลอด ข้อใดอธิบายได้ถูกต้อง (เป้าหมายข้อ 2)

    1. เป็นเรื่องปกติ: noise ของแหล่งจ่าย แรงดันอ้างอิง และ quantization เพียงไม่กี่ mV ก็มากกว่า 0.44 mV ต่อขั้น ลดได้ด้วยการเฉลี่ยหลายตัวอย่าง
    2. ADC เสีย
    3. เพราะ lv_timer ทำงานไม่ตรงเวลา
    4. เพราะหน้าจอรบกวน ADC ต้องปิดจอ
    ดูเฉลย

    คำตอบ: A. เป็นเรื่องปกติ: noise ของแหล่งจ่าย แรงดันอ้างอิง และ quantization เพียงไม่กี่ mV ก็มากกว่า 0.44 mV ต่อขั้น ลดได้ด้วยการเฉลี่ยหลายตัวอย่าง

    README ของ Pot Monitor แนะนำให้เพิ่ม moving average ใน update_channel() เพื่อลด noise ของค่า raw การแกว่ง 1–3 ขั้นคือไม่ถึง 1.5 mV ถือว่าเล็กมากสำหรับงานอ่าน pot

  4. ADC Scope อ่านทุก SCOPE_PERIOD_MS = 60 และกราฟมี SCOPE_POINTS = 100 จุด ถ้าเปลี่ยนคาบเวลาเป็น 30 ms หน้าต่างเวลาบนกราฟจะยาวเท่าไร (เป้าหมายข้อ 2)

    1. 6 วินาที
    2. 12 วินาที
    3. 3 วินาที
    4. 1.5 วินาที
    ดูเฉลย

    คำตอบ: C. 3 วินาที

    LV_CHART_UPDATE_MODE_SHIFT เลื่อนจุดเก่าออกทีละจุด หน้าต่างเวลา = จำนวนจุด × คาบเวลา เดิม 100 × 60 ms = 6 วินาที เป็น 100 × 30 ms = 3 วินาที เห็นรายละเอียดทางเวลามากขึ้นแต่เห็นประวัติสั้นลง

  5. หมุน pot ที่ขา P15.4 แล้ว Pot Monitor แสดงการเปลี่ยนที่การ์ด VR2 แต่ ADC Scope แสดงที่เส้น VR1 เกิดจากอะไร (เป้าหมายข้อ 1)

    1. สายต่อหลวม
    2. ชื่อบนจอเป็นแค่การแมปในซอฟต์แวร์: Pot Monitor แมป VR2 → result index 0 (P15.4) ส่วน ADC Scope แมป VR1 → index 0 สองตัวอย่างจึงตั้งชื่อช่องเดียวกันไม่ตรงกัน ต้องเทียบกับ schematic ของบอร์ด
    3. ADC สลับช่องเองตามลำดับการอ่าน
    4. สองตัวอย่างใช้ ADC คนละตัว
    ดูเฉลย

    คำตอบ: B. ชื่อบนจอเป็นแค่การแมปในซอฟต์แวร์: Pot Monitor แมป VR2 → result index 0 (P15.4) ส่วน ADC Scope แมป VR1 → index 0 สองตัวอย่างจึงตั้งชื่อช่องเดียวกันไม่ตรงกัน ต้องเทียบกับ schematic ของบอร์ด

    pot_channels[] ใน Pot Monitor กำหนด VR1 = P15.5 (index 1) และ VR2 = P15.4 (index 0) ส่วน ADC Scope ใช้ s_ch[] = {0, 1, 2, 3} กับชื่อ VR1–VR4 ตามลำดับ ทั้งคู่อ่าน SAR index 0 ตัวเดียวกัน ชื่อที่ผู้ใช้เห็น ขาจริง และ index ของผลลัพธ์ เป็นสามสิ่งที่ต้องตรวจให้ตรงกันเอง

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

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

"อ่านแรงดันอนาล็อกด้วย SAR ADC 12 บิต" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

ข้อความอ้างอิงภาษาอังกฤษ: "Analog voltages with the 12-bit SAR ADC" 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/tesaiot-firmware-stack/m04-qwa309-hardware/l02-analog-and-adc/

บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/tesaiot/developer-hub/blob/e5c772252e7d20f715463e0d27df9ece4e569c38/prac_qwa309_pot_monitor · Code stays in the Developer Hub and is linked at pinned commits, never copied: the episodes, practice codes and main-branch examples are Apache-2.0; the master template and the OPTIGA client carry Infineon/Cypress EULAs.

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

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

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