Sabtu, 02 November 2013

Akurasi Timer/Counter pada AVR


Beberapa waktu yang lalu ada seorang kawan yang minta diajari membuat timer 1 detik menggunakan mikrokontroler. Kebetulan uC yang dia pakai Atmega8 dengan clock intenal.
Langsung aja tek bikinin pake CodeVision AVR  dengan memanfaatkan interrupt overflow pada timer0.
Pada CodeVision AVR terdapat fasilitas CodeWizard yang mempermudah kita dalam membuat program. Untuk mengaktifkan interrupt overflow timer0 kita tinggal memilih clock source, clock value, Overflow interrupt, dan timer value pada tab timer0.
Untuk membuat timer 1 detik dengan frekuensi clock uC 8MHz internal saya menggunakan parameter timer0 sebagai berikut.
  • Clock source = system clock
  • Clock value = 1000,000 kHz
  • Timer value = 0x9B
  •  Memberi centang pada kotak Overflow interrupt.
Pada proses diatas sebenarnya kita mengubah nilai pada register TCCR0, TCNT0 dan TIMSK. Tiga bit LSB dari register TCCR0 merupakan bit CS0 (Clock select) yang digunakan untuk menentukan frekuensi clock dari timer0.
Nilai bit CS02 s/d CS00 sesuai tabel 1 dibawah.
Tabel.1 Timer/Counter0 Prescaler

Karena menggunakan clock value 1000,000kHz (1MHz) yang berarti 1/8 dari frekuensi clock uC, maka register TCCR0 bernilai 0x02.
TCNT0 merupakan register yang bertugas untuk menghitung pulsa yang masuk kedalam timer0/counter0. Setelah program berjalan maka niai register ini akan bertambah satu setiap clock timernya dan akan mengalami overflow setelah mencapai nilai 0xFF. Ketika register TCNT0 mengalami overflow maka bit TOV0(Timer/Counter0 Overflow Flag) pada register TIFR (Timer/Counter Interrupt Flag Register) akan bernilai 1 dan akan kembali ke-0 jika rutin interrupsi dijalankan. Akan tetapi jika rutin interrupsi tidak diaktifkan maka nilai TOV0 akan tetap set(1) dan harus di clear (0) secara manual.
Nilai TCNT0 dapat diisi dengan nilai 0x00 s/d 0xFF. Nilai ini sebagai inisialisasi nilai timercounter yang kita buat. Pada proses menggunakan codeWizard diatas, saya memasukan nilai timer value 0x9B atau senilai dengan 155 desimal. Ini dimaksudkan untuk mempermudah perhitungan. Jika inisialisasi TCNT0 = 155 dan maksimal nilainya 255 maka nilai TCNT0 hanya akan menghitung sebanyak 100 pulsa. Setiap 100 pulsa timer/counter0 terjadi Overflow pada TOV0.
Memberi tanda centang pada Overflow interrup di CodeWizard dialog akan mengubah nilai register TIMSK(Timer/Counter Interrupt Mask Register). Timer/counter0 hanya memiliki fasilitas interrupt overflow. Jika ini diaktifkan maka Bit TOIE0(Timer/Counter Interrupt Enable) akan bernilai 1 (set). Bit TOIE0 ini berada pada bit ke-0 register TIMSK sehingga jika kita akan mengaktifkan timer0 Overflow interrupt maka nilai register TIMSK adalah 0x01.
Sedangkan untuk mengaktifkan fungsi interrupt global dengan menambahkan #asm(“sei”) yang artinya men-set enable interrupt. Dengan menambahkan perintah ini maka nilai bit I pada register SREG akan bernilai 1. Untuk meng-clear kan bit-I digunakan code #asm(“cli”).
Setelah di-generate maka akan dibuatkan template program sesuai kebutuhan kita. Untuk seting parameter timer0 dan interupsi terdapat pada subrutin void main(void) berikut. 

// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: 1000,000 kHz
TCCR0=0x02;
TCNT0=0x9B;
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x01;
#asm("sei")

Sedangkan subrutin interupsi terdapat pada bagian paling atas program seperti dibawah ini.

// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
// Reinitialize Timer 0 value
TCNT0 = 0x9B;
// Place your code here
}

