AVR -HC-SR04 ile Oransal Kontrol


Zamanlayıcı (timer), kesme (interrupt) kullanımıyla ilgili alıştırma yapmak ve Arduino öğreticilerinin çoğunlukla kullandığı Hc-Sr04' ün AVR ve C ile kullanımını anlatmaya çalışacağım. Hc-Sr04 sensörler ses ile mesafe ölçümü yapmakta kullanılır. İnternette birçok yazı mevcut ama kabaca anlatacağım.
Hc-Sr04
Bilgi sayfasına buradan erişebilirsiniz. Hc-Sr04 3mm hassasiyetinde 2cm ile 400cm arası temassız ölçüm yapabilen bir sensördür. Sensör üstünde 4 adet pin bulunur.
Vcc: +5 volt besleme.
Trig: Sensörün ölçüme başlaması için sinyal gönderilen pin.
Echo: Çıkış pini, high "1" seviyede olduğu süreyi ölçeceğiz.
Gnd: (0 Volt) Negatif besleme.


Sensörün çalışması oldukça basit. Bilgi sayfasına göre ilk yapılması gereken Trig pinini 10us süreyle high "1" yapıp, low "0" yapmaktır. Sonrasında sensör 40kHz frekansında 8 ses dalgası gönderir ve Echo pinini high "1" yapar. Echo pini sensöre ses dönüşü olana kadar high "1" olarak kalır. Echo pininin high "1" olduğu an ve low "0" olduğu an arasında geçen süre sesin bir engele çarpıp geri döndüğü süredir.  Bilgi sayfasına göre yüzey 0,5m² ve daha büyük, ayrıca pürüzsüz olursa daha doğru ölçüm yapılacaktır. Sesin gidiş ve dönüşü arasında geçen süreyi sınırlamak gerekir. Bu yapılmazsa ölçüm sınırının dışına çıkılır ve hatalı sonuç alınır. Ayrıca önlem alınmazsa  döngü içinde tekrar Trig pininin high "1" olması gibi bir durum gerçekleşebilir.
Echo pininin high olduğu sürenin, sesin gidiş dönüş süresi olduğunu ve bu sürenin yarısını almamız gerektiğini unutmamak gerekir. Sesin hızı ortamın yoğunluğuna göre değişir. Katıda suya göre, su içinde havaya göre daha hızlıdır. Havanın sıcaklık ve  yüksekliğe göre yoğunluğu değiştiğinden sesin hızı da değişir. Ben ses hızını 344m/s olarak alacağım. Buna göre ses 1sn de 344 metre gider. Birimleri cm ve us cinsine çevirdiğimizde 0,0344cm/us yani 1us de 0,0344cm yol gider. Yol=hızXzaman formülüne göre işlem yaparak aradaki mesafeyi buluruz. Echo high süresi için kronometre görevi görecek zamanlayıcı birimini kullanacağız.
Timer/Counter1 (TC 1)
Önceki yazıda Timer0 birimini kullandım. Sensöre 400cm ölçüm yaptırmak istediğimizde 11.627us, 1cm için 29us gerekir. (yol=hızXzaman, 400=0,0344Xzaman, zaman= 400/0,0344)
Mikro saniye hassasiyetinde sayabilmeli fakat çok büyük sayaç değerleri olmamalı. 16MHz de prescaler değeri 8 seçilirse en uygun değerleri bulabiliriz. Bu durumda  sayaç değeri limit yani 400cm mesafede  gidiş dönüş için 23.254 gibi bir değer tutmalıdır. Bu ancak 16 bitlik bir zamanlayıcı biriminde yapılabilir. Bu nedenle Timer1 birimini kullanacağız.
TCNT1
TCNT1 kristal veya seçilebilen farklı bir kaynaktan gelen saat darbesiyle artarak sayım yapan registerdir. TCNTL ve TCNTH olarak iki 8 bit registerde veri tutulur.  TCNT1 üst değer olan 65535 0xFFFF e kadar artabilir. Bizim için fazlasıyla yeterli bir değerdir.



Yukarıda TC1 şeması bulunmaktadır. Birimde bulunan kontrol registerleriyle istediğimiz ayarlamaları yapacağız.  Yukarıda bahsettiğim gibi prescaler değerini 8 alacağız. Uygun bir çözünürlük ve kayıt edebileceğimiz büyüklükte değerler vermektedir.


Bu seçim sonucunda 1sn de 16.000.000/8 saat darbesi yani 2.000.000 olacaktır. Bir mikro saniye 2 darbe olacaktır. TCNT1 değerini 2' ye böldüğümüzde "us" cinsinden süreyi buluruz. Prescaler 8 için CS11 isimli bit "1" olmalıdır.
OCR1A
Zamanlayıcı birimini sürenin tespiti yanında sensörün ihtiyaç duyduğu beklemeleri yapması için de kullanacağız. Bunun için OCR1A registerini kullanacağız. TCNT1 gibi 16 bit bir registerdir. OCR1AL ve OCR1AH isimli iki 8 bit registerden oluşur. Timer1 birimiyle istediğimizi yapmak için TCNT1 ile eşleşme olduğunda kesme oluşmalı ve TCNT1 sıfırlanmalıdır. Bunu CTC moduyla yapabiliriz.



Şekilde görüldüğü gibi eşleşmede kesme oluşuyor ve TCNT1 sıfırlanıyor. CTC modu için aşağıdaki şekilde görüldüğü gibi mode 4 seçilmeli bunun için WGM12 "1" yapılmalıdır.


TC1 Kontrol Registerleri
TCCR1A registerinde yer alan bitler bizim seçimlerimizi ilgilendiren bitler olmadığından burada değinmeyeceğim. TCCR1B registeri aşağıdaki gibidir. İstediğimiz ayarlar için WGM12 ve  CS11 "1" yapılacak.



Mesafe Ölçümü ve Kontrol
Bunun için timer1 OCR1A eşleştirme ve Pin değişim  kesmeleri kullanacağız. Zamanlayıcı kesmesi trig pininin high "1" olması ve 10us bu şekilde kalmasını sağlayacak. Bunun sonucunda echo high "1" olacak, bu değişimin olduğu anı belirlemek için pin değişim kesmesini kullanacağız. Sesin geri dönüşüne kadar sayacı başlatıp echo low "0" olana kadar sayacağız. Bu sayım süresini kısıtlayarak hem limit dışı ölçümleri düzelteceğiz hem de ölçüm için gereksiz bekleme yapmayarak döngüyü hızlandıracağız. Echo low "0" olduğunda yine pin değişim kesmesi gerçekleşecek, bu olduğunda sayma işlemini durdurup sayacın değerini kayıt altına alacağız. Böylece bir döngü halinde ölçüm işlemini tamamlayacağız. Sayaç değerini gidiş dönüş süresi ve mikro saniye dönüşümü için 4' e böleceğiz ve ses hızıyla çarparak mesafeyi bulacağız.
Bulduğumuz mesafeyi istediğimiz mesafe ile arasında oluşan farka göre değerlendirip, negatif ise geri pozitif ise ileri yön veriyoruz. Fark oransal (P) katsayı ile çarpılarak motorların hareket eşiği olan PWM değerine eklenerek motorun hızını ayarlıyoruz. Kod içinde yeterli açıklama olduğunu düşünüyorum bu nedenle daha fazla uzatmadan yazıyı burada sonlandırıyorum.

