Wstęp


Witam! Dzisiaj przedstawię projekt zegarka z wyświetlaczem 7- segmentowym. Opiszę po kolei etapy projektu i przedstawię wynik końcowy 🙂 Zegarek jest oparty o mikrokontroler z rdzeniem ARM Cortex M0.

Użyte oprogramowanie:


SW4STM32(eclipse) – środowisko programistyczne

STM32 Cube MX- generator konfiguracji dla mikrokontrolerów STM32

KiCad- Popularne oprogramowanie do pisania schematów i projektowania płytek PCB


Wszystkie wspomniane programy są dostępne w sieci za darmo.


Lista materiałów (prototypowanie +budowa finalnej wersji) :


  • Mikrokontroler STM32F030F4P6
  • Przełącznik DPDT
  • Stabilizator LDO TC1185-3.3VCT8
  • Kondensator 22pF SMD 2x
  • Kondensator 100nF SMD 2x
  • Kondensator 100uF THT(w zestawie KE podst)
  • Rezystor 120R (można użyć 1kOhm w zestawu), x7
  • Rezystor 10k (można użyć 1kOhm w zestawu), x2
  • Wyświetlacz 7 segmentowy BQ-M322RD, barwa zielona
  • Tact switch x2
  • Wysoki rezonator kwarcowy 18.432 MHz
  • Akumulator li-ion 14500
  • Płytka prototypowa (w zestawie KE podst)
  • Płytka uniwersalna raster 2.54mm kolor brązowy
  • Zasilacz płytki prototypowej (alternatywa w zestawie KE podst)
  • Przewody połączeniowe (w zestawie KE podst)
  • Adapter TSSOP20->2.54mm
  • Programator ST-link (odłamany z płytki nucleo 64)
  • Przewód „kynar”
  • Gniazdo goldpin żeńskie 2×1 (do ładowania)
  • Blacha niklowa i zgrzewarka (może być koszyk na baterię AA)
  • Ładowarka TP4056 z prądem ładowania 300mA (rezystor PROG 302)
  • Przewód 2.5mm^2 jako drut do budowy klatki
  • Lutownica

Rozdział pierwszy: wyświetlacz i identyfikacja pinów


Wyświetlacz który zakupiłem ma 8 segmentów i jest typu wspólna anoda. Już objaśniam, o co chodzi.

wyświetlacz
Wyświetlacz 7-segmentowy

Jest to wyświetlacz składający się z 28 diod LED (+ kropki, ale je pomijam w tym artykule). Wyświetlacz ten może wyświetlać 4 cyfry, a każda z tych cyfr składa się z 7 segmentów opisanych od A do G. Segmenty są diodami w obrębie jednej cyfry! Dlaczego wspólna anoda? Dlatego, że to anody diod w każdej cyfrze są ze sobą połączone wewnątrz obudowy i wyprowadzone jako jeden pin. Za to katody diod danego segmentu (np B, bo prawej na górze) ze wszystkich cyfr są połączone razem i również schodzą się w jeden pin. Tak skonfigurowane wyprowadzenia prowadzą do ciekawej metody sterowania takim wyświetlaczem, ale o tym później. Tak wygląda wewnętrzna budowa wyświetlacza, ukazana na schemacie:

Budowa wyświetlacza 7-segmentowego

Rozdział drugi: Konfiguracja pinów mikrokontrolera.


Kolejnym krokiem jest ustalenie, co będą robiły poszczególne piny mikrokontrolera. Ponieważ docelowo programuję zegarek, oprócz wyświetlacza ustawiłem także dwa przyciski: SET oraz CLEAR, a także wyprowadzenia rezonatora kwarcowego, żeby zegarek działał precyzyjnie. Konfiguracja wygląda następująco:

Pinout mikrokontrolera skonfigurowany w Cube MX

Omówię po kolei wszystkie piny.


  • BOOT0- podciągnąłem rezystorem do masy. Jest to zabieg niezbędny do poprawnego startu STM’a
  • 2, 3- piny rezonatora kwarcowego
  • NRST- podciągnąłem rezystorem do zasilania. Pozwoli to zapobiec losowym resetom mikrokontrolera, które mogą się zdarzyć, jeżeli ten pin „wisi”.
  • VDDA- podciągnąłem do zasilania układu. Układ zasilamy napięciem 3.3V, wyższe może uszkodzić mikrokontroler
  • 6-9- piny pól wyświetlacza(anod).
  • 10-14, 19, 20- piny segmentów(katod)
  • 15, 16- piny zasilania Vdd(potencjał drenu- 3,3V oraz Vss-potencjał źródła, masa)
  • 17, 18- przyciski zwierają te piny do masy układu.

