Mikrokontrolery - Jak zacząć?

... czyli zbiór praktycznej wiedzy dot. mikrokontrolerów.

środa, 23 marca 2011

DIY: Bombowy zegarek na ATmega8


Autor: wieloiksiasty
Redakcja: Dondu

Zegarek to jeden z najlepszych pomysłów na prezent. Estetyczny i praktyczny zegarek zawsze znajduję się w widocznym miejscu, a jeśli jest wystarczająco efektowny i starannie wykonany zawsze wywołuje efekt WOW oraz zapewnia doskonałą reklamę konstruktora. Taki właśnie jest mój projekt.


Efekt końcowy


Mój zegarek to prosta konstrukcja oparta na mikrokontrolerze Atmel ATMega8A-PU, wspieranym przez zegar RTC DS1307, termometr DS1820 i rejestr przesuwny 74HC164. Całość mieści się na pojedynczej płytce drukowanej, przyczepionej do efektownej imitacji dynamitu. Układ może działać jako zegar, termometr, stoper oraz minutnik. Dzięki temu jest bardzo wszechstronny i nadaje się zarówno do kuchni, jak i do warsztatu. Koniec lania wody :-D.




Hardware

Schemat układu narysowany w programie Eagle 6.4.0 jest widoczny poniżej oraz załączony w plikach do pobrania w dalszej części artykułu.


Schemat układu (EAGLE 6.4.0)
Pełna rozdzielczość: tutaj


Centralnym punktem układu jest mikrokontroler ATMega8A-PU, taktowany wewnętrznym generatorem RC. Oznacza to, że równie dobrze do tego celu nadaje się ATMega8L i inne z tej serii. Wokół mikrokontrolera znajduje się standardowy osprzęt czyli kondensator i rezystor podciągający RESET do VCC. Kondensator jest tylko jeden, ale został umieszczony pod mikrokontrolerem, pomiędzy dwoma pinami zasilającymi i doskonale spełnia swoją rolę.

Pomiarem czasu i temperatury zajmują się układy DS1307 i DS1820. Zdecydowałem się na zewnętrzny zegar RTC, ponieważ gwarantuje on stabilne działanie oraz maksymalnie długi czas podtrzymywania na jednej baterii. Poza tym uprościło to kod i przyspieszyło prace. Przewagę DS1820 nad innymi termometrami stanowiła przede wszystkim jego obecność w moich zapasach. Analizując schemat można zauważyć, że brakuje rezystorów podciągających linie SDA i SCL na magistrali I²C (TWI).  Jest to mój błąd, który jednak okazał się zupełnie niegroźny, gdyż wewnętrzny pull-up mikrokontrolera z powodzeniem zastępuje zewnętrzne podciąganie. Przy wprowadzaniu poprawek gospodarowanie miejsca na PCB okazało się bardzo trudne dlatego na ostatecznej wersji schematu również ich nie ma. Pomimo tego układ działa bez problemów i całkowicie stabilnie.

Rolę interface użytkownika pełnią cztery przyciski, buzzer z generatorem, cztery diody LED sterowane jednym tranzystorem (kropki pomiędzy wyświetlaczami) oraz trzy wyświetlacze 7-segmentowe ze wspólną anodą. Podczas projektowania okazało się, że brakuje pinów do realizacji multipleksowania, dlatego na płytce znalazł się również rejestr przesuwny 74HC164. Dzięki niemu udało się uniknąć kolejnych kompromisów i kombinacji oraz uprościć sterowanie wyświetlaczami. Objętość kodu również nie ucierpiała, obsługa rejestru przesuwnego nie jest trudniejsza niż przełączanie kolejnych pinów. Oprócz tego na płytce znajduje się gniazdo programowania oraz prosty stabilizator napięcia z układem 7805 (na układzie widocznym na zdjęciach został zastąpiony zworą).


Mozaika ścieżek płytki PCB


Zaprojektowanie płytki drukowanej było podwójnie trudne, ponieważ rozmieszczenie najważniejszych elementów (wyświetlaczy, diód, złączy, przycisków) zostało ustalone na samym początku i nie mogłem ich potem zmieniać. Udało się zaprojektwać dość zwartą płytkę jednostronną, prawie wszystkie elementy są przewlekane (poza dwoma kondensatorami). Oprócz elementów na płytce znajduje się również imponująca ilość zworek, na szczęście większość z nich została ukryta pod wyświetlaczami, dlatego nie szpecą płytki.

Płytka została wykonana na standardowym jednostronnym laminacie metodą termotransferu. Ścieżki zabezpieczone są warstwą kalafonii, a na górnej stronie znajduje się nadruk, również wykonany metodą termotransferu. Po obu stronach płytki znajdują się uchwyty na taśmę klejącą, którą układ jest przymocowany do imitacji bomby. Ukończona płytka prezentuje się imponująco.


Skończona płytka drukowana


Skończona płytka drukowana


Skończona płytka drukowana


Software

Software zegara został napisany w C++, z użyciem IDE Eclipse i kompilatora avr-gcc. Użycie C++ zamiast czystego C, może wydawać się nadużyciem, ale w kodzie nie używałem klas i programowania obiektowego, a jedynie niektóre przydatne sztuczki, na które pozwala C++, np. przeładowywanie funkcji. Rozmiar kodu nie cierpi znacznie (czasami nawet powstaje mniejszy kod), a przystosowanie istniejących już fragmentów kodu na potrzeby publikacji uznałem za zbyt czasochłonne. Cały projekt jest podzielony na części, które znajdują się w osobnych plikach.

Kod jest dość rozwlekły i nie wymaga specjalnego tłumaczenia. Komentarze zawarte w kodzie oraz poniższy tekst z pewnością wystarczą do zrozumienia działania kodu.

Program dla ATmega8 (i pokrewnych ATmega8A, ATmega8L, ...) działających z zegarem 8MHz, dla fusebitów ustawionych następująco:
  • Low: E4 
  • High: D9

Devastator/DS1820/


Pliki ds.h & ds.cpp zawierają funkcje niezbędne do obsługi termometru. W kodzie znajduje tylko niezbędne minimum.  Dwie funkcje pozwalają na inicjalizację konwersji temperatury oraz odczyt. Funkcja odczytująca temperaturę od razu zamienia ją na konwencjonalną 16-bitową zmienną ze znakiem wg algorytmu dla DS1820, więc zmiana termometru na wersję DS18B20 wymaga przepisania tej funkcji na nowo.

Pliki onewire.h & onewire.c to biblioteka do obsługi interface 1-wire za pomocą dowolnego pinu I/O.

Devastator/RTC/


Pliki rtc.h & rtc.cpp zawierają obsługę układu DS1307 oraz rzeczy niezbędne do zarządzania czasem. Tutaj znajduje się między innymi deklaracja struktury do przechowywania czasu oraz instrukcje konwersji pomiędzy systemem dziesiętnym, który jest używany w programie, a systemem BCD, który działa w układzie DS1307.

Pliki twi.h & twi.c to biblioteka do obsługi wbudowanego w mikrokontroler sprzętowego interface TWI (I²C).

Devastator/


Pliki user.h & user.cpp zawierają wszystko co potrzebne do realizacji interface użytkownika. Tutaj znajduję się obsługa wyświetlacza, przycisków, kropki oraz buzzera. W pliku user.h są również opisane funkcje poszczególnych pinów.

Plik main.cpp  zawiera globalne zmienne now, alarm oraz alarm_on, które przechowują zawsze aktualny czas, czas uruchomienia budzika i flagę aktywowania budzika, poza tym znajdują się w niej trzy elementy:

Funkcja main – zawiera inicjalizację wszystkich potrzebnych peryferiów oraz timera2, który steruje głównym przerwaniem programu. Na końcu znajduje się pętla nieskończona, która wykonuje się w czasie bezczynności.

Przerwanie przy porównaniu Timer2 – to główna część całego programu. Przerwanie jest wywoływane z częstotliwością 1kHz. Na początku znajdują się deklaracje zmiennych używanych w całym przerwaniu. Są to:
  1. Zawartość wyświetlacza.
  2. Wciśnięty przycis oraz przycisk wciśnięty podczas ostatniej iteracji.
  3. Wybrany przez użytkownika tryb oraz tryb wybrany w ostatniej iteracji.
  4. Zmienna buzzera.
  5. Czas przytrzymania przycisku 4, potrzebny do wejścia w ustawienia.

Następnie znajdują się zadania wykonywane przed właściwą częścią. Tutaj realizowana jest obsługa przycisków, sztuczny generator milisekund oraz wybór trybu działania układu.

Zastosowanie sztucznego generatora milisekund okazało się konieczne, ponieważ układ DS1307 zapewnia tylko sekundy. Precyzja takiego rozwiązania może budzić wątpliwości, tym bardziej, że mikrokontroler jest taktowany wbudowanym generatorem RC, ale wszystkie wątpliwości okazują się bez znaczenia, gdy zauważymy, że czas reakcji na naciśnięcie przycisku wynosi zazwyczaj około 20-40ms i na dodatek nie jest stały.

Kolejnym elementem jest realizacja poszczególnych funkcji działania układu. Umieszczenie wszystkich potrzebnych instrukcji w jednym miejscu, jedna po drugiej, pozwala na wykonywanie zadań wszystkich elementów jednocześnie, pomimo że wyświetlona może być tylko jedna z funkcjonalności. Dzięki temu można jednocześnie uruchomić stoper, minutnik oraz wyświetlić aktualną godzinę.

Zegar odpowiada za wyświetlanie aktualnej godziny, daty (przycisk 1), temperatury (przycisk 2) oraz włączania, wyłączania budzika (przycisk 3). W tej części jest również realizowana obsługa budzika oraz odczyt temperatury na zawołanie (odczyt na bieżąco trwa zbyt długo i mógłby powodować samo nagrzenie się termometru /self-heating/). Tutaj również znajduje się procedura budzenia o zadanym czasie.

Stoper jest bardziej skomplikowany. Ta część rozpoczyna się od deklaracji flag stopwatchStarted oraz stopwatchFreezen (zamrożenie wyświetlacza) i zmiennych stopwatchStartTime (moment startu), stopwatchOffset (czas wyświetlany podczas wejścia w tryb pauzy), stopwatchOutput (czas aktualnie przekazywany na wyświetlacz). Następnie następuje obsługa przycisków, obliczanie czasu do wyświetlenia oraz przekazanie danych do interface użytkownika (wyświetlacz, kropka, buzzer). Dodatkowo, kiedy nie ma potrzeby wyświetlania godzin, dane przesuwane są o dwa miejsca w prawo, a zwolnione miejsce zajmowane jest przez milisekundy.

Minutnik działa na zasadzie odliczania czasu do zera i jest to funkcja najbardziej adekwatna to wyglądu zewnętrznego urządzenia :-D. Minutnik działa odwrotnie do stopera, a ponadto dodany jest tryb ustawiania czasu od którego rozpoczyna się odliczanie.

Następna jest procedura obsługi specjalnego trybu ustawień. Tutaj ustawiany jest aktualny czas i data oraz godzina zadziałania budzika.  Tryb ustawień aktywowany jest 3 sekundowym przytrzymaniem przycisku 4 oraz jako jedyny samodzielnie obsługuje przycisk 4 (gdy już zostanie aktywowany), ponieważ odbywa się tam zapis ustawień do DS1307 i EEPROM.

Przerwanie kończy procedura końcowa. Znajduje się tam obsługa wyświetlacza, aktualizacja zmiennych last i last_mode oraz obsługa buzzera.
Ostatnim elementem programu jest przerwanie zewnętrzne INT0 pochodzące od układu DS1307, które generuje sygnał o częstotliwości 1Hz. W tym miejscu aktualizowana jest globalna struktura przechowująca zawsze akutalny czas.

Podczas prac okazało się, że komunikacja z układem DS1307 jest zbyt czasochłonna i powoduje miganie wyświetlacza. Dlatego dodałem atrybut ISR_NOBLOCK i podzieliłem przerwanie na dwie części. Pierwsza odpowiada za komunikację z układem DS1307 i podczas jej działania nie są blokowane przerwania. Druga część odpowiada za aktualizację globalnej zmiennej now oraz synchronizację softwarowego generatora milisekund i jest umieszczona w bloku z zablokowanymi przerwaniami. W przekazywaniu danych pośredniczy bufor buffer. Kopiowanie danych z bufora do miejsca przeznaczenia jest na tyle szybkie, że nie powoduje zaburzeń w pracy układu.


Część artystyczna

Każdy projekt, który ma opuścić warsztat, trzeba jakoś wykończyć. W końcu sama płytka nie jest ani ładna, ani praktyczna. Najoczywistszym sposobem na wykończenie, jest obudowa. Niestety gotowe obudowy, które można kupić w elektroniku, nie są zbyt estetyczne. Samodzielne wykonanie akceptowalnej obudowy zdecydowanie przekraczało moje możliwości, dlatego zdecydowałem się na coś bardziej oryginalnego. Przyczepiłem płytkę do prostej imitacji dynamitu, a całość natychmiast zyskała efektowny i mocno filmowy wygląd.

Z ręczników kuchennych wydobyłem rdzeń, przebiłem go patyczkiem od szaszłyków) ok. 2,5cm od końca. W rurze umieściłem tekturowe kółko, korzystając z pomocy patyczka (dzięki patyczkowi denko nie wpadnie do środka). Miejsce styku tektury i rury zalałem klejem. Po wyschnięciu wyciągnąłem patyczek. W środku umieściłem ciasno zwinięte gazety i powtórzyłem procedurę wklejania denka z drugiej strony tuby.

Całość owinąłem taśmą izolacyjną, zostawiając jak najwięcej taśmy na zakładkę. Zakładki zawinąłem do środka i ustabilizowałem kolejnym tekturowym kółkiem.

Imitacja dynamitu została pomalowana farbą w sprayu z użyciem szablonu. Niestety okazało się, że farba nie chce do końca wyschnąć na taśmie izolacyjnej. Nie mam pojęcia dlaczego. Lekko wilgotna farba nie przeszkadza, dopóki nie trzeba będzie wytrzeć kurzu. Niestety nie udało mi się znaleźć lepszej alternatywy.

Po przeschnięciu farby całość skleiłem duct tapem (taśma zbrojona) w plaster miodu. Można również uformować piramidkę. Dla lepszego efektu dodałem jeszcze zwinięte w spiralkę druciki w kolorowych izolacjach.


Efekt końcowy


Działanie i obsługa układu

Działanie układu prezentuje film:



Ze względu na brak bezpośrednich oznaczeń i dodatkowego wyświetlacza informującego o wybranej funkcji obsługa może wydawać się trudna, ale to tylko pozory. Zegar ma trzy tryby działania, w których każdy przycisk ma trochę inną rolę. Najwygodniejsza będzie tabelka:


Zegar Stoper Minutnik Ustawienia (tryb specjalny)
Przycisk 1 Data Start/pauza Start/pauza Dodaj jeden
Przycisk 2 Termometr Reset Reset Odejmij jeden
Przycisk 3 Budzik ON/OFF i podgląd Hold Ustaw czas Następna wartość
Przycisk 4 Następny tryb Następny tryb Następny tryb Zapisz i wyjdź


Wyjaśnienia może wymagać kilka rzeczy. Po pierwsze hold to funkcja pozwalająca zamrozić czas na wyświetlaczu bez zatrzymywania stopera. W trybie minutnika w miejscu funkcji hold znajduje się wejście w podtryb ustawiania czasu startowego. Obsługuje się go w taki sam sposób jak specjalny tryb ustawień, ale wyjście następuje samoczynnie po przejściu przez wszystkie trzy wartości za pomocą przycisku 3. Dobrze widać to na filmie.

Tryb ustawień jest zrealizowany inaczej niż pozostałe. Za pomocą przycisku 3 przełączamy wybraną wartość w lewo. W sumie są tam trzy ustawienia: data, budzik oraz godzina. Godzina jest na końcu ponieważ ustawiamy ją na końcu, chcąc zsynchronizować się z innym zegarem. Kropka na dole wyświetlaczy pozwala zorientować się, gdzie się znajdujemy.

Ostatnią rzeczą, która wymaga wyjaśnienia jest sposób sygnalizacji aktywowania budzika. Kropka na dole skrajnie prawego wyświetlacza świeci się, gdy budzik jest aktywowany.


Errata

Osoby, które dogłębnie przeanalizują projekt zauważą pewne różnice pomiędzy tym co jest w plikach, a tym co na zdjęciach. Te różnice to błędy, które odkryłem, gdy było już za późno. Wszystkie które znalazłem zostały już poprawione.


Literatura



Załączniki do pobrania

W załącznikach znajduje się kompletny projekt urządzenia:





Uwagi redakcji

Dwie istotne uwagi techniczne:

1. brak rezystorów ograniczających prąd diod LED (segmentów) wyświetlacza. To bardzo istotny problem mogący skutkować uszkodzeniem mikrokontrolera: Mikrokontroler vs prądy pinów

W związku z tym, po wprowadzeniu rezystorów nastąpiłoby znaczące przyciemnienie wyświetlanych cyfr, co powinno zostać rozwiązane poprzez dodanie bufora np. ULN2803.

2. zastosowany rejestr przesuwny 74HC164 nie zawiera zatrzasków, co powoduje powstawanie tzw. duchów na wyświetlaczu. Należało zastosować np. 74HC595.
EDIT: 18 stycznia 2014r. Wyjaśnienie przyczyny wykreślenia uwagi w komentarzach poniżej.

Pomimo tych uwag technicznych nasza ocena jest wysoka, ze względu na spory program, który naszym zdaniem może stanowić bazę dla podobnych projektów.

Autor odniósł się do naszych uwag w dyskusji w komentarzu do niniejszego artykułu tutaj.




Pliki źródłowe

Poniżej pliki źródłowe z załącznika.

Pliki dot. DS1820


ds.h

//Obsługa DS1820
//Plik ds.h

#include <avr/io.h>
#include <util/delay.h>
#include "onewire.h"

#ifndef DS18B20_H
#define DS18B20_H

//Zainicjuj konwersję temperatury
uint8_t dsConvertT();

//Odczytaj temperaturę z czujnika
int16_t dsRead();

#endif


ds.ccp

//Obsługa DS1820
//Plik ds.cpp

#include "ds.h"

//--------------------------------------------------
//Zainicjuj konwersję temperatury
uint8_t dsConvertT()
{
  if (!oneWireReset()) return 0;

  oneWireWriteByte(0xcc); //Wybierz wszystkie
  oneWireWriteByte(0x44); //Konwertuj

  return -1;
}

//--------------------------------------------------
//Zainicjuj konwersję temperatury
int16_t dsRead()
{
  uint16_t i, tmp;

  if (!oneWireReset()) return 0;

  oneWireWriteByte(0xcc); //Wybierz wszystkie
  oneWireWriteByte(0xbe); //Odczytaj

  i = oneWireReadByte(); //Odczytaj pierwszy bajt (wartość)
  tmp = oneWireReadByte(); //Odczytaj drugi bajt (znak)


  //Konwersja temperatury
  i *= 10;
  i /= 2;
  if(tmp != 0) i *= -1;

  return i;
}


onewire.h

//Interface 1-wire
//Plik onewire.h

#include <avr/io.h>
#include <util/delay.h>

#ifndef ONEWIRE_H_
#define ONEWIRE_H_

//Interface realizowany na PC0
#define SET_ONEWIRE_PORT     PORTC  |=  _BV(0)
#define CLR_ONEWIRE_PORT     PORTC  &= ~_BV(0)
#define IS_SET_ONEWIRE_PIN   PINC   &   _BV(0)
#define SET_OUT_ONEWIRE_DDR  DDRC   |=  _BV(0)
#define SET_IN_ONEWIRE_DDR   DDRC   &= ~_BV(0)

//Sprawdź stan magistrali
uint8_t oneWireReset();

//Zapisz bajt
void oneWireWriteByte(uint8_t byte);

//Odczytaj bajt
uint8_t oneWireReadByte();

#endif


onewire.ccp

//Interface 1-wire
//Plik onewire.cpp

#include "onewire.h"

//--------------------------------------------------
//Sprawdź stan magistrali
uint8_t oneWireReset()
{
  CLR_ONEWIRE_PORT;

  if (!(IS_SET_ONEWIRE_PIN)) return 0;

  SET_OUT_ONEWIRE_DDR;
  _delay_us(500);
  SET_IN_ONEWIRE_DDR;
  _delay_us(70);

  if(!(IS_SET_ONEWIRE_PIN))
  {
    _delay_us(500);
    return(1);
  }

  _delay_us(500);

return(0);
}

//--------------------------------------------------
//Zapisz bajt
void oneWireWriteByte(uint8_t byte)
{
 uint8_t i;

   CLR_ONEWIRE_PORT;

   for (i=0; i<8; i++)
   {
     SET_OUT_ONEWIRE_DDR;

     if (byte & 0x01)
     {
       _delay_us(7);
       SET_IN_ONEWIRE_DDR;
       _delay_us(70);
     }
     else
     {
        _delay_us(70);
        SET_IN_ONEWIRE_DDR;
        _delay_us(7);
     }

     byte >>= 1;
   }
}

//--------------------------------------------------
//Odczytaj bajt
uint8_t oneWireReadByte()
{
 uint8_t i, byte = 0;

  SET_IN_ONEWIRE_DDR;

  for (i=0; i<8; i++)
  {
     SET_OUT_ONEWIRE_DDR;
     _delay_us(7);
     SET_IN_ONEWIRE_DDR;
     _delay_us(7);
     byte >>= 1;

     if(IS_SET_ONEWIRE_PIN) byte |= 0x80;

     _delay_us(70);
  }

  return byte;
}

Pliki dot. RTC


rtc.h

//Obsługa RTC DS1307
//Plik rtc.h

#include "twi.h"
#include <avr/io.h>

#ifndef RTC_H_
#define RTC_H_

//Przydatne makra
#define set(reg, num) ((reg) |= (1 << (num)))
#define clear(reg, num) ((reg) &= ~(1 << (num)))

//Struktura do przechowywania czasu
struct time
{
 volatile uint16_t msec;
 uint8_t sec;
 uint8_t min;
 uint8_t hour;
 uint8_t wday;
 uint8_t day;
 uint8_t month;
 uint8_t year;
};

//Ta sama struktura nadaje się do czasy w formacie BCD
typedef time BCDTime;

//Zapisz/odczytaj w formacie BCD
void  rtcSetBCDTime(BCDTime data); //Zapisz czas
BCDTime rtcGetBCDTime();    //Odczytaj czas

//Zapisz/odczytaj w formacie dziesiętnym
void rtcSetTime(time data); //Zapisz czas
time rtcGetTime();   //Odczytaj czas

//Sprawdź czy nie doszło do resetu
bool checkReset();

//Konwersja pomiędzy formatami czasu
uint8_t binToBCD(uint8_t data);
BCDTime binToBCD(time data);
uint8_t BCDToBin(uint8_t data);
time BCDToBin(BCDTime data);

//Inicjalizacja komunikacji z DS1307
void rtcInit();

//Okrojone funkje do dodawania i odejmowania czasu
void rtcDiff(time* min, time* sub);  //Odejmowanie
void rtcSum(time* first, time* second); //Dodawanie

#endif /* RTC_H_ */


rtc.ccp

//Obsługa RTC DS1307
//Plik rtc.cpp

#include "rtc.h"

//--------------------------------------------------
//Zapisz czas do DS1307
void rtcSetBCDTime(BCDTime data)
{
 twiStart();  //początek
 twiAddress(208);//adres DSa
 twiWrite(0); //adres zapisu

 twiWrite(data.sec); //zapis
 twiWrite(data.min);
 twiWrite(data.hour);
 twiWrite(data.wday);
 twiWrite(data.day);
 twiWrite(data.month);
 twiWrite(data.year);

 twiStop(); //zakończenie

}

//--------------------------------------------------
//Odczytaj czas z DS1307
BCDTime rtcGetBCDTime()
{
 BCDTime data;

 twiStart();  //początek
 twiAddress(208);//adres DSa
 twiWrite(0); //adres odczytu

 twiStart();  //początek odczytu
 twiAddress(209);//adres odczytu z DSa

 data.sec = twiReadACK(); //odczyt
 data.min = twiReadACK();
 data.hour = twiReadACK();
 data.wday = twiReadACK();
 data.day = twiReadACK();
 data.month = twiReadACK();
 data.year = twiReadNACK();

 twiStop(); //zakończenie
 return data;

}

//--------------------------------------------------
//Zapisz czas
void rtcSetTime(time data)
{
 rtcSetBCDTime(binToBCD(data));
}

//--------------------------------------------------
//Odczytaj czas
time rtcGetTime()
{
 return BCDToBin(rtcGetBCDTime());
}

//--------------------------------------------------
//Sprawdź czy nie doszło do resetu
bool checkReset()
{
 twiStart();  //początek
 twiAddress(208);//adres DSa
 twiWrite(0); //adres odczytu

 twiStart();  //początek odczytu
 twiAddress(209);//adres odczytu z DSa

 uint8_t data = twiReadNACK(); //odczyt

 twiStop(); //zakończenie

 if(data == 0x80)
  return true;
 else
  return false;
}

//--------------------------------------------------
//Konwersja
uint8_t binToBCD(uint8_t data)
{
 uint8_t result;

 result = (data / 10) << 4;
 result += (data % 10);

 return result;
}

BCDTime binToBCD(time data)
{
 BCDTime result;

 result.sec = binToBCD(data.sec);
 result.min = binToBCD(data.min);
 result.hour = binToBCD(data.hour);
 result.wday = binToBCD(data.wday);
 result.day = binToBCD(data.day);
 result.month = binToBCD(data.month);
 result.year = binToBCD(data.year);

 return result;
}

uint8_t BCDToBin(uint8_t data)
{
 uint8_t result;

 result = data & 0x0f;
 result += ((data >> 4) /*& 0x03*/) * 10;

 return result;
}

time BCDToBin(BCDTime data)
{
 time result;

 result.sec = BCDToBin(data.sec);
 result.min = BCDToBin(data.min);
 result.hour = BCDToBin(data.hour);
 result.wday = BCDToBin(data.wday);
 result.day = BCDToBin(data.day);
 result.month = BCDToBin(data.month);
 result.year = BCDToBin(data.year);

 return result;
}

//--------------------------------------------------
//Inicjalizacja komunikacji z DS1307
void rtcInit()
{
 //Pull-up na magistali
 set(PORTC, PC5);
 set(PORTC, PC4);

 //Prędkość transmisji
 twiInit(400000);

 //Wstępna konfiguracja DS1307
 bool reset = checkReset();
 if(reset == true)
 {
  time czas;
  czas = rtcGetTime();
  rtcSetTime(czas);
 }

 twiStart();
 twiAddress(208);
 twiWrite(7);
 twiWrite(0b00010000); //generator 1Hz
 twiStop();

 //Konfiguracja przerwania od czasu
 set(PORTD, PD2); //Pull-up na INT0
 MCUCR |= (1 << ISC00) | (1 << ISC01); //Przerwanie przy rosnącym zboczu
 GICR |= (1 << INT0); //Włącz przerwanie
}

//--------------------------------------------------
//Okrojone funkje do dodawania i odejmowania czasu

//Odejmowanie
void rtcDiff(time* min, time* sub)
{
 //Zmienne przechowujące przeniesienia
 uint8_t carry_sec = 0,
   carry_min = 0,
   carry_hour = 0;

 //Kolejne odejmowania z przeniesieniem

 //Milisekundy
 if(min->msec < sub->msec)
 {
  carry_sec++;
  min->msec += 1000;
 }
 min->msec -= sub->msec;

 //Sekundy
 if(min->sec < (sub->sec + carry_sec))
 {
  carry_min++;
  min->sec += 60;
 }
 min->sec -= (sub->sec + carry_sec);

 //Minuty
 if(min->min < (sub->min + carry_min))
 {
  carry_hour++;
  min->min += 60;
 }
 min->min -= (sub->min + carry_min);

 //Godziny
 if(min->hour < (sub->hour + carry_hour))
 {
  min->hour += 24;
 }
 min->hour -= (sub->hour + carry_hour);

 //TODO można ew. rozszerzyć odejmowanie na pozostałe pola
 //Nie jest to konieczne do poprawnego działania
}

//Dodawanie
void rtcSum(time* element_a, time* element_b)
{
 //Zmienne przechowujące przeniesienia
 uint8_t carry_sec = 0,
   carry_min = 0,
   carry_hour = 0;

 //Kolejne dodawania z przeniesieniem

 //Milisekundy
 if((element_a->msec + element_b->msec) > 999)
 {
  carry_sec++;
  element_a->msec = (element_a->msec + element_b->msec) - 1000;
 }
 else
  element_a->msec += element_b->msec;

 //Sekundy
 if((element_a->sec + element_b->sec + carry_sec) > 59)
 {
  carry_min++;
  element_a->sec = (element_a->sec + element_b->sec + carry_sec) - 60;
 }
 else
  element_a->sec += element_b->sec + carry_sec;

 //Minuty
 if((element_a->min + element_b->min + carry_min) > 59)
 {
  carry_hour++;
  element_a->min = (element_a->min + element_b->min + carry_min) - 60;
 }
 else
  element_a->min += element_b->min + carry_min;

 //Godziny
 if((element_a->hour + element_b->hour + carry_hour) > 23)
  element_a->hour = (element_a->hour + element_b->hour + carry_hour) - 24;

 else
  element_a->hour += element_b->hour + carry_hour;

 //TODO można ew. rozszerzyć dodawnie na pozostałe pola
 //Nie jest to konieczne do poprawnego działania
}


twi.h

//Obsługa TWI
//Plik   twi.h

#ifndef TWI_H_
#define TWI_H_

#include <avr/io.h>

//Inicjalizacja z ustawieniem prędkości transmisji
void twiInit(uint32_t speed);

//Wyślij start/stop
void twiStart();
void twiStop();

//Wyślij adres/dane
void twiAddress(uint8_t data);
void twiWrite(uint8_t data);

//Odczytaj dane z lub bez NACK
uint8_t twiReadACK();
uint8_t twiReadNACK();

#endif


twi.ccp

//Obsługa TWI
//Plik   twi.cpp

#include "twi.h"

//Inicjalizacja z ustawieniem prędkości transmisji
void twiInit(uint32_t speed)
{
 //Włącz TWI
 TWCR = (1<<TWEA) | (1<<TWEN);

 //Ustaw prędkość transmisji
 uint8_t prescaler = 0;
 speed = (F_CPU/speed/100-16)/2;

 while(speed > 255)
 {
  prescaler++;
  speed=speed/4;
 }

 TWSR = (TWSR & ((1<<TWPS1) | (1<<TWPS0))) | prescaler;
 TWBR = speed;
}

//Wyślij start
void twiStart()
{
 TWCR = (1<<TWINT)|(1<<TWSTA)| (1<<TWEN); //wyślij
 while (!(TWCR & (1<<TWINT))); //czekaj
}

//Wyślij stop
void twiStop()
{
 TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO); //wyślij
 while(TWCR & (1<<TWSTO)); //czekaj
}

//Wyślij adres
void twiAddress(uint8_t data)
{
 TWDR = data; //wyślij
 TWCR = (1<<TWINT) | (1<<TWEN);
 while (!(TWCR & (1<<TWINT))); //czekaj
}

//Wyślij dane
void twiWrite(uint8_t data)
{
 TWDR = data; //wyślij
 TWCR = (1<<TWINT) | (1<<TWEN);
 while (!(TWCR & (1<<TWINT))); //czekaj
}

//Odczytaj dane
uint8_t twiReadACK()
{
 TWCR = (1<<TWEA) | (1<<TWINT) | (1<<TWEN); //odbierz
 while (!(TWCR & (1<<TWINT))); //czekaj

 return TWDR; //zwróć wynik
}

//Odczytaj i wyg. NACK
uint8_t twiReadNACK()
{
 TWCR = (1<<TWINT) | (1<<TWEN); //odbierz
 while (!(TWCR & (1<<TWINT))); //czekaj

 return TWDR; //zwróć wynik
}



Pliki główne


user.h

//Projekt -Devastator-
//Plik   user.h
//Autor  wieloiksiasty
//Obsługa użytkownika

#include <avr/io.h>
#include <util/delay.h>

#ifndef USER_H_
#define USER_H_

//Przydatne makra
#define set(reg, num) ((reg) |= (1 << (num)))
#define clear(reg, num) ((reg) &= ~(1 << (num)))

#define on(reg, num) ((reg) &= ~(1 << (num)))
#define off(reg, num) ((reg) |= (1 << (num)))

//--------------------------------------------------
//Wyświetlacz LED

//Definicje segmentów
#define LEDA_DDR DDRB
#define LEDA_PORT PORTB
#define LEDA_NUM PB0

#define LEDB_DDR DDRD
#define LEDB_PORT PORTD
#define LEDB_NUM PD7

#define LEDC_DDR DDRD
#define LEDC_PORT PORTD
#define LEDC_NUM PD4

#define LEDD_DDR DDRB
#define LEDD_PORT PORTB
#define LEDD_NUM PB6

#define LEDE_DDR DDRB
#define LEDE_PORT PORTB
#define LEDE_NUM PB7

#define LEDF_DDR DDRD
#define LEDF_PORT PORTD
#define LEDF_NUM PD6

#define LEDG_DDR DDRD
#define LEDG_PORT PORTD
#define LEDG_NUM PD5

#define LEDDT_DDR DDRD //Kropka
#define LEDDT_PORT PORTD
#define LEDDT_NUM PD3

//Definicje wyprowadzeń rejestru przesuwnego
#define DSP_DATA_DDR DDRD //Data
#define DSP_DATA_PORT PORTD
#define DSP_DATA_NUM PD1

#define DSP_CLK_DDR  DDRD //Zegar
#define DSP_CLK_PORT PORTD
#define DSP_CLK_NUM  PD0

//Inicjalizacja wyświetlacza
void ledInit();

//Obsługa wyświetlacza
void ledProcess(uint8_t content[], uint8_t dot);

//--------------------------------------------------
//Kropka
#define KROPKA_DDR DDRC
#define KROPKA_PORT PORTC
#define KROPKA_NUM PC3

//Inicjalizacja kropki
void kropkaInit();

//Zaświeć/gaś kropkę
void kropkaOn();
void kropkaOff();


//--------------------------------------------------
//Przyciski

//Definicje
#define DB_TIME 10 //Stała filtracji drgań
     //Więcej dłużej/dokładniej
     //Mniej szybciej/bardziej pobieżnie

#define SW1_DDR  DDRB
#define SW1_PORT  PORTB
#define SW1_PIN  PINB
#define SW1_NUM  PB4

#define SW2_DDR  DDRB
#define SW2_PORT  PORTB
#define SW2_PIN  PINB
#define SW2_NUM  PB3

#define SW3_DDR  DDRB
#define SW3_PORT  PORTB
#define SW3_PIN  PINB
#define SW3_NUM  PB2

#define SW4_DDR  DDRB
#define SW4_PORT  PORTB
#define SW4_PIN  PINB
#define SW4_NUM  PB5

//Przydatne makro
#define readPin(pin, num) (~pin & (1<<num))

//Inicjalizacja przycisków
void swInit();

//Obsługa przycisków
//Zwraca wciśnięty przycisk
uint8_t swProcess();

//--------------------------------------------------
//Buzzer

#define BUZZER_DDR DDRB
#define BUZZER_PORT PORTB
#define BUZZER_NUM PB1

//Inicjalizacja buzzera
inline void buzzerInit() {set(BUZZER_DDR, BUZZER_NUM);}

//Włącz/wyłącz buzzer
inline void buzzerOn() {set(BUZZER_PORT, BUZZER_NUM);}
inline void  buzzerOff() {clear(BUZZER_PORT, BUZZER_NUM);}

#endif /* USER_H_ */


user.ccp

//Projekt -Devastator-
//Plik   user.cpp
//Autor  wieloiksiasty
//Obsługa użytkownika

#include "user.h"

//--------------------------------------------------
//Wyświetlacz LED

//Inicjalizacja wyświetlacza LED
void ledInit()
{
 //Segmenty
 //Kierunek
 set(LEDA_DDR, LEDA_NUM);
 set(LEDB_DDR, LEDB_NUM);
 set(LEDC_DDR, LEDC_NUM);
 set(LEDD_DDR, LEDD_NUM);
 set(LEDE_DDR, LEDE_NUM);
 set(LEDF_DDR, LEDF_NUM);
 set(LEDG_DDR, LEDG_NUM);
 set(LEDDT_DDR, LEDDT_NUM);

 //Stan początkowy
 set(LEDA_PORT, LEDA_NUM);
 set(LEDB_PORT, LEDB_NUM);
 set(LEDC_PORT, LEDC_NUM);
 set(LEDD_PORT, LEDD_NUM);
 set(LEDE_PORT, LEDE_NUM);
 set(LEDF_PORT, LEDF_NUM);
 set(LEDG_PORT, LEDG_NUM);
 set(LEDDT_PORT, LEDDT_NUM);

 //Rejestr przesuwny
 set(DSP_DATA_DDR, DSP_DATA_NUM);
 set(DSP_CLK_DDR, DSP_CLK_NUM);

 clear(DSP_DATA_PORT, DSP_DATA_NUM);
 set(DSP_DATA_PORT, DSP_DATA_NUM);

 //Zerowanie wyjść rejestru
 for(uint8_t i = 0; i < 8; i++)
 {
  set(DSP_CLK_PORT, DSP_CLK_NUM);
  clear(DSP_CLK_PORT, DSP_CLK_NUM);
 }
}

//Obsługa wyświetlacza LED
void ledProcess(uint8_t content[], uint8_t dot)
{
 volatile static uint8_t current = 5;

 //Zgaszenie wyświetlacza w celu uniknięcia poświaty
 off(LEDA_PORT, LEDA_NUM);
 off(LEDB_PORT, LEDB_NUM);
 off(LEDC_PORT, LEDC_NUM);
 off(LEDD_PORT, LEDD_NUM);
 off(LEDE_PORT, LEDE_NUM);
 off(LEDF_PORT, LEDF_NUM);
 off(LEDG_PORT, LEDG_NUM);
 off(LEDDT_PORT, LEDDT_NUM);

 //Wystarczy przejść na następny
 if(current < 5)
 {
  set(DSP_DATA_PORT, DSP_DATA_NUM);

  set(DSP_CLK_PORT, DSP_CLK_NUM);
  clear(DSP_CLK_PORT, DSP_CLK_NUM);

  current++;
 }

 //Jeśli nie, wróć do początku
 else
 {
  clear(DSP_DATA_PORT, DSP_DATA_NUM);

  set(DSP_CLK_PORT, DSP_CLK_NUM);
  clear(DSP_CLK_PORT, DSP_CLK_NUM);

  current = 0;
 }

 //---------------------------
 //Wyświetlenie kolejnej cyfry
 if(dot == current)
  on(LEDDT_PORT, LEDDT_NUM);
 else
  off(LEDDT_PORT, LEDDT_NUM);

 if(content[current] == 1)
 {
  off(LEDA_PORT, LEDA_NUM);
  on(LEDB_PORT, LEDB_NUM);
  on(LEDC_PORT, LEDC_NUM);
  off(LEDD_PORT, LEDD_NUM);
  off(LEDE_PORT, LEDE_NUM);
  off(LEDF_PORT, LEDF_NUM);
  off(LEDG_PORT, LEDG_NUM);
 }
 if(content[current] == 2)
 {
  on(LEDA_PORT, LEDA_NUM);
  on(LEDB_PORT, LEDB_NUM);
  off(LEDC_PORT, LEDC_NUM);
  on(LEDD_PORT, LEDD_NUM);
  on(LEDE_PORT, LEDE_NUM);
  off(LEDF_PORT, LEDF_NUM);
  on(LEDG_PORT, LEDG_NUM);
 }
 if(content[current] == 3)
 {
  on(LEDA_PORT, LEDA_NUM);
  on(LEDB_PORT, LEDB_NUM);
  on(LEDC_PORT, LEDC_NUM);
  on(LEDD_PORT, LEDD_NUM);
  off(LEDE_PORT, LEDE_NUM);
  off(LEDF_PORT, LEDF_NUM);
  on(LEDG_PORT, LEDG_NUM);
 }
 if(content[current] == 4)
 {
  off(LEDA_PORT, LEDA_NUM);
  on(LEDB_PORT, LEDB_NUM);
  on(LEDC_PORT, LEDC_NUM);
  off(LEDD_PORT, LEDD_NUM);
  off(LEDE_PORT, LEDE_NUM);
  on(LEDF_PORT, LEDF_NUM);
  on(LEDG_PORT, LEDG_NUM);
 }
 if(content[current] == 5)
 {
  on(LEDA_PORT, LEDA_NUM);
  off(LEDB_PORT, LEDB_NUM);
  on(LEDC_PORT, LEDC_NUM);
  on(LEDD_PORT, LEDD_NUM);
  off(LEDE_PORT, LEDE_NUM);
  on(LEDF_PORT, LEDF_NUM);
  on(LEDG_PORT, LEDG_NUM);
 }
 if(content[current] == 6)
 {
  on(LEDA_PORT, LEDA_NUM);
  off(LEDB_PORT, LEDB_NUM);
  on(LEDC_PORT, LEDC_NUM);
  on(LEDD_PORT, LEDD_NUM);
  on(LEDE_PORT, LEDE_NUM);
  on(LEDF_PORT, LEDF_NUM);
  on(LEDG_PORT, LEDG_NUM);
 }
 if(content[current] == 7)
 {
  on(LEDA_PORT, LEDA_NUM);
  on(LEDB_PORT, LEDB_NUM);
  on(LEDC_PORT, LEDC_NUM);
  off(LEDD_PORT, LEDD_NUM);
  off(LEDE_PORT, LEDE_NUM);
  off(LEDF_PORT, LEDF_NUM);
  off(LEDG_PORT, LEDG_NUM);
 }
 if(content[current] == 8)
 {
  on(LEDA_PORT, LEDA_NUM);
  on(LEDB_PORT, LEDB_NUM);
  on(LEDC_PORT, LEDC_NUM);
  on(LEDD_PORT, LEDD_NUM);
  on(LEDE_PORT, LEDE_NUM);
  on(LEDF_PORT, LEDF_NUM);
  on(LEDG_PORT, LEDG_NUM);
 }
 if(content[current] == 9)
 {
  on(LEDA_PORT, LEDA_NUM);
  on(LEDB_PORT, LEDB_NUM);
  on(LEDC_PORT, LEDC_NUM);
  on(LEDD_PORT, LEDD_NUM);
  off(LEDE_PORT, LEDE_NUM);
  on(LEDF_PORT, LEDF_NUM);
  on(LEDG_PORT, LEDG_NUM);
 }
 if(content[current] == 0)
 {
  on(LEDA_PORT, LEDA_NUM);
  on(LEDB_PORT, LEDB_NUM);
  on(LEDC_PORT, LEDC_NUM);
  on(LEDD_PORT, LEDD_NUM);
  on(LEDE_PORT, LEDE_NUM);
  on(LEDF_PORT, LEDF_NUM);
  off(LEDG_PORT, LEDG_NUM);
 }
 if(content[current] == '-')
 {
  off(LEDA_PORT, LEDA_NUM);
  off(LEDB_PORT, LEDB_NUM);
  off(LEDC_PORT, LEDC_NUM);
  off(LEDD_PORT, LEDD_NUM);
  off(LEDE_PORT, LEDE_NUM);
  off(LEDF_PORT, LEDF_NUM);
  on(LEDG_PORT, LEDG_NUM);
 }
}

//--------------------------------------------------
//Kropka

//Inicjalizacja
void kropkaInit()
{
 set(KROPKA_DDR, KROPKA_NUM);
 set(KROPKA_PORT, KROPKA_NUM);
}

//Zaświeć
void kropkaOn()
{
 clear(KROPKA_PORT, KROPKA_NUM);
}

//Zgaś
void kropkaOff()
{
 set(KROPKA_PORT, KROPKA_NUM);
}

//--------------------------------------------------
//Przyciski

//Inicjalizacja
void swInit()
{
 //Ustaw odczyt
 clear(SW1_DDR, SW1_NUM);
 clear(SW2_DDR, SW2_NUM);
 clear(SW3_DDR, SW3_NUM);
 clear(SW4_DDR, SW4_NUM);

 //Włącz pull_up
 set(SW1_PORT, SW1_NUM);
 set(SW2_PORT, SW2_NUM);
 set(SW3_PORT, SW3_NUM);
 set(SW4_PORT, SW4_NUM);
}

//Obsługa
uint8_t swProcess()
{
 static uint8_t sw1, sw2, sw3, sw4;

 //Odczytaj i zrób deboucing
 if(readPin(SW1_PIN, SW1_NUM) != 0 && sw1 < DB_TIME) sw1++;
 if(readPin(SW1_PIN, SW1_NUM) == 0) sw1=0;

 if(readPin(SW2_PIN, SW2_NUM) != 0 && sw2 < DB_TIME) sw2++;
 if(readPin(SW2_PIN, SW2_NUM) == 0) sw2=0;

 if(readPin(SW3_PIN, SW3_NUM) != 0 && sw3 < DB_TIME) sw3++;
 if(readPin(SW3_PIN, SW3_NUM) == 0) sw3=0;

 if(readPin(SW4_PIN, SW4_NUM) != 0 && sw4 < DB_TIME) sw4++;
 if(readPin(SW4_PIN, SW4_NUM) == 0) sw4=0;

 //Jeśli przeszedł przez debouncing zwróć
 if(sw1 >= DB_TIME)
  return 1;
 if(sw2 >= DB_TIME)
  return 2;
 if(sw3 >= DB_TIME)
  return 3;
 if(sw4 >= DB_TIME)
  return 4;

 //Brak wciśniętych => zwróc zero
 return 0;
}


main.ccp

//Projekt  -Devastator-
//Plik   main.cpp
//Autor  wieloiksiasty

//----------
//Nagłówki
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <util/delay.h>
#include <util/atomic.h>
#include <string.h>
#include "RTC/twi.h"
#include "RTC/rtc.h"
#include "user.h"
#include "DS1820/ds.h"

//Glogalna struktura czasu
time now;
time alarm;
bool alarm_on;

//Dane budzika w EEPROM
EEMEM time eealarm_data;
EEMEM bool eealarm_on;

//----------
//Funkcja main
int main()
{
 //Konfiguracja peryferiów:
 ledInit();  //Wyświetlacza
 swInit();  //Przycisków
 kropkaInit(); //Kropki
 rtcInit();  //Zegara RTC
 buzzerInit(); //Głośniczka

 //Załadowanie danych budzika z EEPROM
 eeprom_read_block(&alarm, &eealarm_data, sizeof(time));
 eeprom_read_block(&alarm_on, &eealarm_on, sizeof(bool));

 //Konfiguracja timer2 do pętli głównej
 TCCR2 |= (1 << WGM21);
 TCCR2 |= (1 << CS20) | (1 << CS21); //Tryb CTC, preskaler 32, ok. 1kHz
 OCR2 = 249;
 TIMSK |= (1 << OCIE2); //Przerwanie przy porównaniu

 kropkaOn(); //Zaświeć kropkę
 sei(); //Włącz przerwania

 while(1)
 {
  //Loopend :-)
  //Taki weekend dla mikrokontrolera :-D
 }
}