/*
 * Ppwm_hcsr04.c
 *
 * Created: 24.12.2019 20:26:48
 * Author : simsek
 */ 

#define F_CPU 16000000ul
#include <avr/io.h>
#include <avr/interrupt.h>
#define UART_Rx_Boyut 64
#define UART_Tx_Boyut 64
#define UART_Rx_Mask (UART_Rx_Boyut-1)
#define UART_Tx_Mask (UART_Tx_Boyut-1)
#define UART_Bos_On UCSR0B|=(1<<UDRIE0)
#define UART_Bos_Off UCSR0B&=~(1<<UDRIE0)

#define  h_sol PORTD5
#define  h_sag PORTD6
#define  echo PORTD2
#define  trig PORTB4

#define  TRIG_H PORTB|=(1<<trig)
#define  TRIG_L PORTB&=~(1<<trig)
// 16MHz, prescaler 8,1us 2 saat darbesi, OCR1A değeri/2=X us
#define  trig_delay 20 // 10us
#define  limit_sayac 20000 // 172cm/0,0344=5000us TCNT sınır  (gidiş-geliş 20000, tek yön 10000)
#define  limit_mesafe 172 // 172cm/0,0344=5000us TCNT sınır  (gidiş-geliş 20000, tek yön 10000)
#define  olcme_delay 2000 //1ms ölçüm arası bekleme
#define  ses_hizi 0.0344 //0.0344cm/us ile cm veya 0.344mm/us ile mm ölçülür

uint8_t ist_mesafe=12, pkat=5;//pkat oransal katsayı
uint8_t m_durum=0;
int8_t hiz=200, *ptrhiz=&hiz;
int16_t  hata=0,  ppwm=0;
uint16_t  pwm_motor=0, ort_mesafe=0;
volatile uint16_t sayac=0, top_mesafe=0;
volatile uint8_t durum=0, olcme=0; 
volatile uint8_t rx_bas=0,rx_son=0,tx_bas=0,tx_son=0;
volatile uint8_t rx_ring[UART_Rx_Boyut];
volatile uint8_t tx_ring[UART_Tx_Boyut];

void dur();
void ileri_git();
void geri_git();
void sag_git();
void sol_git();
void sag_ileri_git();
void sol_ileri_git();
void sag_geri_git();
void sol_geri_git();
void hiz_1();
void hiz_2();
void hiz_3();
void hiz_4();
void hiz_5();
void pwm_pin(uint8_t _pin,uint8_t _pwm);
void pin_kesme();
void kopek_ayar();
void zaman1_ayar();
void uart_basla(uint32_t baud);
uint8_t uart_oku();
void uart_gonder(uint8_t uData);
void uart_dizi(char*str);
uint8_t uart_gelen();
void (*motor[14])(void)={
 dur,
 ileri_git,
 geri_git,
 sag_git,
 sol_git,
 sag_ileri_git,
 sol_ileri_git,
 sag_geri_git,
 sol_geri_git,
 hiz_1,
 hiz_2,
 hiz_3,
 hiz_4,
 hiz_5,
};
ISR (TIMER1_COMPA_vect){
 switch (durum){
  case 0:// timer ilk kesme oldu
   TRIG_H;// trig pin high
   OCR1A=trig_delay;
   durum++; 
   break;
  case 1: // trig pin bekleme sonrası   
   TRIG_L; //trig pin low 
   OCR1A=limit_sayac;
   durum++;// echo kesme olmazsa, ölçüm yapılamadı   
   break;
  case 2: // ölçüm yapılmadı,
   top_mesafe+=limit_mesafe;
   olcme++;//ortalama için toplanan değer sayısını belirliyoruz
   durum=0;// ölçüm yapılmadı başa dön, else kullanmamak için önceden değişti
   if (!(olcme&0x03)){//4 ölçüm olduğunda
    durum=4;// mesafe ölçüldü bilgisi için 4 oldu
    olcme=0;
    TCCR1B=0x00;// timer durdu
   }       
   break;
  case 3:// ölçüm yapıldı,
   top_mesafe+=(sayac*ses_hizi);//YOL=HIZxZAMAN
   olcme++;////ortalama için toplanan değer sayısını belirliyoruz
   durum=0;// başa dön, else kullanmamak için önceden değişti
   if (!(olcme&0x03)){//4 ölçüm olduğunda
    durum=4;// mesafe ölçüldü bilgisi için 4 oldu
    olcme=0;
    TCCR1B=0x00;// timer durdu
   }
   break;  
  default:
   durum=0;
   break;
 }
}
ISR (INT0_vect){
 if (durum==2){ 
  if (!(PIND&(1<<echo))){// echo low kesme oldu, ölçüm yapıldı
   sayac=TCNT1;// sayaç TCNT ile eşitlendi
   sayac>>=2;// sesin gidiş ve dönüşü ve 2 darbe 1us hesabı için  4'e bölüyoruz, geçen us bulundu
   durum++;// zaman kesmesinde durumu değiştiriyoruz. durum=3
   OCR1A=olcme_delay;
  }else{  
   TCNT1=0;//echo ilk kesme, sesin dönüşü için TCNT sıfırlandı 
  }   
 }  
}
ISR (USART_RX_vect){
 rx_bas=(rx_bas+1) & UART_Rx_Mask;
 rx_ring[rx_bas]= UDR0;
}
ISR (USART_UDRE_vect){
 tx_son=(tx_son+1)&UART_Tx_Mask;
 UDR0=tx_ring[tx_son];
 if (tx_son==tx_bas)
 UART_Bos_Off;
}

int main(void){
 DDRB|=(1<<PORTB0)|(1<<PORTB1)|(1<<PORTB2)|(1<<PORTB3)|(1<<trig); 
 uart_basla(115200);
 pin_kesme(); 
 zaman1_ayar();
    while (1){   
  if (durum==4){        
   durum=0;// döngüye devam etmek için 0 yapıldı
   ort_mesafe=(top_mesafe>>2); //ortalama için 4' e böldük
   top_mesafe=0;    
   hata=ort_mesafe-ist_mesafe; //istenen mesafe ile ölçülen mesafenin farkı   
   ppwm=hata*pkat;   
   if (hata>0){    
    pwm_motor=ppwm+165;
    if (pwm_motor>230){// motor hızını sınırladım
    pwm_motor=230;}
    *ptrhiz=pwm_motor;
    ileri_git();
   }
   if (hata<0){
    pwm_motor=-(ppwm-165);
    if (pwm_motor>230){
    pwm_motor=230;}
    *ptrhiz=pwm_motor;
    geri_git();
   }
   if (hata==0){
    pwm_motor=0;
    *ptrhiz=pwm_motor;
    dur();
   }
   
  TCCR1B|=(1<<WGM12)|(1<<CS11);//timer başladı
  }
  /*if (uart_gelen()){
   m_durum=uart_oku();
   motor[m_durum]();
   }else {
   m_durum=0;
  }*/
    }
}

