January 03, 2015

Testing AVR Interrupt di 12.000 RPM

Tujuan keisengan kali ini adalah untuk mengetahui seberapa cepat AVR bisa menghandle interrupt dari sebuah pulse generator… generatornya juga pake AVR, jadi akan ada 2 buah AVR di abuse pada postingan ini. Keisengan ini bagian dari pengembangan ignition control seperti Magneti Marelli Digiplex.
Mohon maaf, saya menuliskan post ini dengan asumsi, Pembaca sudah mengetahui dasar pemrograman C, atau lebih spesifik lagi microcontroller Atmel atau Arduino.

Skematik


AVR Mega2560 port B-6 terkoneksi ke Atmega328p port D-3 dengan pull-down resistor 4.7k ohm (4K7).

Pulse Generator, Atmega 2560

Pulse generator dirancang untuk memberikan simulasi trigger wheel ignition Fiat Uno2 yang menggunakan Magneti Marelli Digiplex2.
Trigger wheel ini memiliki 4 buah pole yang tersebar dengan jarak 90° dan sebuah pole yang berjarak kurang lebih 9° dari salah satu pole, yang menandakan bahwa itu adalah spark silinder pertama.
Pulse terjadi pada posisi derajat: 0 -- 9 -- 90 -- 180 -- 270
Langsung saja, kodenya :)
#include <avr/io.h>
#include <avr/interrupt.h>

#define _OUTPORT   6 // Arduino MEGA 2560 pin 12
#define _INDICATOR 7 // Arduino MEGA 2560 pin 13

// volatile diperlukan karena variabel ini akan digunakan dalam 
// interrupt
volatile unsigned int wheel; 

// setup, dieksekusi pertama kali, dan hanya sekali
void setup()
{
  // set Data Direction port B pada bit 6 dan 7 bernilai "1" 
  // atau "output". Menggunakan operasi logika OR
  DDRB |= _BV(_OUTPORT)|_BV(_INDICATOR);

  wheel = 0;

  // reset konfigurasi register TIMER1
  TCCR1A = 0;
  TCCR1B = 0;

  // Counter register TIMER1, nilainya akan bertambah 1 setiap
  // terjadi clock pada TIMER1
  TCNT1 = 0; 

  // Interrupt mask, yang menentukan apakah ISR akan dieksekusi
  // apabila terjadi interrupt. Saat ini di-clear dulu.
  TIMSK1 = 0;

  // enable CTC mode
  TCCR1B |= (1 << WGM12);
  // set output compare value-nya
  OCR1A = 65535;

  // enable interrupt mask Output Compare Interrupt Enable
  // yang menyebabkan ISR dieksekusi apabila TCNT1 nilainya
  // sama dengan OCR1A
  TIMSK1 |= (1 << OCIE1A);

  // start counter tanpa pre-scaler, 
  // yang berarti TCNT1 akan bertambah
  // sesuai dengan clock CPU (16Mhz)
  TCCR1B |= (1 << CS10);
}

// Interrupt Service Routine ini dieksekusi apabila 
// TCNT1 bernilai sama dengan OCR1A.
// TIMER1_COMPA_vect adalah defined interrupt vector yang akan
// dipanggil jika kondisi diatas terjadi dan OCIE1A di set pada
// TIMER1 Interrupt Mask, TIMSK1.
ISR(TIMER1_COMPA_vect)
{
  // reset counter
  TCNT1 = 0;
  wheel++;

  // tracking putaran 360 derajat. Jika overflow, set menjadi 0.
  if (wheel > 359)
  {
    wheel = 0;
  }

  // sesuai pattern asli trigger wheel Fiat Uno 2
  // saat posisi wheel sesuai dengan angka2 dibawah ini
  // _OUTPORT akan ON.
  if ((wheel == 0)||
    (wheel == 9)||
    (wheel == 90)||
    (wheel == 180)||
    (wheel == 270))
  {    
    // Untuk mengaktifkan port, set "1" pada register PORTB, 
    // bit ke-6.
    // Angka 64 ini adalah 2 pangkat 6. Angka 6 adalah _OUTPORT.
    // Operasi logika menggunakan OR atau tanda "|"
    PORTB |= 64; 
  }

  // _OUTPORT akan aktif selama 5 derajat saja, karena itu
  // saat posisi wheel sesuai dengan angka2 dibawah ini
  // _OUTPORT akan OFF
  else if ((wheel==5)||
    (wheel==14)||
    (wheel==95)||
    (wheel==185)||
    (wheel==275))
  {
    // Ini untuk mematikan port, set "0" pada register PORTB, 
    // bit ke-6. Operasi logika menggunakan AND dari NOT 64.
    // 64 = 00100000, NOT 64 atau ~64 = 11011111
    PORTB &= ~64;
  }
}

