sobota, 12 marca 2011

DIY: Czasomierz na ATtiny4313 z wyświetlaczem 6 LED 1wire


Autor: Dejmos
Redakcja: Dondu


Witam wszystkich

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

Do pobrania pełna dokumentacja wraz z plikami programu i PCB: Dokumentacja_czasomierz.rar (kopia)


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