AKTUALIZACJA!!!
W artykule opisana została wcześniejsza wersja biblioteki. W przykładach do ściągnięcia umieszczona jest już nowa wersja, w której dokonano dość wielu zmian. Ze względów czasowych artykuł pozostanie póki co w formie takiej w jakiej jest. Zachęcam natomiast do zapoznania się ze zaktualizowaną biblioteką.

Jeżeli już potrafimy "porozmawiać" z jednym czujnikiem DS18B20 - to nasz apetyt zaczyna rosnąć. Chętnie użylibyśmu kilku takich czujników do monitorowania temperatur z wielu punktów. I tutaj pojawia się problem: używając jednego czujnika nie musimy zaprzątać sobie głowy jego adresowaniem. Jednak, gdy na jednej magistrali występuje więcej niż jeden czujnik - z każdym z termometrów musimy porozumiewać się używając jego unikalnego adresu. Nie będę tutaj opisywał struktury ramki danych w komunikacji z DS18B20 - tego typu informacje są ogólnodostępne. Skupię się na czysto praktycznym wykorzystaniu przedstawionych kodów.

Aby móc "dogadać" się z czujnikami dołączonymi do magistrali musimy znać ich adresy. Adresy te zaszyte są w wewnętrznej pamięci każdego z czujników i odczytać je możemy wysyłając do czujnika komendę "0x33" oznaczającą ni mniej ni więcej "przedstaw się" - co my rozumiemy przez odesłanie nam numeru seryjnego konkretnego czujnika. I tutaj ważna uwaga. Do określania liczby czujników na magistrali oraz ich numerów seryjnych służy sławetna funkcja SearchROM. W reazultacie działania tej funkcji otrzymujemy zestaw interesujących nas informacji. Jednak znam conajmniej 2 powody, dla których zrezygnowałem z użycia tej funkcji w opisywanym tutaj urządzeniu:

  • Funkcji SearchROM jest "wszystko jedno" w jakiej kolejności podłączone mamy czujniki
  • Funkcja zajmuje sporo miejsca w pamięci

Ponieważ potrzebowałem jednoznacznego rozróznienia o który z czujników chodzi, doszedłem do wniosku iż najrozsądniejszym rozwiązaniem będzie danie użytkownikowi możliwości programowania czujników pojedynczo. Oczywiście rozwiązanie to wiąże się z pewną wadą - na czas programowania danego egzemplarza pozostałe czujniki muszą zostać odpięte. Jednak wada ta na dobrą sprawę nie jest tak uciążliwa (no chyba, że na magistrali mamy kilkadziesiąt czujników...) a daje użytkownikowi, który nie koniecznie musi znać się na systemach mikroprocesorowych, wygodę jednoznacznego rozpoznania konkretnego czujnika. Reasumując: założenie było takie, aby użytkownik po wejściu do menu wybrał interesujący go termometr a następnie przeprowadził jego programowanie. 

Kod realizujący to zadanie bazuje na kodzie obsługi pojedynczego czujnika. Co uległo zmianie? 

  • zmienne wynikowe do których zapisywane były wartości temperatury (DS_tempcalk, DS_tempprzec, DS_znak, dsError) oraz zmienna typu string przechowująca wynik gotowy do wyświetlenia w postaci stringu (tempBuf) zostały zmienione na zmienne tego samego typu, ale tablicowe,
  • numery seryjne czujników przechowywane są w strukturze, wewnątrz której umieszczono tablicę o wymiarach n x 8, gdzie n to liczba termometrów, a 8 to ilość bajtór numeru seryjnego,
  • użycie struktury pozwoliło na bardzo proste zapisywanie i przywracanie z wewnętrznej pamięci eeprom numerów przypisanych termometrów,
  • wprowadzono funkcje read_rom (do odczytu numeru seryjnego) oraz send_rom (dla wysłania numeru na magistralę),
  • wprowadzono zmienną typu ENUM, która używana jest w funkcji sprawdz_rom - dzięki temu, operując na buforze numerów seryjnych umieszczonym w pamięcie RAM w prosty i szybki sposób możemy w programie określić, czy dany termometr nie był jeszcze zaprogramowany (po pierwszym uruchomieniu urządzenia), został wyprogramowany przez użytkownika, lub czy jest dostępny do odczytu,
  • użycie powyższej funkcji pozwala określić, czy dany termometr ma być brany pod uwagę w procedurze odczytu temperatur,
  • wprowadzono funkcję zerujRom, której zadaniem jest wpisanie zer na wszystkie pozycje numeru seryjnego - co równoznaczne jest z wyprogramowaniem termometru,
  • wszystkie operacje wykonywane są w pamięci RAM, dzięki temu działają szybko i nie blokują działania programu nie potrzebnymi odczytami / zapisami magistrali. 

 

 