/////////////////
void zaman1_ayar(){
 TCCR1B|=(1<<WGM12)|(1<<CS11);// CTC  ve prescaler 8, 1us 2 saat darbesi.
 TIMSK1|=(1<<OCIE1A);
 OCR1A=olcme_delay; 
 sei();
}
void pin_kesme(){
 DDRD&=~(1<<echo);//pd2 giriş yapıldı
 EICRA|=(1<<ISC00);// kesme akif
 EIMSK|=(1<<INT0);//echo pin değişim aktif
 sei(); //tüm kesmeler açıldı
}
//////////////// motor
void dur(){
 pwm_pin(h_sag,0);
 pwm_pin(h_sol,0);
 PORTB&=~(0x0F);
}
void ileri_git(){
 pwm_pin(h_sag,hiz);
 pwm_pin(h_sol,hiz);
 PORTB=(PORTB&(0xF0))|(0x03);
}
void geri_git(){
 pwm_pin(h_sag,hiz);
 pwm_pin(h_sol,hiz);
 PORTB=(PORTB&(0xF0))|(0x0C);
}
void sag_git(){
 pwm_pin(h_sag,hiz);
 pwm_pin(h_sol,hiz);
 PORTB=(PORTB&(0xF0))|(0x05);
}
void sol_git(){
 pwm_pin(h_sag,hiz);
 pwm_pin(h_sol,hiz);
 PORTB=(PORTB&(0xF0))|(0x0A);
}
void sag_ileri_git(){
 pwm_pin(h_sag,hiz-(hiz>>3));
 pwm_pin(h_sol,hiz);
 PORTB=(PORTB&(0xF0))|(0x03);
}
void sol_ileri_git(){
 pwm_pin(h_sag,hiz);
 pwm_pin(h_sol,hiz-(hiz>>3));
 PORTB=(PORTB&(0xF0))|(0x03);
}
void sag_geri_git(){
 pwm_pin(h_sag,hiz-(hiz>>3));
 pwm_pin(h_sol,hiz);
 PORTB=(PORTB&(0xF0))|(0x0C);
}
void sol_geri_git(){
 pwm_pin(h_sag,hiz);
 pwm_pin(h_sol,hiz-(hiz>>3));
 PORTB=(PORTB&(0xF0))|(0x0C);
}
void hiz_1(){*ptrhiz=180;}
void hiz_2(){*ptrhiz=190;}
void hiz_3(){*ptrhiz=210;}
void hiz_4(){*ptrhiz=220;}
void hiz_5(){*ptrhiz=230;}
//////////////// pwm
void pwm_pin(uint8_t _pin,uint8_t _pwm){
 if(_pin==5){
  DDRD|=(1<<PORTD5);
  TCCR0A|=(1<<COM0B1)|(1<<WGM00);
  TCCR0B|=(1<<CS00);
  OCR0B=_pwm;
 }
 else if(_pin==6){
  DDRD|=(1<<PORTD6);
  TCCR0A|=(1<<COM0A1)|(1<<WGM00);
  TCCR0B|=(1<<CS00);
  OCR0A=_pwm;
 }
}
//////////////// uart
void uart_basla(uint32_t baud){
 uint16_t baudRate=0;
 baudRate=(F_CPU/baud/16)-1;
 if (baud>=115200){
  baudRate=(F_CPU/baud/8)-1;
  UCSR0A|=(1<<U2X0);
 }
 UBRR0H=(baudRate>>8);
 UBRR0L=baudRate;
 UCSR0B|=(1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0);
 UCSR0C|=(1<<UCSZ01)|(1<<UCSZ00);
 sei();
}
uint8_t uart_oku(){
 rx_son=(rx_son+1) & UART_Rx_Mask;
 return rx_ring[rx_son];
}
void uart_gonder(uint8_t uData){
 tx_bas=(tx_bas+1)&UART_Tx_Mask;
 tx_ring[tx_bas]=uData;
 UART_Bos_On;
}
void uart_dizi(char*str){
 while(*str){
  uart_gonder (*str++);  
 }
}
uint8_t uart_gelen(){
 if (rx_son==rx_bas){
  return 0;
 }
 return 1;
}





AVR - Led Yakma (Delay kullanmadan)

Birçok yerde Arduino eğitimi amacıyla bu başlığı gördünüz. C dili kullanılarak AVR denetleyiciler için de yeterince led örneği var.  Örneklerin alt alta delay kullanılarak yapılması, birden fazla led uygulamasında da aynı şekilde devam etmesini doğru bulmuyorum. Arduino eğitimi için de bu şekilde yapılması uygun değil. Bütün bu örneklerin içinde delay(); kullanmadan yapılanları da var. Bu yazıyı onlardan ayıran yöntem ve yapılan işin anlatılması olacaktır.  Zamanlayıcı (Timer) birimi, millis() ve birçok yerde hayat kurtaran dizilerin (look up table) kullanımını aktarmaya çalışacağım.

Timer/Counter 0 (TC0)

TCNT0

Bu yazıdaTimer0 birimiyle işlem yapacağız. İnternette bolca detaylı anlatım mevcut ama kısaca değinmek istiyorum. TC0 şemasına baktığımızda kristalden gelen saat darbesiyle artarak sayım yapan TCNT0 registeri bulunur. (not: "register" kullanmak ve "rejıstır" yerine "register" olarak okumak daha kolay geliyor)
TCNT0 ile karşılaştırma yapılan OCR0A, OCR0B registerleriyle bu karşılaştırma sonucuna göre çıkış verilen (PWM) OC0A ve OC0B pin bağlantıları görülmektedir. Ayrıca tüm TC0 birimi ayarlamaların yapıldığı TCCR0A ve TCCR0B registerleri ile saat darbesinin değerinin bölündüğü prescaler bulunur.


TCNT0 saat darbesiyle artar ve yapılan ayarlamaya bağlı olarak üst değere (255.0xFF) ulaşıp sıfırlanır veya sıfıra doğru azalır. 16MHz bir kristalin bağlı olduğu denetleyicide 1sn de 16.000.000, 1ms de 16.000 saat darbesi oluşur. 8bit veri tutabilen TCNT0 62.500 defa üst değere ulaşır. Bu çok büyük bir rakam olduğundan Prescaler ile gelen saat darbesini bölerek okunabilir değerlere getireceğiz.


Prescaler tablosunda gördüğünüz gibi 1,8,64,256 ve 1024 gibi değerler bulunmaktadır. Bu değerleri kullanarak bölme işlemini yapacağız. Buradaki bölme işlemi 2' nin katları olduğundan bit kaydırma şeklindedir. Önceki yazılarımda bahsettiğim gibi onluk sistemde "10" a bölmek demek basamak değerinin azaltmak (sola virgül kaydırmak/"0" silmek) ve "10" ile çarpmak basamak değerini artırmak (sağa virgül kaydırmak/"0" eklemek) demektir. İkilik sistemde böyledir. "8" e bölmek değerimizi "3" bit sağa kaydırmak demektir. (16)0B 0001 0000 /8= 0B 0000 0010(2)
Bu değerler içinde "64" bizim için uygundur. Küçük olanlar sayıyı yeterince küçültmez (16.000.000/8=2.000.000/sn ve 2.000/ms) ve büyük olanlarda buçuklu değerlere neden olur. (16.000.000/256=62.500/sn ve 62,5/ms) "64" kullandığımızda TCNT0 16.000.000/64= saniyede 250.000 ve milisaniyede 250 saat darbesi alarak artar. Bu değer taşmaya neden olmaz ve tespiti kolay olur.