// main program loop
void loop()
{
  unsigned int V, val;

  // untuk menyimpan bacaan potensiometer melalui ADC0
  unsigned int sensorValue;

  // membaca input dari potensio meter yang dipasang di port 
  // Analog0 (A0)
  sensorValue = analogRead(A0);

  // mapping nilai konversi ADC 0-1023 menjadi RPM 200-12000
  V = map(sensorValue, 0, 1023, 200, 12000);

  // menentukan banyaknya count yang dibutuhkan untuk melakukan 
  // putaran 1 derajad
  val = ((F_CPU*60)/V)/360;

  // update Output Compare Register
  OCR1A = val;

  // Menyalakan LED indikator.
  // angka 128 adalah 2 pangkat 7, angka 7 adalah _INDICATOR
  PORTB |= 128;

  // tunggu 100ms 
  delay(100);

  // lalu matikan LED indikator
  PORTB &= ~128;

  // tunggu 900ms
  delay(900);

  // total menunggu adalah 1000ms atau 1 detik, tujuannya 
  // supaya ADC bisa ambil nafas, sebelum melakukan konversi
  // ADC berikutnya
}

Tooth Logger, Atmega328p

Yak, µC selanjutnya adalah standalone Atmega328p dengan bootloader Arduino, yang akan di-test, di-bully interrupt-nya, untuk membuktikan, bahwa clock 16Mhz bisa menghandle interrupt hingga 12.000 RPM!
Langsung aja kodenya!
#define pinTriggerWheel 3 // Port D-3, pin 5 pada Arduino Uno
#define pinIndikator 5    // Port B-5, pin 19 pada Arduino Uno

volatile unsigned long
  timerOverflow,
  counter
  ;

volatile boolean
  trigger
  ;

void setup()
{
  // setup serial, gunakan BAUD rate paling besar, 
  // atau reporting-nya tidak akan bisa mengejar kecepatan
  // interrupt pada RPM tinggi
  Serial.begin(115200);

  // setup timer
  TCCR1A = 0;
  TCCR1B = _BV(CS10); // no prescaler

  // Uups, disini pake macro _BV(), yang memiliki fungsi 
  // sama dengan bit shift operator: (1 << TOIE1)
  // TIMER1 Overflow Interrupt Enable!
  TIMSK1 = _BV(TOIE1);

  // reset TIMER1 Counter
  TCNT1 = 0;

  // setup external interrupt-1 triggering
  EICRA = _BV(ISC10) | _BV(ISC11); // rising edge trigger
  // atau menggunakan EICRA = _BV(ISC11); untuk falling edge
  // trigger

  // enable interrupt mask untuk trigger ISR
  // ISR INT1_vect akan dieksekusi jika interrupt pada INT1
  // terjadi. 
  EIMSK = _BV(INT1);

  // port setup
  // PD3 pin mode input -- INT1
  // kebalikan dari OUTPUT, INPUT direction menggunakan value
  // "0" pada Data Direction Register. Operasi logika yang
  // digunakan adalah AND NOT, penjelasan ada di contoh kode
  // dibawah.
  DDRD &= ~_BV(pinTriggerWheel);

  // PB5 pin mode output -- onboard LED
  DDRB |= _BV(pinIndikator);
}

// Main loop, looping forever...
void loop()
{
  if (trigger)
  {
    trigger = false;
    Serial.println(counter);
  }
}

// TIMER1 overflow, terjadi apabila TCNT1 nilainya mentok 
// di 65535, lalu kembali ke 0.
ISR(TIMER1_OVF_vect)
{ 
  // Untuk mengakali keterbatasan TCNT1 yang ukurannya 
  // hanya 16-bit (max. 65535) maka setiap terjadi counter 
  // overflow, maka akan dilakukan count-up terhadap variabel 
  // yang ukurannya lebih besar yaitu 32-bit unsigned long.

  // penulisan dalam bentuk heksa desimal, tiap 2 digit 
  // adalah hexa 00-FF. Empat buah angka 0 itu 16-bit
  // count-up dilakukan mulai byte ke 5.
  timerOverflow += 0x10000UL;
}

// ISR ini dieksekusi saat terjadi interrupt request pada 
// pin INT1.
ISR(INT1_vect)
{
  // disable global interrupt, yang berarti jangan 
  // interupsi kode2 krusial yang akan dieksekusi dibawah ini
  noInterrupts();

  // Nah, disini TCNT1 yang 16-bit ditambahkan ke variable yang
  // lebih besar supaya bisa menghasilkan hitungan yg besar
  // hingga 4 miliar (32-bit)
  counter = timerOverflow + TCNT1;

  // clear TIMER1 counter
  TCNT1 = 0;

  // clear overflow counter
  timerOverflow = 0;

  // kasih bendera, bahwa trigger pada INT1 telah terjadi
  trigger = true;

  // eksekusi kode yang krusial selesai, 
  // silakan yang lain boleh interupsi!
  interrupts();
}

Hasilnya

Pola 4 count panjang dan 1 count pendek, terlihat secara teratur pada RPM rendah dan tinggi.