//----------
//Pzerwanie Timer2 ok. 1kHz
//Pętla główna całego programu
ISR(TIMER2_COMP_vect)
{
 //--------------------------------------------------
 //Zmienna statyczne
 static uint8_t content[6]; //Zawartość wyświetlacza
 static uint8_t button = 0; //Aktualny przycisk
 static uint8_t last = 0; //Ostatni przycisk
 static uint8_t mode = 0; //Aktualny tryb
 static uint8_t last_mode = 0; //Ostatni tryb
 static uint16_t buzzer = 0; //Odliczanie budzika (post działania)
 static bool budzenie = false; //Flaga zadziałania budzika

 static uint16_t hold_time = 0; //Odliczanie 3 sek. do wejścia w ust.

 uint8_t dot; //Którą kropkę zapalić?

 //Sygnalizacja aktywacji budzika
 if(alarm_on == true)
  dot = 0;
 else
  dot = 0xff;


 //--------------------------------------------------
 //Pre- działania
 button = swProcess(); //Wczytaj przycisk

 if(now.msec < 999) //Generator milisekund
  now.msec++;

 //Przełączanie funcji
 if(button == 4 && button != last && mode != 255)
 {
  mode++;
  if(mode > 2)
   mode = 0;
 }
 //Wchodzenie w ustawienia godziny
 if(button == 4)
 {
  if(hold_time <= 3000)
   hold_time++;
  if(hold_time > 3000)
   mode = 255;
 }
 else
  hold_time=0;

 //Piszczenie przy naciśnięciu przycisku
 if(button != last && button != 0)
  buzzer = 100;

 //Sprawdzenie czy nie należy uruchomić budzika
 if(now.hour == alarm.hour &&
    now.min == alarm.min &&
    now.sec == alarm.sec &&
    alarm_on == true)
  budzenie = true;

 //Aktywny budzik wymusz tryb zegara
 if(budzenie == true)
  mode = 0;

 //--------------------------------------------------
 //Funkcja 0 => Zegar
  if(mode == 0)
  {
   //----------
   //Obsługa budzenia
   if(budzenie == true)
   {
    if(button == 0) //Nic nie jest wciśnięte
    {
     //Sygnały akustyczne
     if(now.msec == 1)
      buzzer = 500;
     if(now.msec < 500)
     {
      kropkaOn();
      content[0] = now.sec%10; //Wyświetl godzinę
      content[1] = now.sec/10;
      content[2] = now.min%10;
      content[3] = now.min/10;
      content[4] = now.hour%10;
      content[5] = now.hour/10;
     }
     else
     {
      kropkaOff();
      content[0] = 0xFF; //Sygnały wizualne, miganie wyświetlacza
      content[1] = 0xFF;
      content[2] = 0xFF;
      content[3] = 0xFF;
      content[4] = 0xFF;
      content[5] = 0xFF;
     }
    }

    //Wciśnięcie czegokolwiek dezaktywuje budzenie
    else
     budzenie = false;
   }

   //----------
   //Normalne działanie
   else
   {
    //----------
    //Obsługa przycisków
    if(button == 0) //Nic nie jest wciśnięte
    {
     if(now.msec < 500)
      kropkaOn();
     else
      kropkaOff();

     content[0] = now.sec%10; //Wyświetl godzinę
     content[1] = now.sec/10;
     content[2] = now.min%10;
     content[3] = now.min/10;
     content[4] = now.hour%10;
     content[5] = now.hour/10;
    }
    else if(button == 1) //Przycisk 1
    {
     kropkaOff();
     content[0] = now.year%10; //Wyświetl datę
     content[1] = now.year/10;
     content[2] = now.month%10;
     content[3] = now.month/10;
     content[4] = now.day%10;
     content[5] = now.day/10;
    }
    else if(button == 2) //Przycisk 2, temperatura
    {
     static uint16_t temp;
     static bool readed = false;

     //Przy pierwszej iteracji zapoczątkuj konwersję
     if(last != 2)
      dsConvertT();

     //W następnych odczytuj
     temp = dsRead();

     kropkaOff(); //Wyłącz dwukropki

     content[0] = 0;
     content[1] = (temp/1)%10; //Wyświetl temperaturę
     content[2] = (temp/10)%10;
     content[3] = (temp/100)%10;

     //Zadbaj o znak - jeśli konieczne
     if(temp < 0) content[4] = '-';

     else content[4] = 0xFF;
     content[5] = 0xFF;

     dot = 2; //Przecinek na 2 pozycji
    }
    else if(button == 3 && last != button) // Przycisk 3
    {
     if(alarm_on == true) //Włącz, wyłącz budzik
      alarm_on = false;
     else
      alarm_on = true;

     //Zapisz wynik do eeprom
     eeprom_write_block(&alarm_on, &eealarm_on, sizeof(bool));

     kropkaOn();
     content[0] = alarm.sec%10; //Wyświetl godzinę budzika
     content[1] = alarm.sec/10;
     content[2] = alarm.min%10;
     content[3] = alarm.min/10;
     content[4] = alarm.hour%10;
     content[5] = alarm.hour/10;
    }
   }
  }

 //--------------------------------------------------
 //Funkcja 1 - stoper
  //----------
  //Zmienne stopera
  static bool stopwatchStarted = false;
  static bool stopwatchFreezen;
  static time stopwatchStartTime;
  static time stopwatchOffset;
  static time stopwatchOutput;

  stopwatchFreezen = false;

  //----------
  //Obsługa przycisków
  if(mode == 1)
  {
   //Przycisk 1 - start/pause
   if(button == 1 && button != last)
   {
    stopwatchStarted ^= true;

    if(stopwatchStarted == true) //Przy starcie załaduj czas startu
     stopwatchStartTime = now;
    else
     stopwatchOffset = stopwatchOutput; //Przy pauzie załaduj offset
   }

   //Przycisk 2 - reset
   else if(button == 2 && button != last)
   {
    stopwatchStarted = false;     //Wyzeruj wszystko
    memset(&stopwatchOutput, 0, sizeof(time));
    memset(&stopwatchOffset, 0, sizeof(time));
   }

   //Przycisk 3 - lap - zamrożenie wyświetlacza
   else if(button == 3)
    stopwatchFreezen = true;
  }

  //----------
  //Obliczanie czasu do wyświetlenia
  if(stopwatchStarted == true)
  {
   //Oblicz czas od ostatniego startu
   stopwatchOutput = now;
   rtcDiff(&stopwatchOutput, &stopwatchStartTime);

   //Dodaj offset
   rtcSum(&stopwatchOutput, &stopwatchOffset);
  }

  //----------
  //Obsługa wyjścia stopera
  if(mode == 1) //Włączona funkcja stopera
  {
   //Miganie kropkami
   if(stopwatchOutput.msec < 500 || stopwatchFreezen == true || stopwatchStarted == false)
      kropkaOn();
     else
      kropkaOff();

   //Sygnały dzwiękowe stopera
   if(stopwatchOutput.msec == 0 && stopwatchStarted == true)
    buzzer = 100;
   if(stopwatchOutput.msec == 0 && stopwatchOutput.sec == 0 && stopwatchStarted == true)
    buzzer = 500;

   //Przekazywanie danych do wyświetlenia
   if(stopwatchFreezen == false)
   {
    if(stopwatchOutput.hour > 0)
    {
     content[0] = stopwatchOutput.sec%10; //Wyświetl czas
     content[1] = stopwatchOutput.sec/10;
     content[2] = stopwatchOutput.min%10;
     content[3] = stopwatchOutput.min/10;
     content[4] = stopwatchOutput.hour%10;
     content[5] = stopwatchOutput.hour/10;
    }
    else
    {
     content[0] = (stopwatchOutput.msec/10)%10; //Wyświetl czas,
     content[1] = stopwatchOutput.msec/100;  //nie pokazuj zera w godzinach tylko m. sekundy
     content[2] = stopwatchOutput.sec%10;
     content[3] = stopwatchOutput.sec/10;
     content[4] = stopwatchOutput.min%10;
     content[5] = stopwatchOutput.min/10;
    }
   }
  }
 //--------------------------------------------------
 //Funckja 2 - minutnik/timer

  //Zmienne statyczne minutnika
  static time timerOutput;
  static time timerUserSet;
  static time timerTarget;
  static bool timerStarted = false;
  static uint8_t timerHighlighted = 3; //Tryb normalny na start

  //Jeśli wybrano minutnik
  if(mode == 2)
  {
   //Tryb normalny
   if(timerHighlighted >= 3)
   {
    //Start/stop
    if(button == 1 && button != last)
    {
     timerStarted ^= true;

     timerTarget = timerOutput;
     rtcSum(&timerTarget, &now); //Oblicz czas docelowy
    }

    //Reset
    if(button == 2 && button != last)
    {
     timerStarted = false;  //Wyzeruj
     timerOutput = timerUserSet;
    }

    //Wejdź w ustawianie czasu
    if(button == 3 && button != last)
    {
     timerStarted = false;

     timerOutput = timerUserSet; //Załaduj wstępne dane
     timerHighlighted = 0;
    }

    if(timerStarted == true)
    {
     //Oblicz czas do wyświetlenia
     timerOutput = timerTarget;
     rtcDiff(&timerOutput, &now);

     //Zasygnalizuj koniec czasu i zatrzymaj
     if(timerOutput.msec == 0 &&
        timerOutput.sec == 0 &&
        timerOutput.min == 0 &&
        timerOutput.hour == 0)
     {
      timerStarted = false;
      buzzer = 2000;
     }
    }

    //Wyświetlanie
    if (timerOutput.hour == 0)
    {
     //Jeśli nie trzeba godzin pokaż milisekundy
     content[0] = (timerOutput.msec/10)%10;
     content[1] = (timerOutput.msec/100)%10;
     content[2] = (timerOutput.sec/1)%10;
     content[3] = (timerOutput.sec/10)%10;
     content[4] = (timerOutput.min/1)%10;
     content[5] = (timerOutput.min/10)%10;
    }
    else
    {
     content[0] = (timerOutput.sec/1)%10;
     content[1] = (timerOutput.sec/10)%10;
     content[2] = (timerOutput.min/1)%10;
     content[3] = (timerOutput.min/10)%10;
     content[4] = (timerOutput.hour/1)%10;
     content[5] = (timerOutput.hour/10)%10;
    }

    //Buzzer
    //Gdy mniej niż 10s pikaj częściej
    if(timerOutput.hour == 0 &&
       timerOutput.min == 0 &&
       timerOutput.sec <= 10 &&
       timerStarted == true)
    {
     if(timerOutput.msec % 200 == 0) //co 200ms
      buzzer = 50;
    }
    //Gdy więcej niż 10s pikaj co 1s
    else if(timerOutput.msec % 1000 == 0 && timerStarted == true)
     buzzer = 100;

    //Miganie kropką
    if(timerOutput.msec > 500)
     kropkaOn();
    else
     kropkaOff();

    if(timerStarted == false)
     kropkaOn();
   }

   //Tryb ustawiania czasu
   else if(timerHighlighted < 3)
   {
    kropkaOn();
    //Przcisk 1 - inkrementacja podświetlonej wartości
    if(button == 1 && button != last)
    {
     //Sekundy
     if(timerHighlighted == 0)
     {
      timerUserSet.sec++;
      if(timerUserSet.sec >= 60)
       timerUserSet.sec = 0;
     }

     //Minuty
     if(timerHighlighted == 1)
     {
      timerUserSet.min++;
      if(timerUserSet.min >= 60)
       timerUserSet.min = 0;
     }

     //Godziny
     if(timerHighlighted == 2)
     {
      timerUserSet.hour++;
      if(timerUserSet.hour >= 24)
       timerUserSet.hour = 0;
     }
    }
    //Przycisk 2 - dekrementacja podświetlonego
    if(button == 2 && button != last)
    {
     //Sekundy
     if(timerHighlighted == 0)
     {
      if(timerUserSet.sec == 0)
       timerUserSet.sec = 60;
      timerUserSet.sec--;
     }

     //Minuty
     if(timerHighlighted == 1)
     {
      if(timerUserSet.min == 0)
       timerUserSet.min = 60;
      timerUserSet.min--;
     }

     //Godziny
     if(timerHighlighted == 2)
     {
      if(timerUserSet.hour == 0)
       timerUserSet.hour = 24;
      timerUserSet.hour--;
     }
    }

    //Zmiana podświetlenia na następne lub wyjście
    if(button == 3 && button != last)
    {
     timerHighlighted++;
     timerOutput = timerUserSet;
    }

    //Wyświetlanie
    content[0] = (timerUserSet.sec/1)%10;
    content[1] = (timerUserSet.sec/10)%10;
    content[2] = (timerUserSet.min/1)%10;
    content[3] = (timerUserSet.min/10)%10;
    content[4] = (timerUserSet.hour/1)%10;
    content[5] = (timerUserSet.hour/10)%10;

    //Miganie podświetlonego
    if(now.msec < 500)
    {
     if(timerHighlighted == 0)
     {
      content[0] = 0xFF;
      content[1] = 0xFF;
     }

     else if(timerHighlighted == 1)
     {
      content[2] = 0xFF;
      content[3] = 0xFF;
     }

     else if(timerHighlighted == 2)
     {
      content[4] = 0xFF;
      content[5] = 0xFF;
     }
    }
   }
  }

 //--------------------------------------------------
 //Funckja 255 - ustawienia godziny, daty i budzika
  static uint8_t highlighted = 5;
  if(mode == 255)
  {
   //Wstępna konfiguracja
   kropkaOn();
   static time new_time;

   //Załadowanie wstępnych danych
   if(last_mode != mode)
   {
    new_time = now;
    new_time.msec = 0;
    new_time.sec = 0;
    highlighted = 8;
   }

   //--------------------------------------------------
   //Obsługa przycisków

   //----------
   //Zmień podświetlenie
   if(button == 3 && last != button)
   {
    if(highlighted == 0)
     highlighted = 9;
    highlighted--;
   }

   //----------
   //Dodaj wartość do podświetlonego
   else if(button == 1 && last != button)
   {
    //Sekundy
    if(highlighted == 0)
    {
     new_time.sec++;
     if(new_time.sec >= 60)
      new_time.sec = 0;
    }

    //Minuty
    else if(highlighted == 1)
    {
     new_time.min++;
     if(new_time.min >= 60)
      new_time.min = 0;
    }

    //Godziny
    else if(highlighted == 2)
    {
     new_time.hour++;
     if(new_time.hour >= 60)
      new_time.hour = 0;
    }

    //Dni
    else if(highlighted == 8)
    {
     new_time.day++;
     if(new_time.day >= 32)
      new_time.day = 0;
    }

    //Miesiące
    else if(highlighted == 7)
    {
     new_time.month++;
     if(new_time.month >= 13)
      new_time.month = 0;
    }

    //Lata
    else if(highlighted == 6)
    {
     new_time.year++;
     if(new_time.year >= 100)
      new_time.year = 0;
    }

    //Budzik sekundy
    else if(highlighted == 3)
    {
     alarm.sec++;
     if(alarm.sec >= 60)
      alarm.sec = 0;
    }

    //Budzik minuty
    else if(highlighted == 4)
    {
     alarm.min++;
     if(alarm.min >= 60)
      alarm.min = 0;
    }

    //Budzik godziny
    else if(highlighted == 5)
    {
     alarm.hour++;
     if(alarm.hour >= 24)
      alarm.hour = 0;
    }
   }

   else if(button == 2 && last != button) //Odejmin wartość od podświetlonego
   {
    //Sekundy
    if(highlighted == 0)
    {
     if(new_time.sec == 0)
      new_time.sec = 60;
     new_time.sec--;
    }

    //Minuty
    else if(highlighted == 1)
    {
     if(new_time.min == 0)
      new_time.min = 60;
     new_time.min--;
    }

    //Godziny
    else if(highlighted == 2)
    {
     if(new_time.hour == 0)
      new_time.hour = 60;
     new_time.hour--;
    }

    //Dni
    else if(highlighted == 8)
    {
     if(new_time.day == 0)
      new_time.day = 32;
     new_time.day--;
    }

    //Miesiące
    else if(highlighted == 7)
    {
     if(new_time.month == 0)
      new_time.month = 13;
     new_time.month--;
    }

    //Lata
    else if(highlighted == 6)
    {
     if(new_time.year == 0)
      new_time.year = 100;
     new_time.year--;
    }

    //Budzik sekundy
    else if(highlighted == 3)
    {
     if(alarm.sec == 0)
      alarm.sec = 60;
     alarm.sec--;
    }

    //Budzik minuty
    else if(highlighted == 4)
    {
     if(alarm.min == 0)
      alarm.min = 60;
     alarm.min--;
    }

    //Budzik godziny
    else if(highlighted == 5)
    {
     if(alarm.hour == 0)
      alarm.hour = 24;
     alarm.hour--;
    }

   }
   else if(button == 4 && last != button) //Zapisz do DSa i wyjdź z trybu
   {
    rtcSetTime(new_time);
    eeprom_write_block(&alarm, &eealarm_data, sizeof(time));
    mode = 0;
   }

   //Wyświetlanie
   if(highlighted <= 2) //Podświetlone sek/min/godz
   {
    content[0] = new_time.sec%10; //Wyświetl czas
    content[1] = new_time.sec/10;
    content[2] = new_time.min%10;
    content[3] = new_time.min/10;
    content[4] = new_time.hour%10;
    content[5] = new_time.hour/10;
   }
   else if(highlighted >= 3 && highlighted <= 5) //Budzik
   {
    content[0] = alarm.sec%10;  //Wyświetl budzik
    content[1] = alarm.sec/10;
    content[2] = alarm.min%10;
    content[3] = alarm.min/10;
    content[4] = alarm.hour%10;
    content[5] = alarm.hour/10;
   }
   else if(highlighted >= 6 && highlighted <= 8) //Podświetlone dni/mies/lata
   {
    content[0] = new_time.year%10; //Wyświetl datę
    content[1] = new_time.year/10;
    content[2] = new_time.month%10;
    content[3] = new_time.month/10;
    content[4] = new_time.day%10;
    content[5] = new_time.day/10;
   }

   if(now.msec < 500) //Miganie podświetlonego
   {
    if(highlighted == 0 || highlighted == 3 || highlighted == 6)
    {
     content[0] = 0xff;
     content[1] = 0xff;
    }
    if(highlighted == 1 || highlighted == 4 || highlighted == 7)
    {
     content[2] = 0xff;
     content[3] = 0xff;
    }
    if(highlighted == 2 || highlighted == 5 || highlighted == 8)
    {
     content[4] = 0xff;
     content[5] = 0xff;
    }
   }

   dot = (highlighted/3); //Kropka pomaga w orientacji
  }

 //--------------------------------------------------
 //Post- działania

 //Obsługa wyświetlacza
 ledProcess(content, dot);

 //Załaduj ostatni przycisk i ostatni tryb
 last = button;
 last_mode = mode;

 //Opóźnione odliczanie buzzera
 if(buzzer > 0)
 {
  buzzer--;
  buzzerOn();
 }
 else
  buzzerOff();

}

//----------
//Pzerwanie zewnętrzne INT0 1Hz
//Pętla główna aktualizacji czasu
//Nieblokowanie pozwala na elimicję migotania wyśw.
ISR(INT0_vect, ISR_NOBLOCK)
{
 time buffer;   //Odczyt może być przerywany
 buffer = rtcGetTime();


 ATOMIC_BLOCK(ATOMIC_FORCEON) //Bezpośrednia aktualizacja
 {        //jest operacją atomową
  now = buffer;
  now.msec = 0;
 }
}


Oceń artykuł.
Wasze opinie są dla nas ważne, gdyż pozwalają dopracować poszczególne artykuły.
Pozdrawiamy, Autorzy
Ten artykuł oceniam na:

78 komentarzy:

  1. Projekt ciekawy. Obawiam sie, jednak, że w kontekscie ostatnich zamachów terrorystycznych w Rosji moze budzic u niektorych niesmak :(

    OdpowiedzUsuń
    Odpowiedzi
    1. Faktycznie przykry zbieg okoliczności ... artykuł jednak został opracowany ponad tydzień wcześniej, a sam projekt jeszcze wcześniej.

      Usuń
    2. Przecież prawdziwa bomba nie wygląda jak bomba. To tylko doskonały pomysł na efektowną, humorystyczną i estetyczną alternatywę obudowy - bomba z kreskówki.

      Usuń
  2. I tak projekt przerósł twórcę, gdy pokazałem prototyp przedpremierowo kilkunastu osobom z klasy.Wszyscy dostali głupawki jak małe dzieci, które dostały nową zabawkę :-D

    OdpowiedzUsuń
  3. Chciałbym zamieścić odpowiedź do "Uwag redakcji"

    1. Brak rezystorów nie jest rozwiązaniem w pełni zgodnym ze sztuką. To prawda. Warto jednak zauważyć, że takie rozwiązanie w praktyce działa. Oczywiście zastosowanie takiego a nie innego rozwiązania nie jest podyktowane moją ignorancją lub niewiedzą. Jest to spowodowane ściskiem na płytce drukowanej. Używając tylko elementów przewlekanych (całość miała być prosta do samodzielnego wykonania) niemożliwe okazało się dodanie tak dużej ilości elementów. 8 rezystorów i układ scalony to naprawdę dużo.

    Kompromis - ulubione słowo każdego konstruktora.

    2. Absolutnie nie zgadzam się z punktem 2. Celowo zastosowałem rejestr przesuwny bez zatrzasku. Znacząco uprasza to obsługę i zwalnia jeden pin mikrokontrolera. Proszę zauważyć, że do rejestru nie jest ładowany cały bajt przy każdym przełączeniu wyświetlacza, co mogłoby powodować powidok. W każdym cyklu ładowany jest tylko jeden bit. Na początku ładujemy zero, a następnie same jedynki przesuwając zero w prawo o jeden krok. Eliminuje to problem o którym wspomniała redakcja, powidok nie występuje.

    OdpowiedzUsuń
    Odpowiedzi
    1. Jak zawsze, ocena w pewnej mierze jest subiektywna. Ponieważ problem rezystorów dyskutowaliśmy, wspólnie doszliśmy do wniosku, że jest to poważny błąd. Projekty konkursowe celowo są umieszczane w całości (tzn. źródła + schematy) - chodzi o walor edukacyjny dla nowych adeptów i fanów mikrokontrolerów - stąd też musimy zwracać uwagę na ich poprawność. Stąd to, że coś działa nie jest wystarczającym dowodem na poprawność - LED bez rezystorów się po prostu nie łączy i tyle (wyjątkiem jest dostępność źródła prądowego) co Dondu opisał w jednym z artykułów na temat LEDów. Trudno się też zgodzić z argumentem, że na płytce nie ma miejsca dla rezystorów. IMHO zostało go sporo - a zawsze można było zastosować rezystory w SMD (nie mówię o np. 0402, ale 1206 są całkiem spore i wygodne do lutowania), można było zastosować drabinki rezystorowe w THT.
      Co do rejestru przesuwnego - tu znowu istotny jest walor edukacyjny - na 164 da się to zrobić, ale w najlepszym przypadku ograniczy duty cylce wyświetlacza (konieczność wyłączenia wyświetlacza na czas przesuwania bitów), co skutkuje zmniejszoną jasnością. W tym projekcie to nie problem ale w ogólności jednak tak. Piszesz o konieczności wykorzystania dodatkowego pinu IO w przypadku zastosowania 595 - zgoda, lecz zauważ, że zostały ci wolne piny. Gdyby ich nie było, to takie zastosowanie 164 i sposób sterowania pewnie uznalibyśmy za plus i pomysłowość autora (no chyba, że ktoś by przyjął, że skoro zabrakło pinu to znaczy, że źle dobrany jest procesor:) ).
      Znowu - nasze uwagi to nie tyle zastrzeżenia co do twojego projektu, lecz uwagi dla osób początkujących, które przeglądając wasze projekty się uczą. Chcielibyśmy, aby nabierali dobrych nawyków, stąd nasze komentarze.Jak widać po naszych ocenach, wymienione uchybienia nie spowodowały istotnego obniżenia naszej wysokiej oceny dla projektu.
      Także nie tylko projektowanie jest sztuką kompromisu, ale ocena też - w tym przypadku pomiędzy pomysłowością, walorami estetycznymi projektu i jego generalnym wysokim poziomem wykonania, a celem jaki nam przyświeca - czyli edukacją i pokazywaniem poprawnych (co nie znaczy jedynie słusznych) dróg rozwiązywania problemów.
      Myślę, że Dondu się ze mną zgodzi?

      Usuń
    2. Tak, całkowicie się z Tobą Tmf zgadzam. Tak właśnie podchodzimy do każdego artykułu, a musimy tak robić, bo bardzo często na forum spotykamy początkujących, którzy z braku wiedz i doświadczenia ufają w znalezione w sieci schematy nie widząc, że są na nich przyjęte jakieś kompromisy (o których piszesz Tomku - autorze) i w konsekwencji niszczą swoje projekty lub zastosowane w nich elementy elektroniczne.

      W artykule pod naszymi uwagami dodałem informację i link do niniejszej dyskusji.

      Usuń
  4. Co do rezystorów to rozumiem wasze podejście. Jesteście tu od nauki. Moje rozwiązanie to kompromis, może trochę naciągany, ale jednak konieczny (zakładałem całkowity brak elementów SMD, został jeden).

    Jeśli chodzi o rejestr przesuwny to przecież na czas zmiany wyświetlacz i tak trzeba wyłączyć wyświetlaną cyfrę przed przełączeniem, bo na wyświetlaczu zostaną duchy poprzedniej lub następnej cyfry.

    Rozumiem, że podajecie rozwiązania wzorcowe. Takie jest wasze zadanie, trzymajcie tak dalej :-)

    OdpowiedzUsuń
    Odpowiedzi
    1. "Jeśli chodzi o rejestr przesuwny to przecież na czas zmiany wyświetlacz i tak trzeba wyłączyć wyświetlaną cyfrę przed przełączeniem, bo na wyświetlaczu zostaną duchy poprzedniej lub następnej cyfry."

      Niezupełnie. W rejestrze z zatrzaskiem, np wspomnianym 74HC595 stan wyjść zmieni się dopiero po podaniu stanu wysokiego na pin zatrzasku. W trakcie samego ładowania zmiana nie nastąpi. Tak czy siak, bardzo fajny projekt. Gratulacje!

      Usuń
    2. No właśnie nie! Przecież jeśli w momencie przełączania nadal będzie zaświecona poprzednia cyfra to przez ułamek sekundy pojawi się ona na następnym wyświetlaczu. Lub odwrotnie jeśli załadujemy następną cyfrę przed przełączeniem to pojawi się ona na chwilę na poprzednim. Z zatrzaskiem, czy bez to nie ma znaczenia. Będzie tak nawet bez żadnego rejestru przesuwnego.

      Nie wspominając już o tym, że kiedy ładujemy tylko jeden bit to zatrzask nic nie zmienia.

      Usuń
    3. Nie jest tak jak piszesz. Dla rejestru 164, który nie posiada zatrzasku przed wsuwaniem bitów musisz wyłączyć wyświetlacz, żeby nie pokazywał śmieci. Włączyć możesz go ponownie dopiero po wsunięciu nowych danych. W efekcie LED przez dłuższy czas jest wyłączony. Jeśli masz zatrzask (np. 595), to LED wyświetla stare dane, ty w tym czasie wsuwasz nowe, bez wyłączenia wyświetlacza. Po ich wsunięciu gasisz LED, aktywujesz przepisanie danych do zatrzasku i zapalasz LED. Cała operacja trwa jeden takt zegara, a nie 9. W efekcie LED przez większą część czasu jest włączony i dzieki temu jaśniej świeci, co umożliwia multipleksowanie większej liczby wyświetlaczy na raz.

      Usuń
    4. Tłumaczę, że mój program nie wsuwa "danych", ale tylko jeden bit, więc zatrzask nie ma racji bytu. Nie wsuwam danych, tylko przemieszczam to co już jest o jedno miejsce w prawo. Przecież załadowanie 1 bitu odbywa się natychmiast.

      Przeanalizuj plik user.cpp linie 65-85.

      Usuń
    5. Co do rezystorów w przypadku diod, to z własnego doświadczenia wiem że czasami się przydają.:)
      Jeśli chodzi o rejestry to zatrzaskowy 595 jest lepszy, ale w tym przypadku rację ma autor projektu. Bez patrzenia w kod już na schemacie widzimy, że autor nie używa rejestru nie do zapalania poszczególnych segmentów, ale do przełączania wyświetlaczy poprzez zwykłe "shiftowanie" w tym przypadku zera bo sterujemy tranzystorami PNP. Zastosowanie zatrzasku w tym przypadku (nie zauważalnie) zmniejszyło by czas świecenia wyświetlaczy o konieczność właśnie aktywacji wyjść. Nie chce mi się skupiać w kod bo nie programuję AVRów ale w tym przypadku poświaty unikniemy ustawiając jedynki na piny mikrokontrolera sterujące segmentami wyświetlaczy w czasie przesyłania przełączania tranzystorów, który jednak był by dłuższy przy zastosowaniu *595.

      Swoją drogą w przypadku takiego sterowania wyświetlaczami można by się pokusić o zastosowanie jakiegoś licznika Johnsona i przełączanie wyświetlaczy tylko jednym pinem uC.

      Usuń
    6. W całej dyskusji chyba zaszło małe nieporozumienie. W projekcie autora, istotnie rejestr 164 jest ok - nie steruje on segmentami, lecz jedynie tranzystorem sterującym wyświetlaczem. W takim zastosowaniu jest ok i duchy nie będą powstawały. Pewna wada polega na konieczności wyłączenia wyświetlacza (wyłączenia wszystkich segmentów) przed zmianą rejestru 164. Wada niewielka.
      Myślę, że nasza dyskusją dotyczyła jakby bardziej ogólnego podejścia do sterowania wyświetlaczem z wykorzystaniem SPI - idea takiego sterowania bierze się z konieczności ograniczenia liczby wykorzystanych pinów IO. Dowolnie długi wyświetlacz możemy sterować przy pomocy kilku sygnałów - MOSI, SCK i opcjonalnego LE oraz CLR. Na prezentowanym schemacie wykorzystanych jest 10 sygnałów, co jest uzasadnione brakiem konieczności zastosowania dodatkowego scalaka. A czy 164 można było wyeliminować? Pozornie brakuje nam 2 wolnych pinów IO - ale łatwo można by je uzyskać zmieniając podłączenie switchy.

      Usuń
    7. Ja dodam jeszcze tylko, że ten element nie wpłynął znacząco na ocenę końcową artykułu, a nawet gdybyśmy ocenili projekt perfekcyjnie na 5 i tak nie wyprzedziłby zwycięzcy Świąteczny konkurs DIY (grudzień 2013r.).

      Usuń
  5. Hello!
    I would like to build this structure, it would be so much the question that you do not have LEDs to the resistance? Such a display is used: http://www.hqelektronika.hu/product/da08_11srwa
    Thank Lajos Varga from Hungary.

    OdpowiedzUsuń
    Odpowiedzi
    1. Hello.
      Yes, You are right. Resistors should be added. This is an competition article. For this reason this project has awarded fewer number of points. We wrote about it at the end of article and in our (Dondu & tmf) comments.
      Regards from Poland!

      Usuń
    2. Hello!
      Thank you for your response for each segment do resistance / 48 pieces /? Or just the anodes / 6 pieces /?

      Usuń
    3. At eight microcontroller pins marked by labels from A to G and DT.
      Similar example: One wire LED display

      Usuń
    4. Thank you very much!
      That's what I thought. Trying to plan a panel of resistors, but I do not know the eagle. I sprint layout I'm using!
      Thank you again!
      Louis Greetings.

      Usuń
    5. When you have completed this watch, take a pictures and paste here links to them - we will add them to this article. You may do the same with your schematic :)
      Good luck!

      Usuń
  6. Köszönöm majd úgy teszek!! :)

    OdpowiedzUsuń
    Odpowiedzi
    1. Old polish adage:
      Polak i Węgier dwa bratanki i do szabli i do szklanki!
      Lengyel, Magyar – két jó barát, együtt harcol, s issza borát!

      Usuń
  7. Egy pár alkotásom!!! https://www.youtube.com/user/lajos19690207

    OdpowiedzUsuń
  8. Hello!
    All I would ask that the displays are common anode??
      The term common-cathode in!
    So what now???

    OdpowiedzUsuń
    Odpowiedzi
    1. Hi!
      The author made ​​a mistake in the content of the article. I corrected on common anode.

      Usuń
  9. Hello!
    Thank you! When I'm ready I'll send pictures, videos!

    OdpowiedzUsuń
  10. 5-4-3-2-1-0 !!!
    https://www.youtube.com/watch?v=rn_SZFrPneI

    OdpowiedzUsuń
  11. Hello!
    also has its displays. But I can not watch! There is something wrong! Good to hex which is putting together?? Help me if you can!
    Thank you, Louis.

    OdpowiedzUsuń
  12. Ten komentarz został usunięty przez autora.

    OdpowiedzUsuń
  13. The epromot also be programmed.

    OdpowiedzUsuń
  14. Failed to start! It works perfectly!
    A small initial difficulties.
    Thank you!

    OdpowiedzUsuń
    Odpowiedzi
    1. In the morning I wrote to the author, to answer your questions. But now I see that everything is OK. :)

      Usuń
  15. Perfect!
    https://www.youtube.com/watch?v=vn8UyrrGhUI
    Possibly a hex that could turn a relay??
    ATMEGA8A output is free!
    Thank you!

    OdpowiedzUsuń
    Odpowiedzi
    1. "Possibly a hex that could turn a relay?? "
      Sorry, but no.

      Usuń
  16. Such has been!
    https://www.youtube.com/watch?v=R_tpWqKKVqw&list=UUQAJh2tARDxFKUXh6t3tnvQ

    OdpowiedzUsuń
    Odpowiedzi
    1. Wonderful !!! :-)
      Could you add link to this page into description of your videos?

      Usuń
    2. I do not quite understand what you mean!

      Usuń
    3. Look into "About" of this movie: http://www.youtube.com/watch?v=Muaa3-Mgbmc
      There is a link to this page.

      Usuń
    4. Így gondolod? https://www.youtube.com/watch?v=R_tpWqKKVqw&list=UUQAJh2tARDxFKUXh6t3tnvQ

      Usuń
  17. https://www.youtube.com/watch?v=R_tpWqKKVqw&list=UUQAJh2tARDxFKUXh6t3tnvQ

    OdpowiedzUsuń
  18. https://www.facebook.com/l.php?u=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fvn8UyrrGhUI&h=mAQGlCUmh

    OdpowiedzUsuń
  19. Hello! I can not put up the link!
    The program could not be modified to seconds of the clock go backwards??
    Like that! https://www.youtube.com/watch?v=pkIXVboSMro
    Thank you, Louis.

    OdpowiedzUsuń
  20. Witam, mam problem ze znalezieniem wyświetlaczy segmentowych oraz DS1307. Mógłby ktoś podać zamienniki? Chciałem go ukończyć...

    OdpowiedzUsuń
  21. Its possible to get a hex file?

    OdpowiedzUsuń
    Odpowiedzi
    1. Yes, the HEX file (for 8MHz) is in the ZIP file: Bombowy-zegar-program.zip
      You can find it in this article.

      Usuń
  22. Ten zegar jest mistrzowski. Przymierzam się do niego już z pół roku tylko materiałów brak... ale w końcu się uda :) Dzięki za inspirację (i dokumentację).

    OdpowiedzUsuń
  23. Czy któryś z kolegów byłby łaskaw przesłać wzór prasowanki pcb w pdf na maila? dawidsobolewski@o2.pl z góry wielkie dzięki.

    OdpowiedzUsuń
  24. Proszę kolegów, lub szanownego Dondu o przesłanie mi prasowanki pcb czarno białej w formacie pdf i spisu elementów.....jestem cienki z eagle, a projekt strasznie mnie zaciekawił i zrobił bym go.Wielkie dzięki z góry mój adres dawidsobolewski@o2.pl

    OdpowiedzUsuń
  25. A co trzeba by zmienić w kodzie aby dodać obsługę jeszcze jednego czujnika temp? Powiedzmy że byłyby te czujniki przełączane za pomocą dodatkowego przycisku na pinach PC1 lub PC2.
    Z góry dziękuję za odpowiedź :)

    OdpowiedzUsuń
  26. Mam problem z czujnikiem temperatury. Wskazuje 58*, myślałem, że to wada DS1820 więc kupiłem nowy ale z kolejnym jest to samo. Wszystko działa jak należy poza termometrem. Jakieś pomysły?

    OdpowiedzUsuń
  27. witam czy kolega rozwiązał problem z termometrem mam to samo losowo pokazuje 13, 60,70st???

    OdpowiedzUsuń
  28. Już wiem czujnik nie może być ds 18b20 tylko ds 1820!! i jest ok.

    OdpowiedzUsuń
    Odpowiedzi
    1. Dziwne bo miałem i 1820 i 18b20 i w obu to samo.

      Usuń
  29. Koledzy podpowiedzcie, bo wykonałem ten zegar ale nie mam pod ręką DS1307 i po włączeniu bez niego nic nie ma na wyświetlaczach, jakby nic nie działało. Czy tak ma być może ktoś sprawdzić u siebie ?

    OdpowiedzUsuń
    Odpowiedzi
    1. Nie analizowałem programu, ale DS1307 jest podstawą tego projektu, więc musiałbyś zapewne usunąć nieco kodu, który być może przeszkadza przy braku DS1307.

      Usuń
    2. Dzięki Dondu, dziś doszedł do mnie DS1307 i po włożeniu w podstawkę wszystko ruszyło.

      Usuń
    3. Witam,
      zrobiłem ten zegarek,wydaje mi się że wszystkie funkcje działają tylko stoi dwukropek nie miga, zmieniałem Mega8, Ds1307, kwarc, nic nie pomaga, co to może być?, proszę o poradę
      Pozdrawiam

      Usuń
    4. Załóż może temat na forum a tutaj wklej link, byśmy do niego trafili.

      Usuń
  30. Witam.

    Patrząc na zaprojektowaną płytkę zastanawiam się czy gniazdo isp a konkretnie nóżka vcc i mosi są ze sobą zwarte? nie znam sie na tym tak dobrze dlatego pytam. i kolejne pytanie jesli nie są zwarte to jak dostarczamy napięcie przez programator do mikroprocesora? którą ścieżką? czy podczas programowania musi byc w takim razie uklad zasiloniony z innego zrodla niz z programatora?

    OdpowiedzUsuń
    Odpowiedzi
    1. Witam. Autor projektu założył, że w czasie programowania układ jest zasilany z własnego źródła napięcia. Dlatego też nie podłączył pinu Vcc w gnieździe ISP. Mógł oczywiście to zrobić, bo większość programatorów ma możliwość rozłączenia napięcia zasilania (nie powinno się łączyć równolegle dwóch różnych źródeł zasilania, bo nigdy nie mają dokładnie tego samego napięcia), ale ponieważ jest mu to zbędne i dodatkowo upraszcza płytkę PCB, to za pewne dlatego nie wykonał takiego połączenia.

      Usuń
  31. Witam.

    Napotkałem się z problemem, mianowicie nie działają mi jednocześnie 2 wyświetlacze 7-segmentowe z 3. Nie jestem w stanie określić co może być przyczyną... Proszę o pomoc. Link do zdjęcia : http://pokazywarka.pl/vcwbl7/

    OdpowiedzUsuń
    Odpowiedzi
    1. Wstępnie wygląda to na problemy z montażem lub uszkodzenia ścieżek na PCB.

      Usuń
    2. tylko, że na dwóch wyświetlaczach nie ma nic a na srodkowym normalnie pokazuje. takze sciezki mysle ze sa ok. co ew. moze byc jeszcze przyczna?

      Usuń
    3. Dyskusję kontynuujemy na forum: tutaj

      Usuń
  32. Szkoda, że ustawianie budzika wiąże się z ustawianiem zegara! za każdym razem zmiana czasu budzenia=ustawianie czasu zegara=zmęczenie i znudzenie. Bardzo fajny projekt, ale z dwoma wadami: Pierwsza opisana wyżej, a teraz druga. Nieznaczne nagrzewanie się Atmega powoduje podniesienie odczytywanej temperatury.
    Gaspar.

    OdpowiedzUsuń
    Odpowiedzi
    1. Przecież masz kod źródłowy programu - popraw go wg własnego uznania. W czym problem? Układ w zamkniętej obudowie istotnie może nieznacznie wpływać na odczyt temperatury - dodaj wentylację, lub odsuń na PCB DS1820 i problem rozwiązany.

      Usuń
  33. Does anybody contact the author of the project?

    OdpowiedzUsuń
  34. Może komuś pomoże:
    Zbudowałem ten projekt, zamiast 74HC164 zastosowałem 74HC595 bo akurat miałem pod ręką, dodałem rezystory do segmentów. Działał pięknie, dopóki nie zmieniłem zasilacza laboratoryjnego na jakiś chiński, który zamiast 5V dawał 6.5V i zegarek się zawiesił. Myślałem że spaliło któryś scalak, ale zarówno Atmega jak i RTC są w porządku, ale z jakiegoś powodu scalak nie generował sygnału 1Hz. Gdy sterowałem nim (DS1307) z Arduino, poprawnie wystawiał prostokąt, a w Atmedze nie chciał.
    Doszedłem do tego, że w rtcInit() wywoływana jest funkcja checkReset() sprawdza najstarszy bit odczytany z adresu 0x00 (adres sekund) i na jego podstawie decyduje o resecie. Reset polega na tym, że odczytuje na sztywno godzinę i zapisuje ją ponownie w to samo miejsce. Jednak w adresie sekund dalej zapisywana jest najstarsza jedynka, a więc wracamy do punktu wyjścia. Należy w funkcji rtcSetBCDTime() poprawić i do zapisu sekund dołożyć maskę & 0x7F.

    OdpowiedzUsuń
  35. Czy jest ktoś w stanie napisać program tak aby działał z czujnikiem DS18B20.Ciężko kupić obecnie wersje 1820

    OdpowiedzUsuń

Działy
Działy dodatkowe
Inne
O blogu




Dzisiaj
--> za darmo!!! <--
1. USBasp
2. microBOARD M8


Napisz artykuł
--> i wygraj nagrodę. <--


Co nowego na blogu?
Śledź naszego Facebook-a



Co nowego na blogu?
Śledź nas na Google+

/* 20140911 Wyłączona prawa kolumna */
  • 00

    dni

  • 00

    godzin

  • :
  • 00

    minut

  • :
  • 00

    sekund

Nie czekaj do ostatniego dnia!
Jakość opisu projektu także jest istotna (pkt 9.2 regulaminu).

Sponsorzy:

Zapamiętaj ten artykuł w moim prywatnym spisie treści.