OCR0A

TCNT0 için uygun bölücü değerini bulduk fakat biz bir önlem almazsak TCNT0 sürekli artarak sıfırlanacaktır. Bize lazım olan "250" darbeyi tespit etmemiz ve TCNT0 sıfırlamamız gerekir aksi halde 1ms  zamanı belirleyemeyiz. Bunu yapmanın yolu TCNT0 ile istediğimiz bir değeri karşılaştırmak, sonuca göre işlem yapmaktır. Bu karşılaştırma işlemi OCR0A ve OCR0B registerleridir. Bu iki register "çıkış karşılaştırma yazmaçlarıdır." PWM için de bu registerler kullanılır. Biz eşleşme ile kesme oluşsun veTCNT0 sıfırlansın istiyoruz. Bilgi sayfasında TC0 için üç adet kesme görülüyor. Bunlardan ikisi karşılaştırma (OCR0A, OCR0B) ve TCNT0 taşma kesmesidir. Karşılaştırma yapmamız gerektiğinden OCR0A ve OCR0B registerini kontrol eden  TIMER0_COMPA(OCR0A) ve  TIMER0_COMPB(OCR0B) kesmelerini not alıyoruz.
TC0 çalışma modlarını incelediğimizde normal, PWM, fast PWM ve CTC modu görünmektedir. Bu modlar arasında CTC karşılaştırma sonucu zamanlayıcıyı temizleyen mod olarak tam istediğimiz şeyi yapmaktadır.



Grafikte görüldüğü gibi eşleşme olduğunda kesme oluşuyor, kesme bayrağı aktif oluyor ve TCNT0 sıfırlanıyor. Bilgi sayfasında CTC modunda OCR0A kullanıldığı yazılı yine aynı şekilde aşağıdaki tabloda CTC ile OCR0A kullanıldığı görülmektedir. Bu nedenle TIMER0_COMPA kesmesini kullanacağız.


Buraya kadar zamanlayıcı birimini nasıl kullanacağımıza değindik. Buna göre ayarlamaları yapmamız gerekir. Bu ayarlamaları TCCR0A ve TCCR0B kontrol registerlerini kullanarak yapacağız.

TCCR0A-TCCR0B ve TIMER0_COMPA


Yukarıdaki inceleme sonucunda CTC modu kullanacağız, Prescaler değeri "64" olacak ve OCR0A esmesini kullanacağız. CTC modu için yukarıdaki tabloya göre WGM00,WGM01 ve WGM02 isimli bitlerinden WGM01 "1" diğerleri"0" olacaktır. Bilgi sayfasına göre WGM01 TCCR0A registerinin 1 numaralı bitidir. TCCR0A|=(1<<WGM01); şeklinde ayarlanır.


Prescaler değerini "64" yapmak için yukarıdaki tabloya göre CS00, CS01 ve CS02 isimli bitlerinden CS00 ve CS01 bitlerini "1" yapacağız. CS00 ve CS01 bitleri TCCR0B kontrol registerinin "0" ve "1" numaralı bitleridir. TCCR0B|=(1<<CS00)|(1<<CS01); şeklinde ayarlanır.


Kesme için bilgi sayfasına göre TIMSK0 registeri ayarlanmalıdır. Bu registerin "1" numaralı biti OCIE0A isimli bit "1" yapılmalıdır. TIMSK0|=(1<<OCIE0A); şeklinde ayarlanır. Kesme bu şekilde ayarlandı, kesmenin oluşması için TCNT0ve OCR0A eşitliği gereklidir. Yukarıda "250" saat darbesinin 1ms de gerçekleşeceğini bulduk. Eşleşme sonrası TCNT0 "0" olacağından "0" dahil "250" darbe saymalıyız. Buna göre OCR0A değeri "249" almamız gerekir. OCR0A=249; şeklinde ayalarız.


Bu işlemleri main() içinde yaptığımızda taşınabilir olmaz. Farklı bir çalışma yapmak istersek karıştırma ihtimali olabilir. Bunun yerine bir fonksiyon içinde tanımlamak daha doğru olur. Kesme oluşunca yani her 1ms de bir yapılacak olanları da kesme rutini içine yazacağız. Kesme oluşunca belirlediğimiz değişkeni artıracağız.
volatile uint32_t zaman0;
void zaman0_ayar(void){
 TCCR0A|=(1<<WGM01);//CTC modu 
 TCCR0B|=(1<<CS00)|(1<<CS01);// Prescaler 64,16MHz, 1ms 250 saat darbesi.
 TIMSK0|=(1<<OCIE0A);// OCR0A kesme
 OCR0A=249;//250 devirde bir kesme oluşur.
 sei();// tüm kesmeler aktif
}
ISR (TIMER0_COMPA_vect){
 zaman0++;
}

Milisaniye()

Arduino millis(); fonksiyonu TCNT0 taşma oluşunca kesme oluşturur. Bu fazladan "6" saat darbesi hata ve 41,6 ms de bir düzeltme ihtiyacı çıkartır. Arduinoda  bunu PWM için böyle yapılmıştır aksi halde zamanlayıcı PWM ayarlandığında millis(); çalışmayacaktır. Bu kısa açıklama sonrası millis fonksiyonuyla aynı işi yapacak olan milisaniye fonksiyonuna geçelim.
Kesme oluşunca artan değişkenimizi ana programda istediğimiz an çağırıp işlem yapamayız. Bunu yapmak için başka bir değişkene eşitleyeceğiz. Yine kesme içi değişkenimiz sürekli değişeceğinden bu eşitleme öncesi kesmeleri iptal edeceğiz. Kesmeleri öylece kaldırmak faklı kesmeler ayarlıysa sorun çıkartacaktır.

eskiSREG = SREG;// SREG kopyalandı
 cli();// kesmeler kapatıldı
 // yapılacak işlemler
 SREG = eskiSREG; // SREG tekrar yazıldı kesmeler açıldı
bunun yerine
 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
  // yapılacak işlemler
 }

Bunu önlemek için SREG registerini kopyalayıp kesmeleri kaldırmalı ve yine SREG yazılmalıdır. Bunu yapan hazır makro (ATOMIC_RESTORESTATE)olduğundan makro kullanacağım. Bu makro ve benzeri kütüphanelere buradan ulaşabilirsiniz. Artık kesmeleri durdurup güven içinde aktif edebildiğimize göre istediğimiz an sayaç (zaman0) değerini öğrenebiliriz.  Sayaç 1ms de bir artacağından 1000ms de 1sn hesabına göre istediğimiz işlemi yapabiliriz. Örnek olarak 1sn gecikme istediğimizde yapmamız gereken sayacın önceki değeriyle 1000ms sonraki değerinin farkını kullanmamız gerekir. Şimdiki değeri daha büyük olacağından bunu şu şekilde yazarız. Şimdiki değer-önceki değer >=1000 ise 1sn geçmiş demektir. Bir önemli nokta bu sayaç değeri "4.294.967.295" sonrası sıfırlanır. Önceki ve şimdiki değerleri karşılaştıracağımız için bu hataya neden olabilir. Bunun için ya sayaç değeri sıfırlanmalı ya da örnek olarak (uint32_t)(simdiki-onceki) şeklinde imzasız değişkenler ile hesap yapılmalıdır.
Artık 1ms de bir oluşan kesme, bu kesme ile artan bir sayaç ve ana program içinde bu sayaç değerini kopyalamaya yarayacak fonksiyon için gerekli bilgileri gözden geçirdik buna göre milisaniye fonksiyonu ile 1sn de bir artarak bekleme (delay) kullanmadan led yakmamıza olanak veren "if" şartını yazalım. Kod içinde her dönüşüm yaptığımızda bu yoklamayı yapmak zorunda kalacağız. Bunun yerine bir fonksiyon halinde çözmek doğru olacaktır. Sürekli bir kodu tekrar etmemiz gerekiyorsa bunu fonksiyon olarak düzenlemek doğru olur.
uint32_t milisaniye(){
 uint32_t _milis;
 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {//kesmeler açılıp kapatılıyor
  _milis=zaman0;//sayaç değeri kopyalanıyor
 }
 return _milis;//sayaç değeri 1ms de bir artar
}