Kembali ke permasalahan adalah membuat timer 1 detik. Yang pertama saya buat adalah variabel counter dan variabel second yang bertipe integer dan saya tempatkan sebagai variabel global diatas fungsi interrupt karena kedua variabel ini akan saya tempatkan di dalam fungsi interrupt sehingga ketika diakses oleh fungsi interrupt kedua variabel ini telah terdefinisi.
Setelah mendefinisikan variabel yang diperlukan, langkah selanjutnya adalah menghitung nilai 1 detik.
Sebelumnya kita telah mengetahui bahwa setiap 100 hitungan pada register TCNT0 akan terjadi overflow, karena fungsi Overflow interrupt diaktifkan maka setiap100 hitungan terjadi interrupt. Selang waktu terjadinya interrupt adalah 100x(1/fClock Timer). Dengan fClocktimer adalah 1MHz maka periode interupsi adalah 100uS. Pada saat terjadi interupsi saya manfaatkan untuk menambah nilai variabel counter. Dari sini diperoleh penambahan nilai variabel counter setiap 100uS sehingga dalam 1 detik nilai variabel counter akan bernilai 10000. Ketika variabel counter berniali 10000 maka variabel second akan bertambah 1. Dari sini argument saya adalah bahwa variabel second akan berubah setiap 1 detik.
Selengkapnya untuk rutin interupsi yang saya gunakan untuk membuat timer 1 detik adalah sebagai berikut.

int counter,second = 0;
// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
// Reinitialize Timer 0 value
TCNT0 = 0x9B;
// Place your code here
Counter++;
     If(counter==10000){
     Second++;
     Counter = 0;
     }
}

Metode membuat timer 1 detik ini telah saya gunakan semenjak saya belajar uC hingga beberapa waktu yang lalu baik untuk aplikasi yang bersifat edukasi maupun aplikasi yang bersifat komersil.
Akan tetapi beberapa hari kemarin pikiran bijak datang, “Sudah saatnya saya beralih menggunakan aplikasi yang freeware, apalagi untuk kepentingan yang bersifat komersil rasanya tidak etis ketika menggunakan aplikasi bajakan”.
Seperti yang anda ketahui bahwa CVAVR merupakan aplikasi berbayar dari Hpinfotech. Produsen ini meluncurkan produk CVAVR yang free hanya untuk versi Evaluation yang memiliki keterbatasan fitur. Jika ingin yang full version maka harus membayar $XX atau mencari versi bajakanya.
Sedangkan aplikasi AVR yang bersifat freeware dan diluncurkan langsung oleh produsen atmel adalah AVRstudio. Dari situ saya mencoba untuk beralih ke AVRstudio dan WinAVR sebagai eksternal tool untuk mengakses program avr-gcc.exe dan make.exe.
Pada masa transisi ini sedikit mengalami kendala, karena terbiasa memanfaatkan tool codeWizard dari CVAVR sedangkan menggunakan AVRstudio lebih banyak ngetik sendiri.
Tapi berkat AVRstudio inilah saya menemukan kelemahan beberapa kode yang saya andalkan sejak dulu termasuk pembuatan timer 1 detik diatas.
Untuk membuat timer 1 detik menggunakan metode seperti diatas saya memanfaatkan library interrupt.h dari WinAVR. Register yang diatur bernilai sama. Untuk memanfaatkan fungsi interrupt menggunakan fungsi layanan interupsi berikut.

ISR(interrupt_vector){
//kode program
}

List nama vektor interrupt pada win AVR untuk uC Atmega8 adalah sesuai tabel 2 berikut:
Tabel.2 Vector interrupt pada uC ATmega8

Urutan tabel diatas juga merupakan urutan prioritas interrupt pada uC Atmega8. Dengan variabel yang sama maka source code pada rutin interrupt untuk menghitung 1 detik adalah sebagai berikut.

ISR(TIMER0_OVF_vect){
     TCNT0 = 0x9B;
Counter++;
          If(counter==10000){
          Second++;
          Counter = 0;
     }
}

Untuk nilai register TCCR0 dan TIMSK sama seperti halnya menggunakan CVAVR. Sedangkan pada AVRstudio, untuk mengenable kan fungsi interrupt menggunakan macro intruksi sei () dan untuk meng-clearkan menggunakan cli ().
Berikut perbandingan source code program timer 1 detik menggunakan CVAVR dengan AVRstudio dimana nilai variabel second dikeluarkan pada PORTD.

1. Program menggunakan CVAVR
  
/*****************************************************
This program was produced by the
CodeWizardAVR V2.05.3 Standard
Automatic Program Generator
© Copyright 1998-2011 Pavel Haiduc, HP InfoTech s.r.l.
http://www.hpinfotech.com

Project :
Version :
Date    : 30/10/2013
Author  : Dwi Kurniawan
Company : Comutech
Comments:


Chip type               : ATmega8
Program type            : Application
AVR Core Clock frequency: 8,000000 MHz
Memory model            : Small
External RAM size       : 0
Data Stack size         : 256
*****************************************************/

