การเขียนโปรแกรมบางอย่าง อาจจะต้องใช้ ตัวนับเวลาเข้ามาเกี่ยวข้อง เช่น นับเวลาถอยหลังเพ่อ ปิดไฟ หรือ อื่นๆ บทความนี้ เรามา สรุปการใช้งาน TIM2 หรือ สรุป วิธีการใช้งาน General-purpose Timer (GPTM), ข้อจำกัด และ ขั้นตอนการเขียนโปรแกรม สำหรับ CH32V003 ได้ดังนี้:
✦ วิธีการใช้งาน General-purpose Timer (GPTM)
ใน CH32V003 ตัว GPTM คือ TIM2 เป็น Timer 16-bit ที่มีความสามารถหลายแบบ ได้แก่:
- Input Capture : จับเวลาเมื่อตรวจพบสัญญาณขาเข้าเปลี่ยนแปลง
- Output Compare : เทียบค่า Timer และส่งสัญญาณเมื่อถึงเวลา
- PWM Generation : สร้างสัญญาณ PWM
- Pulse Counting : นับจำนวนพัลส์
- Incremental Encoder Interface : อ่านค่าจาก Encoder แบบสัญญาณสี่เหลี่ยม
โดย GPTM (TIM2) ทำงานโดย:
- ใช้ Clock จากบัส AHB (ผ่านการตั้ง RCC)
- สามารถตั้ง Prescaler (PSC) เพื่อแบ่งความถี่ Clock ลง
- สามารถตั้ง Auto-reload Register (ARR) เพื่อกำหนดค่าการนับสูงสุด
- สามารถจับ Event ผ่าน Channel (CH1~CH4)
การใช้งานโดยทั่วไปแบ่งเป็น 2 รูปแบบหลัก:
- Counter Mode (Up/Down/Center-aligned)
- PWM Mode (Edge-aligned)
✦ ข้อจำกัดการใช้งาน
- เป็น 16-bit Timer : ค่าสูงสุดที่นับได้คือ 0xFFFF (65535)
- จำนวน Channel จำกัด : มี 4 Channel สำหรับ Capture/Compare
- แบ่งใช้งานร่วมกับ ADC Trigger หรือ DMA ได้บางรูปแบบเท่านั้น
- Clock Source : ใช้จาก System Clock หรือ PCLK1 เท่านั้น (ไม่รองรับ External Clock Input ผ่านขา)
✦ ขั้นตอนการเขียนโปรแกรมใช้งาน GPTM (TIM2)
ตัวอย่าง: การตั้ง Timer ให้นับขึ้นทุก 1ms
- Enable Clock สำหรับ TIM2
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // เปิด Clock ให้กับ TIM2
➔ ฟังก์ชันจาก Peripheral/RCC/rcc.c
- ตั้งค่าโครงสร้าง Timer
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period = 1000-1; // ตั้งค่า Period 1ms (สมมติ Clock หลัง Prescaler คือ 1MHz) TIM_TimeBaseStructure.TIM_Prescaler = 48-1; // ตั้งค่า Prescaler ให้ได้ 1MHz จาก 48MHz (48-1) TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // ไม่แบ่ง Clock เพิ่ม TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // นับขึ้น TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
➔ ฟังก์ชันจาก Peripheral/TIM/tim.c
- เริ่มต้น Timer
TIM_Cmd(TIM2, ENABLE); // เปิดการทำงานของ TIM2
➔ ฟังก์ชันจาก Peripheral/TIM/tim.c
- (Optional) เปิด Interrupt ถ้าต้องการ
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // เปิด Interrupt เมื่อ Counter overflow NVIC_EnableIRQ(TIM2_IRQn); // อนุญาต Interrupt ที่ NVIC
➔ ฟังก์ชันจาก Peripheral/TIM/tim.c
และ core/core_riscv/core_riscv.c
- (Optional) เขียน ISR สำหรับ TIM2
__attribute__((interrupt)) void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) // ตรวจสอบว่าเกิดเหตุการณ์ Update { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // เคลียร์ Flag // เขียนโค้ดที่ต้องการทำเมื่อครบ 1ms ที่นี่ } }
➔ โครงสร้างมาตรฐานของ Interrupt Service Routine (ISR)
✦ สรุปฟังก์ชัน SDK ที่เกี่ยวข้อง
RCC_APB1PeriphClockCmd()
➔ เปิด Clock ให้ TimerTIM_TimeBaseInit()
➔ ตั้งค่าพารามิเตอร์พื้นฐาน TimerTIM_Cmd()
➔ เปิดหรือปิด TimerTIM_ITConfig()
➔ เปิดการใช้งาน Interrupt ของ TimerTIM_GetITStatus()
➔ เช็คสถานะ InterruptTIM_ClearITPendingBit()
➔ เคลียร์สถานะ InterruptNVIC_EnableIRQ()
➔ เปิด Interrupt ระดับ NVIC
✦ หมายเหตุสำคัญ
- ต้องเปิด Clock ของ Timer ก่อนตั้งค่าทุกครั้ง
- ทุกครั้งที่ตั้งค่า Timer ใหม่ ต้อง ปิด Timer ก่อน (Disable) แล้วค่อยตั้งค่า แล้วจึง เปิด Timer อีกครั้ง
- ถ้าใช้ PWM ต้องตั้งค่าเพิ่มเติมเช่น
TIM_OCInitTypeDef
และTIM_OCxInit()
📢 สรุปสุดท้าย:
ถ้าเขียน | ใช้ Attribute | ได้ผลลัพธ์ |
---|---|---|
ต้องการความปลอดภัย เน้น Stack ถูกต้อง 100% | __attribute__((interrupt)) | ใช้ปกติ |
ต้องการความเร็วสูงสุด ลด Latency Interrupt | __attribute__((interrupt("WCH-Interrupt-fast"))) | ต้องเข้าใจว่ามีการลด Stack/Restore บางส่วนเอง |
- ✅ ถ้าอยากทำ Interrupt ธรรมดา → ใช้
__attribute__((interrupt))
- ✅ ถ้าอยากทำ Interrupt ความเร็วสูงมาก (Fast Interrupt) → ใช้
__attribute__((interrupt("WCH-Interrupt-fast")))