สอบถามการใช้ timer เพื่อให้ได้ 1 วินาทีคะ

  • 16 Replies
  • 10111 Views
*

Offline manaw

  • **
  • 35
    • View Profile
ตอนนี้ดูในหนังสือใช้ crystal 8MHz โดยใช้ timer 0 คะใช้ปรีสเกลที่ 1024 แต่ทำตามในหนังสือแล้วจะได้แค่ 983.04 วินาทีคะ
จะทำยังงัยให้ได้ 1 วินาทีพอดีคะหรือต้องไปใช้ timerอื่น (เพิ่งหัดเล่นคะเลยไม่ค่อยเข้าใจ)

*

Offline JMew

  • ***
  • 180
  • เจ๊มารอค่ะ
    • View Profile
บวกเพิ่มเข้าไปอีกสิคะ แต่คิดว่าเป๊ะ ๆ คงไม่ได้ น่าจะได้ใกล้ ๆ มากกว่า

สมมติ ใช้ clock 8MHz แล้ว clock DIV8 หนึ่ง instruction = 1E-6 วินาที

Timer0 prescaler = 1 หนึ่ง overflow = 256 ครั้ง ทำให้เกิด overflow 3906 ครั้ง = 0.999936 วินาที

แต่

ถ้าเขียน code ให้บวกสะสม เช่น ใช้ Tcount เป็น integer แล้ว increment Tcount ทุกครั้งที่เกิด Timer0_overflow จะกลายเป็นว่า มีการหน่วง clock cycle ทุกรอบ



:Timer0_Overflow

Incr Tcount   < ตรงนี้ 1 clock cycle = 0.000001 วินาที

if Tcount = 3906 then < ตรงนี้ 2 clock cycles = 0.000002 วินาที // ต้องเปลี่ยนเป็น 3846
   
     if Timer0 = 35 then  < ตรงนี้ 2 clock cycles
          Toggle Tick0 < ตรงนี้ 2 clock cycles = 0.000002 วินาที
          Tcount = 0 < ตรงนี้ 1 clock cycle = 0.000001 วินาที
     end if

end if

NOPE <  ตรงนี้อีก 1 clock cycle


สรุปว่า ในส่วนของ Timer0 Overflow นั้นไม่ได้มีเพียงแต่ code ของ Timer0 overflow เพียงอย่างเดียว กลายเป็นว่ามีคำสั่งนับ คำสั่งเพิ่ม คำสั่งเปรียบเทียบ เยอะแยะ ซึ่งทำให้ Timer0 Overflow ไม่ได้ใช้เพียงแค่ 256 clock cycle เพราะพอถึง OverFlow ปุ๊บ ต้องมาทำบ้าบอที่เป็นเงื่อนไขมันด้วย ซึ่งคิดแบบนี้

เมื่อเกิด overflow แล้ว 256 clock cycle มันจะนับเพิ่ม Tcount อีก 1 clock กลายเป็น 257 clock
ต่อมา มันจะเปรียบเทียบว่า Tcount ของมันใกล้ 1 วินาทีหรือยัง อีก 2 clock cycles กลายเป็น 259 clock เลขมันไม่สวย ก็เลยเพิ่มคำสั่ง NOPE เข้าไปอีก 1 clock cycle รวมเป็น 260 clock cycle จะได้หารลงตัวได้ง่าย อย่างนั้นสรุปว่า เกิด overflow 1 ครั้ง ต้องใช้เวลาถึง 0.000260 วินาที กว่าจะเปรียบเทียบบ้าบออะไรนั่น

อย่างนั้นสรุปว่า ต้องใช้เวลาที่ใกล้เคียงกับ 1 วินาทีมากที่สุด คือ 1/0.000260 ไม่เอาเศษ จะได้ 3846 ครั้ง จะได้ 0.99996 วินาที เศษที่เหลืออีก 0.00004 วินาที หรือ 40 clock cycles จะถูกใช้ในการเปรียบเทียบ 2 clock cycles และ toggle tick อีก 2 clock cycles ใช้ reset Tcount อีก 1 clock cycle รวม 5 clocks หักจาก 40 เหลือ 35 นำเลข 35 ไปใส่ที่ Timer0 ที่เหลือจากการนับ 3846 ครั้ง จะได้เวลาที่ใกล้เคียงกับ 1.000000 วินาทีมากที่สุด ทั้งนี้ขึ้นอยู่กับ calibration ของ XTAL ด้วย
โครงการกะเทยท่องโลก

*

Offline JMew

  • ***
  • 180
  • เจ๊มารอค่ะ
    • View Profile
น้องใ้ช้ AVR เบอร์อะไรคะ? พี่จำได้ว่า มันมีอยู่หลายรุ่นที่มี Asyc. Mode ที่น้องสามารถใช้สัญญาณนาฬิกาภายใน ร่วมกับสัญญาณนาฬิกาภายนอกได้ คือน้องใช้ Internal Clock ที่ 8MHz แล้วใช้ XTAL 32,768 Hz ต่อเข้าที่ XT1 XT2 ได้เลย แล้วใช้ Mode Asyc. เรียกเวลาออกมาได้เลยเป็นวินาที นาที ชั่วโมง วัน เดือน ปี ตามปฏิทินได้เลย
โครงการกะเทยท่องโลก

*

Offline manaw

  • **
  • 35
    • View Profile
ใช้ ATmega16A - PU คะ ใช้ภาษาซีในการเขียนคะพี่ mew

เริ่มจากการเลือก Mode ของ Timer ให้ตรงตามความต้องการก่อนครับ
ในที่นี้ต้องการให้นับ ให้ครบ 1 วินาทีพอดี เพราะฉนั้น Mode ที่จะทำได้ง่ายที่สุดคือ
Clear Timer on Compare or CTC mode ซึ่งใน Mode นี้
Timer จะเริ่มนับจาก 0 จนถึงค่า OCR0 เและเนื่องจาก ใช้ X'TAL 8MHZ
เพราะฉนั้น ค่าที่จะนับ คือ
8MHz / Prescaler = 8,000,000 / 1024 = 7812.5 ซึ่งไม่ลงตัว เพราะฉนั้น เราจะไม่ใช้ค่า 1024
ถ้า Prescaler = 64 จะได้ 8,000,000 / 64 = 125,000 ลงตัวพอดี
แต่ค่าที่ได้เยอะมาก ไม่สามารถใส่ใน OCR0 ได้ เนื่องจาก OCR0 เป็น Register ขนาด 8 Bit
ฉนั้น เราจะให้ Timer0 นับแค่ 1 msec เพราะฉนั้น OCR0 = 125,000 / 1000 = 125
แต่เนื่องจาก Timer เริ่มนับจาก 0 เพราะฉนั้นค่า OCR0 ต้องลบออกอีก 1 จะได้ 125-1 = 124

เมือได้ 1 msec แล้ว จะทำยังไงให้ได้ 1 sec พอดี ก็ให้มับสัก 1000 รอบ ก็ได้ 1 sec พอดี

มาดู CTC Mode กันนิดนึงครับ ที่เลือกใช้ Mode นี้เพราะ เมื่่่อ Timer นับจนถึง OCR0 แล้ว
Timer จะถูก Clear เป็น 0 และจะเริ่ม นับใหม่ไปเรื่อยๆ ทำให้ไม่ต้องกังวล ว่าต้องชดเชยค่าการนับ
แต่ใน ISR ควรจะมี Code ให้น้อยที่สุด (ต้องไม่มากกว่า 124 Cycle)
เพราะหากมากกว่า 124 Cycle ในขณะที่ยังทำงานใน ISR ไม่เสร็จ ก็เกิด Interrupt ใหม่ทำให้ค่าที่นับเพี้ยนไป

เพียงแค่นี้ก็จะได้ 1 วินาที่พอดี

Code: [Select]
static inline void InitTimers(void)
{
// Clear Timer on Compare or CTC mode and Prescaler of 64
TCCR0 |= 1<<WGM01|3<<CS00;
//Enable compare match interrupt
TIMSK |= 1<<OCIE0;
//Set compare value for 1 mSec
OCR0 = (F_CPU / 64 / 1000)-1;
//Enable global interrupts
sei();                 
}

ISR(TIMER0_COMP_vect)
{
if(--msec == 0)
{
msec = 1000;
sec++;
}
}


*

Offline samira

  • ***
  • 128
    • View Profile
ผมเห็นวิธีของท่าน crywolf แล้ว ผมคงจะใช้วิธีนี้ เพราะมัน ชัดเจน จัดแจ้งดี