#include <mega8.h>

int count=0;
int second = 0;

// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
// Reinitialize Timer 0 value
TCNT0=0x9B;
// Place your code here
count++;
if(count==10000){
    second++;
    count = 0;
    }

}

void main(void)
{
// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: 1000,000 kHz
TCCR0=0x02;
TCNT0=0x9B;
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x01;
// Global enable interrupts
#asm("sei")

while (1)
      { 
      PORTD=second;
        }
} 

2. Program menggunakan AVRstudio

#include <avr/io.h>
#include <avr/interrupt.h>

int count=0;

int second=0;

ISR(TIMER0_OVF_vect){

      TCNT0 = 0x9B;
      count++;
      if(count==10000){
             second++;
             count=0;
             }
      }
 

int main(void){
      DDRD=0xff;
      TCCR0 = 0x02;
      TCNT0 = 0x9B;
      TIMSK = 0x01;
      sei ();

      while(1){

              PORTD=second;
              }
      return (0);
}


Tampak bahwa kedua program sama persis. Setelah didownload atau disimulasikan menggunakan Proteus (yang ini belum nemu sainganya yang freeware) program berjalan sama persis.
Akan tetapi, dengan menggunakan AVRstudio akhirnya saya menemukan kelemahan dari program timer 1 detik yang selama ini saya gunakan.  Pada AVRstudio terdapat fasilitas debug yang jika dijalankan kita dapat melihat isi dari masing-masing register.
Gambar.1 Tampilan pada saat debuging program

Untuk program timer 1 detik diatas yang perlu diperhatikan adalah register TCNT0. Sebelumnya, saya beranggapan ketika memberikan nilai pada timer value saat menggunakan CodeWizard maka nilai register TCNT0 setelah overflow akan langsung bernilai sesuai dengan yang kita masukan. Sesuai program diatas bernilai 0x9B sehingga saya beranggapan setelah TCNT0 overflow nilai TCNT0 akan kembali ke 0x9B. Ternyata setelah mengamati perilaku register TCNT0 pada debuging AVRstudio nilai TCNT0 akan kembali ke 0x00 setelah mengalami overflow setelah melalui inisialisasi ulang pada rutin interupsi baru program ini bernilai sesuai yang kita masukan. Inisialisasi TCNT0 saya tempatkan pada awal proses intrrupt maka dalam hal ini saya telah kehilangan akurasi sebesar 1 clock timer yang berarti 1us. Untuk menanggulangi ini maka nilai TCNT0 dtambah 1 pada inisialisasi sehingga menjadi 0x9C.
Ternyata sampai sini permasalahan belum selesai, setelah diamati lagi selama perpindahan dari baris ISR ke baris dibawahnya TCNT0 telah berubah nilai menjadi 0x03. 
Gambar 2 Perpindahan dari baris ISR ke baris berikutnya, TCNT0 telah berubah  menjadi 0x03

Hal ini dikarenakan ketika meng-eksekusi fungsi ISR diperlukan 4 siklus clock untuk interrupt acknowledge, 2 siklus clock untuk jump dari vektor interrupt, 2 siklus clock untuk set dan reset TCNT0, 2 siklus clock untuk set dan reset register I, 4 siklus clock untuk operasi RETI (Interrupt return), 2 siklus clock untuk clear TOV0.
Dari analisa saya diatas baru diperoleh 16 siklus clock yang berarti 2 siklus clock timer. Yanng berarti perpindahan nilai TCNT0 yang dapat dideteksi baru 0x02 sedangkan 1 clock timer belum dapat saya deteksi.
Hanya saja sampai disini dapat disimpulkan bahwa metode saya selama ini telah kehilangan akurasi sebanyak 4 clock timer. Jadi interupsi timer0 terjadi setiap 104 us. Ketika membatasi nilai counter 10000 berati selang waktu dari counter = 0 hingga 10000 adalah 104uS x 10000 = 1.04 s.
Untuk mengatasi masalah tersebut dapat dilakukan dengan cara mengubah nilai TCNT0. Jika menghendaki selang waktu terjadinya interupsi 100us maka nilai TCNT0 = 255 – (100 – 4) = 159 atau 0x9F.
Dengan memodifikasi program diatas dan hasilnya memang jauh lebih presisi ketika menambahkan 4 clock timer pada register TCNT0.
Jika menggunakan simulator proteus, dapat diamati perubahan nilai detik dengan waktu eksekusi program yang ditampilkan distatus bar bawah sebelah status bar message.

2 komentar: