Watchdog
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”เมื่อจบบทเรียนนี้ คุณจะ
- อธิบายหน้าที่ของ watchdog และผลเมื่อไม่ได้รับการป้อนภายในเวลาที่กำหนด
- ระบุจุดที่ถูกต้องในการป้อน watchdog ในโปรแกรมที่มีหลาย task และอธิบายว่าทำไมการป้อนใน ISR ของ timer เป็นกับดัก
- ออกแบบการบันทึกสาเหตุการรีเซ็ตเพื่อให้รู้ว่า watchdog ทำงานเมื่อใด
ใช้เวลาประมาณ 70 นาที (แนวคิด 15 · ฝึก 25 · แล็บ 25 · เช็ก 5)
ก่อนเริ่ม
หัวข้อที่มีชื่อว่า “ก่อนเริ่ม”ทวนจากบทก่อนหน้าสองข้อ
- ตัวเฝ้าการค้างของเรดาร์ใน SDK รายงาน 0 ครั้งทั้งที่ task ค้าง เพราะมันอยู่ตรงไหน (บทเรียน 3.2)
- ISR ของ timer ยังทำงานได้ไหม ถ้า task ทุกตัวติดอยู่ในลูปไม่รู้จบ (บทเรียน 4.1 และ 4.2)
ดูของจริงก่อน
หัวข้อที่มีชื่อว่า “ดูของจริงก่อน”เปิด examples/09_watchdog_sim.c ระบบจำลองมีสาม task และ watchdog ที่รีเซ็ตเมื่อไม่ถูกป้อนเกิน 10 tick
ที่ tick 40 task sensor ค้าง แล้วลองสองกลยุทธ์: A ป้อนใน ISR ของ timer ทุก tick, B ให้ผู้ดูแลป้อนเฉพาะเมื่อทุก task รายงานความคืบหน้าครบ
ทายก่อนรัน ว่ากลยุทธ์ A จะรีเซ็ตที่ tick ไหน
gcc -std=c11 -Wall -Wextra -o watchdog_sim examples/09_watchdog_sim.c./watchdog_simกลยุทธ์ A ไม่รีเซ็ตเลย ทั้งที่ task ค้างไปแล้ว 60 tick เพราะ ISR ของ timer ไม่รู้และไม่สนว่า task เป็นอย่างไร กลยุทธ์ B รีเซ็ตภายในเวลาที่ตั้ง และบอกได้ด้วยว่า task ไหนคือตัวที่หายไป watchdog ที่ป้อนผิดที่ดีกว่าไม่มีเลยนิดเดียว
1. watchdog คืออะไร และทำงานอย่างไรบน PSOC™ Edge
หัวข้อที่มีชื่อว่า “1. watchdog คืออะไร และทำงานอย่างไรบน PSOC™ Edge”watchdog คือตัวนับที่เดินด้วยสัญญาณนาฬิกาของตัวเอง แยกจาก CPU ซอฟต์แวร์ต้อง “ป้อน” (ล้าง) มันเป็นระยะ ถ้าไม่ป้อนจนถึงค่าที่ตั้ง มันรีเซ็ตทั้งชิป แนวคิดคือถ้าซอฟต์แวร์ยังป้อนได้ แปลว่ายังทำงาน ถ้าป้อนไม่ได้ แปลว่าค้าง และการเริ่มใหม่ดีกว่าค้างไปเรื่อย ๆ
PDL มีไดรเวอร์ WDT ใน cy_wdt.h BSP ของบอร์ดนี้ประกาศ
CY_IP_MXS22SRSS (cy_device_headers_ns.h)
ซึ่งทำให้ใช้ชุดฟังก์ชันแบบ match value ของ PDL ได้ ลำดับการตั้งตามเอกสารของไดรเวอร์คือ
| ขั้น | ฟังก์ชัน |
|---|---|
| ปลดล็อกและปิดก่อนแก้ | Cy_WDT_Unlock() แล้ว Cy_WDT_Disable() |
| เลือกสัญญาณนาฬิกา (สำหรับ MXS22SRSS คือ PILO หรือ clk_bak) | Cy_WDT_SetClkSource() |
| ตั้งเวลา | Cy_WDT_SetMatch() และถ้าต้องการสั้นลง Cy_WDT_SetIgnoreBits() |
| เปิด แล้วล็อกกันแก้โดยไม่ตั้งใจ | Cy_WDT_Enable() แล้ว Cy_WDT_Lock() |
| ป้อน | Cy_WDT_ClearWatchdog() ซึ่งเอกสารเขียนว่า “Clears (“feeds”) the watchdog, to prevent a XRES device reset” |
ข้อเตือนจากเอกสารของ PDL ที่ต้องรู้ก่อนเปิดใช้: เวลาของ watchdog ต้องยาวกว่าเวลาบูตของชิป (“a WDT reset can be generated faster than a device start-up”) การเปลี่ยนค่ามีผลช้าไปสองสามจังหวะของสัญญาณนาฬิกาความถี่ต่ำ และ oscillator ความแม่นยำต่ำต้องเผื่อ margin ให้มาก (เอกสารยกตัวอย่าง ILO ของชิปรุ่นอื่นที่คลาดได้ ±30%) ความแม่นยำของ PILO หรือ clk_bak บนชิปนี้ให้ดูใน datasheet ของชิป และระหว่างดีบัก การหยุด CPU ที่ breakpoint อาจทำให้ watchdog รีเซ็ตกลางทาง (บทเรียน 3.1)
สถานะในแม่แบบของ SDK: เราค้นซอร์สของแม่แบบ mtb-only ที่ commit นี้แล้ว ไม่พบการเปิด hardware watchdog (Cy_WDT_Enable หรือ Cy_WDT_ClearWatchdog)
มีเพียง Cy_WDT_Unlock() ในโค้ดตั้งสัญญาณนาฬิกาที่ BSP สร้าง (cycfg_clocks.c บรรทัด 584)
ส่วนที่ SDK ใช้จริงคือ watchdog แบบซอฟต์แวร์ของ task ตัวหนึ่ง ซึ่งเป็นหัวข้อถัดไป
2. ป้อนที่ไหน: ที่ที่พิสูจน์ว่าระบบคืบหน้า
หัวข้อที่มีชื่อว่า “2. ป้อนที่ไหน: ที่ที่พิสูจน์ว่าระบบคืบหน้า”watchdog ตรวจได้เฉพาะสิ่งที่ “คนป้อน” รู้ ถ้าป้อนจาก ISR ของ timer มันพิสูจน์ได้แค่ว่า interrupt ของ timer ยังทำงาน ซึ่งมักจริงแม้ task ทุกตัวตาย ถ้าให้แต่ละ task ป้อนเอง task ตัวไหนก็ได้ที่ยังรอดจะป้อนแทนตัวที่ตายไปแล้ว รูปแบบที่ใช้ได้คือ check-in กับผู้ดูแลหนึ่งคน
- task ที่สำคัญแต่ละตัวตั้งบิตของตัวเองเมื่อ ทำงานคืบหน้าจริง (อ่านเซนเซอร์ได้ ส่งข้อความได้) ไม่ใช่แค่ตื่นขึ้นมา
- ผู้ดูแลเป็น task ที่ความสำคัญต่ำ ตรวจเป็นระยะ ป้อนเฉพาะเมื่อบิตครบ แล้วล้างบิตทั้งหมด
- เวลาของ watchdog ยาวกว่ารอบที่ช้าที่สุดของ task ทุกตัว บวก margin
ผู้ดูแลที่ความสำคัญต่ำยังจับอีกอาการได้ฟรี: task ความสำคัญสูงที่วนไม่ปล่อย CPU ผู้ดูแลจะไม่ได้รัน watchdog จึงรีเซ็ต
SDK มีตัวอย่าง watchdog แบบซอฟต์แวร์ของจริง 08_deepcraft_link.c
อธิบายว่า task ของ model link เคย “asleep inside its own queue receive with a command outstanding” หลังสลับโมเดลไปเป็นร้อยครั้ง
deepcraft_task_watchdog() ต้องถูกเรียกราว 1 Hz “from a context that never blocks” และมันตรวจพบงานค้างแล้วปลุก task ให้ทำต่อ
คอมเมนต์เดียวกันยอมรับตรง ๆ ว่า “The watchdog does not fix the underlying fault” watchdog คือการฟื้นตัว ไม่ใช่การแก้บั๊ก ต้องบันทึกทุกครั้งที่มันทำงาน
3. บันทึกสาเหตุการรีเซ็ต: รู้ให้ได้ว่า watchdog ทำงานเมื่อไร
หัวข้อที่มีชื่อว่า “3. บันทึกสาเหตุการรีเซ็ต: รู้ให้ได้ว่า watchdog ทำงานเมื่อไร”watchdog ที่รีเซ็ตเงียบ ๆ ทำให้ระบบดูเหมือน “บางทีก็บูตใหม่เอง” สิ่งที่ต้องมีคือบันทึกที่รอดข้ามการรีเซ็ต
- อ่านสาเหตุ
Cy_SysLib_GetResetReason()คืนชุดบิต เช่นCY_SYSLIB_RESET_HWWDT(0x0001)CY_SYSLIB_RESET_SOFT(0x0010)CY_SYSLIB_RESET_SWWDT0..3ของ multi-counter watchdog (ดูตารางในcy_syslib.h) ทดสอบด้วย&เพราะอาจมีหลายบิตพร้อมกัน BSP ของ SDK เองใช้ฟังก์ชันนี้ตอนตั้งสัญญาณนาฬิกา และเขียนกำกับว่าค่า 0 หมายถึง “POR, XRES, or BOD” (cycfg_clocks.c บรรทัด 571-575) หลังบันทึกแล้วเรียกCy_SysLib_ClearResetReason()ไม่อย่างนั้นบูตครั้งต่อไปจะเห็นสาเหตุเดิม - เก็บร่องรอยไว้ใน RAM ที่ไม่ถูกล้างตอนบูต linker script ของ SDK มี section
.noinitที่ startup ไม่แตะ (pse84_ns_cm33.ld บรรทัด 339-343) ผู้ดูแลเขียน “task ไหนยังไม่ check in” ลงไปทุกรอบ หลังรีเซ็ตจึงรู้ว่าใครค้าง หลังเปิดไฟครั้งแรกค่าใน RAM เป็นขยะ ต้องมี magic number ไว้แยก CM55 ของแม่แบบใช้หลักเดียวกัน มันเขียน fault marker ไว้ที่ที่อยู่คงที่ใน SRAM พร้อมคอมเมนต์ว่า “survives until power cycle” - ส่งออก พิมพ์ตอนบูต นับจำนวน และถ้ามีการเชื่อมต่อ ส่งขึ้นระบบที่เก็บ log ได้
ตัวอย่างสมบูรณ์
หัวข้อที่มีชื่อว่า “ตัวอย่างสมบูรณ์”examples/09_watchdog_sim.c ทำงานเป็นสามท่า
- ท่าที่ 1 แต่ละ task ทำงานหนึ่งรอบแล้ว check in ด้วยการตั้งบิต (task
sensorค้างตั้งแต่ tick 40) - ท่าที่ 2 กลยุทธ์ A ป้อนทุก tick ไม่ดูอะไร กลยุทธ์ B ป้อนเมื่อบิตครบแล้วล้างบิต
- ท่าที่ 3 watchdog จำลองนับต่อ ถ้าเกินเวลาพิมพ์ว่ารีเซ็ต พร้อมชื่อ task ที่ขาด check in
ลองแก้แล้วทายก่อนรัน
- ในกลยุทธ์ B ลบบรรทัด
checkin = 0u;ออก แล้วรัน watchdog ยังจับการค้างได้ไหม เพราะอะไร - เปลี่ยน
WDT_TIMEOUT_TICKSเป็น 1 กลยุทธ์ B ทำอะไร นั่นบอกอะไรเรื่องการเลือกเวลาของ watchdog - ทำให้ task
uiทำงานคืบหน้าแค่ทุก 5 tick (check in ทุก 5 tick) แล้วหาค่าWDT_TIMEOUT_TICKSที่เล็กที่สุดที่ไม่รีเซ็ตผิด
ฝึกเติม
หัวข้อที่มีชื่อว่า “ฝึกเติม”เปิด practice/09_watchdog.c มีช่องให้เติม 5 จุด ผู้ดูแลแบบ check-in และบันทึกการบูตที่รอดข้ามการรีเซ็ต
wd_checkin()ตั้งบิตของ tasksupervisor_poll()ไม่ป้อนถ้ายังขาด tasksupervisor_poll()ล้างบิตหลังป้อนboot_record_update()แยกขยะใน RAM ออกด้วย magicboot_record_update()นับการรีเซ็ตจาก watchdog และเก็บชื่อ task ที่ขาด
gcc -std=c11 -Wall -Wextra -o watchdog practice/09_watchdog.c && ./watchdogลองเองก่อนอย่างน้อย 15 นาที แล้วเปิด solution/09_watchdog.c
สองจุดที่ควรเทียบ: เฉลยทดสอบสาเหตุการรีเซ็ตด้วย & ไม่ใช่ == และคอมเมนต์เตือนว่า |= ใน wd_checkin() ที่หลาย task เรียกพร้อมกันบนบอร์ดจริง
ต้องมี critical section หรือใช้ event group ของ FreeRTOS แทน ซึ่งเป็นความรู้จากบทเรียน 1.3 ที่กลับมาในที่ที่ไม่คาดคิด
เช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”ตอบคำถาม 5 ข้อใน quiz.yaml (บนเว็บไซต์อยู่ท้ายหน้านี้) ครอบคลุมเป้าหมายทั้งสามข้อ ตอบถูกตั้งแต่ 4 ข้อขึ้นไปถือว่าจบบทเรียน
งาน: อ่านสาเหตุการรีเซ็ตของบอร์ดจริงในสถานการณ์ต่าง ๆ แล้วออกแบบการป้อน watchdog ให้แม่แบบ
- ในโปรเจกต์ของคุณ (บน branch ใหม่ ตามบทเรียน 2.3) เพิ่มสามบรรทัดนี้ใน
proj_cm33_ns/main.cทันทีหลังinit_retarget_io();(เอกสาร B1 ของ SDK บอกว่าจากจุดนี้ UART พร้อมแล้ว)build แล้ว flashuint32_t reset_reason = Cy_SysLib_GetResetReason();printf("[RST] reason=0x%08lx%s\r\n", (unsigned long)reset_reason,(reset_reason & CY_SYSLIB_RESET_HWWDT) ? " HWWDT" : ""); - บันทึกค่าที่พิมพ์ในสี่สถานการณ์: ถอดสายเสียบใหม่, กดปุ่มรีเซ็ตบนบอร์ด (ถ้ามี), หลัง
make program, และถอดเสียบอีกครั้งหลังจากนั้น ค่าไหนเป็น 0 ค่าไหนมีบิต และบิตค้างข้ามการรีเซ็ตหรือไม่ (โค้ดนี้ยังไม่ล้างสาเหตุ นั่นเป็นส่วนหนึ่งของสิ่งที่คุณสังเกต) - เทียบค่าที่ได้กับตารางใน
cy_syslib.hข้อไหนตรงกับที่คาด ข้อไหนไม่ตรง เขียนสิ่งที่เห็นจริงแยกจากสิ่งที่อนุมาน - ออกแบบ (บนกระดาษ) การเปิด hardware watchdog ให้แม่แบบ: ระบุ task ที่ต้อง check in อย่างน้อยสามตัวจาก task ที่แม่แบบสร้าง
(ดู
main()ใน proj_cm33_ns/main.c) รอบที่ช้าที่สุดของแต่ละตัว เวลาของ watchdog ที่เลือกพร้อม margin และจุดที่วางผู้ดูแล (หลักสูตรนี้ยังไม่ได้ทดสอบการเปิด hardware watchdog บนแม่แบบนี้ ถ้าคุณลองจริง ให้ทำบน branch แยก และบันทึกผลทั้งหมด รวมผลต่อการดีบัก)
หลักฐานที่เก็บไว้ใน portfolio: diff ของการแก้ main.c ตารางค่าสาเหตุการรีเซ็ตทั้งสี่สถานการณ์พร้อมคำอธิบาย และแบบร่างการออกแบบ watchdog ข้อ 4
- อ่านหัวข้อ Functional Description, Clearing WDT และ Reset Detection ในหัวไฟล์ของ
cy_wdt.hแล้วคำนวณว่าถ้าใช้ match value แบบในเอกสาร ต้องป้อนบ่อยแค่ไหน - เปรียบเทียบ watchdog แบบ window (ห้ามป้อนเร็วเกินไปด้วย) กับแบบธรรมดา ใน Watchdog timer (Wikipedia) แบบ window จับบั๊กอะไรได้เพิ่ม
บทถัดไป: บทเรียน 4.4 DMA
สะท้อนคิด
หัวข้อที่มีชื่อว่า “สะท้อนคิด”- ระบบที่คุณเคยใช้ ระบบไหนที่ “บูตใหม่เองเป็นบางครั้ง” ตอนนี้คุณจะถามผู้พัฒนาว่าอะไร
- ถ้า watchdog ช่วยให้ผลิตภัณฑ์ดูเหมือนไม่เคยค้าง ทีมจะรู้ได้อย่างไรว่ามีบั๊กที่ต้องแก้
แหล่งอ้างอิง
หัวข้อที่มีชื่อว่า “แหล่งอ้างอิง”- SDK: cm55/edge_ai/08_deepcraft_link.c (deepcraft_task_watchdog)
- SDK: cycfg_clocks.c ของ BSP (การใช้ Cy_SysLib_GetResetReason และ Cy_WDT_Unlock)
- Infineon mtb-pdl-cat1 (Peripheral Driver Library) @ release-v3.24.0
- Watchdog timer (Wikipedia)
คำถามทบทวน
ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย
-
ถ้าเฟิร์มแวร์ไม่ป้อน hardware watchdog ภายในเวลาที่ตั้ง จะเกิดอะไร (เป้าหมายข้อ 1)
- watchdog พิมพ์ข้อความเตือนลง console แล้วรอต่อ
- ชิปถูกรีเซ็ต และสาเหตุถูกบันทึกให้อ่านได้ด้วย Cy_SysLib_GetResetReason() หลังบูต
- task ที่ค้างถูกลบทิ้งอัตโนมัติ ส่วนที่เหลือทำงานต่อ
- ไม่มีอะไรเกิดจนกว่าจะถอดไฟ
ดูเฉลย
คำตอบ: B. ชิปถูกรีเซ็ต และสาเหตุถูกบันทึกให้อ่านได้ด้วย Cy_SysLib_GetResetReason() หลังบูต
watchdog รีเซ็ตทั้งชิป ไม่เลือก task เอกสาร cy_wdt.h แนะนำให้ใช้ Cy_SysLib_GetResetReason() ตรวจว่า WDT เป็นสาเหตุหรือไม่ (บิต CY_SYSLIB_RESET_HWWDT)
-
ข้อใดเป็นข้อควรระวังเมื่อเลือกเวลาของ watchdog ตามเอกสารของ PDL (เลือกได้หลายข้อ) (เป้าหมายข้อ 1)
- เวลาของ watchdog ต้องยาวกว่าเวลาบูตของชิป
- oscillator ความถี่ต่ำอาจคลาดได้มาก ต้องเผื่อ margin
- การเปลี่ยนค่าของ WDT มีผลทันทีในคำสั่งถัดไปเสมอ
- ยิ่งสั้นยิ่งดี เพราะจับการค้างได้เร็ว
ดูเฉลย
คำตอบ: A. เวลาของ watchdog ต้องยาวกว่าเวลาบูตของชิป · B. oscillator ความถี่ต่ำอาจคลาดได้มาก ต้องเผื่อ margin
เอกสารเตือนว่า WDT reset อาจเกิดเร็วกว่าเวลาบูต ความแม่นยำของ oscillator ต้องเผื่อ และการเปลี่ยนค่ามีผลช้าไปสองสามจังหวะ เวลาที่สั้นเกินจะทำให้รีเซ็ตผิดเมื่อระบบแค่ช้า
-
ทำไมการป้อน watchdog ใน ISR ของ timer จึงเป็นกับดัก (เป้าหมายข้อ 2)
- เพราะ ISR ป้อน watchdog ไม่ได้ทางเทคนิค
- เพราะ ISR ของ timer ยังทำงานแม้ task ทุกตัวค้าง watchdog จึงไม่เคยรีเซ็ตทั้งที่ระบบตายแล้ว
- เพราะทำให้ timer ช้าลง
- เพราะ watchdog จะรีเซ็ตทุกครั้งที่ interrupt เกิด
ดูเฉลย
คำตอบ: B. เพราะ ISR ของ timer ยังทำงานแม้ task ทุกตัวค้าง watchdog จึงไม่เคยรีเซ็ตทั้งที่ระบบตายแล้ว
คนป้อนต้องพิสูจน์ว่าระบบคืบหน้า ISR ของ timer พิสูจน์ได้แค่ว่า interrupt ยังทำงาน การจำลองในบทนี้แสดงว่ากลยุทธ์นี้ไม่รีเซ็ตเลยแม้ task ค้างไป 60 tick
-
เรียงขั้นของรูปแบบ check-in กับผู้ดูแลหนึ่งคนในหนึ่งรอบ (เป้าหมายข้อ 2)
- ผู้ดูแลล้างบิตทั้งหมดสำหรับรอบหน้า
- แต่ละ task ตั้งบิตของตัวเองเมื่อทำงานคืบหน้าจริง
- ผู้ดูแลเรียก Cy_WDT_ClearWatchdog()
- ผู้ดูแลตรวจว่าบิตของทุก task ครบ
ดูเฉลย
ลำดับที่ถูก: B. แต่ละ task ตั้งบิตของตัวเองเมื่อทำงานคืบหน้าจริง → D. ผู้ดูแลตรวจว่าบิตของทุก task ครบ → C. ผู้ดูแลเรียก Cy_WDT_ClearWatchdog() → A. ผู้ดูแลล้างบิตทั้งหมดสำหรับรอบหน้า
task รายงานความคืบหน้า ผู้ดูแลตรวจว่าครบ ป้อน แล้วล้าง ถ้าไม่ล้าง บิตเก่าจะป้อนแทน task ที่ตายไปแล้วตลอดไป
-
ออกแบบให้รู้ว่า watchdog รีเซ็ตเมื่อไรและเพราะ task ไหน ข้อใดควรทำ (เลือกได้หลายข้อ) (เป้าหมายข้อ 3)
- อ่าน Cy_SysLib_GetResetReason() ตอนบูต ทดสอบบิต CY_SYSLIB_RESET_HWWDT ด้วย & แล้วล้างด้วย Cy_SysLib_ClearResetReason()
- ให้ผู้ดูแลเขียน task ที่ยังไม่ check in ลงบันทึกใน section .noinit ทุกรอบ พร้อม magic number
- เก็บบันทึกไว้ในตัวแปร .bss ธรรมดา เพราะ startup ไม่แตะมัน
- นับจำนวนครั้งที่ watchdog รีเซ็ต แล้วพิมพ์หรือส่งออกตอนบูต
ดูเฉลย
คำตอบ: A. อ่าน Cy_SysLib_GetResetReason() ตอนบูต ทดสอบบิต CY_SYSLIB_RESET_HWWDT ด้วย & แล้วล้างด้วย Cy_SysLib_ClearResetReason() · B. ให้ผู้ดูแลเขียน task ที่ยังไม่ check in ลงบันทึกใน section .noinit ทุกรอบ พร้อม magic number · D. นับจำนวนครั้งที่ watchdog รีเซ็ต แล้วพิมพ์หรือส่งออกตอนบูต
.bss ถูกล้างเป็นศูนย์ตอนบูต บันทึกจึงหาย ต้องใช้ .noinit ที่ linker script ของ SDK เตรียมไว้ และต้องมี magic เพราะหลังเปิดไฟครั้งแรกค่าใน .noinit เป็นขยะ สาเหตุการรีเซ็ตเป็นชุดบิตจึงทดสอบด้วย &
อ้างอิงบทเรียนนี้
ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน
"Watchdog" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
ข้อความอ้างอิงภาษาอังกฤษ: "The watchdog" 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/embedded-c-foundations/m04-peripherals/l03-watchdog/
บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/tesaiot/tesaiot-pse84-devkit-sdk/tree/ef72c1b658178eee8c38b1e47d28b006f80a59b5 · SDK examples and docs are linked at this commit, not copied into this course. Lessons quote short excerpts (at most 25 lines) with a link to the file at this commit and the credit (Apache-2.0, tesaiot-pse84-devkit-sdk).
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA