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