Uzun zaman önce başladığım projemin yazılım kısmını nihayet sonlandırdım. Biçerdöverlerin hasat ettiği ürünü kesen tablanın yerden yüksekliğini kontrol edip buna göre ayarlama yapıyorum. Bunun için 15m bir kabloyla tabla altında bulunan sensörlerden veri almam gerekiyor. Bu işi yaparken profesyonellerin yönlendirmesi ve araştırmalarım sonucunda öğrendiklerimi ve daha önceki yazılarımın güncellenmiş halini paylaşmak istedim. Bir hatırlatma ben bir amatörüm ve yazdıklarım amatörler içindir. Profesyonellere anlatım basit gelebilir. Hatalarım konusunda uyarıları her zaman bekliyorum. Öncelikle ADC konusuyla başlayalım.
ADC (Analog to Digital Converter) Analog
dijital çevirici demektir. MCU (Mikrodenetleyici) "1" ve
"0" lar ile işlem yapar. MCU gibi bakarsak olaya bir musluktan su ya
akıyor ya da akmıyordur. Musluğu az veya çok açmanız önemsiz. Bir alt ve üst
limit geçerli buna göre akıyor veya akmıyor olarak algılar. Ama gerçek hayatta
bu şekilde değil suyun akış miktarı ölçülebilir ve "1-0" kadar keskin
değildir. Su örneği verdim ama elektrik için de aynı şey geçerlidir.
Kullandığım MCU 10bit (0B11 1111 1111=1023) çözünürlükte ADC birimine sahiptir.
Bu ölçüm için referans alınan gerilim değerinin en alt değerinin "0"
üst değerinin "1023" olması demektir. ADC ile okunmak istenen
gerilim, bu değere (1024) bölünen referans gerilim ile karşılaştırılır. Bu
şekilde referansa göre analog değerin dijitale çevrilmiş halini öğrenebiliriz.
Giriş değerinin dijitale çevrilmiş
haliyle girişteki değeri bulmak istersek (ADC*VRef=Vin*1024) formülünü
kullanmamız gerekir. Farklı MCU'ların çözünürlük değerine göre 1024 rakamı
değişir. Buna göre örnek olarak referans voltajı 4 volt ve okunan ADC değeri
512 olduğunda formül 512*4=1024*Vin olur, sonuç Vin=2 volt bulunur.
Atmega328p'nin ADC birimini uzun uzun anlatmayacağım. ADC birimi blok şeması bu şekilde ve ilgili registerler görünmektedir.
Atmega328p'nin ADC birimini uzun uzun anlatmayacağım. ADC birimi blok şeması bu şekilde ve ilgili registerler görünmektedir.
ADMUX
ADC okuma için gerekli ayarlamaları
yapmamız gerekiyor, öncelikle ölçüm için referans kaynağını seçiyoruz. Bunun
için ADMUX registerinin ilgili pinlerini ayarlıyoruz. REFS1,0 Bitleri ile bu
seçimi alttaki tabloya göre yapılır. Bu arada AVcc pini ADc biriminin besleme
ayağıdır. Buraya enerji verilmezse birim çalışmaz.
Datashette bazı bağlantı şemaları var göz atmak isteyebilirsiniz. Tabloda
görüldüğü gibi ilk seçenek AREF pinine verilecek olan voltaja göre ölçüm
yapılmasıdır. İkinci AVcc pini ve son olarak iç 1,1 volt referansıdır. AVcc
pini yani ADC beslemesinin referans olmasını istersek ADMUX =(1<<REFS0)
yapmamız gerekir.
MUX 3,0 bitleri ADC biriminin bağlı
olduğu pinleri seçmemizi sağlar. Alttaki tabloya göre bu bitleri 1-0 yaparak
seçim yapılır. Örnek olarak ADC2 (Arduino A2) seçmek istersek ADMUX
|=(1<<MUX1) yapmamız gerekir. Bunu bir fonksiyonda tanımladığımızda her
seçim için ayrı girmek veya maskelemek gerekir. Analog okuma için 8 adet pin
olduğundan seçmek istediğimiz pin ikilik sayı sistemine göre 0b000 (0)
ile 0b111 (7) arasında olabilir. Bu durumda 0b111 (0x07) ile "AND"
işlemi uygular ve sonucu MUX bitlerine yazarsak kullanışlı bir fonksiyon
oluşturabiliriz. Fonksiyon için altta kodlara bakabilirsiniz.
ADCSRA
ADCSRA diğer ayarlamaları yaptığımız ve
ADC işlemini başlattığımız kontrol registeridir. ADEN "1"
olursa ADC birimi çalışır. ADCSRA|=(1<<ADEN) şeklinde yapılır. ADATE
okuma çevrimini sürekli veya başka bir kesme vb. kaynakla başlaması için
"1" yapmamız gerekir ADCSRA|=(1<<ADATE). Başlatma seçenekleri
ADCSRB registerinde ayarlanır. ADSC biti "1" yapıldığında okuma
başlar. ADCSRA|=(1<<ADSC) şeklinde "1" yapılır okuma başlar ve
bitene kadar "1" olarak kalır. Bu sayede (ADCSRA&(1<<ADSC))
şeklinde çevrimin tamamlandığını öğrenebiliriz. ADIE Kesmeyi
aktifleştiren bit, çevrim sonunda kesme oluşması için "1" yapılır.
ADCSRA|=(1<<ADIE)
ADPS2,0 bitleri ADC ölçümü için kullanılan
saat sinyalini ayarlayan bitlerdir. Datasheet'e göre 50kHz ile 200khz arası
olması uygundur. 16MHz saat frekansını bu aralığa getirmek için
16.000.000/128=125kHz yapmamız gerekir. Böylede ADC saat çevrimi MCU saat
sinyalinin 128 de biri olarak çalışır.
Bir ADC çevrimi ilk okumada 25 sonraki
tek okumalarda 13 ADC saat döngüsü zaman alıyor. Bu ayarlamayla 104us bir okuma
süresidir. Bu ayarla bundan daha hızlı okuma yapamayacaktır. Yukarıdaki tablo frekans bölücü değerlerini göstermektedir. 16MHz den farklı frekans ile
çalışıyorsanız farklı bölme değeri seçebilirsiniz. Benim seçimime göre
ADCSRA|=(1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2) yapmamız gerekir.
ADCSRB
ADCSRB bir diğer kontrol registeridir.
Bu register içinde otomatik veya sürekli çevrim ayarları yapılır. ADTS2,0
bitleri aşağıdaki tabloya göre "1"- "0" yapılarak
ayarlanır. Bir zamanlayıcı veya dış kesme ile okuma işlemini başlatabiliriz.
Benim seçimime göre 6. seçenek olan ADCSRB|=(1<<ADPS0)|(1<<ADPS2)
yapmamız gerekir.
Bütün bu ayarlamaları yapmak için bir
ADC başlatma fonksiyonu ve program içinde çağırıp istenen pinden okuma yapmak
için adc okuma fonksiyonu tanımlıyoruz.
void
adc_basla(){
cli();// kesmeler kaptıldı
ADMUX=(1<<REFS0);
//referans AVcc seçildi
//adc birimi başladı,bölme faktörü
128,adc kesme aktif,otomatik başlatma aktif
ADCSRA=(1<<ADEN)|(1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2)|(1<<ADIE)|(1<<ADATE);
ADCSRB
|=(1<<ADTS2)|(1<<ADTS0);//otomatik başlatma timer1 ocr1b seçili
ADCSRA
|=(1<<ADSC);//ilk okuma yapıldı 25 çevrim
sei();
}
//kesme
veya otomatik dışında ADC okuma için fonksiyon
/*uint16_t
adc_oku(uint8_t _pin){
_pin&=0x07;//7 den büyük
değer girilmesi durumunda sınırlama
ADMUX=(ADMUX & 0xF8) |
_pin;// pin seçimi
ADCSRA
|=(1<<ADSC);//okuma başlatma
while(ADCSRA &
(1<<ADSC));//çevrim bitene kadar bekleme
return (ADCW);// sonuç
registeri dönüş alındı
}*/
Ben okumayı fonksiyon çağırma şeklinde
yapmayacağım. Bir while ile beklemek yerine çevrim bitince kesme oluşsun
istiyorum. Bunun için Timer1 zamanlayıcısını CTC modunda ayarladım ve OCR1B
kesmesini açtım. "TIMER1_COMPB_vect" kesme rutini içine pin seçimi
yapılabilir ama ADC0 ile okuma yapacağımdan bunu da yapmama gerek yok. Okuma ve
sonrası yapılan işlemleri "ADC_vect" kesme rutini içinde yapıyorum.
Zamanlayıcı konularını daha önce yazdığım için burada tekrar etmeyeceğim.
ISR
(TIMER1_COMPB_vect){
//ADMUX=(ADMUX
& 0xF8) | pin; //şeklinde farklı pin seçilebilir.
}
ISR
(ADC_vect){
adc_okunan=ADCW;// bu
satırdan sonrası ana döngü içinde olabilir.
sonuc=(float)((sonuc)*oranH)+(float)(adc_okunan*oranL); // Şenol EKER Devreforum.
adc_giden=(int)sonuc;//gidecek olan veri
veri_toplam=((adc_giden>>8)&0xFF)+(adc_giden&0xFF);//gidecek
verinin 8 bit haldeki toplamı checksum
}
ADC Veri Ortalaması
Ben ve eminim birçok amatör peş peşe ölçüm yapıp aldığı sonuçları bölerek ortalama alma yöntemini uyguluyor. Bunun yerine burada Şenol Beyin bir paylaşımını denedim. Mantık yapılan ölçümü belirlediğimiz oranla çarparak toplama eklemek. Böylece sanki oran kadar sayıyı toplayıp bölmüşüz gibi oluyor. Örneğin 0,1 ile çarptığımızda değerin toplama etkisi onda biri kadar olduğundan on adet veriyi toplayıp bölmek veya hareketli bir ortalama alarak ilk veriyi atıp son veriyi okumakla aynı işi daha hızlı yapıyoruz.
ADC konusu bu kadar artık bu aldığımız
veriyi nasıl aktaracağız şimdi buna bakalım.
RS485
ile Haberleşme
TX
Kesmesi
RS485 daha önce bahsettiğim bir konu
fakat orada UART kullanırken kesmeleri kullanmamıştım. Şimdi kesmelerle daha
sağlıklı bir iletişim için gerekli güncellemeleri aktaracağım. UART
gönderim sırasında 485 sürücüsünün tekrar veri alma pozisyonuna geçmemesi için
bekleme koymuş ve Baudrate düştükçe bekleme süresi uzar demiştim. İletim
hızımıza bağlı olarak giden her bir bit için gereken süre değişir. Biz her Baud
değeri için bir hesaplama yapmak zorunda kalmamalıyız.
Örneğin baud 9600 olduğunda yaklaşık bir
bit 104us de giderken 115200 olduğunda 9us de gider. Start,8 bit veri ve stop
biti eklersek bir veri için birinde 1ms gerekirken diğerinde 87us gerekir. Bu
süreleri hesaplayıp bekleme koyabiliriz. Güvenli taraf için daha fazla koyup
boş yere de bekleyebiliriz. MCU içinde bu iş için yapılmış kesmeler varken
bunlara hiç gerek yok. Daha önce UART gelen ve gönderim kesmelerini
kullanmıştım ama giden kesmesini kullanmamıştım. Bu aralar takıntı haline gelen
Delay kullanmama hastalığım nedeniyle ve değiştirilen baud değerlerinde
uğraşmamak için "USART_TX_vect" kesmesini kullanarak RS485 konusu ve
Uart fonksiyonlarımı güncellemiş olayım. İşte bu kadar basit bir ekleme
ile işlem tamam.
#define
MAX_OUT DDRD|=(1<<PORTD2)//RS485 sürücü pinleri 1-0 yapacak makrolar
#define
MAX_AL PORTD&=~(1<<PORTD2)
#define
MAX_VER PORTD|=(1<<PORTD2)
ISR(USART_TX_vect){
MAX_AL;//tx registeri boş kesmesi max485 veri
gitmeden veri okuma pozisyonuna geçmemesi için açıldı.
}
Veri
Paketi
ADC ile okunan veriyi göndermek
gürültüden korunmak için yapılan tüm emeklerden daha önemli. Siz ne kadar
ortalama alsanız filtre uygulasanız da veri hatalı giderse bir anlamı kalmaz.
Bunun için öncelikle paket oluşturuyoruz. Bu konuda Şenol Eker'e teşekkür
ederim. Bu paketin başında iletişimin başladığını belirten bir önsöz (preamble)
seçiyoruz. Bu 0x55,0xAA,0xCC ve 0x33 gibi birçok şey olabilir. Ben 0x55 ve 0XAA
seçip ilk olarak gönderiyorum. Sonra hangi sensörden geldiğini bilmem gerek
bunun için sensörlere özel bir adres tanımlıyorum. Mesela 0x01 gibi bir sayı
seçiyorum. Her sensör farklı olsun yeterlidir. Adreste gittikten sonra ADC ile
okuduğum değeri 8 bit iki sayıya ayırmam gerekiyor. Çünkü UART ile 8 bitlik
olarak yollayabiliyoruz. Bunun için gidecek veriyi
"(adc_giden>>8)&0xFF" şeklinde kaydırma yapıyoruz. Yön
önemli değil yeter ki diğer tarafta tersi yönde yapılsın.
"adc_giden&0xFF" bu şekilde 10 bit ADC verisini ikiye böldük.
Karşı tarafta hata denetimi için bu iki veriyi topluyoruz.
"veri_toplam=((adc_giden>>8)&0xFF)+(adc_giden&0xFF)" Bu
şekilde karşı tarafta aynı işlemi yaparak karşılaştırma yapacağız. sonucun eşit
çıkması net doğru demek değil ama hatayı tespit için hızlı bir yöntemdir.
CRC16-8 gibi başka hesaplamalara hiç girmek istemedim. Tüm bunları bir araya
getirince bir paket oluşturmuş olduk. Bu paketi ne zaman göndereceğiz?
uart_gonder(0x55);
uart_gonder(0xAA);
uart_gonder(SEN);
uart_gonder((adc_giden>>8)&0xFF);//16bit
sayıyı 8 bit olarak iki parçada gönderiyoruz.
uart_gonder(adc_giden&0xFF);//16bit
sayıyı 8 bit olarak iki parçada gönderiyoruz.
uart_gonder(veri_toplam);//veri
toplamı gönderiyoruz.
Aynı hatta birden fazla sensör varken
sensörler sürekli veri yollarsa herkesin kafasına göre konuştuğu bir sınıf gibi
gürültü olacaktır. Bu paketi sadece istendiği zaman göndereceğiz. Bu istek
içinde yine önsöz ve sensör adresi olmalı ki iletişimin başladığı ve muhatabın
kim olduğu belli olsun. Gelen veriyi kesme içinde kontrol edeceğiz. Paketin
sırası ve verinin doğruluğunu denetleyeceğiz. Bir hata durumunda iletişimi
devam ettirmeyeceğiz.
RX
Kesmesi
UART ile veri geldiğinde kesme
oluşacaktır. Bu her bir veri gelişinde tekrarlanır. Her kesme oluşumunda veriyi
kontrol eden bir switch case yapısı kullanacağım. İlk durumda (case 0:) önsöz
kontrol edilecek 0x55 ise durum değeri bir artırıp sıradaki kesme
oluşumunda 0xAA kontrol edilecek. Bu işlem pakette yer alan veriler kadar
tekrar edilecek. Eğer bir hata varsa durum tekrar en başa alınarak hatalı
iletişimin önüne geçilmiş olacak. Gelen veride bir hata olmadığında
göndereceğimiz veri paketini göndereceğiz. Tüm bu işlemler aşağıdaki gibi
olacaktır.
ISR
(USART_RX_vect){//uart veri alma kesmesi
uint8_t veri=UDR0;
switch (rx_durum){
case 0:
if
(veri==0x55){rx_durum=1;}else{rx_durum=0;}// veri 0x55 ise bir sonraki durum,
değilse başa döner
break;
case 1:
if
(veri==0xAA){rx_durum=2;}else{rx_durum=0;}// veri 0xAA ise bir sonraki durum,
değilse başa döner
break;
case 2:
if
(veri==SEN){// veri SEN adresi ise bir cevap verilir, değilse başa döner
uart_gonder(0x55);
uart_gonder(0xAA);
uart_gonder(SEN);
uart_gonder((adc_giden>>8)&0xFF);//16bit
sayıyı 8 bit olarak iki parçada gönderiyoruz.
uart_gonder(adc_giden&0xFF);//16bit
sayıyı 8 bit olarak iki parçada gönderiyoruz.
uart_gonder(veri_toplam);//veri
toplamı gönderiyoruz.
rx_durum=0;
}
else{rx_durum=0;}
break;
default:
rx_durum=0;
break;
}
}
Sensöre istek yapıldı ve sensör istenen
veriyi bu şekilde gönderdi. Sensör tarafının yapacağı tüm iş bu kadar. Veriyi
alan işleyen veya sadece bir ekrana yazan tarafta benzer işlemler var. Rx
kesmesinde fazladan checksum ve ADC verisini tekrar 16 bit olarak birleştirme
işlemi var. Bu kısmı en altta görebilirsiniz.
Timer
Kesmesi
Veri için istek yapıldıkça sensörler
veriyi gönderecektir. Bu isteğin zamanlanması için de kesme kullanıyorum.
Sensörler 2ms de bir ölçüm yaparken 10ms bir veri istenmesi burada yeterlidir.
Benim projemde farklı hesaplar bulunmakta bunları da eklediğimde 60ms de bir
sorgulama yapıyorum. Bu da ortalama hızı 5km/s olan bir biçerdöverin 3mm de bir
okuma yapması ve 8 cm de bir bu veriyi analiz etmem demek oluyor. Bu arazinin
yüzeyini düşündüğümde oldukça yeterli bir ölçüdür. Kesme oluşunca önsöz ve
kesme adresini gönderiyoruz.
ISR
(TIMER1_COMPA_vect){//her kesme oluşumunda sensöre veri gönderiyoruz.
uart_gonder(0x55);
uart_gonder(0xAA);
uart_gonder(SEN);
}
Sensöre istek yaptık veriyi aldık bir
değişkene yazdık. Bunu istediğimiz şekilde kullanabiliriz. Ben burada hem LCD
ekrana hem UART ile seri ekrana yazacak şekilde paylaşıyorum. Artık bu paylaşım
için yazacağım şeyler öncekilerin tekrarı olacağından burada bitiriyorum. Ana
modül ve sensör tarafını ayrı ayrı altta paylaşıyorum. Umarım faydalı
olacaktır.
Bu
yazı Haluk ŞİMŞEK tarafından yazılmıştır.
/*
* adc_sensor.c
*
* Created: 17.05.2020 13:31:13
* Author : haluk
*/
#define
F_CPU 16000000ul
#include
<avr/io.h>
#include
<avr/interrupt.h>
//////RS
PORT
#define
MAX_OUT DDRD|=(1<<PORTD2)
#define
MAX_AL PORTD&=~(1<<PORTD2)
#define
MAX_VER PORTD|=(1<<PORTD2)
//////uart
makro
#define
UART_Rx_Boyut 2
#define
UART_Tx_Boyut 32
#define
UART_Rx_Mask (UART_Rx_Boyut-1)
#define
UART_Tx_Mask (UART_Tx_Boyut-1)
#define
UART_Bos_On UCSR0B|=(1<<UDRIE0)// uart UDR register boş kesmesi
#define
UART_Bos_Off UCSR0B&=~(1<<UDRIE0)// uart UDR register boş kesmesi
////////////////////////////////////////////////////
#define
SEN 0x01 // sensörler için farklı olacak
#define olcme_aralik 499// ölçüm arası bekleme 2ms 499
#define oranH 0.9// Şenol beyin Devreforum'daki
yazısından aldım.
#define oranL 0.1
//////değişkenler
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];
volatile
uint8_t rx_durum=0, t_durum=0, veri_toplam=0;
volatile
uint16_t adc_okunan=0,adc_giden=0;
volatile
float sonuc=0.0;
//////fonksiyonlar
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
adc_basla();
void
zaman1_ayar();
//////
kesmeler
ISR
(TIMER1_COMPB_vect){
//ADMUX=(ADMUX & 0xF8) |
pin;
}
ISR
(USART_RX_vect){//uart veri alma kesmesi
uint8_t veri=UDR0;
switch (rx_durum){
case 0:
if
(veri==0x55){rx_durum=1;}else{rx_durum=0;}// veri 0x55 ise bir sonraki durum,
değilse başa döner
break;
case 1:
if
(veri==0xAA){rx_durum=2;}else{rx_durum=0;}// veri 0xAA ise bir sonraki durum,
değilse başa döner
break;
case 2:
if
(veri==SEN){// veri SEN adresi ise bir cevap verilir, değilse başa döner
uart_gonder(0x55);
uart_gonder(0xAA);
uart_gonder(SEN);
uart_gonder((adc_giden>>8)&0xFF);//16bit
sayıyı 8 bit olarak iki parçada gönderiyoruz.
uart_gonder(adc_giden&0xFF);//16bit
sayıyı 8 bit olarak iki parçada gönderiyoruz.
uart_gonder(veri_toplam);//veri
toplamı gönderiyoruz.
rx_durum=0;
}
else{rx_durum=0;}
break;
default:
rx_durum=0;
break;
}
}
ISR
(USART_UDRE_vect){ //uart veri
gönderim kesmesi
tx_son=(tx_son+1)&UART_Tx_Mask;
UDR0=tx_ring[tx_son];
if (tx_son==tx_bas)
UART_Bos_Off;
}
ISR(USART_TX_vect){
MAX_AL;//tx registeri boş
kesmesi max485 veri gitmeden veri okuma pozisyonuna geçmemesi için açıldı.
}
ISR
(ADC_vect){
adc_okunan=ADCW;// bu
satırdan sonrası ana döngü içinde olabilir.
sonuc=(float)((sonuc)*oranH)+(float)(adc_okunan*oranL); // Şenol beyin Devreforum'daki
yazısından aldım.
adc_giden=(int)sonuc;//gidecek
olan veri
veri_toplam=((adc_giden>>8)&0xFF)+(adc_giden&0xFF);//gidecek
verinin 8 bit haldeki toplamı checksum
}
int
main(void){
MAX_OUT;//rs 485 port çıkış
yapıldı
MAX_AL;//rs 485 veri alma
durumunda
adc_basla();// adc başladı
uart_basla(115200); //uart başladı
zaman1_ayar();// timer1
başladı
while (1){
}
}
//////
uart
void
uart_basla(uint32_t baud){
cli();
uint16_t baudRate=0;
baudRate=(F_CPU/baud/16)-1;
if
(baud>=115200){//115200 ve üstünde U2X 1 yapılıyor.
baudRate=(F_CPU/baud/8)-1;
UCSR0A|=(1<<U2X0);
}
UBRR0H=(baudRate>>8);
UBRR0L=baudRate;
UCSR0B|=(1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0)|(1<<TXCIE0);
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){
MAX_VER;
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;
}
//////adc
void
adc_basla(){
cli();
ADMUX=(1<<REFS0);
ADCSRA=(1<<ADEN)|(1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2)|(1<<ADIE)|(1<<ADATE);
ADCSRB
|=(1<<ADTS2)|(1<<ADTS0);
ADCSRA |=(1<<ADSC);
sei();
}
//////timer1
void
zaman1_ayar(){
cli();
TCCR1B|=(1<<WGM12)|(1<<CS11)|(1<<CS10);//
CTC ve prescaler 64
TIMSK1|=(1<<OCIE1B);
OCR1A=olcme_aralik;
sei();
}
/*
* adc_ana_modul.c
*
* Created: 17.05.2020 14:16:02
* Author : haluk
*/
#define
F_CPU 16000000UL
#include
<avr/io.h>
#include
<avr/interrupt.h>
#include
<util/delay.h>
#include
<stdio.h>
//RS485
#define
MAX_OUT DDRD|=(1<<PORTD2)
#define
MAX_AL PORTD&=~(1<<PORTD2)
#define
MAX_VER PORTD|=(1<<PORTD2)
//uart
#define
UART_Rx_Boyut 2
#define
UART_Tx_Boyut 32
#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)
//lcd
#define
DATA_PORT PORTD
#define
CMD_PORT PORTB
#define
EN_ PORTB1
#define
RS_ PORTB0
#define
DATA_OUT
DDRD|=(1<<PORTD4)|(1<<PORTD5)|(1<<PORTD6)|(1<<PORTD7)
#define
CMD_OUT DDRB|=(1<<RS_)|(1<<EN_)
#define
EN_HIGH CMD_PORT|=(1<<EN_)
#define
EN_LOW CMD_PORT&=~(1<<EN_)
#define
RS_HIGH CMD_PORT|=(1<<RS_)
#define
RS_LOW CMD_PORT&=~(1<<RS_)
//lcd
komutlar
#define
LCD_CL lcd_kmt(0x01)
#define
LCD_HOME lcd_kmt(0x02)
#define
LCD_NSCR_RL lcd_kmt(0x04)
#define
LCD_SCR_RL lcd_kmt(0x05)
#define
LCD_NSCR_LR lcd_kmt(0x06)
#define
LCD_SCR_LR lcd_kmt(0x07)
#define
LCD_DOFF lcd_kmt(0x08)
#define
LCD_DON lcd_kmt(0x0C)
#define
LCD_DBON lcd_kmt(0x0D)
#define
LCD_DCON lcd_kmt(0x0E)
#define
LCD_DCBON lcd_kmt(0x0F)
#define
LCD_CR_L lcd_kmt(0x10)
#define
LCD_CR_R lcd_kmt(0x14)
#define
LCD_SC_L lcd_kmt(0x18)
#define
LCD_SC_R lcd_kmt(0x1C)
#define
LCD_4L1 lcd_kmt(0x20)
#define
LCD_4L2 lcd_kmt(0x28)
//////sensör
makro
#define
SEN 0x01 // sensör adres
#define olcme_aralik 2499 // okuma arası bekleme 10ms
2499
////////////////////////////////////////////////////
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];
volatile
uint8_t rx_durum=0,veri_toplam=0;
volatile
uint16_t gelen_adc, sen_adc=0,sayac=0;
char
str_sen_adc[10],str_sayac[10];//sayac hatalı veri sayısını tutar
volatile
uint8_t veri_geldi=0;
//////fonksiyonlar
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
lcd_basla();
void
lcd_data(uint8_t gelen);
void
lcd_kmt(uint8_t cmd);
void
lcd_yaz(uint8_t data);
void
lcd_dizi (char *str);
void
lcd_git(uint8_t x, uint8_t y);
void
zaman1_ayar();
ISR
(TIMER1_COMPA_vect){//her kesme oluşumunda sensöre veri gönderiyoruz.
uart_gonder(0x55);
uart_gonder(0xAA);
uart_gonder(SEN);
}
ISR
(USART_RX_vect){//uart veri alma kesmesi
uint8_t veri=UDR0;
switch (rx_durum){
case 0:
if
(veri==0x55){rx_durum=1;}else{rx_durum=0;}// veri 0x55 ise bir sonraki durum,
değilse başa döner
break;
case 1:
if
(veri==0xAA){rx_durum=2;}else{rx_durum=0;}// veri 0xAA ise bir sonraki durum,
değilse başa döner
break;
case 2:
if
(veri==SEN){rx_durum=3;}else{rx_durum=0;}// veri SEN ise bir sonraki durum, değilse başa döner
break;
case 3:
gelen_adc=(veri<<8);//
gelen veriyi 8 bit kaydırıp geçici değişkene yazdık
veri_toplam=veri;//veri
toplama ekledik
rx_durum=4;
break;
case 4:
gelen_adc+=veri;//
kalan 8 biti geçici değişkene yazdık
veri_toplam+=veri;//veri
toplama ekledik
rx_durum=5;
break;
case 5:
if
(veri_toplam==veri){// sensörden gelen toplam ile burada yapılan toplama eşitse
sen_adc=gelen_adc;//
gelen adc veri kaydedildi
veri_geldi=1;//veri
geldi bayrağı 1 yapıldı
}
else{sayac++;}//veri
hatalı geldiğinde sayac artar.
rx_durum=0;
break;
default:
rx_durum=0;
break;
}
}
ISR
(USART_UDRE_vect){ //uart veri
gönderim kesmesi
tx_son=(tx_son+1)&UART_Tx_Mask;
UDR0=tx_ring[tx_son];
if (tx_son==tx_bas)
UART_Bos_Off;
}
ISR(USART_TX_vect){
MAX_AL;//tx registeri boş
kesmesi max485 veri gitmeden veri okuma pozisyonuna geçmemesi için açıldı.
}
int
main(void){
MAX_OUT;
MAX_AL;
lcd_basla();
uart_basla(115200);
zaman1_ayar();
while (1) {
sprintf(str_sen_adc,"%d",sen_adc);//dizi
içine alıyorum
sprintf(str_sayac,"%d",sayac);
/*uart_dizi(str_sen_adc);
uart_gonder('\n');
uart_dizi(str_sayac);
uart_gonder('\n');*/
lcd_git(0,0);
lcd_dizi(str_sen_adc);
lcd_git(0,1);
lcd_dizi(str_sayac);
if
(veri_geldi==1){
veri_geldi=0;
LCD_CL;
}
}
}
//////
uart
void
uart_basla(uint32_t baud){
cli();
uint16_t baudRate=0;
baudRate=(F_CPU/baud/16)-1;
if
(baud>=115200){//115200 ve üstünde U2X 1 yapılıyor.
baudRate=(F_CPU/baud/8)-1;
UCSR0A|=(1<<U2X0);
}
UBRR0H=(baudRate>>8);
UBRR0L=baudRate;
UCSR0B|=(1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0)|(1<<TXCIE0);
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){
MAX_VER;
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;
}
//lcd
void
lcd_basla(){
CMD_OUT;
DATA_OUT;
_delay_ms(150);
lcd_data(0x30);
_delay_us(4100);
lcd_data(0x30);
_delay_us(100);
lcd_data(0x30);
_delay_us(100);
lcd_data(0x20);
_delay_us(100);
LCD_4L2;
_delay_us(50);
LCD_DON;
_delay_us(50);
LCD_NSCR_LR;
_delay_us(50);
LCD_HOME;
_delay_ms(2);
LCD_CL;
_delay_ms(2);
}
void
lcd_data(uint8_t gelen){
DATA_PORT=(DATA_PORT&0x0f)|(gelen
& 0xF0);
EN_HIGH;
_delay_us(1);
EN_LOW;
_delay_us(100);
}
void
lcd_kmt(uint8_t cmd){
RS_LOW;
lcd_data(cmd);
lcd_data(cmd<<4);
}
void
lcd_yaz(uint8_t data){
RS_HIGH;
lcd_data(data);
lcd_data(data<<4);
}
void
lcd_dizi (char *str){
while(*str){
lcd_yaz
(*str++);
}
}
void
lcd_git(uint8_t x, uint8_t y){
if (y==0)
lcd_kmt(0x80+x);
if (y==1)
lcd_kmt(0xC0+x);
if (y==2)
lcd_kmt(0x94+x);
if (y>=3)
lcd_kmt(0xD4+x);
}
//////timer1
void
zaman1_ayar(){
cli();
TCCR1B|=(1<<WGM12)|(1<<CS11)|(1<<CS10);//
CTC ve prescaler 64, .
TIMSK1|=(1<<OCIE1A);
OCR1A=olcme_aralik;
sei();
}