AVR- ADC Okuma ve RS485 ile Aktarma
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
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 burada gerekli açıklama var. 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 birimini ç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. Alttaki 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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.
1 2 3 4 5 6 7 8 9 |
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.
1 2 3 4 5 6 7 |
#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?
1 2 3 4 5 6 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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.
1 2 3 4 5 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
/* * 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(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
/* * 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(); } |
Merhaba Haluk bey,
Kıymetli paylaşımlarınız için çok teşekkürler.
Bu uygulama için fuse ayarlarını ne şekilde yaptınız.Donanıma yüklediğimde genel olarak çalıştı fakat hızlı sorgu yapınca mcu reset atıyor neden olabilir.
Merhaba Çağrı Bey, yorumunuz için ben teşekkür ederim.
Sigorta ayarları şu şekildedir: Low=0xFF, High=0xDE, Ext=0xFD veya 0x05 (Ext ilk üç bit dışında kullanımı yok)
Sizin sorununuzun sigorta ayarlarında olacağını sanmıyorum. Öncelikle hızlı sorgudan kastınız ADC okuması için gerçekleşen Timer kesmesi bekleme süresinin azalması mı. UART ile veri alış verişini sağlayan ana modüldeki Timer bekleme süresinin azalması mı?
ADC çevrimi yukarıda yapılan ayarlara göre 104us sürmektedir. Timer kesmesi bu süreden daha az olursa çevrim bitmeden yenisi başlatılmak istenir. Bu durumda çevrim bitene kadar beklenir ama her seferinde bu durum gerçekleşir. Bir de ADMUX 1,5 ADC çevrimi sonunda kilitlenir. Bundan daha önce kanal değişikliği veya yeni çevrim isteği olursa çevrim başlayamaz. Bu limit değer yukarıdaki ayarlara göre 12 us dir. En doğru okuma için bekleme süresi 105us üstünde olmalıdır. UART 115200Baudrate ile verinin gitmesi ve ADC verinin gelmesi 780us sürecektir. Bu durumda bu sürenin altında veri talep etmemek doğru olacaktır.
Sorununuz hakkında çözüm için Facebook grubundan sormanız konu hakkında bilgisi olan başkalarının da görmesi ve cevaplaması için doğru yöntem olur.