if ((uint32_t)(simdiki-onceki)>=1000){//1sn de bir if bloku işletilir.
   onceki=simdiki;

Port Kontrol

Atmega328P de D portu (Arduino D0-D7  arası pinler) üstünde işlem yapacağım. Bu pinlerin çıkış ayarı için DDRD registerinde istenen bitleri "1" yapacağız. Tüm port için 0B11111111 veya 0xFF yazarak DDRD|=0B11111111; şeklinde ayarlarız. PORTD registerinde hangi biti "1" yaparsak o bitin gösterdiği pin "1" (HIGH) çıkış verecektir. ("0" LOW) Bunun için örnek olarak PORTD|=0B11110000; şeklinde ayarlarsak ilk 4 pin (D0-D3) sönükken son 4 pin (D4-D7) led yanacaktır. İstenen ledin yanıp sönmesi için XOR (^) operatörü ile toggle  PORTD^=(1<<PORTD7); kullanabiliriz. Led "0" yapmak için AND,NOT (ve ,&-değil,~)(PORTD&=~(1<<PORTD7); ) ve "1" için OR (veya, |)(PORTD|=(1<<PORTD7);) operatörleriyle işlem yapabiliriz.
Port üstündeki ledleri sırayla yakmak için left shift (<<) ve right shift (>>) operatörleriyle sağa veya sol kaydırma kullanabiliriz. PORTD=(PORTD<<1); şeklinde kullanılır. Bu yazım ile bu PORTD<<=1; aynı şeydir. Burada yapılan  0000 0001 şeklinde olan PORTD bit değerlerini her adımda bir bit 0000 0010, 0000 0100 şeklinde sola kaydırılır. PORTD>>=1;  şeklinde de sağa kaydırılır.
Port çıkışını dizileri kullanarak da yönetebiliriz. Bunun için oluşturacağımız 8 bitlik dizinin elemanlarını PORTD registerine eşitleyeceğiz. Bu eşitleme işleminin yine sayaç değerini kullanarak yapacağız. Sayaç değeri belirlediğimiz süre (1000ms) doldukça değişecek böylece istediğimiz beklemeyi yaparak çıkışı düzenleyeceğiz. dizi[0] dediğimizde dizinin ilk elemanı dizi[1] ikinci elemanı işaret eder. Bu şekilde dizinin tüm elemanlarını seçebiliriz. Buraya kadar olan kısım aşağıdaki halde çalışır durumdadır.
/*
 * avr_led_yakma_2.c
 *
 * Created: 13.11.2019 19:26:29
 * Author : haluk simsek
 */ 

#define F_CPU 16000000ul
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <util/delay.h>
volatile uint32_t zaman0=0; //1ms zamanı tuttuğumuz değişken
uint32_t onceki=0, simdiki=0; //sorgulama anındaki zamanın değeri ve önceki sorgulama değeri
uint8_t sayac=0;// birden fazla şkilde led yakmak için kullanılan sayaç
void zaman0_ayar(void);
uint32_t milisaniye();
uint8_t tablo[]={// port çıkışı için oluşturduğumuz dizi
 0x01,//0000 0001
 0x03,//0000 0011
 0x07,//0000 0111
 0x0F,//0000 1111
 0x1F,//0001 1111
 0x3F,//0011 1111
 0x7F,//0111 1111
 0xFF,//1111 1111
 0xFE,//1111 1110
 0xFC,//1111 1100
 0xF8,//1111 1000
 0xF0,//1111 0000
 0xE0,//1110 0000
 0xC0,//1100 0000
 0x80,//1000 0000
 0x00,//0000 0000
 0xF0,//1111 0000
 0x0F,//0000 1111
 0xF0,//1111 0000
 0x0F,//0000 1111
 0x55,//0101 0101
 0xAA,//1010 1010
 0x55,//0101 0101
 0xAA,//1010 1010
 0x55,//0101 0101
 0xAA //1010 1010
 };
ISR (TIMER0_COMPA_vect){// zaman kesmesi
 zaman0++; //kesme oluşunca bir artar
}
int main(void){ 
 zaman0_ayar();// TC0 ayarlaması için
 DDRD|=0xFF; // Portd pinler çıkış 
    while (1){
  if (sayac<8){
   simdiki=milisaniye();
   if ((uint32_t)(simdiki-onceki)>=300){// her koşul için sorgu yapılıyor. 300ms ayarlı, 1000ms= 1sn   
    PORTD^=(1<<PORTD0);// belirlenen saniye aralıkla led yakma
    sayac++;
    onceki=simdiki;
   }
  }
  if (sayac>=8 && sayac<16){// döngü içinde bir koşuldan başka koşula geçmemesi için büyük-küçük koşulu koyuyoruz
   simdiki=milisaniye();
   if ((uint32_t)(simdiki-onceki)>=300){
    PORTD=sayac%2;// sayac değerinin 2' ye bölünmesinden kalan çift sayılarda "0" tek sayılarda "1"dir. 
    /* if (sayac%2){// burada üst satır yerine bu satırlarda kullanılabilir ama üst kısım daha uygun.
     PORTD&=~(1<<PORTD0);
    }else{
     PORTD|=(1<<PORTD0);
    }*/
    sayac++;    
    onceki=simdiki;    
   }
  }
  if (sayac>=16 && sayac<23){
    simdiki=milisaniye();
    if ((uint32_t)(simdiki-onceki)>=300){
     PORTD<<=1; 
     sayac++;     
     onceki=simdiki;
    }
  }
  if (sayac>=23 && sayac<30){
   simdiki=milisaniye();
   if ((uint32_t)(simdiki-onceki)>=300){
    PORTD>>=1;
    sayac++;
    onceki=simdiki;
   }
  }
  if (sayac>=30 && sayac<55){
   simdiki=milisaniye();
   if ((uint32_t)(simdiki-onceki)>=300){
    PORTD=tablo[sayac-30];// sayaç değerini dizinin ilk elemanını göstersin diye çıkartıyoruz
    sayac++;
    onceki=simdiki;
   }
  }
  if (sayac>=55){// döngüyü başa almak için sayaç sıfırlandı
   sayac=0;//sayac sıfırlandı
   PORTD=0;//port sıfırlandı
  }  
    }
}
void zaman0_ayar(void){
 TCCR0A|=(1<<WGM01);//CTC modu 
 TCCR0B|=(1<<CS00)|(1<<CS01);// Prescaler 64,16MHz, 1ms 250 saat darbesi.
 TIMSK0|=(1<<OCIE0A);// OCR0A kesme
 OCR0A=249;//250 devirde bir kesme oluşur.
 sei();// tüm kesmeler aktif
}
uint32_t milisaniye(){
 uint32_t _milis;
 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
  _milis=zaman0;   
 }
 return _milis;
}

