AVR - TWI használata - LM75 hőmérő

2016-05-29

 

Az elmúlt időben sokat vacakoltam olyan dolgokkal, amikkel nem is akartam foglakozni. Azt egyik dolog, ami eddig is ismert volt, hogy az általam eddig használt WinAVR nem up-to-date. Fórumokon leírták, hogy bizony, ha C++-ban akarunk programozni akkor előjönnek problémák. Az egyik megoldás, hogy az Arduino fejlesztő környezetében található fordítót használjuk. A másik, hogy letöltjük az Atmel Studio-t (7. V.). Ez frisseb, viszont 800MB mászik szét a gépünkön, cserében egy egész csomó dolgot tud. Ezeket majd érdemes lesz birtokba venni/megtanulni. Most az Arduino környezetének használatát mutatom be. Az alábbi linkről tölthetjük le a teljesen ingyenes csomagot.

https://www.arduino.cc/en/Main/Software

Én Windows-t használok, ezt tudom bemutatni. Én nem az installálható, hanem a ZIP változatot javaslom letölteni. A gyökérben csinálunk egy Arduino könyvtárat, és abba kicsomagolunk mindent. A Windows-unk a jó öreg DOS-tól megörökölte a PATH környezeti változót ( Saját gép ikonon jobb klatty, Tulajdonságok, Speciális rendszerbeállítsok, Környezeti változók, Rendszerváltozók, Path). Ebben a WinAVR-hez benne kellett lennie:


C:\WinAVR-20090313\bin;

Ezt most cseréljük le, vagy ha eddig nem volt benne, írjuk bele:


c:\Arduino\hardware\tools\avr\bin\;

A Windowst/PC-t újraindítva máris müxik.

A másik dolog, amivel nem akartam foglalkozni, de nem bírtam abbahagyni az eddig már megírt .h, .cpp file-k toldozgatását, javítgatását. Szerettem volna valami olyan dologot bemutatni, amiről még nem írtam. A TWI-re esett a választásom. Ezt igazából eredetileg I2C busz névre keresztelte a megalkotója a Philips. Gondolom valami licensz dolog miatt nevezi az Atmel TWI-nek. Én most lusta vagyok az I2C-t bemutatni, aki nem ismeri, nézzem meg az alábbi linken található írást:

http://www.hobbielektronika.hu/cikkek/hmc6352_iranytumodul_-_i2c_twi_hasznalata_avr-rel.html

Annyiban nem értek egyet a szerzővel, hogy az I2C használata master módban elég egyszerű. Vagy 15 éve egy olyan uC-vel dolgoztam, amelyikben nem volt I2C interface, két port láb billegtetésével meg lehetett oldani a vezérlést. Az AVR-ek dedikált áramköreinek/regisztereinek használata sokkal macerásabb. Ennek az az oka, hogy használhatók slave módban, meg multi master módban is, ezek kezelése sokkal körmönfontabb. Az alábbiakban bemutatott kódban a master módra szorítkoztam, szerintem a feladatok többségében erre van szükség, vagyis amikor kizárólag mi akarjuk vezérelni/lekérdezni a TWI buszra csatlakoztatott slave eszközöket.

Így néz ki a TWI kezelésére szolgáló osztály. Igazából ez egy függvény gyűtemény, amit majd az adott TWI eszközkezelő objektumban fogok használni. Érdemes tudni, hogy az I2C sebessége szabványosan 100kbit/sec, vagy 400kbit/sec. Én csak ezekhez csináltam meg a makrókat, de kisebb sebességek is használhatók. Alapból 100kbit/sec-re állítottam a sebességet. Az SCL és az SDA lábat belső ellenálással felhúztam, de szükség lehet külső felhúzó ellenállásra. A két felhúzó makró típusfüggő, mert az egyes AVR típusokon más-más portlábakra sikerült a két jelet kivezetni. Az egész kód első felét nagyrészt csak azért csinátam, hogy master funkcióhoz kapcsolható deklarációk teljes körűek legyenek, mintegy dokumentációként, vannak közöttük olyanok amiket azután nem is használok. Ezeknek az egyszerű fuggvényeknek két hátrányuk van. Az egyik, hogy egy multitaszkos rendszerben nagy pocsékolás, hogy a proc. várakozik a végrehajtásra, és közben semmit sem csinál. Ilyenkor érdemes megszakítással kezelni őket. Másik gyengeség, ha mondjuk kihúzzuk a tesztpanelból a slave eszközt, a főprogram belefagy a várakozásba.


