Autor: Dejmos
Redakcja: Dondu
Przyszedł czas aby wykorzystać przedstawiony przeze mnie w tym artykule wyświetlacz 6 LED 1wire w konkretnym projekcie. Oczywiście nie obyło się bez zmian w programie wyświetlacza w celu odpowiedniego wyświetlania danych (wszystko w załączniku).
Do skonstruowania tego czasomierza skłoniła mnie częsta potrzeba pomiaru odcinków czasu pomiędzy dwoma różnymi zdarzeniami, reprezentowanymi przez sygnały elektryczne a raczej zmiany tych sygnałów (zbocza napięć opadające lub narastające). Myślę, że zastosowanie urządzenia może być różne – wszędzie tam gdzie wymagany jest dość dokładny (ale nie krytyczny) pomiar odcinków czasu: bieżnia, tory wyścigowe itp. Oczywiście dla tych przykładów należało by użyć np. fotokomórek lub innych czujników.
Założenia jakie przed projektem postawiłem to:
1. Dokładność na poziomie 0,002% lub lepsza,
2. Uniwersalność:
- duża rozpiętość napięć wyzwalających (od 2V do 20V),
- możliwość wyboru zbocza na wejściu start i stop,
3. Ergonomiczny i łatwy w użyciu,
4. Możliwość komunikacji z PC.
SCHEMAT I PCB
Mając określone wstępne założenia przystąpiłem do pracy. Schemat oraz projekt PCB powstały dość sprawnie. Chociaż niewielki problem miałem podczas projektowana części wejściowej sygnałów START i STOP. We wstępnym projekcie sygnały wejściowe miały być podawane bezpośrednio na wejścia bramek Schmitta zabezpieczone diodami D6 i D7.Jednak przy takim rozwiązaniu, założenie napięciowe nie zostałyby spełnione ze względu na to, że minimalny poziom napięcia dla bramki HC dla stanu „1” to około 3V, a sygnał wejściowy po przejściu przez jedną bramkę byłby zanegowany. Co do negacji sygnału to w sumie nie byłoby problemu bo rozwiązania są dwa i to bardzo proste: 1) druga bramka w szereg, 2) rozwiązanie programowe. O tyle z poziomem napięć wejściowych już był trochę większy kłopot.
Dlatego padło na najprostsze możliwe rozwiązanie, mianowicie zastosowanie tranzystora przełączającego na wejściu. Dzięki niemu miałem załatwione dwie sprawy: 1) Negację sygnałów wejściowych i 2) Czułość na poziomie 2V. W takim rozwiązaniu diody D6 i D7 już nie są potrzebne ponieważ spadek napięcia na złączu B-E tranzystora nie przekroczy wartości 0,7V ale jakoś w ferworze pracy zostały.
Duża rozdzielczość: schemat |
Projekt PCB powstał pod konkretną obudowę, która już od kilku lat leżała sobie i czekała na swój czas. Uprzedzając wszystkie pytania co do wielkości płytki od razu powiem, że takie było założenie aby płytka wpasowała się idealnie w obudowę (spokojnie można ją zmniejszyć kilkukrotnie). Stwierdziłem, że przy częstotliwości rzędu kilku MHz taka wielkość płytki nie będzie miała żadnego wpływu na pracę układu.
Bottom layer - mirror |
Wstępnie miał być wykorzystany procesor ATtiny2313 z rezonatorem 16MHz (jeszcze wcześniej generator kwarcowy 10MHz) ale w czasie pisania programu brakło mi trochę pamięci i żadne próby ograniczenia kodu nie dawały zadowalającego efektu, więc ostatecznie zastosowałem ATtiny4313, który ma 4kB pamięci. Rezonator także został zmieniony na 8MHz ze względu na to, że wstępne próby i pomiary były dużo dokładniejsze przy tym rezonatorze.
Taka ciekawostka związana z kondensatorami przy rezonatorze. Przy wartości 22pF czasomierz fałszował wynik o kilkanaście ms przy pomiarze czasów rzędu 100s. Gdzieś kiedyś obiło mi się o uszy, że zmieniając wartości pojemności kondensatorów można delikatnie wyregulować jego częstotliwość, i tak było w moim przypadku. Zastosowałem kondensatory o wartości 47pF (po kilku próbach z innymi wartościami) i dokładność pomiarów wzrosła do poziomu około 0,0002% co dziesięciokrotnie przekroczyło moje wstępne założenia (potwierdzenie w tabelce 1 poniżej). Już po złożeniu czasomierza i wykonaniu kilkuset pomiarów stwierdziłem, że dokładność czasomierza można jeszcze zwiększyć wprowadzając poprawkę programową.
Jak można zauważyć w tabelce 1, błąd bezwzględny wychodzi zawsze dodatni czyli czasomierz trochę śpieszy.
Należy więc od pomiaru wykonanego przez czasomierz odjąć niewielką wartość wyliczoną na podstawie tabelki 1 i wzorów zamieszczonych poniżej. Gdyby czasomierz trochę spóźniał należy wartość tej poprawki dodać do pomiaru.
Jak demonstruje tabelka 2 dokładność czasomierza dzięki tej operacji zwiększyła się kilkukrotnie. W tabelkach 2 i 3 znajdują się pomiary tych samych odcinków czasu wykonane w ciągu dwóch kolejnych dni.
Możemy tu zaobserwować jak na pracę czasomierza wpływa dokładność rezonatora. Jednego dnia błąd wychodzi ujemny, kolejnego – dodatni. Wynika z tego, że osiągnąłem maksymalną dokładność jaką mogłem uzyskać w tym układzie. Na marginesie dodam, że drugiego dnia po południu ponownie wykonałem pomiary tych czasów i błąd był już dużo mniejszy (np.: pomiar 8 błąd -51us, pomiar 9 błąd -77us, pomiar 10 błąd -85us).
Zasada działania
Za pomiar czasu odpowiedzialny jest TIMER0 pracujący w trybie CTC z przerwaniem co 50us. W obsłudze przerwania zwiększany jest o 5 licznik dziesiątek mikrosekund (time_counter). Przy założonej dokładności taki interwał czasowy w zupełności wystarczy.W obsłudze przerwania Timera1 znajduje się skanowanie klawiszy. Co 100 ms sprawdzana jest starsza część portu D. Jeżeli zostało wykryte naciśnięcie klawisza, zostaje ustawiona flaga wciśniętego klawisza oraz jego kod. W programie głównym w funkcji switch – case następuje reakcja w zależności od kodu na wciśnięty klawisz.
W obsłudze klawiszy „Zbocze START” i „Zbocze STOP” w zależności od flag „start_edge” i „stop_edge” następuje ustawienie rejestru MCUCR tak aby wejścia INT0 i INT1 odpowiednio reagowały na wybrane zbocze. Wybraną opcję wizualizują diody D1, D2, D3, D4.
Każde naciśnięcie klawisza sygnalizowane jest 10ms sygnałem dźwiękowym.
W obsłudze klawisza „Reset” na początku następuje wyłączenie przerwań od: Timera0, wejść INT0 i INT1 oraz „zerowanie” flag przerwań z tych wejść. Następnie układ komunikuje się z wyświetlaczem w celu wyświetlenia zera. Na końcu na 10ms generowany jest sygnał dźwiękowy a po nim zostaje włączone przerwanie z wejścia INT1 (wejścia START). Po tej procedurze czasomierz gotowy jest do pomiaru.
Sygnał START i STOP jak widać na schemacie przechodzi przez tranzystor Q1 i Q2, bramki Schmitt’a i podawany jest odpowiednio na wejścia INT1 i INT0 procesora.
W obsłudze przerwania INT1 (START) następuje wyzerowanie licznika czasu (time_counter) i rejestru TCNT0 następnie zostaje włączone przerwanie od Timera0, wyłączone przerwanie z wejścia INT1 (START) i włączenie z wejścia INTO (STOP). Włączony zostaje sygnał dźwiękowy, który po 10 ms zostaje wyłączony w programie głównym. Na koniec zostaje zapalona dioda D5 sygnalizująca pomiar.
W obsłudze przerwania z INT0 (STOP) następuje wyłączenie Timera0 zablokowanie przerwania z INT0 (STOP), zostaje zgaszona dioda D5 sygnalizująca pomiar. Liczona i wprowadzona jest poprawka. Wartość licznika czasu zostaje przekonwertowana na postać dziesiętną i poszczególne liczby zostają wpisane do tablicy MeasuredData[]. Zawartość tablicy zostaje wysłana do wyświetlacza, który wyświetla wynik w sekundach dbając o wstawienie w odpowiednie miejsce przecinka. Następnie generowany jest sygnał dźwiękowy zakończenia pomiaru i przesłanie wyniku przez UART.
Komunikacja z PC została zrealizowana poprzez RS232 na układzie MAX232 (kilka z nich leżało mi w szufladzie więc trzeba ich powoli wykorzystywać). Prędkość transmisji to 19200 bps ramka: 8 bitów danych, jeden bit stopu, bez bitu parzystości. Odbiór danych realizowany jest przez program HyperTerminal.
Czasomierz wzorcowałem przy pomocy czasomierza HAMEG HM 8123 co widać na zdjęciach.
Koszty szacuję na około 150 ÷ 200zł
Program
//***************************************************************************** //*** Czasomierz *** //*** by Dejmos *** //*** uC - ATtiny4313 F_CPU - 8MHz FUSES - low: 0xFF; high: 0xDF *** //*** Atmel Studio 6 Version: 6.1.2730 Servce Pack 2 *** //***************************************************************************** #include <avr/io.h> #include <stdlib.h> #include <util/delay.h> #include <avr/interrupt.h> #include <util/crc16.h> #define TIMER0_ON TIMSK |= _BV(OCIE0A) //makra #define TIMER0_OFF TIMSK &= ~_BV(OCIE0A) //dla #define TIMER1_ON TIMSK |= _BV(OCIE1A) //timerów #define TIMER1_OFF TIMSK &= ~_BV(OCIE1A) //i #define START_ON GIMSK |= _BV(INT1) //wejść #define START_OFF GIMSK &= ~_BV(INT1) //int0 #define STOP_ON GIMSK |= _BV(INT0) //i #define STOP_OFF GIMSK &= ~_BV(INT0) //int1 #define KEY ~PIND & 0x70 //klawiatura na porcie D (D4, D5, D6) #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 linii 1wire do 0 #define SET_1WIRE DDRB &= ~_BV(0) //wejście (zwolnienie linii 1wire) //pin w stanie wysokiej impedancji //linia 1wire podciągana przez rezystor #define VUART 19200UL //prędkość transmisji #define VUBRR F_CPU/(VUART*16)-1 //oblicz dla prędkości transmisji 19200 #define BUZER PORTB &= ~_BV(6); \ _delay_ms(10); \ PORTB |= _BV(6) //sygnał dźwiękowy uint8_t measuring_flag = 0; //flaga pomiaru long long int time_counter=0; //mierzony czas long long int correction=0; //poprawka programowa int measure_counter=0; //licznik pomiarów uint8_t start_edge = 0; //flaga zbocza start uint8_t stop_edge = 0; //flaga zbocza stop volatile uint8_t key_flag = 0; //flaga naciśnięcia klawisza volatile uint8_t key_code = 0; //kod naciśniętego klawisza uint8_t MeasuredData[11]; //tablica konwersji pomiaru //************************* Komunikacja UART ****************************** void USART_Transmit(unsigned char data) //wysyłanie znaku przez UART { while(!(UCSRA&(1<<UDRE))); //czekaj na pusty bufor transmisji UDR = data; //wpisz dane do bufora } void Uart_write(char *str) //wysyłanie ciągu znaków przez UART { while(*str !='\0') { USART_Transmit(*str++); //wysyłaj dopóki jest coś do wysłania } } void Uart_number(int data) //wysyłanie liczby przez UART { char buf[16], *p = buf; //tablica znaków i wskaźnik ltoa(data, buf, 10); //Zamiana liczby na znaki ASCII while(*p) { USART_Transmit(*p++); //wysyłaj cyfry } } void Uart_result(uint8_t *measure) //wysłanie pomiaru przez UART { uint8_t i=0,z=0; //z, i measure_counter++; //zwiększ licznik pomiarów Uart_number(measure_counter); //wyślij przez UART wartość licznika USART_Transmit(0x09); //tab USART_Transmit(0x7c); //znak "|" USART_Transmit(0x09); //tab while(measure[i]==0) { //część funkcji //odpowiedzialna za pominięcie i++; //zer wiodących w części if(i==4) { break; //całkowitej pomiaru } } for(z=i; z<5; z++) { //wyślij przez UART cyfry znaczące USART_Transmit(measure[z]+0x30); //części całkowitej pomiaru } USART_Transmit(0x2e); //wyślij kropkę dziesiętną for(z=5; z<8; z++) { //wyślij przez UART cyfry USART_Transmit(measure[z]+0x30); //milisekund } USART_Transmit(0x20); //wyślij spację for(z=8; z<10; z++) { //wyślij przez UART cyfry USART_Transmit(measure[z]+0x30); //mikrosekund } USART_Transmit(0x20); //wyślij spację USART_Transmit(0x73); //wyślij literę "s" USART_Transmit(0x0d); //powrót kursora USART_Transmit(0x0a); //nowy wiersz } //********************* Komunikacja z wyświetlaczem ********************** unsigned char ResetPresence() //detekcja obecności urządzenia { uint8_t PRESENCE; //deklaracja zmiennej PRESENCE CLEAR_1WIRE; //ściągnięcie linii 1wire do 0 _delay_us(480); //przytrzymaj przez 480us (impuls resetu) SET_1WIRE; //zwolnienie linii 1wire _delay_us(180); //odczekaj część impulsu obecności if(!(PIN_1WIRE&INPUT)) { //sprawdź czy urządzenie obecne PRESENCE=1; //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 byte) //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 = byte & 0x01; //wyłuskaj najmniej znaczący bit CLEAR_1WIRE; //zeruj 1wire if(temp) { //jeżeli najmniej znaczący bit = 1 _delay_us(10); //generuj opóźnienie 10 us SET_1WIRE; //zwolnij 1wire _delay_us(110); //generuj opóźnienie 110 us } else { //w przeciwnym wypadku _delay_us(110); //generuj opóźnienie 110 us SET_1WIRE; //zwolnij 1wire _delay_us(10); //generuj opóźnienie 10 us } byte>>=1; //przesuń bajt o 1 w prawo. } } void SendMeasure(uint8_t *str) //funkcja wysyłająca tablicę danych { uint8_t Present, i; //deklaracja zmiennych 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 for(i=0; i<10; i++) { //wykonaj 10 razy crc8 = _crc_ibutton_update(crc8, str[i]); //oblicz crc z pierwszych //10 bajtów } str[10]=crc8; //wpisz wartość crc jako ostatni bajt //do wysłania TIMER1_OFF; //na czas przesyłania wyłącz skanowanie //klawiatury Present = ResetPresence(); //resetuj linię 1wire if(Present) { //sprawdź czy wyświetlacz się zgłosił Present=0; //wyzeruj flagę zgłoszenia for(i=0; i<11; i++) { //wykonaj 11 razy SendByte(str[i]); //wyślij dane } } TIMER1_ON; //włącz skanowanie klawiatury do { //wykonuj i sprawdzaj flagę Err _delay_us(350); //czekaj 350 us if(!(PIN_1WIRE&INPUT)) { //sprawdź stan linii 1wire //jeżeli zero na linii -błąd danych crc- ponowne wysłanie Err = 1; //ustaw flagę Err _delay_ms(3); //odczekaj 3 ms TIMER1_OFF; //na czas przesyłania wyłącz skanowanie //klawiatury Present = ResetPresence(); //resetuj linię 1wire if(Present) { //sprawdź czy wyświetlacz się zgłosił Present=0; //wyzeruj flagę zgłoszenia for(i=0; i<11; i++) { //wykonaj 11 razy SendByte(str[i]); //wyślij dane } } TIMER1_ON; //włącz skanowanie klawiatury } else { //jeżeli linia wolna Err = 0; //zeruj flagę Err _delay_ms(3); //odczekaj 3 ms } } while(Err); //wykonuj dopóki flaga Err jest ustawiona } void Conversion(long long int data) //funkcja konwertująca liczbę { MeasuredData[0]=(data/1000000000)%10; //na postać MeasuredData[1]=(data/100000000)%10; //dziesiętną MeasuredData[2]=(data/10000000)%10; //i wpisująca jej MeasuredData[3]=(data/1000000)%10; //wartość MeasuredData[4]=(data/100000)%10; //do MeasuredData[5]=(data/10000)%10; //odpowiednich MeasuredData[6]=(data/1000)%10; //komórek MeasuredData[7]=(data/100)%10; //w MeasuredData[8]=(data/10)%10; //tablicy MeasuredData[9]=data%10; //MeasuredData } //************************* Przerwania ************************************ ISR(TIMER1_COMPA_vect, ISR_NOBLOCK)//przerwanie z timera1 { if(KEY) { //jeżeli został naciśnięty klawisz key_flag=1; //ustaw flagę naciśniętego klawisza key_code=KEY; //zapamiętaj kod klawisza } } ISR(TIMER0_COMPA_vect) //obsługa przerwania timera0 { time_counter+=5; //licznik zwiększany o 5 co 50 us } //teoretyczna rozdzielczość czasomierz - 50us ISR(INT1_vect) //obsługa przerwania z INT1 (START) { time_counter=0; //wyzeruj licznik czasu TCNT0=0; //wyzweruj rejest timera TIMER0_ON; //włącz pomiar START_OFF; //wyłącz przerwanie z INT1 (zablokuj //wejście start) EIFR |= _BV(INTF1); //wyzeruj flagę przerwania z INT0 STOP_ON; //odblokuj przerwanie z INT0 (odblokuj //wejście stop) measuring_flag = 1; //ustaw flagę pomiaru PORTB &= ~_BV(6); //włącz buzer PORTB &= ~_BV(1); //zapal diodę GATE } ISR(INT0_vect) //obsługa przerwania z INT0 (STOP) { TIMER0_OFF; //wyłącz timer0 STOP_OFF; //zablokuj przerwanie z INT0 EIFR |= _BV(INTF0); //wyzeruj flagę przerwania z INT0 PORTB |= _BV(1); //zgaś diodę GATE measuring_flag = 0; //wyzeruj flagę pomiar correction=0; //wyzeruj poprawkę programową correction=time_counter/500000; //oblicz wartość poprawki time_counter=time_counter-correction;//wprowadź poprawkę Conversion(time_counter); //konwertuj zmierzony czas SendMeasure(MeasuredData); //wyświetl zmierzony czas BUZER; //sygnał dźwiękowy zakończonego pomiaru Uart_result(MeasuredData); //wyślij zmierzony czas przez UART } //************************* Funkcje inicjujące **************************** void USART_Init(unsigned int baud) //Konfiguracja UART { UBRRH = (unsigned char)(baud>>8); //ustawianie prędkości UBRRL = (unsigned char)baud; //transmisji na 19200 UCSRC |= _BV(UCSZ0) | _BV(UCSZ1); //8 n 1 UCSRB |= _BV(TXEN); //włącz nadajnik } void TIMER0_Init() //Konfiguracja TIMER0 { //(odpowiedzialny za pomiar czasu) TCCR0A |= _BV(WGM01); //tryb CTC TCCR0B |= _BV(CS01); //prescaler 8 OCR0A = 49; //przerwanie co 50us } void TIMER1_Init() //Konfiguracja TIMER1 { //(odpowiedzialny za przyciski) TCCR1B |= _BV(WGM12)|_BV(CS12)|_BV(CS10); //tryb CTC prescaler 1024 OCR1A = 780; //przerwanie co 100 ms } void INPUTS_config() //początkowa konfiguracja czasomierza { PORTB |= _BV(3); //zapal diodę D2 zgaś D1 MCUCR |= _BV(ISC11) | _BV(ISC10); //ustaw przerwanie z int1 na zbocze narastające start_edge=0; //zeruj flagę start_edge PORTB &= ~_BV(2); //zapal diodę D3 zgaś D4 MCUCR |= _BV(ISC01); //ustaw przerwanie z int0 MCUCR &= ~_BV(ISC00); //na zbocze opadające stop_edge=1; //ustaw flagę stop_edge START_ON; //włącz przerwanie z INT1 (START) } //************************** Program główny ******************************** int main(void) //main { DDRB=0xfe; //PB0 wejście pozostałe wyjścia DDRD=0x02; //PD1 wyjście pozostałe wejścia PORTB=0xfe; //port B = 11111110 PORTD=0xfd; //port D = 11111101 USART_Init(VUBRR); //inicjacja UART _delay_ms(5); //opóźnienie USART_Transmit(0x09); //tab USART_Transmit(0x09); //tab Uart_write("***Czasomierz cyfrowy***"); //wyślij napis USART_Transmit(0x0d); //powrót kursora USART_Transmit(0x0a); //nowy wiersz USART_Transmit(0x0d); //powrót kursora USART_Transmit(0x0a); //nowy wiersz USART_Transmit(0x0d); //powrót kursora USART_Transmit(0x0a); //nowy wiersz Uart_write("Lp."); //wyślij napis USART_Transmit(0x09); //tab USART_Transmit(0x7c); //znak "|" USART_Transmit(0x09); //tab Uart_write("Pomiar"); //wyślij napis "Pomiar" USART_Transmit(0x0d); //powrót kursora USART_Transmit(0x0a); //nowy wiersz Uart_write("--------|-------------------------"); //linia pozioma tabelki USART_Transmit(0x0d); //powrót kursora USART_Transmit(0x0a); //nowy wiersz TIMER0_Init(); //inicjacja TIMER0 TIMER1_Init(); //inicjacja TIMER1 TIMER1_ON; //włącz skanowanie klawiatury (timer1) sei(); //odblokuj przerwania MeasuredData[0]=20; //zero do wyświetlenia na wyświetlaczu SendMeasure(MeasuredData); //wyświetl zero BUZER; //sygnał dźwiękowy INPUTS_config(); //początkowa konfiguracja czasomierza while(1) { //pętla nieskończona if(measuring_flag && time_counter >= 1000){//wyłącz buzer po 10ms od startu //przy mierzonym czasie mniejszym //od 10ms wyłączenie buzera PORTB |= _BV(6); //następuje po impulsie STOP wtedy //sygnał dźwiękowy } //start i stop tworzą jeden dłuższy //sygnał = 10ms+czas pomiaru. if(key_flag) { //jeżeli naciśnięto klawisz key_flag=0; //zeruj flagę naciśniętego klawisza switch(key_code) { //wykonaj zależnie od kodu klawisza case 0x10: //wybór zbocza start if(start_edge==0) { //jeżeli flaga start_edge==0 PORTB &= ~_BV(3); //zapal diodę D1 zgaś D2 MCUCR |= _BV(ISC11); //ustaw przerwanie z int1 MCUCR &= ~_BV(ISC10); //na zbocze opadające start_edge=1; //ustaw flagę start_edge } else { //jeżeli flaga start_edge==1 PORTB |= _BV(3); //zapal diodę D2 zgaś D1 MCUCR |= _BV(ISC11) | _BV(ISC10);//ustaw przerwanie z int1 start_edge=0; //na zbocze narastające } //zeruj flagę start_edge BUZER; //sygnał dźwiękowy while((KEY)==0x10); //czekaj na puszczenie klawisza break; //przerwij pętlę case 0x20: //wybór zbocza stop if(stop_edge==0) { //jeżeli flaga stop_edge==0 PORTB &= ~_BV(2); //zapal diodę D3 zgaś D4 MCUCR |= _BV(ISC01); //ustaw przerwanie z int0 MCUCR &= ~_BV(ISC00); //na zbocze opadające stop_edge=1; //ustaw flagę stop_edge } else { //jeżeli flaga stop_edge==1 PORTB |= _BV(2); //zapal diodę D4 zgaś D3 MCUCR |= _BV(ISC01) | _BV(ISC00);//ustaw przerwanie z int0 stop_edge=0; //na zbocze narastające } //zeruj flagę stop_edge BUZER; //sygnał dźwiękowy while((KEY)==0x20); //czekaj na puszczenie klawisza break; //przerwij pętlę case 0x40: //reset TIMER0_OFF; //wyłącz licznik czasu (timer0) START_OFF; //wyłącz przerwania z STOP_OFF; //wejść start i stop EIFR |= _BV(INTF1); //zeruj flagę INTF1 EIFR |= _BV(INTF0); //zeruj flagę INTF0 PORTB |= _BV(1); //zgaś diodę GATE MeasuredData[0]=20; //zero do wyświetlenia na wyświetlaczu SendMeasure(MeasuredData); //wyświetl zero BUZER; //sygnał dźwiękowy START_ON; //włącz przerwanie z wejścia START while((KEY)==0x40); //czekaj na puszczenie klawisza break; //przerwij pętlę } key_code=0; //zeruj kod klawisza; } } }
Do pobrania: Czasomierz_kod_c.c (kopia)
Program procesora wyświetlacza.
//***************************************************************************** //*** *** //*** 6-cyfrowy wyświetlacz LED 1wire *** //*** by Dejmos *** //*** uC - ATtiny2313 F_CPU-8MHz FUSES - low: 0xFF; high: 0xDF *** //*** 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 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[12]; //tablica odebranych danych volatile uint8_t flaga_good = 0; uint8_t z=0; //deklaracja zmiennej globalnej z 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 { PORTD = 0x00; //wygaś wszystkie PORTB = 0x00; //wyświetlacze 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ę na wyświetl. 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ę na wyświetl. 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ę na wyświetl. 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ę na wyświetl. 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ę na wyświetl. 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ę na wyświetl. 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 lini 1wire } 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 RecData=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()) { RecData |= 0x01<<i; //jeżeli ReadBit==1 ustaw 1 } } //w miejsce i-tego bitu w odbieranym bajcie return RecData; //zwróć odebrany bajt } void ReceiveData() //funkcja odpowiedzialna za odbiór danych { uint8_t i,n, 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<11; i++) { //odbierz 7 bajtów temp[i] = ReceiveByte(); //odbierz bajt } for(i=0; i<10; i++) { //wykonaj 6 razy crc8 = _crc_ibutton_update(crc8, temp[i]); //oblicz crc z pierwszych //9 bajtow } if(temp[10]==crc8) { //porównaj crc odebrane z obliczonym //jeżeli crc zgodne if(temp[0]!=20) { //i //jeżeli i=0; //temp[0] różne od kodu 20 while(temp[i]==0) { //szukaj //pierwszej i++; //znaczącej cyfry if(i==4) { break; //do wyswietlenia } } for(n=0; n<6; n++) { //przepisz //wynik dane[n]=temp[i]; //pomiaru do i++; //tablicy dane } dane[10-i]=dane[10-i]+10;//wstaw przecinek w odpowiednie miejsce } else { //jeżeli temp[0] = 20 //oznacza to dane[5]=0; //że wyświetlacz for(n=0; n<5; n++) { //ma wyświetlić //zero dane[n]=20; //na } //ostatnim } //wyśietlaczu } 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 blokowanie //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 }
Do pobrania: Wyswietlacz_do_czasomierza_kod_c.c (kopia)
SPECYFIKACJA
Do pobrania
Errata
1) Zwiększyłem pady pod transformator. Pozwoli to na zastosowanie innego transformatora niż podany w specyfikacji ze względu na to, że ten trochę się nie mieścił do obudowy i musiałem go delikatnie przesunąć na płytce.2) Film przedstawia czasomierz jeszcze z wersją programu gdzie przerwanie od Timera0 wykonywało się co 40us.
3) Wprowadzona została programowa poprawka mierzonego czasu (na zdjęciach i filmiku jeszcze tego nie ma w plikach hex i kodzie już jest).
Zapraszam do komentowania.
Pozdrawiam Dejmos.
Zdjęcia
Pomiar 1
Pomiar 2
Pomiar 3
Brak komentarzy:
Prześlij komentarz