แต่พอมาดูคำถามของ กขจก วิธีของเขาเพี้ยนไป ~ 17 ms เลยทำให้คิดว่า
จขกท เขาคงต้องการความละเอียดมากพอสมควรทีเดียว เพราะอยากได้ให้มัน เป๊ะๆ 1 sec เลย

กลับไปดู code ของท่าน crywolf อีกที การทำงานของมัน พบว่า

มันต้องทำการ interrupt หนึ่งพันครั้ง ถึงจะได้เวลา 1 sec
ทำให้สงสัยว่า Overhead ของการทำ interrupt ของ Timer 0 นี้ มันจะกินเวลามากกว่า 17 sec หรือเปล่า
อยากจะคิดคร่าวๆว่า ( หากท่่าน crywolf เห็นที่ผิด ตรงใหน ช่วยแก้ด้วยครับ )
 
ดังนั้น ทุกๆ interrupt นี้ ต้องใช้
1 jump interrupt service routine = 1 cycle
2 ต้อง return jump กลับไปยัง main() = 1 cycle
3 ใน ISR นั้น ต้อง execute code อยู่ 3 บรรทัด

อยากถามว่า การที่มันต้อง execute interrupt ตั้ง พันครั้ง กว่าจะเลื่อนไป 1 sec นี้ มันจะกินเวลามากน้อยแค่ใหน

คิดว่า หากจะให้เห็นชัดเจน คงต้องมานับ( disassemble hex file ) ว่า ใน ISR นี้ มันใช้กี่ cycle
แล้ว ค่อยทอนมาเป็นกี msec ???

อีกทางออก อาจจะใช้ TIMER ที่ granularity มันละเอียดกว่า เช่น Timer 1 ซึ่งเป็น 16 bit Timer อาจจะทำให้ได้ตามที่ จขกท ต้องการ

8,000,000/256 ( prescale ) =  32,1250 ( OCR1A/ICR1 ) ซึ่งทำให้ได้ 1 sec ตรงเลย









“ If you’re born poor, it’s not your mistake. But if you die poor, it’s your mistake"
Bill Gates.

ตอบท่าน samira ว่า ท่านไม่ต้องกังวลเรื่อง Overhead เพราะว่า

    Timer ใน CTC Mode จะนับอยู่ตลอดเวลา ในตัวอย่างคือ นับ 0..1..2..3..4..5......120..121..122..123..124..0..1..2....
และเมื่อนับ จาก 124..0 จะเกิด Interrupt ขึ้น และเมื่อกระโดดไปทำงานใน ISR ค่า Timer ก็นับไป เป็น 1
และเมื่อทำงาน Code ที่อยู่ใน Interrupt สมมุติว่า ทำไป 13 Cycle ค่า Timer ก็จะเป็น 14
และ จะนับเป็น 15 เมือกระโดดกลับไปที่ main และจะนับไปจนถึง 124 และ จะเกิด interrupt อีกรอบ
หมายความว่า จะไม่มีรอยต่อของ Timer ในการนับ ทำให้ได้ค่าคงที่ ในการ interrupt ที่ 1 msec
ฉนั้นไม่ว่าจะ Interrupt กี่ครั้ง หรือ ใน ISR จะมี Code กี่บรรทัด ค่า Timer 1 วินาที่ ที่ต้องการก็ยังคงถูกต้อง
โดยไม่จำเป็นต้องชดเชยใดๆ
 

คิดง่ายๆก็คือ วงรอบของการนับจะคงที่อยู่ที่ 125 cycle คือ
ถ้าใน ISR มีคำสั่งอยู่น้อย เช่น 10  Cycle จะเหลือเวลาอีก 125-10 = 115 cycle เพื่อทำงานใน main
ถ้าใน ISR มีคำสั่งอยู่มาก เช่น 100  Cycle จะเหลือเวลาอีก 125-100 = 25 cycle เพื่อทำงานใน main
หรือใน ISR มีเงือนใขอยู่เยอะ ทำให้แต่ล่ะครั้งที่เข้า ISR จะใช้ เวลาไม่เท่ากัน ค่าเวลาที่เหลือเพื่อทำงานใน main ก็จะเปลี่ยนไปด้วย
แค่วงรอบของการ interrupt ก็ยังคงเท่าเดิมคือ 125 cycle