/*******************************************************************************
*   Author       -  Kiraly Tibor
*                   http://www.tkiraaly.hu
*   Date         -  2016.05.25.
*   Chip         -  AVR
*   Compiler     -  avr-gcc
*
*   ktw.cpp
*   TWI master kezelo osztaly
*   
*******************************************************************************/


#include "kavr.h"


// TWAR - TWI Address Register (Slave)
// TWDR - TWI Data Register 
// TWBR - TWI Bit Rate Register

// TWCR - TWI Control Register - bits
#define K_TWINT          0B10000000              // TWI Interrupt Flag
#define K_TWEA           0B01000000              // TWI Enable ACKnovledge
#define K_TWSTA          0B00100000              // TWI START Condition
#define K_TWSTO          0B00010000              // TWI STOP Condition
#define K_TWWC           0B00001000              // TWI Write Collision Flag
#define K_TWEN           0B00000100              // TWI Enable
#define K_TWIE           0B00000001              // TWI Interrupt Enable

// TWSR - TWI Status Register - master states
// TWSR= 0 - TWI prescaler= 1/1
// TWSR= 1 - TWI prescaler= 1/4
// TWSR= 2 - TWI prescaler= 1/16
// TWSR= 3 - TWI prescaler= 1/64
#define KTWS_START       0x08                    // Master transmitted start condition
#define KTWS_RSTART      0x10                    // Master transmitted repeated start condition

#define KTWS_MTAW_ACK    0x18                    // Master transmitted address+ write, ACK received
#define KTWS_MTAW_NACK   0x20                    // Master transmitted address+ write, ACK no received
#define KTWS_MTAR_ACK    0x40                    // Master transmitted address+ read, ACK received 
#define KTWS_MTAR_NACK   0x48                    // Master transmitted address+ read, ACK no received 

#define KTWS_MTD_ACK     0x28                    // Master transmitted data, ACK received 
#define KTWS_MTD_NACK    0x30                    // Master transmitted data, ACK no received
#define KTWS_MRD_ACK     0x50                    // Master received data, ACK received 
#define KTWS_MRD_NACK    0x58                    // Master received data, ACK no received 

#define KTWS_ARB_LOST    0x38                    // Master arbitration lost 
#define KTWS_NO_INFO     0xF8                    // No state information available
#define KTWS_BUS_ERROR   0x00                    // Illegal start or stop condition

#define KTWI_100         TWSR= 0; TWBR= (U8)((F_CPU/ 100000UL- 16UL)/ 2UL)    // 100kHz - F_CPU min 3,6 MHz, alapertelmezett
#define KTWI_400         TWSR= 0; TWBR= (U8)((F_CPU/ 400000UL- 16UL)/ 2UL)    // 400kHz - F_CPU min 14,4 MHz
#define KTWI_STATUS      ( TWSR & 0B11111000)    // allapot
#define KTWI_READ                   1            // address R/~W bit
#define KTWI_WRITE                  0            // address R/~W bit


class ktwi                                      // TWI kezelo osztaly
{
   public:
     ktwi();
     void start();
     void stop();
     U8 get_byte_ack();                          // egy byte kerese, ACK bittel
     U8 get_byte_nack();                         // egy byte kerese,ACK bit nelkul
     U8 send_byte( U8); 
};


ktwi::ktwi()
{
   SCL_PULLUP;                                   // AVR fuggo
   SDA_PULLUP;                                   // AVR fuggo
   KTWI_100;
};


void ktwi::start( void)                          // start
{
   TWCR= K_TWINT+ K_TWEN+ K_TWSTA;
   while( !( TWCR & K_TWINT));                   // varakozas start vegeig
}


void ktwi::stop( void)                           // stop
{
   TWCR= K_TWINT+ K_TWEN+ K_TWSTO;
   while( TWCR& K_TWSTO);                        // varakozas stop vegeig
}


U8 ktwi::get_byte_ack()                          // egy byte kerese, ACK bittel
{
   TWCR=  K_TWINT+ K_TWEN+ K_TWEA; 
   while( !( TWCR & K_TWINT));                   // varakozas olvasas vegeig
   return TWDR;
}


U8 ktwi::get_byte_nack()                         // egy byte kerese, ACK nelkul
{
   TWCR=  K_TWINT+ K_TWEN; 
   while( !( TWCR & K_TWINT));                   // varakozas olvasas vegeig
   return TWDR;
}


U8 ktwi::send_byte( U8 data)                     // egy byte kuldese, ACK visszadasa
{	
   TWDR= data;
   TWCR= K_TWINT+ K_TWEN;
   while( !( TWCR& K_TWINT));                    // varakozas atvitel vegeig
   if( KTWI_STATUS == KTWS_MTAW_ACK || KTWI_STATUS == KTWS_MTAR_ACK) return 1; 
   else                                                              return 0;
}

Néhány régen vásárolt/megmaradt I2C chipet találtam a láda fiában. Először a Microchip 24C04-vel küzdöttem meg, de egyszerűbb az Analog Devices AD7416 hőmérő IC kezelése, inkább kezdjünk azzal. A kezelése megegyezik az LM75 IC-vel, csak az LM75 olcsóbb, kevésbé pontos, és szűkebb tartományban mér. Időközben elrohantam a HQ video-hoz, és vettem néhány LM75-t, mivel az AD7416 nehezen hozzáférhatő, inkább ezt mutatom be. Az AD érthetetlen módon 2-3 fokon belül billeget, míg az LM75 stabil volt. A hőmérséklet változásra/mérésre a billegés nem jellemző, sőt, ha nagy időállandójú hibajelenséggel hoz össze a sors (> 10 sec), akkor általában melegedési problémára gyanakodhatunk. Tehát nézzük a kapcsolási rajzot:

Egy TWI bus-ra 8 db LM75 csatlakoztatható, én egyszerűen a cím lábakat 0-ra kötöttem, tehát ez a 0-ás egység. Az LM75-nek van egy open drain-es kapcsoló kimenete (OS), ami a hozzá tartozó regisztereken keresztül felprogramozható, hogy milyen hőfoknál kapcsoljon be, illetve ki. Ez használható termosztát fűtőelemének, vagy hűtés ventillátorának kapcsolására, vagy ráköthetjük a uC-nek megszakítás lábára is. Szerintem ha már egy uC-vel kezeljük, majd az foglalkozik a további teendőkkel, szóval szimplán a hőmérséklet lekérdezésére szoríkoztam.


/*******************************************************************************
*   Author       -  Kiraly Tibor
*                   http://www.tkiraaly.hu
*   Date         -  2016.05.29.
*   Chip         -  LM75
*   Compiler     -  avr-gcc
*
*   klm75.cpp
*   LM75 homero kezelo objektum
*
********************************************************************************
*
*   LM75
*   
*   address=  1001AAAR
*                 Addresss
*                    R/~W
*   
*          +---------+
*    SDA  ++ 1    8  |  +VCC
*    SCL  |  2    7  |  A0
*     OS  |  3    6  |  A1
*    GND  |  4    5  |  A2
*         +----------+
*
********************************************************************************

Pelda:

#include "klm75"
..

t= klm75.celsius( 0);

*******************************************************************************/

#ifndef tkiraaly_klm75
#define tkiraaly_klm75


#include "ktwi.cpp"


class klm75_ : public ktwi
{
   public:
      klm75_() {};
      char celsius( U8);                         // homerseklet leolvasasa, 0..7 IC cime
};


klm75_ klm75;


char klm75_::celsius( U8 address)                // homerseklet leolvasasa, 0..7 IC cime
{
   char a;
   address= 0B10010000+ (( address & 0B00000111)<< 1); 
   start();
   send_byte( address+ KTWI_WRITE);
   send_byte( 0);                                // homerseklet regiszter cime
   start();
   send_byte( address+ KTWI_READ);
   a= get_byte_ack();                            // csak a H byte erdekes
   get_byte_nack();
   stop();
   return a;
}


#endif

Ellestem az Arduino kódokból, amint fentebb lázható, hogy rögtön létrehoznak egy objektumot, és a fő programban már alig kell valamit csinálni.


/*******************************************************************************
*   Author       -  Kiraly Tibor
*                   http://www.tkiraaly.hu
*   Date         -  2016.05.29.
*   Chip         -  Atmel ATmega8
*   Compiler     -  avr-gcc
*
*   klm75_demo.cpp
*   klm75 demo
*
*******************************************************************************/

#define F_CPU                4 MHZ


#include "katmega8.h"
#include <util/delay.h>
#include "kserial.cpp"
#include "klm75.cpp"


int main( void)
{ 
   kserial.newline();
   kserial.print( F( "klm75 demo"));
   kserial.newline();
   for(;;)
   {
      kserial.print( klm75.celsius( 0));
      kserial.print( F( " C fok"));
      kserial.newline();
      _delay_ms( 1000);
   }
}

LM75-ből csináltam egy kis hőmérő fejet, amit egy M3-as csavarral egyszerűen rá lehet csavarozni, mondjuk egy hűtőbordára, vagy egy fém alkatrészre, aminek a hőmérsékletét kell mérnünk.

Itt jobban látható, hogyan épül fel a szendvics. A legfelső panel darabka a NYÁK, erre van ráforrasztva a chip, egy darab 4 eres telefon zsinór, meg tettem a táp vezetékek közé egy 100nF kondit is. A chip magasságát (kb.:1,5mmm) egy üres panelleldarabkával egyenlítettem ki, és erre került egy darab alu lemez. A panel már több mint 10 éve várta, hogy megint szükségem legyen rá, ma már egy kicsit máshogy csináltam volna.

Itt látható, ahogy belevágtam. Édesapám szerette mondogatni nekem: "Ahogy azt Móriczka elképzeli!" Arra rögtön rájöttem, hogy kábelt, kondit leforraszt. Az is egyértelmű, hogy az IC teteje és az alu lemez közé kell egy kis szilikon zsír, vagy hővezető paszta. Az is volt már, hogy két bigyót összeragasztottam, de sokkal nagyobb kihívást jelent három lemez darabka, ami kicsit még szilikonzsírral is be van kenve, és minden csúszkál össze-vissza. Hamarosan az ujjaim is ragadtak. De mivel fogok levágni egy darabka papírt, amivel betakarom a cuccot, hogy majd a szorítóbol vala ki s tudjam venni? Megvolt az esélye, hogy ezután ollókezű Tiborként tengessem napjaimat. A ragadás után kifűrészeltem a tervezett darabot, majd a satuba befogva elengedett a ragasztás, így újra ragasztottam. A reszelésnél, kifúrásnál, forrasztásnál és a zsugorcső rámelegítésnél már szerencsére nem jött szét. Szóval, így utólag már okosan, azt mondanám, először elkészítjk a panelt. Ráragasztjuk a távtartóként alkalmazott csíkot, szerintem jó lessz a pillanatragasztó is. Kivágjuk, körbereszeljük. A panelre felforrasztjuk az IC-t. Az alu lemezből csinálunk egy kicsit szélesebb talpat, így nem zavaró, hogy nincs egybereszelve. Az IC tetejére teszünk egy csöpp szilikonzsírt, a távtartó lemezkére ragasztót, majd az egészet összenövesztjük. Fúrunk rá egy 3mm-es lukat. Ráforrasztjuk a kondit, majd a kábelt. Rátolunk egy darab zsugorcsövet és rámelegítjük. Ennyi az egész :).

Egyszer már belefutottam, hogy egy lapon több dolgot is be akartam mutatni, azután összezavartam a dolgokat, ezért inkább a többi I2C chip kezlését külön lapra teszem.

És itt a vége, fussatok el véle, innen letölthetitek a .hex file-t összecsomagolva.