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:

Termometr na mikrokontrolerze

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:

Termometr z płytki atmel_display

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.

Możesz również polubić…

2 komentarze

  1. piracz.pl pisze:

    Mam pomysł prosty licznik do nawijarki +1 DO 9999

Leave a Reply

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.