Cyfrowy termometr z wykorzystaniem płytki atmel_display

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.

Termometr – baza sprzętowa

Osoby, które nie wiedzą czym jest płytka atmel_display, zapraszam do kilku krótkich wpisów z 2012 i 2013 roku:

  1. Mini płytka testowa AVR z odzysku
  2. Usprawnienie mini płytki testowej AVR z dekodera Cyfrowego Polsatu
  3. Test mini płytki AVR z ATtiny2313 – pierwszy program

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.

Termometr na attiny2313 – kod programu w C

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:

Using DS18B20 digital temperature sensor on AVR microcontrollers – Research Project Documents Gerard Marull Paretas September 2007

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.

Działanie termometru na attiny2313

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.

Wojtek

Zobacz komentarze

Ostatnie posty

Tani moduł IoT z kamerką ESP32 CAM – pierwsze uruchomienie

Mega tanie, bezprzewodowe moduły Internet of Things na dobre zadomowiły się w naszych sieciach. Od…

3 dni temu

Aktualizacja oprogramowania w stacji lutowniczej AiXun T3A

Pewnie nie każdy posiadacz tytułowej stacji lutowniczej wie, że posiada ona możliwość aktualizacji firmware'u. Producent…

3 tygodnie temu

Programator USB AVR ISP z Arduino Nano

Jakiś czas temu, przeglądając Aliexpress natknąłem się na ciekawy shield do Arduino Nano. Według opisu…

4 tygodnie temu

Tester elementów elektronicznych na atmega – aktualizacja firmware’u

W mailach i komentarzach kilka razy przewijała się prośba o ten wpis. Chodzi o aktualizację…

2 miesiące temu

Wzmacniacz słuchawkowy Lovely Cube – popularny klon Lehmanna

Dziś tematyka audio, a nawet audiofilska. Uznany wzmacniacz słuchawkowy Lehmann Black Cube Linear o dość…

2 miesiące temu

Podsumowanie 2023 roku

Tradycyjnie w styczniu, publikuję podsumowanie minionego roku na stronie grylewicz.pl. Poniżej trochę liczb, lista najpopularniejszych…

3 miesiące temu