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ń