Autor: Dejmos
Redakcja: Dondu
Witam wszystkich!
Jest to mój pierwszy artykuł i wpis na tym blogu. Mam nadzieję, że nie ostatni. :-)
Chciałbym tu zaprezentować swoją konstrukcję sześciocyfrowego wyświetlacza LED z interfejsem 1-wire. O tym, że 1-wire jest chronione patentem dowiedziałem się już po zlutowaniu płytki, w trakcie pisania programu. Dlatego też protokół komunikacyjny jest zmieniony (uproszczony), co wcale nie ogranicza jego funkcjonalności w tym projekcie.
Udostępniam dla ogółu użytkowników pełną dokumentację projektu wraz z kodem źródłowym, w którym znajdują się funkcje z komentarzami, na podstawie których można napisać pełnowartościową aplikację do obsługi 1-wire. Wszystko znajdziesz na końcu artykułu.
Wyświetlacz LED 1wire. |
Założenia
Zacznijmy od początku. Stwierdziłem, że czasami podczas projektowania jakiegoś urządzenia na mikrokontroler zaczyna brakować nam pinów lub urządzenie wykonuje krytyczne zadania, które utrudnią obsługę wyświetlacza LED. Można w takim przypadku zastosować większy procesor, szybszy zegar itd. Można też zostawić wyświetlanie dla zewnętrznego urządzenia, a dane mu przesyłać tylko w chwili kiedy jest to konieczne.Ważnym założeniem była również poprawność komunikacji wyświetlacza z urządzeniem (masterem), w którym będzie on pracował. Wyobraźmy sobie, że przyrząd będzie rejestrował pojedyncze zdarzenia, które ciężko powtórzyć, lub będzie wykorzystywany przy badaniach, które najprościej rzecz ujmując są drogie. Nie może być takiej sytuacji , że przyrząd zarejestruje zdarzenie poprawnie, a na wyświetlaczu pojawią się „krzaki”.
I z tych założeń wyszedłem. Tak powstał 6-cyfrowy wyświetlacz z interfejsem 1-wire na procesorze ATtiny2313. Poniżej kilka zdjęć.
Początkowe problemy
Początkowo projekt zakładał 7 wyświetlaczy, a procesor miał pracować na wewnętrznym zegarze. Jednak komunikacja mi się ciągle rozjeżdżała. Nie pomagało konfigurowanie bitu kalibracyjnego. Układ zachowywał się tak, że jednego dnia po kilku godzinach pracy wszystko wyglądało obiecująco, transmisja prawie bezbłędna. Następnego dnia po włączeniu układu cały czas występował błąd crc.Dlatego zastosowałem rezonator zewnętrzny i stwierdziłem, że 6 wyświetlaczy spokojnie wystarczy do większości zastosowań.
Schemat i PCB
Płytka PCB została zaprojektowana tak, aby można ją było wykonać w warunkach domowych. Podczas projektowania płytki schemat był ciągle modyfikowany przeze mnie tak, aby jak najbardziej uprościć wzór ścieżek.
I tutaj rada dla początkujących projektantów układów mikroprocesorowych. Nigdy nie trzymajcie się sztywno wcześniej zaprojektowanego schematu. Prawie zawsze można pomiędzy dwoma pinami przełożyć linie, a projekt płytki dzięki temu może się diametralnie uprościć, zaś program napisać tak aby zadbał o poprawne działanie układu. Co innego gdy wykorzystujmy alternatywne funkcje portów. W takich przypadkach nie możemy tak postąpić.
Płytka:
Schemat multipleksowanego wyświetlacza LED. |
Płytka:
Płytka strona TOP - mirror. |
Płytka strona BOTTOM - mirror. |
Na końcu artykułu zamieściłem zbiorczy plik, w którym znajdziesz między innymi płytkę PCB w formie plików PDF.
Zasada działania
Komunikacja między masterem, a wyświetlaczem 6xLED wygląda następująco:- Master wykonuje reset linii, który trwa 480us.
- Wyświetlacz 6xLED po tym czasie generuje impuls obecności również o długości 480us.
- Master wysyła 10 bajtów z czego 6 pierwszych to cyfry do wyświetlenia.
- 7 i 8 bajt - rezerwa na przyszłość.
- 9 bajt - adres wyświetlacza.
- 10 bajt - crc.
Wyświetlacz 6xLED po odebraniu całej paczki danych oblicza swoje crc (sumę kontrolną) i porównuje z odebranym (ostatnim bajtem paczki). Jeżeli crc się zgadza sprawdza adres (przedostatni bajt paczki danych) jeżeli to jego adres - wyświetla na wyświetlaczu 6 pierwszych bajtów, które muszą odpowiadać tablicy znaków zadeklarowanej w programie. Jeżeli crc jest błędne wyświetlacz zwiera linię na 480us co master odbiera jako prośbę o ponowne wysłanie danych.
W przypadku kilka wyświetlaczy 6xLED podłączonych do jednej linii, procedura wygląda następująco (przynajmniej jest takie założenie, bo nie testowałem tego na większej ilości wyświetlaczy) Wszystkie odbierają wysyłane przez mastera dane. Wszystkie obliczają crc. Dana zostaje wyświetlona tylko przez wyświetlacz o adresie z 9 bajtu. Jednak wystarczy, że tylko jednemu (niekoniecznie temu do którego jest adresowana dana) nie zgodzi się crc wtedy linia jest zwierana przez niego na 480us, a master jest zmuszony do ponownego wysłania danych, niezależnie od tego czy błąd zgłosił wyświetlacz, do którego była adresowana paczka danych, czy też inny.
Multipleksowaniem wyświetlaczy steruje timer0, który jest ustawiony w tryb CTC z częstotliwością 180Hz, czyli każdy wyświetlacz zapala się na około 5,5ms 30 razy na sekundę. Odbiór danych zrealizowany jest na przerwaniu z INT0, które po wykryciu zbocza opadającego (impulsu reset generowanego przez master) jest blokowane na czas odbioru danych. W programie wykorzystana jest instrukcja ISR_NOBLOCK, dzięki której wyeliminowane jest miganie wyświetlacza podczas odbioru danych.
Czas trwania jednego bitu wynosi 120us a cała ramka wraz z impulsem reset i ewentualnym błędem crc trwa 11,04ms z czego wynika, że w tym czasie występuje dwu lub trzykrotne przerwanie od timera0 i wywołanie funkcji obsługującej wyświetlanie. Czas wykonywania funkcji obsługującej wyświetlacze to około 6,5us co przy trzykrotnym wywołaniu podczas odbioru danych przesuwa moment próbkowania linii 1-wire o jakieś 20us.
Dlatego przyjąłem tak długi czas trwania jednego bitu (120us), aby transmisja się nie rozjechała. Układ testowy, który co 10 ms wysyłał wartość licznika zwiększanego za każdym razem o 1 i zrobił to milion razy, przy takich czasach opóźnień nie zarejestrował ani jednego błędu transmisji. Dlatego aby upewnić się że wszystko jest poprawne napisałem też program, który co jakiś czas wysyłał błędne crc i wszystko było tak jak powinno. Ilość błędów transmisji zgadzała się z ilością błędnych crc.
Schemat układu do testów (master):
Koszty
Koszty projektu to około 35 zł bez wytrawiacza, pasty, cyny, itp.
Może w przyszłości
Można do programu dodać kilka funkcji, które będą np. migały wyświetlanymi danymi, powodowały efekt wjazdu z prawej lub lewej strony itp. wykorzystując do tego 8 bajt paczki danych, w którym master mógłby określał co wyświetlacz ma robić. Ale to już pozostawiam w kwestii otwartej w zależności od wykorzystania wyświetlacza.Kody:
//********************************************************************* //*** *** //*** 6-cyfrowy wyświetlacz LED 1wire *** //*** by Dejmos *** //*** uC - ATtiny2313 F_CPU - 8MHz *** //*** FUSES - low: 0xFF; high: 0xFF *** //*** Eclipse SDK Version: 3.7.1 *** //*** AVR Eclipse Plugin Version: 2.4.0.201203041437 *** //*** *** //********************************************************************* #include <avr\io.h> #include <avr/interrupt.h> #include <util/delay.h> #include <util/crc16.h> #define LED_1 6 //definicja #define LED_2 5 //makr #define LED_3 4 //dla #define LED_4 3 //wyświetlaczy #define LED_5 1 //LED #define LED_6 0 //-- #define ADRESS 0x01 //adres wyswietlacza #define INPUT 0x04 //nr pinu 1wire #define PIN_1WIRE PIND //wejście 1wire na porcie D #define CLEAR_1WIRE DDRD |= _BV(2) //wyjście - ściągnięcie lini 1wire do 0 #define SET_1WIRE DDRD &= ~_BV(2) //wejście (zwolnienie lini 1wire) //pin w stanie wysokiej impedancji //linia 1wire podciągana przez rezystor uint8_t dane[6]; //tablica danych do wyświetlenia uint8_t temp[10]; //tablica odebranych danych uint8_t z=0; //deklaracja zmiennej globalnej z uint8_t flaga=0; uint8_t Digits[] = {0xed,0x44,0x79,0x75,0xd4,0xb5,0xbd,0x64,0xfd,0xf5,0xef,0x46,0x7b,0x77,0xd6,0xb7,0xbf,0x66,0xff,0xf7,0x00}; //w. wyswietlana: 0 1 2 3 4 5 6 7 8 9 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. wygas //kod wysw: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //---------------------------------------------------------------- void DisplayDigit(uint8_t digit) //wyświetlanie cyfr na wyświetlaczu { PORTB = Digits[digit]; //ustaw wyjścia poru B w zależności od tablicy Digits } //---------------------------------------------------------------- void Display(uint8_t *str) //obsługa wyświetlaczy { uint8_t i=0; //deklaracja zmiennej i PORTD = 0x00; //wygaś wszystkie PORTB = 0x00; //wyświetlacze while(str[i]==0) { //funkcja wygaszająca //zera wiodące str[i]=20; //w i++; //danej if(i==5) { break; //do wyswietlenia } } switch(z) { //funkcja multipleksująca wyswietlacze case 0: //dla z = 0 if(str[z]!=20) { PORTD |= _BV(LED_1); //zapal pierwszy wyświetlacz jeżeli nie jest //zerem wiodącym } DisplayDigit(str[z]); //wywołaj funkcję wyświetlającą cyfrę z++; //zwiększ z break; //przerwij case 1: //dla z = 1 if(str[z]!=20) { PORTD |= _BV(LED_2); //zapal drugi wyświetlacz jeżeli //nie jest zerem wiodącym } DisplayDigit(str[z]); //wywołaj funkcję wyświetlającą cyfrę z++; //zwiększ z break; //przerwij case 2: //dla z = 2 if(str[z]!=20) { PORTD |= _BV(LED_3); //zapal trzeci wyświetlacz jeżeli //nie jest zerem wiodącym } DisplayDigit(str[z]); //wywołaj funkcję wyświetlającą cyfrę z++; //zwiększ z break; //przerwij case 3: //dla z = 3 if(str[z]!=20) { PORTD |= _BV(LED_4); //zapal czwarty wyświetlacz jeżeli //nie jest zerem wiodącym } DisplayDigit(str[z]); //wywołaj funkcję wyświetlającą cyfrę z++; //zwiększ z break; //przerwij case 4: //dla z = 4 if(str[z]!=20) { PORTD |= _BV(LED_5); //zapal piąty wyświetlacz jeżeli //nie jest zerem wiodącym } DisplayDigit(str[z]); //wywołaj funkcję wyświetlającą cyfrę z++; //zwiększ z break; //przerwij case 5: //dla z = 5 PORTD |= _BV(LED_6); //zapal szósty wyświetlacz DisplayDigit(str[z]); //wywołaj funkcję wyświetlającą cyfrę z=0; //z = 0 break; //przerwij } } //---------------------------------------------------------------- unsigned char ReadBit() //funkcja odbierająca bit { uint8_t bit=0; //deklaracja zmiennej bit _delay_us(35); //opóźnienie if(PIN_1WIRE&INPUT) { bit=1; //jeżeli jeden na linii 1-wire } else { bit=0; //jeżeli zero } _delay_us(85); //opóźnienie - pozostały czas trwania bitu return bit; } //---------------------------------------------------------------- unsigned char ReceiveByte() //funkcja odbierająca 1 bajt { uint8_t SetData=0; //początkowa wartoć odbieranego bajtu uint8_t i; //deklaracja zmiennej i for(i=0; i<8; i++) { //wykonaj 8 razy (odbieraj bit po bicie) if(ReadBit()) { SetData |= 0x01<<i; //jeżeli ReadBit==1 ustaw 1 } } //w miejsce i-tego bitu w odbieranym bajcie return SetData; //zwróć odebrany bajt } //---------------------------------------------------------------- void ReceiveData() //funkcja odpowiedzialna za odbiór danych { uint8_t i, crc8=0; //deklaracja zmiennych: i, crc8 _delay_us(480); //odczekaj aż minie impuls zerujący CLEAR_1WIRE; //ściągnięcie lini 1wire do 0 _delay_us(480); //przytrzymaj przez 480us (impuls obecności) SET_1WIRE; //zwolnienie lini 1wire for(i=0; i<10; i++) { //odbierz 10 bajtów temp[i] = ReceiveByte(); //odbierz bajt } for(i=0; i<9; i++) { //wykonaj 9 razy crc8 = _crc_ibutton_update(crc8, temp[i]); //oblicz crc z pierwszych //9 bajtow } if(temp[9]==crc8) { //porównaj crc odebrane z obliczonym //jeżeli crc zgodne if(temp[8]==ADRESS) { //sprawdz adres urządzenia //jeżeli adres zgodny for(i=0; i<6; i++) { dane[i] = temp[i]; //przepisz 6 bajtów do wyswietlenia } } _delay_us(480); //opóźnenie 480us } else { //jeżeli crc niezgodne CLEAR_1WIRE; //ściągnij linię 1wire do 0 _delay_us(480); //i przytrzymaj przez 480us SET_1WIRE; //zwolnij linię } } //---------------------------------------------------------------- ISR(INT0_vect, ISR_NOBLOCK) //obsługa przerwania z INT0 bez // blokowania innych przerwań { GIMSK &= ~_BV(INT0); //zablokuj przerwanie z INT0 ReceiveData(); //odbierz dane EIFR |= _BV(INTF0); //"wyzeruj" flagę przerwania z int0 GIMSK |= _BV(INT0); //odblokuj przerwanie z INT0 } //---------------------------------------------------------------- ISR(TIMER0_COMPA_vect) { Display(dane); //wyświetlaj } //---------------------------------------------------------------- int main(void) //main { GIMSK |= _BV(INT0); //włącz przerwanie z INT0 MCUCR=0x02; //przerwanie z INT0 wyzwalane zboczem // opadającym TIMSK |= _BV(OCIE0A); //włącz timer0 TCCR0A |= _BV(WGM01); //tryb CTC TCCR0B |= _BV(CS02) | _BV(CS00); //prescaler 1024 OCR0A = 20; //wartość przy której TCNT0 ma być //czyszczony( 180Hz ) DDRB=0xff; //port B - wyjscia PORTB=0x00; //port B = 00000000 DDRD=0xfb; //PD2 - wejscie pozostałe wyjscia PORTD=0x00; //port D = 00000000 sei(); //odblokuj przerwania while(1); //petla nieskończona //wszystko działa w przerwaniach :-) }Do pobrania: Wyswietlacz_6_LED_1wire.c (kopia)
Program nadajnika układu testowego:
//********************************************************************* //*** Program testowy dla *** //*** 6-cyfrowego wyświetlacza LED 1wire *** //*** by Dejmos *** //*** uC - ATtiny2313 F_CPU - 8MHz *** //*** FUSES - low: 0xFF; high: 0xFF *** //*** Eclipse SDK Version: 3.7.1 *** //*** AVR Eclipse Plugin Version: 2.4.0.201203041437 *** //*** *** //********************************************************************* #include <avr\io.h> #include <util/delay.h> #include <util/crc16.h> #define adress_device_1 0x01 //adres pierwszego wyświetlacza #define INPUT 0x01 //nr pinu 1wire #define PIN_1WIRE PINB //wejście 1wire na porcie B #define CLEAR_1WIRE DDRB |= _BV(0) //wyjście - ściągnięcie lini 1wire do 0 #define SET_1WIRE DDRB &= ~_BV(0) //wejście (zwolnienie lini 1wire) //pin w stanie wysokiej impedancji //linia 1wire podciągana przez rezystor uint8_t MeasuredData[10]; //tablica danych pomiarowych i instrukcji long int counter=0; //licznik long int counter2=0; //licznik2 //---------------------------------------------------------------- unsigned char ResetPresence() //detekcja obecnoci urządzenia { uint8_t PRESENCE; //deklaracja zmiennej PRESENCE CLEAR_1WIRE; //ściągnięcie lini 1wire do 0 _delay_us(480); //przytrzymaj przez 480us (impuls resetu) SET_1WIRE; //zwolnienie lini 1wire _delay_us(180); //odczekaj część impulsu obecności if(!(PIN_1WIRE&INPUT)) { PRESENCE=1; //sprawdź czy urządzenie obecne } else { PRESENCE=0; //urządzenie nieobecne } _delay_us(300); //odczekaj pozostałą część impulsu obecności return PRESENCE; //zwróć wartość PRESENCE } //---------------------------------------------------------------- void SendByte(uint8_t bajt) //funkcja wysyłająca bajt { uint8_t i, temp; //deklaracja zmiennych i , temp for(i=0; i<8; i++) { //powtórz 8 razy (wysyłaj 8 bitów) temp = bajt & 0x01; //wyłuskaj najmniej znaczący bit CLEAR_1WIRE; //zeruj 1wire if(temp) { //jeżeli najmniej znaczący bit = 1 _delay_us(10); //generyj opóźnienie 10 us SET_1WIRE; //zwolnij 1wire _delay_us(110); //generyj opóźnienie 110 us } else { //w przeciwnym wypadku _delay_us(110); //generyj opóźnienie 110 us SET_1WIRE; //zwolnij 1wire _delay_us(10); //generyj opóźnienie 10 us } bajt>>=1; //przesuń bajt o 1 w prawo. } } //---------------------------------------------------------------- void SendMeasure(uint8_t *str) //funkcja wysyłająca tablicę danych { uint8_t Present, i; //deklaracja zmienneych Present, i uint8_t Err=0; //deklaracja zmiennej Err //i przypisanie jej wartości 0 uint8_t crc8=0; //deklaracja zmiennej crc8 //i przypisanie jej wartości 0 str[8]=adress_device_1; //adres wyswietlacza do 9 komórki tablicy for(i=0; i<9; i++) { //wykonaj 9 razy crc8 = _crc_ibutton_update(crc8, str[i]); //oblicz crc z pierwszych //9 bajtów } str[9]=crc8; //wpisz wartość crc jako ostatni bajt //do wysłania Present = ResetPresence(); //resetuj linię 1wire if(Present) { //sprawdź czy wyświetlacz się zgłosił Present=0; //wyzeruj flagę zgłoszenia for(i=0; i<10; i++) { //wykonaj 10 razy SendByte(str[i]); //wyślij dane i instrukcje } } do { //wykonuj i sprawdzaj flagę Err _delay_us(250); //czekaj 250 us if(!(PIN_1WIRE&INPUT)) { //sprawdź stan lini 1wire //jeżeli zero na lini - bład danych crc - ponowne wysłanie crc8=0; //zeruj crc8 counter2++; //zwiększ licznik 2 (licznik ilości //błędów transmisji) PORTB ^= _BV(7); //zmień stan diody Err = 1; //ustaw flagę Err _delay_ms(3); //odczekaj 3 ms str[8]=adress_device_1; //do 9 komórki tablicy for(i=0; i<9; i++) { //wykonaj 9 razy crc8 =_crc_ibutton_update(crc8,str[i]); //ponownie oblicz crc //z pierwszych 9 bajtów } str[9]=crc8; //wpisz wartość crc jako ostatni //bajt do wysłania Present = ResetPresence(); //resetuj linię 1wire if(Present) { //sprawdź czy wyświetlacz się zgłosił Present=0; //wyzeruj flagę zgłoszenia for(i=0; i<10; i++) { //wykonaj 10 razy SendByte(str[i]); //wyślij dane i instrukcje } } } else { //jeżeli linia wolna Err = 0; //zeruj flagę Err _delay_ms(3); //odczekaj 3 ms } } while(Err); //wykonuj dopuki flaga Err jest ustawiona } //---------------------------------------------------------------- void Conversion(long int value) //funkcja konwertująca liczbę { //na poszczególne cyfry MeasuredData[0]=(value/100000)%10; //i MeasuredData[1]=(value/10000)%10; //wpisująca MeasuredData[2]=(value/1000)%10; //ich MeasuredData[3]=(value/100)%10; //wartość MeasuredData[4]=(value/10)%10; //do odpowiednich MeasuredData[5]=value%10; //komórek tablicy } //---------------------------------------------------------------- int main(void) //main { DDRB=0xfd; //PB1 wejście pozostałe wyjścia PORTB=0xfe; //port B = 11111110 uint8_t i, j; while(1) { //pętla nieskończona Conversion(counter); //konwertuj licznik _delay_ms(100); //opóźnienie 100 ms SendMeasure(MeasuredData); //wyślij dane counter++; //zwiększ counter o 1 if(counter>1000) { //jeżeli licznik przekroczył wartość _delay_ms(100); //opóźnienie 100ms counter=0; //wyzeruj licznik for(i=0; i<6; i++) { MeasuredData[i]= i; //wpisz wartości do wysłania } for(j=0; j<20; j++) { //wykonaj 20 razy for(i=0; i<6; i++) { //wykonaj 6 razy MeasuredData[i]++; //zwiększ wartość komórki i if(MeasuredData[i]>20) { MeasuredData[i]=20; //jeżeli przekroczyła zakres } } _delay_ms(1); //opóźnienie 1ms SendMeasure(MeasuredData); //wyślij dane do wyświetlenia _delay_ms(500); //opóźnienie 500ms } Conversion(counter2); //konwertuj licznik błędów SendMeasure(MeasuredData); //wyslij wartość licznika błędów counter2=0; //wyzeruj licznik 2 while(PINB&0x02); //czekaj na wciśnięcie przycisku _delay_ms(5); //opóźnienie while(!(PINB&0x02)); //czekaj na puszczenie przycisku } } }
Do pobrania: Nadajnik_1wire_do_wyswietlacza_6_LED_1wire.c (kopia)
Komplet plików
Do pobrania komplet plików: Dokumentacja.rar (kopia)
Pytania?
Jeżeli masz jakieś pytania, śmiało zadawaj za pomocą komentarzy.
Pozdrawiam,
Dejmos :-)
Witam. Na początku piszesz, że uprościłeś protokół. Czy jest on w takim razie zgodny z 1wire?
OdpowiedzUsuńJeżeli chciałbyś podłączyć jakiś układ Dallasa do tej samej magistrali to raczej się nie uda. Czasy i struktura bajtów zostały takie jak są dopuszczone dla 1wire dlatego napisałem też, że na podstawie umieszczonych tutaj listingów można napisać pełnowartościową procedurę obsługi magistrali. Wyświetlacz zachowuje się jak pasywny slave, który zgłasza tylko swoją obecność i dla potrzeb aplikacji błąd crc.
UsuńDejmos.
Dziękuję za odpowiedź. W weekend wykorzystam twój protokół transmisji do innego projektu, gdzie mam mało pinów. Fajnie, że podzieliłeś się całym projektem. Mam nadzieję, że to nie będzie jedyny twój artykuł. :) Pozdrawiam. Arek
Usuń