W pewnym sensie tytuł postu jest trochę mylący, gdyż metodologia obsługi tego układu jest niezależna od zastosowanego procka. Użyłem jedynie procka AVR do prezentacji algorytmu (który można z łatwością przenieść na dowolny inny) i przykładowy program jest na AVR'a.
Dużą popularnością w zastosowaniach związanych z pomiarami temperatury cieszy się układ scalony oferowany przez firmę Dalls o symbolu DS18B20. Z punktu widzenia sterowania jest to element cyfrowy (sterowanie do układu oraz odczyt danych odbywa się w sposób cyfrowy) pozwalający na pomiar temperatury, w którym stosowana jest magistrala 1 wire (komunikacja po jednym drucie). Ten sposób przesyłania danych jest stosowany w wielu wynalazkach Dallas'a, toteż poniższy algorytm może być po ewentualnie koniecznych korektach zastosowany do obsługi innych układów scalonych, w których stosowany jest ten sam sposób komunikacji.
Schemat układu badawczego przedstawia następujący rysunek 1 oraz 2. Jest to typowa aplikacja mikrokontrolera ATMEGA88 zawierająca niezbędne elementy (układ resetu, przyłącze do programatora, obwód generator kwarcowego) oraz aplikację układu DS18B20, gdzie istotnym elementem jest rezystor R201, którego zadaniem jest wymuszanie stanu logicznej jedynki na linii danych magistrali 1 wire w sytuacji, gdy żadna strona (mikrokontroler lub czujnik tempreatury) nie wymusza stanu logicznego zera. Rysunek 2 pokazuje implementację interfejsu szeregowego zrealizowanego na bazie popularnego układu MAX232 ze złączem DB9 (żeńskim) do bezpośredniego połączenia z komputerem (lub innym systemem nadrzędnym).
Przykładowy program do obsługi czujnika temperatury jest pokazany niżej. W programie tym do odmierzania wybranych interwałów czasowych używany jest licznik/zegar 1 generujący przerwanie po przepełnieniu 16-bitowego licznika. Zmierzona wartość temperatury jest wysyłana poprzez układ asynchronicznej transmisji szeregowej.
Kod: Zaznacz cały
#define _1WirePort PORTB
#define _1WireDirPort DDRB
#define _1WirePin PINB
#define _1WireDataPin 0 // PORTB.0
- 1WirePort – port, do którego przyłączony jest czujnik
- 1WireDirPort – port określający rejestr sterujący kierunkiem (wejście/wyjście) portu, do którego przyłączony jest czujnik,
- 1WirePin – port, z którego należy odczytywać dane z magistrali 1 wire,
- 1WireDataPin – wskazanie na pin w obrębie portu, do którego przyłączony jest czujnik.
Kod: Zaznacz cały
#define Set_1WireAsInput _1WireDirPort&=~((uint8_t)1<<_1WireDataPin)
#define Set_1WireAsOutput _1WireDirPort|=(uint8_t)1<<_1WireDataPin
#define Set_1WireLow _1WirePort&=~((uint8_t)1<<_1WireDataPin)
#define Set_1WireHigh _1WirePort|=(uint8_t)1<<_1WireDataPin
#define Get_1WirePin ((_1WirePin)&((uint8_t)1<<_1WireDataPin))
- Set_1WireAsInput – do ustawiania odpowiedniego pinu jako wejście sygnału (z punktu widzenia mikrokontrolera),
- Set_1WireAsOutput – do ustawiania odpowiedniego pinu jako wyjście sygnału (z punktu widzenia mikrokontrolera),
- 1WireLow – do ustawiania przez mikrokontroler na magistrali 1 wire stanu logicznego zera,
- 1WireHigh – do ustawiania przez mikrokontroler na magistrali 1 wire stanu logicznej jedynki,
- 1WirePin – do wczytania bieżącego stanu na magistrali 1 wire.
Kod: Zaznacz cały
static volatile TXDRecT TXDRec ;
static volatile RXDRecT TXDRec ;
static volatile uint8_t TimeEvent ;
static volatile uint16_t _1WireTimeDelay ;
static volatile uint8_t _1WireEvent ;
static volatile uint16_t TimeCounter ;
static uint8_t _1WireAutomatState ;
static int16_t CurrentTemp ;
static uint8_t SensorPresent ;
static uint8_t TempDataValid ;
- TimeEvent – flaga sygnalizująca upływ interwału czasowego,
- 1WireTimeDelay – licznik przerwań do odmierzania interwałów czasowych do wyzwalania pomiaru temperatury,
- 1WireEvent – flaga sygnalizująca upływ interwału czasowego wyzwalająca akcję dotyczącą czujnika na magistrali 1 wire,
- TimeCounter – licznik przerwań do odmierzania interwałów czasowych,
- 1WireAutomatState – zmienna przechowująca aktualny stan automatu używanego przy pomiarze temperatury,
- CurrentTemp – aktualnie zmierzona wartość temperatury,
- SensorPresent – flaga sygnalizująca czy do mikrokontrolera przyłączony jest czujnik temperatury,
- TempDataValid – flaga sygnalizująca ważność danych pomiarowych.
Kod: Zaznacz cały
SIGNAL ( TIMER1_OVF_vect )
{
/*-------------------------------------------------------------------*/
_1WireTimeDelay ++ ;
if ( _1WireTimeDelay > _1WireTimeMaxDelay )
{
_1WireEvent = 1 ;
_1WireTimeDelay = 0 ;
} /* if */ ;
TimeCounter ++ ;
if ( TimeCounter > TimeIntervalCounterLimit )
{
TimeCounter = 0 ;
TimeEvent = TRUE ;
} /* if */ ;
} /* TIMER1_OVF_vect */
Funkcja do obsługi przerwań od zegara/licznika 1. Jej zadaniem jest generowanie sygnałów (poprzez ustawienie odpowiednich flag) do pomiaru temperatury oraz „konsumpcji” danych pomiarowych. Oczywiście można zrealizować to jako jedno zdarzenie, tu jest implementacja pozwalająca na niezależne (w innym tempie) pomiary temperatury oraz wykorzystanie tych danych pomiarowych.
Kod: Zaznacz cały
void _1WireDelay ( uint16_t DelayTime )
{
uint16_t StartTimer ;
uint16_t CurrTimer ;
/*-------------------------------------------------------------------*/
StartTimer = TCNT1 ;
for ( ; ; )
{
CurrTimer = TCNT1 ;
if ( ( CurrTimer - StartTimer ) >= DelayTime )
break ;
} /* for */ ;
} /* _1WireDelay */
Funkcja do odmierzania dużych interwałów czasowych. Jej działanie opiera się na sprzętowo inkrementowanym rejestrze licznika/zegara 1. Parametr wywołania DelayTime jest wyrażony w liczbie „tick'ów” licznika (częstotliwość taktująca mikrokontroler z uwzględnieniem preskalera). Algorytm opiera się na znajdowaniu różnicy pomiędzy aktualnym stanem licznika zegara z jego stanem w momencie wejścia do funkcji (obliczanie różnicy daje poprawny wynik nawet w sytuacji, gdy w międzyczasie nastąpiło przepełnienie stanu licznika). Ze względu na duże wartości parametru wywołania (przykładowo do odmierzenia 480 μs wartość parametru wyrażona jest w tysiącach) do odmierzania jest używany licznik 16-bitowy.
Kod: Zaznacz cały
static uint8_t _1WireTempSensorReset ( void )
{
uint8_t PortData ;
/*-------------------------------------------------------------------*/
Set_1WireAsOutput ;
Set_1WireLow ;
_1WireDelay ( TimerDelay480us ) ;
Set_1WireAsInput ;
_1WireDelay60us ( ) ;
PortData = Get_1WirePin ;
_1WireDelay ( TimerDelay150us ) ;
return ( PortData ) ;
} /* _1WireTempSensorReset */
Sprowadza się to do:
- przełączenia wyprowadzenia, gdzie przyłączona jest magistrala 1 wire do stanu „wyjście”,
- wymuszenia stanu logicznego zera,
- odczekanie 480 μs.
- Elementy na rysunku 3 oznaczone kolorem.
- przełączenia wyprowadzenia, gdzie przyłączona jest magistrala 1 wire do stanu „wejście”, co jednocześnie wprowadza stan pasywny na magistrali 1 wire,
- odczekania interwału czasowego trochę większego od 60 μs, by znaleźć się w obszarze(rysunek 3),
- odczytania stanu magistrali 1 wire (jeżeli jest przyłączony czujnik, to on wymusi stan logicznego zera, jeżeli czujnika nie ma, to .... nikt nie wymusi),
- odczekania resztę czasu.
Kod: Zaznacz cały
static void _1WireTempSensorBitWrite ( uint8_t BitData )
{
/*-------------------------------------------------------------------*/
cli ( ) ;
Set_1WireAsOutput ;
Set_1WireLow ;
_1WireDelay15us ( ) ;
if ( BitData )
{
Set_1WireAsInput ;
} /* if */ ;
_1WireDelay60us ( ) ;
Set_1WireAsInput ;
sei ( ) ;
_1WireDelay1us ( ) ;
} /* _1WireTempSensorBitWrite */
Funkcja do zapisu sekwencji sterującej do czujnika temperatury. Jej działanie odpowiada wymogom opisanym w odpowiedniej dokumentacji. Wykonanie tych operacji realizowane jest przy zablokowanych przerwaniach by nie zaburzać pomiaru czasu dla małych interwałów czasowych. Sprowadza się to do:
- wysterowania pinu, do którego przyłączony jest czujnik jako wyjścia,
- wymuszenia stanu logicznego zera,
- odczekania 10 μs czasu,
- w przypadku gdy na magistralę 1 wite zapisywana jest logiczna jedynka, należy „odpuścić dla drutu 1 wire” (przełączyć pin sterujący na wejście, co spowoduje wymuszenie napięcia na magistrali jedynie przez rezystor),
- w przeciwnym wypadku, gdy na magistralę zapisywane jest logiczne 0, wymuszony dotychczas stan jest przedłużany,
- po odpowiednim wysterowaniu stanu logicznego na magistrali 1 wire realizowane jest odczekanie reszty czasu przeznaczonego na transmisję pojedynczego bitu na magistralę 1 wire,
- odczekanie czasu 1 μs czasu jako odstęp między poszczególnymi bitami.
Kod: Zaznacz cały
static uint8_t _1WireTempSensorBitRead ( void )
{
uint8_t PortData ;
/*-------------------------------------------------------------------*/
cli ( ) ;
Set_1WireAsOutput ;
Set_1WireLow ;
_1WireDelay1us ( ) ;
Set_1WireAsInput ;
_1WireDelay15us ( ) ;
PortData = Get_1WirePin ;
sei ( ) ;
_1WireDelay45us ( ) ;
if ( PortData )
return ( 1 ) ;
else
return ( 0 ) ;
} /* _1WireTempSensorBitRead */
- wysterowania pinu, do którego przyłączony jest czujnik jako wyjścia,
- wymuszenia stanu logicznego zera,
- odczekania 1 μs czasu,
- przełączenia pinu, do którego przyłączony jest czujnik jako wejście do mikrokontrolera,
- odczekanie kolejnych 15 μs czasu,
- odczytanie stanu z magistrali 1 wire,
- odczekanie reszty czasu przeznaczonego na odczyt pojedynczego bitu.
Kod: Zaznacz cały
static void _1WireTempSensorWrite ( uint8_t Data )
{
uint8_t Loop ;
/*-------------------------------------------------------------------*/
for ( Loop = 0 ; Loop < 8 ; Loop ++ )
{
_1WireTempSensorBitWrite ( Data & 0x01 ) ;
Data = Data >> 1 ;
} /* for */ ;
_1WireDelay5us ( ) ;
} /* _1WireTempSensorWrite */
Funkcja do zapisu bajtu na magistralę 1 wire jako złożenie ciągu zapisów pojedynczych bitów. Bity zapisywane są zaczynając od pozycji najmniej znaczącej.
Kod: Zaznacz cały
static uint8_t _1WireTempSensorRead ( void )
{
uint8_t Data ;
uint8_t Loop ;
/*-------------------------------------------------------------------*/
Data = 0 ;
for ( Loop = 0 ; Loop < 8 ; Loop ++ )
{
Data = Data >> 1 ;
if ( _1WireTempSensorBitRead ( ) )
Data |= 0x80 ;
} /* if */ ;
return ( Data ) ;
} /* _1WireTempSensorRead */
Funkcja do odczytu bajtu z magistrali 1 wire jako złożenie ciągu odczytów pojedynczych bitów. Odczytane bity zapisywane są w lokalnej zmiennej zaczynając od pozycji najmniej znaczącej.
Pokazane operacje pokazują obsługę termometru na magistrali 1 wire. Wszelkie inne szczegóły są zawarte w przykładowym programie.
Po zaprogramowaniu mikrokontrolera można za pomocą programu HyperTerminal przechwycić strumień przychodzących danych do pliku na dysku.