การเขียนโปรแกรมบางอย่าง อาจจะต้องใช้ ตัวนับเวลาเข้ามาเกี่ยวข้อง เช่น นับเวลาถอยหลังเพ่อ ปิดไฟ หรือ อื่นๆ บทความนี้ เรามา สรุปการใช้งาน 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)

✦ ข้อจำกัดการใช้งาน

  1. เป็น 16-bit Timer : ค่าสูงสุดที่นับได้คือ 0xFFFF (65535)
  2. จำนวน Channel จำกัด : มี 4 Channel สำหรับ Capture/Compare
  3. แบ่งใช้งานร่วมกับ ADC Trigger หรือ DMA ได้บางรูปแบบเท่านั้น
  4. Clock Source : ใช้จาก System Clock หรือ PCLK1 เท่านั้น (ไม่รองรับ External Clock Input ผ่านขา)

✦ ขั้นตอนการเขียนโปรแกรมใช้งาน GPTM (TIM2)

ตัวอย่าง: การตั้ง Timer ให้นับขึ้นทุก 1ms

  1. Enable Clock สำหรับ TIM2
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // เปิด Clock ให้กับ TIM2

➔ ฟังก์ชันจาก Peripheral/RCC/rcc.c

  1. ตั้งค่าโครงสร้าง 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

  1. เริ่มต้น Timer
TIM_Cmd(TIM2, ENABLE); // เปิดการทำงานของ TIM2

➔ ฟังก์ชันจาก Peripheral/TIM/tim.c

  1. (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

  1. (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 ให้ Timer
  • TIM_TimeBaseInit() ➔ ตั้งค่าพารามิเตอร์พื้นฐาน Timer
  • TIM_Cmd() ➔ เปิดหรือปิด Timer
  • TIM_ITConfig() ➔ เปิดการใช้งาน Interrupt ของ Timer
  • TIM_GetITStatus() ➔ เช็คสถานะ Interrupt
  • TIM_ClearITPendingBit() ➔ เคลียร์สถานะ Interrupt
  • NVIC_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")))