O wykorzystaniu płytki atmel_display do pomiaru i wyświetlania temperatury pisałem już bardzo dawno temu. Jednak dopiero ostatnio zmobilizowałem się i w jedno popołudnie powstał prosty termometr o dość szerokim zakresie mierzonych temperatur. Program napisałem w C, korzystając z gotowych bilbliotek, bo ciężko na nowo odkrywać obsługę magistrali 1-wire czy multipleksowanie siedmiosegmentowego wyświetlacza.
Osoby, które nie wiedzą czym jest płytka atmel_display, zapraszam do kilku krótkich wpisów z 2012 i 2013 roku:
Do budowy termometry wykorzystałem przerobioną płytkę (z wyprowadzonymi na gniazdo dodatkowymi portami), ale równie dobrze będzie działać wersja oryginalna, zdemontowana z dekodera. Jako czujnik temperatury użyłem tani i popularny układ DS18B20 wraz z rezystorem 4k7Ω, którym linia DATA została podciągnięta do plusa zasilania. Schemat całości wygląda tak:
Posiadacze płytki bez przeróbek linię DQ czujnika powinny podpiąć do portu PB5, PB6 lub PB7, gdyż w oryginale PB4 nie jest wyprowadzony na goldpiny. Taka zmiana będzie wymagała tylko niewielkiej korekty w oprogramowaniu o czym niżej.
Pisząc program skorzystałem z opisu obsługi czujnika, którego autorem jest Gerard Marull Paretas. Dokument pochodzi z 2007 roku, ale kod praktycznie bez problemu skompilował się na obecnej wersji gcc. Wart przejrzenia dokument można pobrać stąd:
Biblioteka ds18b20.h – definicja portu i pinu pod który jest podpięty port DQ została opatrzona stosownym komentarzem:
/* * ds18b20.h * * Created on: 20 sie 2015 * Author: wojtek */ #ifndef DS18B20_H_ #define DS18B20_H_ // Definiujemy pod jaki pin jest podpieta magistrala 1-wire #define DS18B20_PORT PORTB #define DS18B20_DDR DDRB #define DS18B20_PIN PINB #define DS18B20_DQ PB4 //Pin jako wejscie i wyjscie #define DS18B20_INPUT_MODE DS18B20_DDR &= ~(1<<DS18B20_DQ) #define DS18B20_OUTPUT_MODE DS18B20_DDR |= (1<<DS18B20_DQ) //Ustawianie stanu wysokiego i nieskiego #define DS18B20_LOW DS18B20_PORT &= ~(1<<DS18B20_DQ) #define DS18B20_HIGH DS18B20_PORT |= (1<<DS18B20_DQ) //Komendy dla DS18B20 #define DS18B20_CMD_SKIPROM 0xCC #define DS18B20_CMD_CONVERTTEMP 0x44 #define DS18B20_CMD_RSCRATCHPAD 0xBE #define DS18B20_CMD_WSCRATCHPAD 0x4E //Deklaracje funkcji uint8_t ds18b20_reset(void); void ds18b20_write_bit(uint8_t bit); uint8_t ds18b20_read_bit(void); void ds18b20_write_byte (uint8_t byte); uint8_t ds18b20_read_byte(void); void ds18b20_config (void); uint8_t ds18b20_read_temperature(void); #endif /* DS18B20_H_ */
Plik ds18b20.c – kod jest prosty, nie sprawdzamy tu sumy kontrolnej wyniku, ani adresu czujnika ds18b20. Stąd też wynika ograniczenie obsługi tylko jednego układu 1-wire:
/* * ds18b20.c * * Created on: 20 sie 2015 * Author: wojtek */ #include <avr/io.h> #include <util/delay.h> #include "ds18b20.h" //Reset magistrali i odpowiedz ukladu 1-wire uint8_t ds18b20_reset(void) { uint8_t i; //ustawianie 0 linii 1-wire DS18B20_LOW; DS18B20_OUTPUT_MODE; _delay_us(480); //oczekiwanie na odpowiedz z ds18b20 DS18B20_INPUT_MODE; _delay_us(60); //sprawdzanie czy ds18b20 odpowiedzial (ustawil 0) i = (DS18B20_PIN & (1<<DS18B20_DQ)); _delay_us(420); // zwracamy wynik resetu 1 - blad, ds18b20 nie odpowiedzial, 0 - OK return i; } //zapis bitu na magistrale void ds18b20_write_bit(uint8_t bit) { DS18B20_LOW; DS18B20_OUTPUT_MODE; _delay_us(1); if (bit) DS18B20_INPUT_MODE; _delay_us(60); DS18B20_INPUT_MODE; } //odczyt bitu z magistrali uint8_t ds18b20_read_bit(void) { uint8_t bit = 0; DS18B20_LOW; DS18B20_OUTPUT_MODE; _delay_us(1); DS18B20_INPUT_MODE; _delay_us(14); if (DS18B20_PIN & (1<<DS18B20_DQ)) bit=1; _delay_us(45); return bit; } //zapis bajtu na magistralę void ds18b20_write_byte (uint8_t byte) { uint8_t i=8; while(i--) { ds18b20_write_bit(byte&1); byte>>=1; } } //odczyt bajtu z magistrali uint8_t ds18b20_read_byte(void) { uint8_t i=8, n=0; while(i--) { //przesunięcie w prawo o jedną pozycję i zapisanie odczytanej wartości n>>=1; n |= (ds18b20_read_bit()<<7); } return n; } // ustawienie rozdzielczosci 9-bitowej void ds18b20_config (void) { ds18b20_reset(); ds18b20_write_byte(DS18B20_CMD_SKIPROM); ds18b20_write_byte(DS18B20_CMD_WSCRATCHPAD); ds18b20_write_byte(0); //zapis do TH Register ds18b20_write_byte(0); //zapis do TL Register ds18b20_write_byte(0x1F); //zapis do conf. register rozdzielczosci 9 bit } //odczyt temperatury uint8_t ds18b20_read_temperature(void) { uint8_t temperature[2]; uint8_t digit; ds18b20_reset(); ds18b20_write_byte(DS18B20_CMD_SKIPROM); ds18b20_write_byte(DS18B20_CMD_CONVERTTEMP); while(!ds18b20_read_bit()); //Reset, pominięcie odczytu adresu, komenda odczytu Sratchpada ds18b20_reset(); ds18b20_write_byte(DS18B20_CMD_SKIPROM); ds18b20_write_byte(DS18B20_CMD_RSCRATCHPAD); //Odczyt dwóch bajtów scratchpada i zapis do tablicy temperature temperature[0] = ds18b20_read_byte(); // mlodsze bity LSB temperature[1] = ds18b20_read_byte(); // starsze bity MSB ds18b20_reset(); digit = temperature[0]>>4; //pozbywam sie czesci ulamkowych digit |= (temperature[1]&0xF)<<4; //przesuwam wartosc w lewo o 4 pozycje, sumuje z mlodszym bajtem, mam znak na najstarszym bicie //digit = (temperature[0]&0xF); return digit; //zwracam temperaturę w liczbie całkowitej, znak na najstarszym bicie }
Do obsługi wyświetlacza wykorzystałem kod z książki Mirosława Kardasia – Mikrokontrolery AVR Język C – podstawy programowania. Ze względu na całkowicie odmienny sprzęt, w kodzie musiałem wprowadzić małe zmiany. W obsłudze przerwania inkrementuję dodatkową zmienną check_temp, która jest sprawdzana w pętli głównej – jeśli jest większa niż 200, następuje odczyt z DS18B20. Dlaczego akurat 200? 200*5ms (co tyle jest generowane przerwanie) = 1 sekunda – jest to czas odświeżania wyniku. Wyświetlacz jest multipleksowany z częstotliwością 200Hz. Biblioteka led_7seg.h:
/* * led_7seg.h * * Created on: 20 sie 2015 * Author: wojtek */ #ifndef LED_7SEG_H_ #define LED_7SEG_H_ //definicje portow i pinow #define LED_DATA PORTD //segmenty dolaczone do portu D #define LED_DATA_DIR DDRD //kierunek portu #define ANODY_PORT PORTB //anody podlaczone do portu B #define ANODY_DIR DDRB // kierunek portu B #define CA0 (1<<PB0) //anoda pierwszej cyfry sterowana z PB0 #define CA1 (1<<PB1) #define CA2 (1<<PB2) #define CA3 (1<<PB3) #define SEG_A (1<<0) //przyporzadkowanie numeru portu do segmentu #define SEG_B (1<<1) #define SEG_C (1<<2) #define SEG_D (1<<3) #define SEG_E (1<<4) #define SEG_F (1<<5) #define SEG_G (1<<6) //extern - zmienne beda dostepne w kazdym pliku, ktory dolaczy ten plik naglowkowy, volatile - nie beda odkladane do rejestru extern volatile uint8_t cy0; extern volatile uint8_t cy1; extern volatile uint8_t cy2; extern volatile uint8_t cy3; extern volatile uint8_t check_temp; void d_led_init(void); #endif /* LED_7SEG_H_ */
Zawartość pliku led_7seg.c:
/* * led_7seg.c * * Created on: 20 sie 2015 * Author: wojtek */ #include <avr/io.h> #include <avr/interrupt.h> #include <avr/pgmspace.h> #include "led_7seg.h" volatile uint8_t cy0; //bufory cyfr volatile uint8_t cy1; volatile uint8_t cy2; volatile uint8_t cy3; volatile uint8_t check_temp; //do opoznienia odczytu temperatury const uint8_t cyfry[15] PROGMEM = { ~(SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F), //0 ~(SEG_B | SEG_C), //1 ~(SEG_A | SEG_B | SEG_D | SEG_E | SEG_G), //2 ~(SEG_A | SEG_B | SEG_C | SEG_D | SEG_G), //3 ~(SEG_B | SEG_C | SEG_F | SEG_G), //4 ~(SEG_A | SEG_C | SEG_D | SEG_F | SEG_G), //5 ~(SEG_A | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G), //6 ~(SEG_A | SEG_B | SEG_C | SEG_F), //7 ~(SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G), //8 ~(SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G), //9 ~(SEG_A | SEG_B | SEG_F | SEG_G), //stopien ~(SEG_G), //- ~(SEG_A | SEG_D | SEG_E | SEG_F | SEG_G), //E ~(SEG_E | SEG_G), //r 0xFF //pusty }; void d_led_init(void) { LED_DATA_DIR = 0xFF; //wszystkie porty jako wyjscia - sterowanie katodami LED_DATA = 0xFF; //wygaszenie wszystkich katod /* 4 piny portu B jako wyjscia (sterowanie anodami wyswietlaczy) */ ANODY_DIR |= CA0 | CA1 | CA2 | CA3; /* wygaszenie wszystkich wyswietlaczy */ ANODY_PORT &= 0xF0; // ustawienie TIMER0 TCCR0A |= (1<<WGM01); //tryb CTC TCCR0B |= (1<<CS01) | (1<<CS00); //preskaler 64 OCR0A = 77; //dodatkowy podział‚ przez 77, dający przerwanie co ~200Hz TIMSK |= (1<<OCIE0A); //zezwolenie na przerwanie Compare Match } // =================== PROCEDURA OBSŁUGI PRZERWANIA ============================== ISR (TIMER0_COMPA_vect) { static uint8_t licznik = 1; //zmienna do przelaczania kolejno anod wyswietlaczy ANODY_PORT &= 0xF0; //wygasza cyfry, zeby nie bylo podswietlania segmentow if(licznik == 1) LED_DATA = pgm_read_byte( &cyfry[cy0] ); //na port wystaw dane zapalajace segmenty else if (licznik == 2) LED_DATA = pgm_read_byte( &cyfry[cy1] ); else if (licznik == 4) LED_DATA = pgm_read_byte( &cyfry[cy2] ); else if (licznik == 8) LED_DATA = pgm_read_byte( &cyfry[cy3] ); //cykliczne przelaczanie w kazdym przerwaniu, podanie zasilania na anodę 0001-0010-0100-1000 ANODY_PORT = (ANODY_PORT & 0xF0) | (licznik & 0x0F); //licznik na port anod licznik <<= 1; //przesun zawartosc licznika o 1 w lewo i wpisz w licznik if(licznik>8) licznik = 1; check_temp++; //zwiększ o 1 wartość check_temp }
W pętli głównej programu jest odpytywany czujnik temperatury wraz ze sprawdzeniem jego podłączenia oraz przetworzenie odczytanej wartości – przekształcenie, jeśli temperatura jest ujemna i rozbicie wyniku na cyfry.
/* * main.c * * Created on: 20 sie 2015 * Author: wojtek * : v.1.3 */ #include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> #include "led_7seg.h" #include "ds18b20.h" int main(void) { d_led_init(); //inicjalizacja wyswietlacza 7-seg //wyswietla wersje softu cy0 = 3; cy1 = 1; sei(); //wlaczenie globalnego zezwolenia na przerwania ds18b20_config(); //ustawia rodzielczosc 9-bitow _delay_ms(500); //petla glowna while(1) { if (check_temp > 200) //pomiar co ~sekunde, check_temp zwiekszane w przerwaniu { if(ds18b20_reset()) // sprawdzam obecnosc ds18b20, jesli nie podlaczony, Err na LED { cy0 = 14; cy1 = 13; //r cy2 = 13; //r cy3 = 12; //E } else { //odczyt temperatury z ds18b20 uint8_t temp = ds18b20_read_temperature(); uint8_t sign = (temp & 0x80) >> 7; //sprawdzanie, czy temp ujemna if (sign) //jesli ujemna { temp = ~temp; //konwersja liczby ujemnej temp ++; //na dodatnia } cy0 = 10; //symbol stopien cy1 = temp%10; //jednosci /* wariant 1, temp + i -, znak - na cy3, brak nieznaczacych '0' cy2 = (temp < 10 ? 14 : temp/10); //dziesiatki lub pusto gdy temp<10 cy3 = (sign == 0 ? 14 : 11); //pusto lub '-' gdy temp < 0 */ // wariant 2, - na cy2 gdy temp <10 if (temp < 10) //jesli mniejsza od 10 { cy2 = (sign == 1 ? 11 : 14); //i ujemna, to minus, jak dodatnia, to pusto } else { cy2 = temp/10; //jesli > 10, to cyfra dzisiatek } cy3 = ((sign == 1 && temp>9) ? 11 : 14);//jestli ujemna i > 10 to '-', inaczej pusto } check_temp = 0; //wyzeruj kontrolke odczytu z ds18b20 } } }
Napis Err na wyświetlaczu oznacza, że nie został podłączony czujnik DS18B20, jego linia DQ lub masa. Jeśli wyświetlany wynik to 85ºC i nie zmienia się, to czujnik 1-wire nie ma podłączonego zasilania +5V. Termometr sprawdziłem w temperaturach od -20ºC do ponad +100ºC, kod programu nie uwzględnia temperatur wyższych niż 99ºC.
Program napisałem w linuksie, korzystając z IDE Eclipse w wersji 4.5.0 (Mars). Jeśli ktoś jest zainteresowany instalacją i konfiguracją środowiska programistycznego dla mikrokontrolerów AVR, to polecam ten wpis: Instalacja narzędzi dla AVR i IDE Eclipse w Linux Mint.
Firmware zajmuje tylko 33,4% (684 bajty) pamięci flash attiny2313 i 7 bajtów w RAM, można więc dopisać dodatkowe funkcje, które zamienią termometr np. w termostat.
Mikrokontroler AVR attiny2313 musi mieć ustawione fabryczne wartości fusebitów, czyli Fuse High Byte: DFh (1101 1111) i Fuse Low Byte: 64h (0110 0100). Płytka zdemontowana z dekodera Polsatu nie posiada zaprogramowanego bitu CKDIV8 (podział częstotliwości zegara przez 8, czyli faktyczne taktowanie 1MHz), stąd konieczność zmiany w Fuse Low Byte z E4h na 64h.
Udostępniam cały projekt, który można wygodnie zaimportować do Eclipsa: atmel_display_termometr.
Termometr po włączeniu przez pół sekundy wyświetla wersję firmware’u w postaci 0013, czyli 1.3. Następnie przez krótką chwilę wyświetli się temperatura 85ºC i dalej zmierzona już wartość. Z uwagi na brak kropek (przecinków) na wyświetlaczu, zrezygnowałem z wyświetlania ułamkowych części temperatury – chyba bez szkody dla całości, biorąc pod uwagę dokładność samego czujnika. Poniżej kilka zdjęć z działania termometru:
Implementację wszystkich funkcjonalności kodu przedstawia poniższy film:
Termometr pobiera około 60mA przy zasilaniu napięciem +5V. Układ pozytywnie przeszedł testy kilkudniowej ciągłej pracy, nie było też problemów z obsługą czujnika DS18B20 – podłączałem w sumie 5 sztuk pochodzących z różnych źródeł.
Po publikacji wpisu o atmel_display, Grzegorz podesłał link do swojego programu termometru, który można znaleźć tutaj: https://bitbucket.org/grzechu81/dsb616_thermometer/src/. Nie testowałem tego kodu w mikrokontrolerze, ale widać tu m.in. funkcję sprawdzania crc. Inne przykłady realizacji termometru na tej płytkce (głównie Bascom) można znaleźć na elektrodzie. Napiszcie, jeśli wykorzystujecie atmel_display w inny sposób, lub macie pomysł na jakiś układ.
Mamy w domu dość wiekowy (2012) Boombox Philips, model AZ385/12 używany przez dzieci głównie jako…
Mega tanie, bezprzewodowe moduły Internet of Things na dobre zadomowiły się w naszych sieciach. Od…
Pewnie nie każdy posiadacz tytułowej stacji lutowniczej wie, że posiada ona możliwość aktualizacji firmware'u. Producent…
Jakiś czas temu, przeglądając Aliexpress natknąłem się na ciekawy shield do Arduino Nano. Według opisu…
W mailach i komentarzach kilka razy przewijała się prośba o ten wpis. Chodzi o aktualizację…
Dziś tematyka audio, a nawet audiofilska. Uznany wzmacniacz słuchawkowy Lehmann Black Cube Linear o dość…
Zobacz komentarze
Mam pomysł prosty licznik do nawijarki +1 DO 9999
Do tego ta płytka nada się idealnie.