Fonksiyonlar

Ledlerin farklı düzenlerde yanması için sayaçla port çıkışını belirli koşullara bağladık. Gördüğünüz gibi her seferinde sorgu yapmak zorunda kaldık. Koşul içinde de fazladan sorgu yaptık. Tüm bunları tekrar tekrar yapmamak için fonksiyon oluşturmalıyız. Bekleme isminde bir fonksiyon oluşturacağım. Yukarıdaki koşul ile aynı sorguyu yapan ve değer döndüren bir fonksiyon olacak. Parametre ile belirlenen süre şartı sağlanmışa "1" sağlanmamışsa "0" döndürecek. While döngüsü içinde bu fonksiyonu sorgulayarak işlem yapacağız.

Bekleme fonksiyonu ile ledlerin nasıl yanacağına karar vereceğimiz koşulları sadeleştirerek bir fonksiyon oluşturacağız. Önceki örnekte bir koşuldan diğerine geçmemesi için büyük-küçük kontrolü yaptık. Bu örnekte sadece küçüktür koşulu kontrol edeceğiz. Ayrı bir fonksiyon olduğu için işi biten koşul sonrası fonksiyondan (return) çıkabileceğiz. Bunu daha önce kullanamazdık while sonsuz döngüsünden çıkardık.
Birçok kişi Arduino eğitimi için sürekli aynı şeyleri yapıyor, anlatıyor ve yazıyor. Bir sürü delay() alt alt yığılıp eğitim veriliyor. Bir amatör olarak bundan rahatsız oluyorum ve bir adım öteye taşımaya çalışıyorum. Profesyonellerin de bu işe el atıp daha ileri taşımaları bilmediğimiz yöntemleri bize öğretmesini bekliyorum. AVR için son hali bu şekilde.
Not: kod içine buton kontrolü koyup ledlerden bağımsız işlem yapabilirsiniz
/*
 * avr_led_yakma.c
 *
 * Created: 10.11.2019 19:53:49
 * Author : haluk simsek
 */ 
#define F_CPU 16000000ul
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <util/delay.h>
volatile uint32_t zaman0=0; //1ms zamanı tuttuğumuz değişken
uint32_t onceki=0, simdiki=0; //sorgulama anındaki zamanın değeri ve önceki sorgulama değeri
uint8_t sayac=0;// birden fazla şkilde led yakmak için kullanılan sayaç
void zaman0_ayar(void);
uint32_t milisaniye();
uint8_t bekleme();
void led_yak();
uint8_t tablo[]={
 0x01, 0x03, 0x07, 0x0F, 0x1F,
 0x3F, 0x7F, 0xFF, 0xFE, 0xFC,
 0xF8, 0xF0, 0xE0, 0xC0, 0x80,
 0x00, 0xF0, 0x0F, 0xF0, 0x0F,
 0x55, 0xAA, 0x55, 0xAA, 0x55,
 0xAA 
 };
ISR (TIMER0_COMPA_vect){// zaman kesmesi
 zaman0++; //kesme oluşunca bir artar
}
int main(void)
{ zaman0_ayar();// TC0 ayarlaması için
 DDRD|=0xFF; // Portd pinler çıkış
 DDRB|=(1<<PORTB5);// PORTB5 (D13) çıkış yapıldı
 DDRB&=~(1<<PORTB4);// PORTB4 (D12) giriş yapıldı, Pull down direnç ile buton bağlanıtısı yapılabilir.
  while (1){
  if (PINB&(1<<PINB4)){// buton basılı ise koşul doğru olur
   PORTB|=(1<<PINB5);//buton basılıyken led yanar
  }else{
   PORTB&=~(1<<PINB5);// buton basılı değilse led söner
  }   
  if (bekleme(100)){    
    led_yak();
    sayac++;    
   }
  }
    }
void zaman0_ayar(void){
 TCCR0A|=(1<<WGM01);//CTC modu 
 TCCR0B|=(1<<CS00)|(1<<CS01);// Prescaler 64,16MHz, 1ms 250 saat darbesi.
 TIMSK0|=(1<<OCIE0A);// OCRA kesme
 OCR0A=249;//250 devirde bir kesme oluşur.
 sei();// tüm kesmeler aktif
}
uint32_t milisaniye(){
 uint32_t _milis;
 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
  _milis=zaman0;   
 }
 return _milis;
}
uint8_t bekleme(uint16_t sure){// bekleme fonksiyonu belirlenen süre boyunca bekleme sağlar
 simdiki=milisaniye();
 if ((uint32_t)(simdiki-onceki)>=sure){
  onceki=simdiki;
  return 1; 
 }
 return 0;
}
void led_yak(){// sayaç değerine göre farklı şekilde led yakar.
 if (sayac<8){
  PORTD^=(1<<PORTD0);
  return;
 }
 if (sayac<16){
  PORTD=sayac%2;
  return;
 }
 if (sayac<23){
  PORTD<<=1;
  return;
 }
 if (sayac<30){
  PORTD>>=1;
  return; 
 }
 if (sayac<55){
  PORTD=tablo[sayac-30];
  return;
 }
 if (sayac>=55){
  sayac=0;
  PORTD=0;
  return; 
 }
}












AVR- Bluetooth Kontollü Araba

Gruplarda birkaç kişinin satışını gördüğüm aslında çok basit olan bluetooth kontrol konusuna değinmek istiyorum. Bir yıl önce çocuklar için yaptığım minik bir araba üstünden konuyu aktaracağım. Benim gibi amatörlerin bunları satın almasına karşıyım. Oturup yapmak yerine, bu tür şeyleri hazır almak öğrenme açısından size bir şey katmaz. Kullanılan modül ve motor sürücü için çok fazla Türkçe kaynak var.Bu nedenle çok detaya girmeyeceğim.


Bluetooth Modül (HC-06)

Kolay ve ucuz ulaşabileceğiniz bir modül (GSM-LORA vb. kıyasla) HC-06, eşleşme çağrısına yanıt verebilen, eşleştirme isteği gönderemeyen bir modüldür. Yalnızca köle modunda çalışır. GSM modülleri gibi AT komut seti kullanılarak gerekli ayarlamalar yapılır. Kullandığım modül HC-06 (ZS-040)(EGBT-046S)


Resim: www.martyncurrey.com/arduino-and-hc-06-zs-040/
Pinler sırasıyla  RXD, TXD, GND, VCC şeklindedir. (State ve En bağlı değil).
RXD: Şekilde görüldüğü gibi 5V ile çalışan bir denetleyicide voltaj bölücü ile bağlanmalıdır. Denetleyicinin Tx pinine bağlantı yapılacak.
TXD: Denetleyicinin Rx pinine bağlanacak. Bu iki pin UART iletişimin yapıldığı pinlerdir, UART konusunu burada anlatmaya çalıştım.
GND: Negatif besleme
VCC: +3,3 ve +6V arası pozitif besleme pinleridir.
State bağlantı durumunu gösteren pindir, "1" olduğunda eşleşme gerçekleşmiştir. EN pini komut veya veri modu seçimi için kullanılan pindir.

AT Komutları

AT komut setini kullanarak ayarlama yapmak isterseniz USB seri çevirici kullanmanız gerekir. Elinizde bu yoksa Arduino kartlarını da kullanabilirsiniz. Bunun için Arduino İde de örnekler, "Software Serial" kütüphanesi içinde bulunan "SoftwareSerialExample" ile Rx-Tx pinlerini bağlayarak yapabilirsiniz. (Voltaj bölücü bağlantıyı unutmayın) Baud rate 9600 olarak ayarlamanız ve seri port ekranı altında yer alan "Satır sonu yok" seçeneğini "NL ve CR ile birlikte" seçeneğini seçmek gerekir.
AT: UART bağlantısı kurulmuşsa "OK" cevabını döndürür.
AT+ADDR: Modül adresi cevap olarak gelir.
AT+NAME: Modül ismi cevap olarak gelir. Değiştirmek isterseniz, "AT+NAME=BLUECAR" şeklinde yazabilirsiniz.
AT+UART: Baud rate, Stop bit sayısı, Eşlik biti şeklinde cevap döndürür. Fabrika ayarları olarak 9600,0,0 cevabı gelir. Baud rate=9600,Stop bit sayısı 1 bit ve eşlik biti yok şeklindedir. Burada sadece baud rate değişikliği yapmanızı tavsiye edeceğim. Bunun için "AT+UART=38400,0,0" şeklinde komut göndermeniz gerekir. Diğer komutlar için Buraya bakabilirsiniz.

Motor Sürücü

L293D elimde olduğundan onu kullandım. 600mA çıkış vermekte, bu kullandığım motorlar için yeterli. Yüksek akım çeken motorlar için farklı sürücü kullanmanız gerekir. L293D birbirinden bağımsız iki motoru iki yönde çalıştırma imkânı verir.
Bilgi sayfasından alıntı bu resimde pinler açıklanmış. Resmin durumuna göre Sol-Sağ motor olarak isimlendireceğim.
1-9 (1,2EN-3,4EN): Bu pinler "1" olduğunda motorlar hareket edecektir. PWM ile motor dönüş hızı kontrol edilebilir.
2-7 (1A-2A) ve  10-15 (3A-4A: Sol-Sağ motor ileri-geri dönüş pinleridir. 2 numaralı pin (1A)  "1" ve 7 numaralı pin (2A) "0" olduğunda sol motor ileri tersi olduğunda (2 "0" ve 7 "1") sol motor geri dönecektir. Sağ motor içinde ilgili pinlerle aynı şey geçerlidir.
3-6(1Y-2Y) ve 11-14(3Y-4Y): Motor besleme pinleridir. 3-6 sol motor ve 11-14 sağ motor.
4-5,12-13 : GND negatif besleme ve soğutucu bağlantısı yapılacak pinlerdir.
VCC1: Entegrenin +5V besleme pinidir.
VCC2: Motorların pozitif besleme pinidir. +4,5 ve +36V arası besleme yapılabilir.

PWM

Kabaca değinmek gerekirse PWM bir çıkışı "1" ve "0" yaparak Analog bir sistem gibi voltaj ayarı yapmamıza olanak verir. "1" ve "0" arası  geçişlerin süresine göre bu ayarlamayı yapabiliyoruz. Atmega 328p de bu işlemler için 3 adet zamanlayıcı ve bu zamanlayıcılara bağlı 6 adet pin bulunur. Ben PD5 (OC0B) ve PD6 (OC0A)  pinlerini kullandım. (Uno D5-D6). Bu pinler Timer 0 zamanlayıcısına bağlıdır ve ilgili registerler ve daha fazlası için Atmega 328p
Timer0 da bir sayaç (TCNT0) üst sınıra kadar çıkar (0xFF)(255), tekrar sıfırlanır veya sıfıra doğru azalır. Bu işlem sürekli tekrar eder. Ayarlayacağımız değer ile eşleştiğinde (OCR0A veya OCR0B) OC0A (PD6) veya OC0B (PD5) pinleri "1" veya "0" olur. PWM için farklı modlar bulunmaktadır. Bunlar pinlerin frekansını değiştirmemize olanak sağlar. Prescaler ile birlikte istenilen frekans değerine ulaşılabilir.  Ben Faz Doğrulamalı PWM Modu kullanacağım. Benim için esas olan "1" ve "0" durumunun birbirine göre yüzdesi olduğundan frekans çok düşük olmadıkça önemi yok.

Timer 0 kontrol register A :

7 (COM0A1)  ve 6 (COM0A0)numaralı bitler OC0A (PD6)pininin OCR0A ile TCNT0 eşleştiğinde ne olacağını seçmemizi sağlayan bitlerdir. Seçilen moda göre ve WGM02 bit durumuna göre değişkenlik gösterir. Faz doğrulamalı mod için tablo:
Tabloda görüldüğü gibi COM0A1 (7) ve COM0A0 (6) bitlerine göre OC0A (PD6)pininin eşleşme olduğunda toggle (tersi), clear (0) veya set (1) seçimini yapmamıza olanak verir.  Örnek olarak OCR0A=64, COM0A1=1 ve COM0A0=0 şeklinde ayarlayıp PWM aktif ettiğimizde %25 ile  1,25V gibi bir çıkış etkisi verecektir. Sadece COM0A1=0 ve COM0A0=1 yaptığımızda  %75 ile 3,75V çıkış etkisi verecektir. Aradaki fark eşleşme oluşunca pinin "1" veya "0" olmasıdır.
5 (COM0B1)  ve 4 (COM0B0)numaralı bitler OC0B (PD5)pininin OCR0B ile TCNT0 eşleştiğinde ne olacağını seçmemizi sağlayan bitlerdir.
1 (WGM01) ve 0 (WGM00) numaralı bitler Timer0 zamanlayıcısının modlarını seçmemizi sağlayan bitlerdir. Tabloda 1 numaralı mod olan PWM Phase Correct için (WGM00) "1" yapılmalıdır.

 

Timer 0 kontrol register B :

7 ve 6 numaralı bitler PWM olmadığında etkindir yani bu konunun dışındadır.  3 numaralı (WGM02) bit TCCR0A register 1 ve 0 numaralı bitler ile birlikte zamanlayıcı mod seçiminde kullanılır. (tablo 19-9).
2,1 ve 0 numaralı (CS02,CS01 ve CS00) bitler Prescaler seçiminin yapılmasını sağlar. 16Mhz ile çalışan denetleyici kristalden gelen her saat darbesinde TCNT0 sayacının değerini bir artırır. Presacler değerini /64 yaptığımızda 64 darbede bir artırır. Böylece saniyede 16.000.000/256 defa üst değere ulaşan TCNT0 16.000.000/64/256=976,56 Hz  üst değere ulaşır. Arduino uno için en yüksek frekans değeri budur. C dili ile aynı denetleyici faz doğrulamalı mod ile 31.372 Hz değerine ulaşır. (Fast Pwm 62.500 Hz).
Kısaca dedim ama oldukça uzun oldu. Umarım sıkıcı veya haddimi aşarak yanlış bilgiler vermiyorum. PWM konusunda register ve bitler bu şekilde. Bu işlemleri yapmak için bu şekilde bir fonksiyon tanımlıyorum.
void pwm_pin(uint8_t _pin,uint8_t _pwm){
  if(_pin==5){
   DDRD|=(1<<PORTD5);
   TCCR0A|=(1<<COM0B1)|(1<<WGM00);
   TCCR0B|=(1<<CS00);
   OCR0B=_pwm;
  }
  else if(_pin==6){
   DDRD|=(1<<PORTD6);
   TCCR0A|=(1<<COM0A1)|(1<<WGM00);
   TCCR0B|=(1<<CS00);
   OCR0A=_pwm;
  } 
}

Android Uygulama

Telefon veya tablet ile yönlendirme yapmak için http://ai2.appinventor.mit.edu/ adresinde uygulama oluşturabilirsiniz.








    

Kodlar

/*
 * blue_araba.c
 *
 * Created: 20.10.2019 14:10:35
 * Author : haluk simsek
 */ 

#define F_CPU 16000000ul
#include <avr/io.h>
#include <avr/interrupt.h>
#define UART_Rx_Boyut 64
#define UART_Tx_Boyut 64
#define UART_Rx_Mask (UART_Rx_Boyut-1)
#define UART_Tx_Mask (UART_Tx_Boyut-1)
#define UART_Bos_On UCSR0B|=(1<<UDRIE0)
#define UART_Bos_Off UCSR0B&=~(1<<UDRIE0)

#define  h_sol PORTD5 //L293-1 sol PWM
#define  h_sag PORTD6 //L293-9 sag PWM
#define  sol_g PORTB3 //L293-7
#define  sag_g PORTB2 //L293-10
#define  sag_i PORTB1 //L293-15
#define  sol_i PORTB0 //L293-2

volatile uint8_t rx_bas=0,rx_son=0,tx_bas=0,tx_son=0;
volatile uint8_t rx_ring[UART_Rx_Boyut];
volatile uint8_t tx_ring[UART_Tx_Boyut];

int hiz=200, m_durum=0;
int *ptrhiz=&hiz;

void dur();
void ileri_git();
void geri_git();
void sag_git();
void sol_git();
void sag_ileri_git();
void sol_ileri_git();
void sag_geri_git();
void sol_geri_git();
void hiz_1();
void hiz_2();
void hiz_3();
void hiz_4();
void hiz_5();
void pwm_pin(uint8_t _pin,uint8_t _pwm);
void uart_basla(uint32_t baud);
uint8_t uart_oku();
void uart_gonder(uint8_t uData);
void uart_dizi(char*str);
uint8_t uart_gelen();
void (*motor[14])(void)={
 dur,
 ileri_git,
 geri_git,
 sag_git,
 sol_git,
 sag_ileri_git,
 sol_ileri_git,
 sag_geri_git,
 sol_geri_git,
 hiz_1,
 hiz_2,
 hiz_3,
 hiz_4,
 hiz_5,
};
ISR (USART_RX_vect){
 rx_bas=(rx_bas+1) & UART_Rx_Mask;
 rx_ring[rx_bas]= UDR0;
}
ISR (USART_UDRE_vect){
 tx_son=(tx_son+1)&UART_Tx_Mask;
 UDR0=tx_ring[tx_son];
 if (tx_son==tx_bas)
 UART_Bos_Off;
}
int main(void){
 DDRB|=(1<<PORTB0)|(1<<PORTB1)|(1<<PORTB2)|(1<<PORTB3);  
 uart_basla(38400);  
    while (1){  
  if (uart_gelen()){
   m_durum=uart_oku();   
   motor[m_durum]();
  }else {
   motor[0]();
  }  
    }
}
/////////////////
void dur(){
 pwm_pin(h_sag,0);
 pwm_pin(h_sol,0);
 PORTB&=~(0x0F); 
}
void ileri_git(){
 pwm_pin(h_sag,hiz);
 pwm_pin(h_sol,hiz);
 PORTB=(PORTB&(0xF0))|(0x03);
}
void geri_git(){
 pwm_pin(h_sag,hiz);
 pwm_pin(h_sol,hiz);
 PORTB=(PORTB&(0xF0))|(0x0C);
}
void sag_git(){
 pwm_pin(h_sag,hiz);
 pwm_pin(h_sol,hiz);
 PORTB=(PORTB&(0xF0))|(0x05);
}
void sol_git(){
 pwm_pin(h_sag,hiz);
 pwm_pin(h_sol,hiz);
 PORTB=(PORTB&(0xF0))|(0x0A);
}
void sag_ileri_git(){
 pwm_pin(h_sag,hiz-(hiz>>3));
 pwm_pin(h_sol,hiz);
 PORTB=(PORTB&(0xF0))|(0x03);
}
void sol_ileri_git(){
 pwm_pin(h_sag,hiz);
 pwm_pin(h_sol,hiz-(hiz>>3)); 
 PORTB=(PORTB&(0xF0))|(0x03);
}
void sag_geri_git(){
 pwm_pin(h_sag,hiz-(hiz>>3));
 pwm_pin(h_sol,hiz);
 PORTB=(PORTB&(0xF0))|(0x0C);
}
void sol_geri_git(){
 pwm_pin(h_sag,hiz);
 pwm_pin(h_sol,hiz-(hiz>>3));
 PORTB=(PORTB&(0xF0))|(0x0C);
}
void hiz_1(){*ptrhiz=180;}
void hiz_2(){*ptrhiz=190;}
void hiz_3(){*ptrhiz=210;}
void hiz_4(){*ptrhiz=220;}
void hiz_5(){*ptrhiz=230;}
//////////////// pwm
void pwm_pin(uint8_t _pin,uint8_t _pwm){
  if(_pin==5){
   DDRD|=(1<<PORTD5);
   TCCR0A|=(1<<COM0B1)|(1<<WGM00);
   TCCR0B|=(1<<CS00);
   OCR0B=_pwm;
  }
  else if(_pin==6){
   DDRD|=(1<<PORTD6);
   TCCR0A|=(1<<COM0A1)|(1<<WGM00);
   TCCR0B|=(1<<CS00);
   OCR0A=_pwm;
  } 
}
//////////////// uart
void uart_basla(uint32_t baud){
 uint16_t baudRate=F_CPU/baud/16-1;
 UBRR0H=(baudRate>>8);
 UBRR0L=baudRate;
 UCSR0B|=(1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0);
 UCSR0C|=(1<<UCSZ01)|(1<<UCSZ00);
 sei();
}
uint8_t uart_oku(){
 rx_son=(rx_son+1) & UART_Rx_Mask;
 return rx_ring[rx_son];
}
void uart_gonder(uint8_t uData){
 tx_bas=(tx_bas+1)&UART_Tx_Mask;
 tx_ring[tx_bas]=uData;
 UART_Bos_On;
}
void uart_dizi(char*str){
 while(*str){
  uart_gonder (*str++);  
 }
}
uint8_t uart_gelen(){
 if (rx_son==rx_bas){
  return 0;
 }
 return 1;
}