- zawartość pliku DS1Wire_libr_conf.h

/*
 * DS1Wire_libr_conf.h
 *
 * Created on: 11-04-2012
 * Author: Tomasz Sklenarski
 */
#ifndef DS1WIRE_LIBR_CONF_H_
#define DS1WIRE_LIBR_CONF_H_
//*************************
#define WIRE_CON 1 //numer pindu DS
#define WIRE_PORT PORTA //port do którego podpiety jest termometr
#define TempResolution 1 //ilosc miejsc po przecinku
#define TempDoZmiennych 1 //określa, czy odczyty temperatury mają być udostepnione jako bezpośrednia wartość liczbowa w osobnych zmiennych
#define IloscTermometrow 2 //ilosc termometrow na magistrali
//*************************
#endif /* DS1WIRE_LIBR_CONF_H_ */

- zawartość pliku DS1Wire.h

#ifndef DS1WIRE_H_
#define DS1WIRE_H_
#include "MACRO_PORT.h"
#include <avr/io.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include "../TIMERY/opoznienie.h"
#include "../WODOTRYSKI/definicje_typow.h"
#include "../WODOTRYSKI/val_conv.h"
#include "DS1Wire_libr_conf.h"
#include "DSromStruct.h"
extern TDSNUM dsnum_ram;
//***************************************************************************
enum {DS_pomieszczenie, DS_woda, DS_iloscTermometrow};
enum {DSwyprogramowany, DSaktywny, DSniezaprogramowny};
void WczytajNumerySeryjneDS(void);
void ZapiszNumerySeryjneDS(void);
void read_rom(uint8_t l);
// Funkcje pomocnicze obsługi DS18B20
uint8_t RESET_PULSE(void); //reset magistrali
void send_byte (uint8_t data); //wyslanie bajtu do DS
uint8_t read_byte (void); //odczyt bajtu z DS
//wlasciwa funkcja obsługi termometru
char sprawdz_rom(uint8_t numerRomu);
void zerujRom(uint8_t nrRom);
bool DSconvTemp(uint8_t nrDS); //konwersja odczytanej temperatury i zapis do bufora "tempBuf"
/*
 * Funkcja DSconvTemp() zwraca true jezeli wykryto błąd magistrali. Jeżeli odczyt jest ok, to
 * w buforze tempBuf znajduje się gotowy do wyświetlenia na LCD wynik.
 * Dodatkowo w zmiennych tempcalk i tempprzec znajduje się bezpośrednia wartość liczbowa wyniku.
 */
//***************************************************************************
#if TempDoZmiennych==1
extern volatile u8 DS_tempcalk[IloscTermometrow];
extern volatile u8 DS_tempprzec[IloscTermometrow];
extern volatile u8 DS_znak[IloscTermometrow];
#endif
extern bool dsError[IloscTermometrow];
#if TempResolution==0
extern char tempBuf[IloscTermometrow][6];
#endif
#if TempResolution==1
extern char tempBuf[IloscTermometrow][8];
#endif
#if TempResolution==2
extern char tempBuf[IloscTermometrow][9];
#endif
#if TempResolution==3
extern char tempBuf[IloscTermometrow][10];
#endif
#define WIRE_DIR DDR(WIRE_PORT)
#define WIRE_PIN PIN(WIRE_PORT)
//***************************************************************************
#if TempResolution>3
#error "Blędna rozdzielczoć temperatury!"
#endif
//------------------------------------------------
#endif

- zawartość pliku DS1Wire.c

#include "DS1Wire.h"
TDSNUM dsnum_eeprom EEMEM;
TDSNUM dsnum_ram;
void WczytajNumerySeryjneDS(void)
{
 eeprom_read_block(&dsnum_ram, &dsnum_eeprom, sizeof(dsnum_ram) );
 return;
}
//------------------------------------------------
void ZapiszNumerySeryjneDS(void)
{
 eeprom_write_block( &dsnum_ram, &dsnum_eeprom, sizeof(dsnum_ram) );
 return;
}
char sprawdz_rom(uint8_t numerRomu)
{
char SprWylaczenie=0;
char SprCzyZaprogramowany=0;
for(uint8_t c = 0; c <= 7; c++)
 {
 if(dsnum_ram.ow_rom[numerRomu][c]==0)
 SprWylaczenie++;
 if(dsnum_ram.ow_rom[numerRomu][c]==255)
 SprCzyZaprogramowany++;
 }
if (SprCzyZaprogramowany>=7)
{return DSniezaprogramowny;}
else if(SprWylaczenie>=7)
{return DSwyprogramowany;}
else
{return DSaktywny;}
}
void zerujRom(uint8_t nrRom)
{
 for(uint8_t c = 0; c <= 7; c++)
 {
 dsnum_ram.ow_rom[nrRom][c]=0;
 }
 return;
}
bool dsError[IloscTermometrow];
//-------------------------------------------------------------------
// 1-WIRE RESET
//-------------------------------------------------------------------
uint8_t RESET_PULSE(void)
{
//rom[1].one_wire_rom[1]=1;
WIRE_DIR|=1<<WIRE_CON; // pin 1wire jako wyjscie
 WIRE_PORT&=~(1<<WIRE_CON); // pin 1wire -> 0
 _delay_us(650); // tx reset pulse
 WIRE_PORT|=1<<WIRE_CON; // pin 1wire -> 1 (koniec reset pulse)/pullup na wejscie
 WIRE_DIR&=~(1<<WIRE_CON); // pin 1wire jako wejscie
 _delay_us(70);
 if (bit_is_set(WIRE_PIN,WIRE_CON))
 {
 _delay_us(500); // tx reset pulse
 return 1; // zwraca jedynke jesli slave milczy
 }
 else
 {
 _delay_us(500); // tx reset pulse
 return 0; // zwraca zero jesli slave odpowiedzial
 }
}
//-------------------------------------------------------------------
// 1-WIRE WRITE BYTE
//-------------------------------------------------------------------
void send_byte (uint8_t data)
{wdt_reset();
 uint8_t i;
 WIRE_DIR|=1<<WIRE_CON; // pin 1wire jako wyjscie
 WIRE_PORT|=1<<WIRE_CON; // pin 1wire -> 1
 for (i=0; i<8; i++)
 {
 if((data&0b00000001) == 1)
 {
 WIRE_PORT&=~(1<<WIRE_CON); // pin 1wire -> 0
 _delay_us(5);
 WIRE_PORT|=1<<WIRE_CON; // pin 1wire -> 1
 _delay_us(65);
 }
 else
 {
 WIRE_PORT&=~(1<<WIRE_CON); // pin 1wire -> 0
 _delay_us(70);
 WIRE_PORT|=1<<WIRE_CON; // pin 1wire -> 1
 }
 _delay_us(5);
 data>>=1;
 }
}
//-------------------------------------------------------------------
// 1-WIRE READ BYTE
//-------------------------------------------------------------------
uint8_t read_byte (void)
{wdt_reset();
 uint8_t data=0;
 uint8_t i;
 for (i=0; i<8; i++)
 {
 data>>=1;
 WIRE_DIR|=1<<WIRE_CON; // pin 1wire jako wyjscie
 WIRE_PORT&=~(1<<WIRE_CON); // pin 1wire -> 0
 _delay_us(1);
 WIRE_PORT|=1<<WIRE_CON; // pin 1wire -> 1 / pullup on
 _delay_us(13);
 WIRE_DIR&=~(1<<WIRE_CON); // pin 1wire jako wejscie
 if(bit_is_set(WIRE_PIN,WIRE_CON))
 {
 data|=0b10000000;
 }
 else
 {
 data&=0b01111111;
 }
 _delay_us(70);
 }
 return data;
}
//-------------------------------------------------------------------
#if TempResolution==0
char tempBuf[IloscTermometrow][6];
#endif
#if TempResolution==1
char tempBuf[IloscTermometrow][8];
#endif
#if TempResolution==2
char tempBuf[IloscTermometrow][9];
#endif
#if TempResolution==3
char tempBuf[IloscTermometrow][10];
#endif
//-------------------------------------------------------------------
#if TempDoZmiennych==1
volatile u8 DS_tempcalk[IloscTermometrow];
volatile u8 DS_tempprzec[IloscTermometrow];
volatile u8 DS_znak[IloscTermometrow];
#endif
//-------------------------------------------------------------------
typedef struct
{
 unsigned char Calkowita;
 unsigned char Ulamek;
 unsigned char Znak;
}TTemperatura;
//-------------------------------------------------------------------
/**/
//-------------------------------------------------------------------
char * DSTempToStr(char *buf, unsigned char DStempLSB, unsigned char DStempMSB, uint8_t nrDs)
{
 char *ret = buf;
TTemperatura temp;
unsigned short DStemp = (DStempMSB << 8 ) | DStempLSB;
temp.Znak = DStempMSB >> 7;
 if( temp.Znak )
 {
 DStemp = ~DStemp + 1;
 }
temp.Calkowita = ( unsigned char )(( DStemp >> 4 ) & 0x7F );
#if TempResolution==1
 temp.Ulamek = ( unsigned char )((( DStemp & 0xF ) * 625) / 1000 );
#endif
#if TempResolution==2
 temp.Ulamek = ( unsigned char )((( DStemp & 0xF ) * 625) / 100 );
#endif
#if TempResolution==3
 temp.Ulamek = ( unsigned char )((( DStemp & 0xF ) * 625) / 10 );
#endif
if( temp.Znak )
 {
 *buf++ = '-';
 }
buf = val_conv( temp.Calkowita, buf, 10, 0 );
#if TempResolution>0
 *buf++ = '.';
#if TempResolution==1
 buf = val_conv( temp.Ulamek, buf, 10, 1);
#endif
#if TempResolution==2
 buf = val_conv( temp.Ulamek, buf, 10, 2);
#endif
#if TempResolution==3
 buf = val_conv( temp.Ulamek, buf, 10, 3);
#endif
#endif
 *buf++ = 0xDF;
 *buf++ = 'C';
 *buf = 0;
#if TempDoZmiennych==1
DS_tempcalk[nrDs]=temp.Calkowita;
DS_tempprzec[nrDs]=temp.Ulamek;
DS_znak[nrDs]=temp.Znak;
#endif
 return ret;
}
//-------------------------------------------------------------------
void send_rom(uint8_t k)
{
RESET_PULSE();
send_byte(0x55); //match rom
for(uint8_t j=0; j<8; j++) //pętla wyświetlająca zawartość bufora
 {
 send_byte(dsnum_ram.ow_rom[k][j]);
 }
return;
}
void read_rom(uint8_t l)
{
 if(!RESET_PULSE())
 {
 send_byte(0x33);
 for(uint8_t i=0; i<8; i++)
 {
 dsnum_ram.ow_rom[l][i] = read_byte();
 }
 }
return;
}
bool DSconvTemp(uint8_t nrDS)
{
 if(!RESET_PULSE())
 {
 dsError[nrDS]=false;
 send_rom(nrDS);
 send_byte(0xBE);
 DSTempToStr( &tempBuf[nrDS][0], read_byte(), read_byte(),nrDS );
 if(DS_znak[nrDS] && (!DS_tempcalk[nrDS]) && (!DS_tempprzec[nrDS]))
 dsError[nrDS]=true;
 RESET_PULSE();
 send_byte(0xCC); //skip rom
 send_byte(0x44); //convert temp
 }
 else
 dsError[nrDS]=true;
 return dsError[nrDS];
}
//***********************************************************

- zawartość pliku DSromStruct.h

/*
 * DSromStruct.h
 *
 * Created on: 06-06-2012
 * Author: Tomasz Sklenarski
 * e-mail: <a href="mailto:
 Ten adres pocztowy jest chroniony przed spamowaniem. Aby go zobaczyć, konieczne jest włączenie obsługi JavaScript.
 ">
 Ten adres pocztowy jest chroniony przed spamowaniem. Aby go zobaczyć, konieczne jest włączenie obsługi JavaScript.
 </a>
 * <a href="http://stsystem.elektroda.pl">http://stsystem.elektroda.pl</a>
 */
#ifndef DSROMSTRUCT_H_
#define DSROMSTRUCT_H_
//------------------------------------------------
#include <avr/io.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <avr/wdt.h>
#include <avr/eeprom.h>
#include "DS1Wire_libr_conf.h"
//------------------------------------------------
typedef struct
{
 uint8_t ow_rom[IloscTermometrow][8];
} TDSNUM;
//------------------------------------------------
#endif /* DSROMSTRUCT_H_ */
DSconvTemp();

Do projektu dodajemy (poprzez include) tylko plik DS1Wire.h. W pliku DS1Wire_libr_conf.h definiujemy pin do którego podłączone są czujniki, ilość czujników, oraz rozdzielczość z jaką będzie wyświetlany wynik (od 0 do 3, 0-bez miejsc po przecinku, 3 - trzy miejsca po przecinku). W programie co 750ms (np. przy użyciu timera programowego) wywołujemy funkcję:

DSconvTemp(numerTermometru);

Funkcja DSconvTemp() zwraca true jezeli wykryto błąd magistrali. Jeżeli odczyt jest ok, to w buforze tempBuf znajduje się gotowy do wyświetlenia na LCD wynik. Dodatkowo w zmiennych tempcalk, tempprzec oraz tempznak znajduje się bezpośrednia wartość liczbowa wyniku. Zmienne te wykorzystuję do przesyłania wyniku odczytu np. przez RS485. Jeżeli ich nie potrzebujesz, to możesz z nich zrezygnować w kodzie.

Poniżej znajduje się przykład mojego wykorzystania funkcji DSconvTemp(numerTermometru). Użyłem tutaj 2 timerów programowych - jednego do odpytywania termometru co 760ms, drugiego do migania napisu awaryjnego podczas problemów z DS.

 
inline void temp_task(void) __attribute__((always_inline));
inline void temp_task(void)
{
if(!inMenu && !L_wyswNominaly && !L_wejdzDoMenu)
{
 bLCDxy(9,2);
 switch(przelaczanieTermometrow)
 {
 case DS_pomieszczenie:
 bLCDpgmtext(pomieszczenie);
 break;
 case DS_woda:
 bLCDpgmtext(woda);
 break;
 }
 if(sprawdz_rom(przelaczanieTermometrow)==DSaktywny)
 {
 bLCDxy(10,2);
 if(!L_DStimer)
 {
 DSconvTemp(przelaczanieTermometrow);
 L_DStimer=76;
 }
 if(dsError[przelaczanieTermometrow])
 {
 if (TrybNatrysku<Serwis && !wejdzDoMenu)
 {
 if(!L_DSerrBlink)
 {
 L_DSerrBlink=37;
 DSerrBlink=!DSerrBlink;
 }
 if(DSerrBlink)
 bLCDpgmtext(DSerr1);
 else
 {
 bLCDpgmtext(DSerr2);
 L_buzz=1;
 }
}
 }
 else
 {
 bLCDtext(tempBuf[przelaczanieTermometrow]);
 }
 }
 else
 {
 if (TrybNatrysku<Serwis && !wejdzDoMenu)
 {
 bLCDxy(10,2);
 bLCDpgmtext(natrysk);
 }
 }
}
return;
}

Powyższy kod realizuje naprzemienną prezentację temperatury z 2 termometrów z uwzglęnieniem tego, czy dany termometr jest aktywny i sprawny. Z punktu widzenia programowego najważniejszym jest dla mnie to, że w pętli głównej umieszczam wywołanie funkcji temp_task(); i nic więcej mnie już nie interesuje. Odczyty termometru następują niejako w swoim własnym zdarzeniu - tyknięciu timera programowego. Optymalnym byłoby wprowadzenie tutaj obsługi callbacków, jednak na tym etapie taka funkcjonalność nie była mi potrzebna.

UWAGA!!!

Dzięki zaangażowaniu jednego z Użytkowników udało się wychwycić błąd w kodach obsługi termometrów. Błąd polega na zadeklarowaniu zbyt krótkiego bufora dla stringu przechowującego temperaturę do wyświetlenia. Objawiał się podczas próby zmierzenia temperatur poniżej -10st. C. Obecnie na stronie znajdują się już poprawione kody obsługi.

Serdeczne podziękowania za nadesłane info dla Konrada Frygi-Kolskiego.


 

Do pobrania:

Użyj opcji uploadu plików, aby przekazać mi kod do sprawdzenia. Postaram się zajrzeć do niego tak szybko jak będzie to możliwe. Paczkę z poprawionym kodem umieszczę w tym samym miejscu.

 

 

Jeżeli wystąpi sytuacja, w której zostanie wyświetlona zawartość folderu z innego działu - należy wyczyścić cache przeglądarki! Następnie przeładować stronę.

Komentarze  

 
# GeorgeGeorge 2016-11-17 09:46
You made some really good points there. I checked on the internet for additional information about the issue and found most people will go along with your views on this site.
Odpowiedz | Odpowiedz z cytatem | Cytować
 

Licznik odwiedzin

Mapa odwiedzin



 

Copyright © ST WebSite 2017

Stronę hostuje FutureHost. []

Strona korzysta z plików cookie. Dane przechowywane na Twoim komputerze służą wyłą…cznie do poprawienia funkcjonalnoś›ci witryny. Jeżeli tego nie akceptujesz - powinieneś› ją… opuś›cić‡.

Akceptujesz ciasteczka z tej witryny?