Oprócz pinoutu trzeba się także zająć odpowiednym podciągnięciem pinów. Oto konfiguracja GPIO:

Podciągnięcia pinów mikrokontrolera

Przyciski ustawiłem jako wejścia i podciągnąłem programowo do plusa(pull-up).

Rozdział trzeci: połączenie prototypu


Używając własnej platformy dla małego STM’a zbudowałem układ na płytce prototypowej:

Budowa układu na płytce stykowej

Na tym etapie nie podłączałem jeszcze przycisków- na tą chwilę zająłem się jedynie wysterowaniem wyświetlacza.


Do programowania użyłem układu ST-Link odłamanego od płytki Nucleo-64

Trzeba też pamiętać, że segmenty wyświetlacza A-G należy podłączyć przez rezystory- są to zwyczajne diody LED i obowiązują tutaj takie same zasady- należy ograniczyć prąd który płynie przez diodę, żeby jej nie uszkodzić. Do testów użyłem rezystorów 1k, natomiast w finalnej wersji użyłem 120Ohm.

Rozdział czwarty: biblioteka do obsługi segmentów i pól


Żeby uniknąć długiego pisania linijek odpowiedzialnych za zapalanie i gaszenie diod wyświetlacza napisałem bibliotekę za pomocą której uproszczę te czynności. W wygenerowanym za pomocą Cube projekcie wyszukuję miejsce na PrivateDefine’y i piszę:

* USER CODE BEGIN PD */

//Common anode display
//segments. dot is not user, so we have 7 segments, from A to G.
//Segments are cathodes, so you can turn it on by pulling to ground.
#define SA_ON HAL_GPIO_WritePin(SA_GPIO_Port, SA_Pin, 0)
#define SB_ON HAL_GPIO_WritePin(SB_GPIO_Port, SB_Pin, 0)
#define SC_ON HAL_GPIO_WritePin(SC_GPIO_Port, SC_Pin, 0)
#define SD_ON HAL_GPIO_WritePin(SD_GPIO_Port, SD_Pin, 0)
#define SE_ON HAL_GPIO_WritePin(SE_GPIO_Port, SE_Pin, 0)
#define SF_ON HAL_GPIO_WritePin(SF_GPIO_Port, SF_Pin, 0)
#define SG_ON HAL_GPIO_WritePin(SG_GPIO_Port, SG_Pin, 0)

#define SA_OFF HAL_GPIO_WritePin(SA_GPIO_Port, SA_Pin, 1)
#define SB_OFF HAL_GPIO_WritePin(SB_GPIO_Port, SB_Pin, 1)
#define SC_OFF HAL_GPIO_WritePin(SC_GPIO_Port, SC_Pin, 1)
#define SD_OFF HAL_GPIO_WritePin(SD_GPIO_Port, SD_Pin, 1)
#define SE_OFF HAL_GPIO_WritePin(SE_GPIO_Port, SE_Pin, 1)
#define SF_OFF HAL_GPIO_WritePin(SF_GPIO_Port, SF_Pin, 1)
#define SG_OFF HAL_GPIO_WritePin(SG_GPIO_Port, SG_Pin, 1)

//digits. We have 4, from 1 to 4. Digits are anodes, so you can turn it on by pulling
//to supply voltage
#define D1_ON HAL_GPIO_WritePin(D1_GPIO_Port, D1_Pin, 1)
#define D2_ON HAL_GPIO_WritePin(D2_GPIO_Port, D2_Pin, 1)
#define D3_ON HAL_GPIO_WritePin(D3_GPIO_Port, D3_Pin, 1)
#define D4_ON HAL_GPIO_WritePin(D4_GPIO_Port, D4_Pin, 1)

#define D1_OFF HAL_GPIO_WritePin(D1_GPIO_Port, D1_Pin, 0)
#define D2_OFF HAL_GPIO_WritePin(D2_GPIO_Port, D2_Pin, 0)
#define D3_OFF HAL_GPIO_WritePin(D3_GPIO_Port, D3_Pin, 0)
#define D4_OFF HAL_GPIO_WritePin(D4_GPIO_Port, D4_Pin, 0)

//turning off
#define D_OFF D1_OFF; D2_OFF; D3_OFF; D4_OFF
#define S_OFF SA_OFF; SB_OFF; SC_OFF; SD_OFF; SE_OFF; SF_OFF; SG_OFF
#define ALL_OFF D_OFF; S_OFF

//display delay
#define WAIT HAL_Delay(50)

//buttons
#define CLEAR HAL_GPIO_ReadPin(CLEAR_GPIO_Port, CLEAR_Pin) == 0
#define SET HAL_GPIO_ReadPin(SET_GPIO_Port, SET_Pin) == 0

/* USER CODE END PD */

Pierwszy akapit jest odpowiedzialny za włączanie i wyłączanie katod wyświetlacza- mamy tutaj podział na segmenty od A do G. Zauwazyliście zapewne, że komenda ON ustawia pin w stan niski. Dlaczego? Segmenty są katodami i podpięcie ich do masy włączy diodę 🙂 dlatego segmenty są włączane stanem niskim, a wyłączane wysokim.

Drugi akapit odpowiada za sterowanie cyframi. Poszczególne cyfry są anodami, zatem tutaj należy to zrobić zgodnie z logiką- cyfrę włączamy stanem wysokim, a wyłączamy niskim.

Warto też na początku zdefiniować komendy, które wyłączają wszystkie segmenty i cyfry. W tym fragmencie kodu będzie to ALL_OFF. Niżej zdefiniowałem opóźnienie wyświetlacza (WAIT). Później opiszę, do czego jest potrzebne 🙂 No i koniecznie obsługa przycisków (SET, CLEAR). Jeżeli pin przycisku zostanie zwarty do masy, warunek będzie spełniony.

Rozdział szósty: multipleksowanie wyświetlacza


Nadszedł moment na opisanie działania tego wyświetlacza. Zauważyliście, że nie mam dostępnej anody oraz katody każdej spośród 28 diod wyświetlacza? Są połączone ze sobą w 12 wyprowadzeń- 4 anody (cyfry) oraz 7 katod (segmenty).

Upraszczając- w jednej chwili mogę zapalić dowolną cyfrę, na dowolnych polach. Ale jedną cyfrę. Tą samą.

Wpisuję w pętli głównej:

//segmenty cyfry 7
SA_ON;
SB_ON;
SC_ON;

//pole na którym ma się wyświetlić
D2_ON;

efektem będzie wyświetlenie cyfry 7 na polu drugim:

Pierwsza cyfra

Spróbuję teraz wyświetlić kilka cyfr. Piszę w pętli głównej:

//segmenty cyfry 5
SA_ON;
SF_ON;
SG_ON;
SC_ON;
SD_ON;

//pola na których ma się wyświetlić
D1_ON;
D2_ON;
D3_ON;
D4_ON;

A oto efekt:

wyświetlacz
Cztery piątki

Okej. Mogę teraz sterować segmentami oraz polami. Ale wyprowadzenia wyświetlacza sprawiają, że nie panuję nad sprawą do końca. Jak rozwiązać ten problem?

Tego typu wyświetlacze można wysterować multipleksując je. Oznacza to tyle, że aby działał tak, jak tego chcemy należy wykorzystać…. czas. Oraz bezwładność naszego wzroku 🙂

Nasze oczy widzą wyraźnie migotanie do ok 30Hz (30 cykli na sekundę). Powyżej widziane obrazy zaczynają się zlewać, dając wrażenie ruchu. Wie o tym z pewnością każdy gamer, wyciągając jak najwięcej frapsów ze swojej maszyny 🙂

Napisałem zatem funkcję, która wyświetla inne cyfry na kolejnych polach, jedna po drugiej.

Funkcję zadeklarowałem w polu Private functions prototypes:

/* USER CODE BEGIN PFP */
void display(int position, int number);
/* USER CODE END PFP */

oraz zdefiniowałem w obszarze user code 4:

/* USER CODE BEGIN 4 */
void display(int position, int number) {
	ALL_OFF
	;
	int ok = 1;
	switch (position) {
	case 1:
		D1_ON;
		break;
	case 2:
		D2_ON;
		break;
	case 3:
		D3_ON;
		break;
	case 4:
		D4_ON;
		break;
	default:
		ok = 0;		//secure. Dont turn LEDs, if position is different than 1-4
	}
	if (ok == 1) {
		switch (number) {
		case 0:
			SA_ON;
			SB_ON;
			SC_ON;
			SD_ON;
			SE_ON;
			SF_ON;
			break;
		case 1:
			SB_ON;
			SC_ON;
			break;
		case 2:
			SA_ON;
			SB_ON;
			SG_ON;
			SE_ON;
			SD_ON;
			break;
		case 3:
			SA_ON;
			SB_ON;
			SG_ON;
			SC_ON;
			SD_ON;
			break;

		case 4:
			SB_ON;
			SG_ON;
			SF_ON;
			SC_ON;
			break;
		case 5:
			SA_ON;
			SF_ON;
			SG_ON;
			SC_ON;
			SD_ON;
			break;

		case 6:
			SA_ON;
			SF_ON;
			SG_ON;
			SC_ON;
			SE_ON;
			SD_ON;
			break;

		case 7:
			SA_ON;
			SB_ON;
			SC_ON;
			break;

		case 8:
			SA_ON;
			SB_ON;
			SC_ON;
			SD_ON;
			SE_ON;
			SF_ON;
			SG_ON;
			break;

		case 9:
			SA_ON;
			SB_ON;
			SC_ON;
			SD_ON;
			SG_ON;
			SF_ON;
			break;

		}
	}
}


/* USER CODE END 4 */

Jest to funkcja z dwoma argumentami. Pierwszy z nich odpowiada za ustawienie ‘kursora’ na odpowiednim polu(odpalenie anody danego pola). Drugi zaś odpowiada za wskazanie cyfry, która ma się pokazać. Teraz żeby wyświetlić cyfrę 7 na polu dwa, wystarczy wpisać:

display(2, 7);

Funkcja oprócz tego na samym początku używa komendy ALL_OFF, która gasi wszystkie poprzednio zapalone diody. Dzięki temu nie trzeba pamiętać, aby zgasić poprzednie diody przed zapaleniem następnych. Przy okazji wspomnę, że należy pamiętać o komentarzach, żebyśmy mogli sami później połapać się w kodzie 🙂 Dobrym zwyczajem jest także pisanie ich w języku angielskim.

Wyświetliłem teraz 4 różne cyfry, na 4 róznych polach- jedna po drugiej, dodając opóźnienie zdefiniowane w bibliotece jako WAIT. wpisuję w pętli głównej:

display(1, 1);
		WAIT;
		display(2, 2);
		WAIT;
		display(3, 3);
		WAIT;
		display(4, 4);
		WAIT;

Oto efekt. Teraz każda cyfra wyświetla się po kolei:

Widzicie teraz, jak w zwolnionym tempie pracuje wyświetlacz 7-segmentowy. Deklarując komendę WAIT użyłem czasu 50ms. Zmieńmy ten czas na 1ms:

//display delay
#define WAIT HAL_Delay(1)

Rozdział siódmy: konfiguracja zegara czasu rzeczywistego


Właśnie o to chodziło. Teraz 4 różne cyfry widać na 4 różnych polach jednocześnie. Mimo, że tak naprawdę w żadnym momencie nie świeci się więcej niż jedna cyfra 🙂

Wykorzystajmy teraz umiejętność sterowania wyświetlaczem w praktyce, zaprzęgająć go do konkretniejszej pracy.

Mikrokontrolery z rodziny STM32 posiadają wbudowany układ zegara czasu rzeczywistego. To znaczy, że mam uproszczone zadanie. Należy jedynie uruchomić tę funkcję, a następnie zczytywać wartości lub zapisywać je (wszak każdy zegarek musi mieć możliwość ustawienia poprawnej godziny).

Żeby jednak zegarek działał poprawnie musimy zadbać o odpowiednią prędkość pracy mikrokontrolera a także jej precyzję. Pozwoli nam na to użycie rezonatora kwarcowego. Ten konkretny model nie wspiera rezonatorów zegarkowych o cz. 32.768 Khz. Mamy możliwość użycia jedynie kwarcu do taktowania wysoką częstotliwością.

Na szczęście szybkie rezonatory umożliwiające precyzyjne odmierzanie czasu są w sprzedaży, mają 3.2768MHz, a więc wielokrotność tych zegarkowych 🙂

Zacznijmy od konfiguracji zegarów uC:

Konfiguracja zegara

Widzę, że mam możliwość użycia rezonatora od 4 do 32MHz. Niestety nie mogę użyć każdego! Musi on być dobrany tak, aby używając odpowiedniego preskalera móc otrzymać równiutko jedną sekundę. Ja użyłem rezonatora 18.432MHz, ale równie dobre będą np: 9.216MHz czy 7.3728MHz, albo też 4.194304MHz

Teraz moim celem jest uzyskanie 32.768KHz na RTC. Jest to niestety niemożliwe, ponieważ dzielnik do RTC jest ustawiony stale i nie można go zmodyfikować. Na wyjściu mam zatem 576KHz.

Aktywuję źródło taktowania, oraz włączam kalendarz i alarm. Te opcje są niezbędne do poprawnego działania zegarka.

Teraz muszę odpowiednio zmodyfikować dzielnik, żeby zegarek poprawnie odmierzał czas. Na wyjściu RTC uzyskałem 576KHz, a docelowo potrzebujemy 32.768KHz. Należy zatem sprawdzić, ilokrotnie trzeba podzielić aktualną częstotliwość, aby uzyskać docelową.

18.432MHz/32 = 0.576MHz = 576KHz – to obliczenie wykonał za nas Cube.

576KHz / 32.768 = 17.578125

Liczba dość ciekawa, wyszło aż 6 miejsc po przecinku. O taką wielokrotność musimy spowolnić zegarek.

W polu Asynchronous predivider mam wpisaną wartość 255.

Wykonuję następujące działania:

255+1 = 256

256*17.578125 = 4500

4500-1 = 4499

Jak widać, wynik który wyszedł jest liczbą całkowitą. Tylko rezonatory „zegarkowo zgodne” dadzą nam w wyniku liczbę całkowitą.

Taki dzielnik wprowadzam zamiast 255.

Uwaga! Należy pamiętać, żeby dla rezonatorów o innych „zegarkowo zgodnych” wymienionych wcześniej częstotliwościach wykonać obliczenia podstawiając odpowiednie dla nich wartości!

Teraz zegar RTC będzie poprawnie odmierzał czas. Jeżeli w Waszym przypadku zegarek będzie działał zbyt szybko lub zbyt wolno (wielkości rzędu sekund na dobę), możemy go spowolnić zwiększająć dzielnik, bądź przyspieszyć zmiejszając go.

Generuję poprawki i wracam do kodu.

Zacznę od zadeklarowania zmiennych do przechowywania godzin, minut oraz sekund. Wyszukuję pole PV oraz dodaję deklaracje:

/* USER CODE BEGIN PV /
int hours;
int minutes;
int seconds;
/ USER CODE END PV */

Do poprawnego odczytywania i zapisywania godziny należy jeszcze zadeklarować struktury przetrzymujące czas z RTC.

Wyszukuję pole PDT i dodaję deklaracje:

/* USER CODE BEGIN PTD */
RTC_TimeTypeDef gDate;
RTC_TimeTypeDef gTime;

RTC_TimeTypeDef sDate;
RTC_TimeTypeDef sTime;
/* USER CODE END PTD */

Pierwsze dwie odpowiadają za odczyt (get), kolejne dwie za zapis (set).

Data również jest konieczna do poprawnego odczytu godziny.

Rozdział ósmy: wyświetlanie liczb


W rozdziale szóstym przedstawiłem funkcję display(), za pomocą której możemy wyświetlać cyfry na wyświetlaczu. Potrzebuję teraz drugiej prostej funkcji, która będzie mogła wyświetlać dwucyfrowe liczby.

Deklaruję ją w PFP:

/* USER CODE BEGIN PFP*/
void display(int position, int number);
void display_twice(int position, int number);
/* USER CODE END PFP */

oraz definiuję w user code 4:

void display_twice(int position, int number) {
	int first_digit;
	int second_digit;
	ALL_OFF
	;
//brake a number into two digits
	if (number <= 9) {
		first_digit = 0;
		second_digit = number;
	}
	if ((number >= 10) && (number <= 19)) {
		first_digit = 1;
		second_digit = number - 10;
	}
	if ((number >= 20) && (number <= 29)) {	
		first_digit = 2;
		second_digit = number - 20;
	}
	if ((number >= 30) && (number <= 39)) {	
		first_digit = 3;
		second_digit = number - 30;
	}
	if ((number >= 40) && (number <= 49)) {	
		first_digit = 4;
		second_digit = number - 40;
	}
	if ((number >= 50) && (number <= 59)) {
		first_digit = 5;
		second_digit = number - 50;
	}

	display(position, first_digit);
	WAIT;
	display(position + 1, second_digit);
	WAIT;

}
/* USER CODE END 4 */

Funkcja ta rozbija zmienną na dwie składowe cyfry, oraz przedstawia je na dwóch kolejnych polach wyświetlacza.

Dodałem równiez wbudowane delaye, nie trzeba będzie ich wpisywać ‘ręcznie’.

Funkcja ta używa napisaniej wcześniej display() do wyświetlania pojedynczych cyfr i podobnie jak ona przyjmuje dwa argumenty- pozycja kursora i liczba do wyświetlenia.

Rozdział dziewiąty: budowa pętli głównej


Mam już wszystko, czego potrzebuję. Bibliotekę wyświetlacza 7-segmentowego, odpowiednie funkcje wyświetlające liczby a także gotową konfigurację zegara RTC.

Teraz wpisuję w pętli głównej odczyt i wyświetlenie godziny:

HAL_RTC_GetDate(&hrtc, &gDate, RTC_FORMAT_BIN);
HAL_RTC_GetTime(&hrtc, &gTime, RTC_FORMAT_BIN);
hours = gTime.Hours;
minutes = gTime.Minutes;
seconds = gTime.Seconds;



if (SET) {
	display_twice(3, seconds);
} else {
	display_twice(1, hours);
	display_twice(3, minutes);
	}

Na tym etapie podłączyłem także przycisk SET, którego przytrzymanie spowoduje pokazanie sekund 🙂

Oto efekt:

Moim zdaniem prezentuje się pięknie. I satysfakcjonująco 🙂

Godzina została pobrana z konfiguracji Cube i tam mogę ją zmienić. Ale nie widzi mi się podłączanie zegarka do komputera zawsze, gdy trzeba go będzie przestawić. Dlatego oprogramuję teraz ustawianie zegarka.

Deklaruję kolejne zmienne w PV:

int settings = 0;
int set_delay = 100;

Pierwsza zmienna będzie flagą trybu ustawień, druga zaś przechowa nam opóźnienie dla drgań styków. Już spieszę z wyjaśnieniami.

Jesteśmy w stanie poczuć pyknięcie przycisku kiedy go wciskamy bądź puszczamy. I wydaje się, że jest to pojedynczy impuls. Mikrokontroler jednak działa znacznie znacznie szybciej. Na tyle szybko, że wyraźnie „widzi” drgania membrany przycisku wynikające z jej bezwładności i interpretuje to jako kilka wciśnięć.

Drgania styków

STM po wciśnięciu przycisku powinien odczekiwać określony czas, aż drgania ustaną. Kolejną zaletą takiego opóźnienia jest fakt, że np. podczas inkrementowania jakiejś zmiennej nawet przy jak najkrótszym kliknięciu mikrokontroler doda jedynkę tylko raz zamiast kilkudziesięciu czy kilkuset 🙂

Teraz podzieliłem główną pętlę while na sekcje, dla poprawy czytelności.

Tak wygląda kompletna zawartość pętli głównej:

/* USER CODE BEGIN WHILE */
	while (1) {


		//___________________SECTION 1 -setting clock

		if (CLEAR) { //clear button enter clock set.
			settings = 1;
			HAL_Delay(100);

		}
		//if set are bigger than 0, you are entering setting mode.
		while (settings >= 1) {

			if (CLEAR) {
				settings++;
				if (settings >= 4) {
					settings = 0;
				}
				HAL_Delay(300);
			}

			//reading from RTC also in settings mode
			HAL_RTC_GetDate(&hrtc, &gDate, RTC_FORMAT_BIN);
			HAL_RTC_GetTime(&hrtc, &gTime, RTC_FORMAT_BIN);
			hours = gTime.Hours;
			minutes = gTime.Minutes;
			seconds = gTime.Seconds;

			//display only this, what you are actually setting
			if (settings == 1) {
				display_twice(1, hours);
			} else if (settings == 2) {
				display_twice(3, minutes);
			} else if (settings == 3) {
				display_twice(3, seconds);
			}

			ALL_OFF
			;

			if (settings == 1) {
				if (SET) {
					hours++;
					if (hours >= 24) {
						hours = 0;

					}

					sTime.Hours = hours;
					sTime.Minutes = minutes;
					sTime.Seconds = seconds;
					HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
					HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
					HAL_Delay(set_delay);

				}
			}

			if (settings == 2) {
				if (SET) {
					minutes++;
					if (minutes >= 60) {
						minutes = 0;

					}

					sTime.Hours = hours;
					sTime.Minutes = minutes;
					sTime.Seconds = seconds;
					HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
					HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
					HAL_Delay(set_delay);

				}
			}

			if (settings == 3) {
				if (SET) {
					seconds = 0;
					sTime.Hours = hours;
					sTime.Minutes = minutes;
					sTime.Seconds = seconds;
					HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
					HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
					HAL_Delay(set_delay);

				}

			}
		}

		//___________________SECTION 2 -display clock
		if (SET) {
			display_twice(3, seconds);
		} else {
			display_twice(1, hours);
			display_twice(3, minutes);
		}

		//___________________SECTION 3 -read from RTC

		HAL_RTC_GetDate(&hrtc, &gDate, RTC_FORMAT_BIN);
		HAL_RTC_GetTime(&hrtc, &gTime, RTC_FORMAT_BIN);
		hours = gTime.Hours;
		minutes = gTime.Minutes;
		seconds = gTime.Seconds;

		/* USER CODE END WHILE */

Sekcja pierwsza odpowiada za ustawianie zegarka, druga za wyświetlanie, a trzecia za odczyt.

Po wciśnięciu przycisku CLEAR ustawiam zmienną settings na 1, co uruchamia pętlę while trybu ustawień.

W tym trybie również na bieżąco jest odczytywana godzina, natomiast wyświetlana jest tylko aktualnie ustawiana wartość. Godziny, minuty lub sekundy.

Teraz za pomocą SET mogę ustawić godzinę. Kiedy to zrobię, przyciskiem CLEAR ustawiam minuty. Ponownie wciskam CLEAR. Teraz przyciskiem SET mogę wyzerować sekundy. Ostatnie kliknięcie CLEAR ustawia zmienną settings na 0, a STM wychodzi z pętli ustawień i zajmuje się teraz wyłącznie odczytem i wyświetlaniem godziny.

Rozdział 10: budowa prototypu


Zegarek działa świetnie, ale jest dużo przewodów i płytek. Spróbuję to trochę zminimalizować, stosując jednocześnie lubiany przeze mnie steampunkowy styl.

Użyłem brązowej płytki uniwersalnej raster 2,54mm w kolorze brązowym.

Czas przygotować elementy do lutowania:

Komponenty

Skoro działam w klimacie steampunkowym, wypada użyć do montażu lutownicy gazowej 🙂

Schemat zegarka:

Układając elementy trzeba pamiętać, aby rezonator kwarcowy był jak najbliżej mikrokontrolera.

Kolejnym krytycznym elementem okazał się stabilizator napięcia. Różnica napięć wejście-wyjście zwyczajnych stabilizatorów to ok 2V. Przy stabilziatorach typu LDO jest to 1V. Napięcie pracy akumulatora litowo-jonowego do od ok 3V do 4.2V. Zatem nawet stabilizator LDO nie będzie pracował poprawnie, podając napięcie znacznie niższe od 3.3V. Przeszukując układy scalone do specjalnych zastosowań znalazłem to, czego szukałem- stabilizator liniowy TC1185-3.3VCT8. Ma on napięcie dropout na poziomie 80mV przy poborze prądu 100mA. W dodatku jest niedrogi. Do tego projektu nadał się idealnie. Dzięki niemu zegarek będzie działał stabilnie i jasno od 4.2 do 3.38V, przez prawie pełny zakres napięć akumulatora. Nalezy pamiętac o kondensatorach filtrujących napięcie wejściowe i wyjściowe. Użyłem w tym celu dwóch kondensatorów ceramicznych SMD o pojemności 100nF każdy.

Rezystory do segmentów to 120R (120 Ohmów). Zastosowanie takich zapewni dostatecznie jasne świecenie wyświetlacza i średni pobór prądu pomiędzy 25 a 35mA. Przy akumulatorze o pojemności 800mAh powinno to zapewnić 35h ciągłej pracy zegarka. Ponieważ akumulator pracuje w zakresie od ok 20 do 100% można oszacować, że będzie to ok 30 godzin pracy. Po tym czasie należy go naładować lub wyłączyć aby zapobiec uszkodzeniu akumulatora. Rozładowanie ogniwa litowo-jonowego poniżej 2.5V powoduje jego trwałe uszkodzenie. Rozwiązaniem tego problemu może być użycie ogniwa z wbudowanym układem PCM, który sam odłączy akumulator kiedy napięcie będzie zbyt niskie.

Do ładowania zegarka używam popularnego układu TP4056. Jest to liniowa ładowarka ogniwa litowo-jonowego. Scalak ten ładuje ogniwo poprawnie, w dwóch etapach. Pierwszy to etap CC- ze stałym prądem ładowania. Działa dopóki napięcie na ogniwie jest niższe niż 4.2V. Gdy to napięcie zostanie osiągnięte, układ przechodzi w fazę CV- ze stałym napięciem ładowania 4.2V. TP4056 jest dostępny na gotowych już modułach z portem micro USB, i standardowo prąd ładowania wynosi 1A. Jest to trochę za dużo dla zegarka, użyte w nim ogniwo 14500 można ładować maksymalnym prądem 0.5A. Na szczęście na płytce znajduje się jeszcze rezystor PROG- wymieniając go na inny możemy zmniejszyć prąd ładowania.

Oto zlutowany układ:

Zegarek

Płytka jest delikatna więc zbudowałem klatkę z miedzianego przewodu jednożyłowego 2.5mm2. Pełni ona rolę obudowy/osłonki oraz mocowania paska.

Płytkę od spodu zabezpieczyłem taśmą kaptonową. Jest to specjalna taśma odporna na przetarcia oraz wysoką temperaturę. Często stosowana w budowie pakietów litowo-jonowych.

mde

Kolej na pasek. Standardowe paski zegarkowe są zbyt wąskie. Zaopatrzyłem się więc w skórzaną pieszczochę o szerokości 8cm, która pasuje idealnie. Kolejny problem to brzydki niebieski akumulator. Tutaj pomoże mi okleina w szczotkowane aluminium.

A oto efekt końcowy:

zegarek na rękę i okulary
Gotowy zegarek

Zdjęcie przedstawia pierwsza wersję zegarka. Po jakimś czasie użytkowania dołożyłem także osłonę rezonatora, oraz zintegrowałem ładowarkę TP4056 z paskiem 🙂

Obecna wersja:

zegarek na rękę
Zegarek na rękę

Pełny projekt SW4STM oraz Cube MX a także schemat udostępniam pod artykułem. Zachęcam do budowy takiego zegarka 🙂 Pamiętajcie, że całą niezbędną do tworzenia podobnych projektów wiedzę możecie zdobyć na naszych kursach elektroniki.

Będę mega usatysfakcjonowany widząc indywidualne interpretacje tego projektu!

A teraz rozpalam kocioł i ruszam swoim parowym pojazdem w poszukiwaniu nowych inspiracji 🙂

Łączny czas projektu (prototypowanie, program + lutowanie) : około 18h.

Bart

Epilog

Po pewnym czasie użytkowania zegarka stwierdziłem, że jest nieco zbyt duży i niewygodny. Podjąłem więc odpowiednie kroki. Zaprojektowałem i wytrawiłem płytkę, zmieniłem wyświetlacz na nieco mniejszy, akumulator wymieniłem na litowo-polimerowy, oraz doprogramowałem tryb ondemand, w celu poprawy czasu pracy na jednym ładowaniu(zegarek świeci się tylko przez 10 sekund od naciśnięcia przycisku). Zlutowałem także nową, mniejszą klatkę, użyłem nieco wolniejszego kwarcu. Efekt możecie zobaczyć poniżej. Link do pobrania projektu zawiera najnowszą wersję, razem z projektem płytki PCB.

przycisk pobierz

Kilka słów o autorze:


Bartosz „Ramzes” Pracz jest z wykształcenia humanistą, z zawodu konstruktorem elektroniki w branży oświetlenia LED, a z pasji programistą systemów wbudowanych z kilkoma większymi i mniejszymi projektami w portfolio. Oprócz pisania programów lubi także pisać dla Was ciekawe artykuły, oraz uczyć elektroniki wszystkich tych, którzy wykazują chęć rozwoju w tym kierunku. Prowadzący zajęć z podstaw elektroniki oraz programowania miktrokontrolerów ARM (STM32) oraz AVR (arduino). Jego pozostałe zainteresowania to webmastering, motoryzacja (w szczególności silniki i mechanika), jazda motocyklem, czytanie książek i jazda na łyżwach 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *