LinkedIn YouTube Facebook
Szukaj

Newsletter

Proszę czekać.

Dziękujemy za zgłoszenie!

Wstecz
Artykuły

[PROJEKT] Wielokanałowy termometr i termostat z wyświetlaczem OLED [1]

Inicjalizację można podzielić na kilka etapów:

  • wyzerowanie liczników: kolumn, stron pamięci i linii początkowej. Domyślnie po włączeniu zasilania (POR) te liczniki są wyzerowane,
  • ustawienie powiązania zawartości pamięci RAM obrazu z pozycją na panelu OLED (orientacja wyświetlania),
  • ustawienie kontrastu, przetwornicy DC/DC zasilającej drivery, i częstotliwości taktowania, oraz włączenie wyświetlenia.

Matryca wyświetlacza jest monochromatyczna i jednemu pikselowi odpowiada jeden bit w pamięci obrazu. Pamięć ma rozmiar 132×64 bity, ale w rzeczywistości jest zorganizowana bajtowo. Host wysyła kolejne bajty po magistrali SPI pod lokacje określone przez liczniki kolumn i stron. Jeden bajt w pamięci obrazu odpowiada pionowej linijce o długości 8 pikseli. Najmłodszy bit tego bajta jest pikselem położonym najwyżej w linijce, a bit najstarszy pikselem położonym najniżej. Położenie linijki w poziomie określa licznik kolumn zmieniający się w zakresie 0…131, a położenie w pionie określa licznik stron zmienia się w zakresie 0…7. Po ustaleniu numeru strony kolejne zapisywane bajty tworzą pasek o szerokości 8 pikseli. Każdy zapis danej powoduje inkrementację licznika kolumn i nowa dana jest zapisywana po kolejna lokację. Kiedy licznik kolumn osiągnie wartość 131, to po następnym zapisie danych jest zerowany. Przepełnienia licznika kolumn nie powoduje inkrementacji licznika stron i jeżeli ni zmodyfikujemy go wykorzystując do tego celu komendę „ustawienie licznika stron”, to kolejne wpisy do pamięci będą nadpisywać dane począwszy od pozycji zerowej (wyzerowanie licznika kolumn). To ważna właściwość, o której trzeba pamiętać. Na listingach 6 i 7 pokazano procedury ustawienia licznika kolumn i licznika stron.

List. 6. Ustawienie licznika kolumn

#define COLADDLOW 0
#define COLADDHIGH 0x10
#define PAGEADD 0xB0
#define LINEADD 0x40
#define OLEDON 0xAE
unsigned char SetColumn(int column)
{
 if(column>128)
    return(1);
  ++column;
  ++column;
  OledCmd(COLADDLOW|(column&0x0f));
  OledCmd(COLADDHIGH|(column>>4));
  return(0);
}

List. 7. Ustawienie licznika stron

unsigned char SetPage(unsigned char page)
{
  if(page>7)
  return(1);
  OledCmd(PAGEADD|page);
  return(0);
}

Pamięć EEPROM

Pamięć EEPROM typu 24C04 ma wbudowany interfejs I2C. Do komunikacji z pamięcią został wykorzystany sprzętowy interfejs I2C2 wbudowany w mikrokontroler. Dostęp do pamięci jest realizowany przez 2 sekwencje: zapisu pojedynczej komórki pamięci i odczytu pojedynczej komórki pamięci.

Zapisanie komórki pamięci zaczyna się sekwencją START, po niej wysyłany jest adres slave z bitem R/W=0. Kolejne 2 bajty to adres komórki i bajt do zapisania w pamięci i na koniec sekwencja STOP.

Na listingu 8 został pokazana procedura zapisywania danej do komórki pamięci pod konkretny adres. Dla linii adresowych A0=A1=A2=0 adres slave jest równy 0xA0.

List. 8. Zapis danej do komórki pamięci EEPROM

void EEWr(unsigned char addr, unsigned char data)
{
  UINT config1;
  CloseI2C2(); //zamknij interfejs I2C
  ConfigIntI2C2(MI2C_INT_OFF); //zablokuj przerwania od I2C2
  config1 = (I2C_ON | I2C_7BIT_ADD);//konfiguruj interfejs I2C2
  OpenI2C2(config1,39);  //
  IdleI2C2();
  StartI2C2();                                        //sekwencja START
  while(I2C2CONbits.SEN); //czekaj na zkończenie sekwencji START
  MI2C2_Clear_Intr_Status_Bit; //Zeruj flagi przerwania
  IdleI2C2();
  MasterWriteI2C2(0xA0);    //Zapisz adres SLAVE
  while(I2C2STATbits.TBF);   //Czekaj na wysłanie 8 bitów
  while(!IFS3bits.MI2C2IF);  //Czekaj na 9-ty takt zegara
  MI2C2_Clear_Intr_Status_Bit; //Zeruj flage przerwania od I2C2
  while(I2C2STATbits.ACKSTAT);//czekaj ba bit potwierdzenia
  IdleI2C2();
  MasterWriteI2C2(addr);   //wyślij adres komórki eeprom
  while(I2C2STATbits.TBF);   
  while(!IFS3bits.MI2C2IF); 
  MI2C2_Clear_Intr_Status_Bit;
  while(I2C2STATbits.ACKSTAT);
  IdleI2C2();
  MasterWriteI2C2(data);   //wyślij daną do zapisu
  while(I2C2STATbits.TBF);   
  while(!IFS3bits.MI2C2IF); 
  MI2C2_Clear_Intr_Status_Bit;
  while(I2C2STATbits.ACKSTAT);
 
  IdleI2C2();
  StopI2C2();       //sekwencja STOP
  while(I2C2CONbits.PEN); //czekaj na zakończenie sekwencji STOP
  IdleI2C2();
  __delay_ms(6); // czekaj na zakończenie zapisu do komórki eeprom
   CloseI2C2();                       //zamknij interfejs I2C2
}

Odczytywanie danej z konkretnej lokalizacji rozpoczyna się od wysłania sekwencji START, adresu slave z bitem RW=0, a po nim bajtu z adresem w pamięci EEPROM. Po tym na magistralę jest ponownie wysyłana sekwencja START, a po niej adres slave z bitem RW=1 informującym pamięć, ze ma odesłać dane. Po wysłaniu tego adresu mikrokontroler wywyła sekwencję odczytania danej z pamięci i kończy wszystko sekwencja STOP.

List. 9. Sekwencja odczytywania danej z pamięci EEPROM

unsigned char EERead(unsigned char addr)
{
  unsigned char buf[15];
  IdleI2C2();
  StartI2C2();
  while(I2C2CONbits.SEN);      //Wait till Start sequence is completed
  MI2C2_Clear_Intr_Status_Bit; //Clear interrupt flag
  IdleI2C2();
  MasterWriteI2C2(0xA0);       //Write Slave address and set master for transmission
  while(I2C2STATbits.TBF);     //Wait till address is transmitted
  while(!IFS3bits.MI2C2IF);    //Wait for ninth clock cycle
  MI2C2_Clear_Intr_Status_Bit; //Clear interrupt flag
  while(I2C2STATbits.ACKSTAT);
  IdleI2C2();
  MasterWriteI2C2(addr);
  while(I2C2STATbits.TBF);     //Wait till address is transmitted
  while(!IFS3bits.MI2C2IF);    //Wait for ninth clock cycle
  MI2C2_Clear_Intr_Status_Bit; //Clear interrupt flag
  while(I2C2STATbits.ACKSTAT);
 
  IdleI2C2();
  StartI2C2();
  while(I2C1CONbits.SEN);      //Wait till Start sequence is completed
  MI2C2_Clear_Intr_Status_Bit; //Clear interrupt flag
  IdleI2C2();
  MasterWriteI2C2(0xA1);       //Write Slave address and set master for receive
  while(I2C2STATbits.TBF);     //Wait till address is transmitted
  while(!IFS3bits.MI2C2IF);    //Wait for ninth clock cycle
  MI2C2_Clear_Intr_Status_Bit; //Clear interrupt flag
  while(I2C2STATbits.ACKSTAT);
  IdleI2C2();
  MastergetsI2C2(2,buf,1000);  //Master recieves from Slave upto 10 bytes
 
  IdleI2C2();                  //wait for the I2C to be Idle
  StopI2C2();                  //Terminate communication protocol with stop signal
  while(I2C2CONbits.PEN);      //Wait till stop sequence is completed
  return(buf[0]);
  //CloseI2C1();               //Disable I2C
}

Czujnik temperatury DS18B20

W prezentowanym urządzeniu do jednej magistrali 1-wire dołączamy wiele czujników. Program najpierw musi wykryć ile czujników jest podłączonych, odczytać i zapamiętać ich identyfikatory zapisane w pamięci ROM każdego z czujników. Potem na podstawie tablicy identyfikatorów cyklicznie odczytywać i wyświetlać temperatury z każdego z czujników. Napisanie procedury identyfikacji nie jest zadaniem prostym, ale na szczęście firma Maxim, obecny producent DS18B20 udostępnia notę katalogową numer 162 „Interfacing DS18X20/DS1822 -1wire Temperature Sensor In a Microcontroller Enviroment. W tej nocie są dokładnie opisane czynności, które należy wykonać żeby identyfikacja została wykonana prawidłowo. Oprócz wyczerpującego opisu zamieszczono tam przykładowe fragmenty programów. Procedury obsługi magistrali 1-wire i wyszukiwania termometrów na magistrali są napisane w oparciu o informacje zawarte w tej nocie.

Na listingu 10 pokazano procedury zerowanie magistrali DS._reset, zapisania bitu na magistrali write_bit i odczytanie bitu na magistrali red_bit. Opóźnienia czasowe są generowane przez funkcję biblioteczną kompilatora MPLAB XC16.

List. 10. Procedury zerowania magistrali oraz zapisu i odczytu bitu z magistrali

unsigned char DS_reset(void) {
  unsigned char presence;
 
  DQPORT=0;
  DQ=OUTPUT;
  DQ = 0; //pull DQ line low
  __delay_us(480); // stan niski przez 480us
  DQ=INPUT; // allow line to return high
  __delay_us(70); // czakaj na stan presence
  presence = DQPORT; // odczytaj sygnał presence
  __delay_us(410); // czekaj na koniec szczeliny czasowej
   
  return(presence); // 0=presence, 1 = nie ma układu na magistrali
}
 
// WRITE_BIT – zapis bitu bitval na magistrlę 1-wire
void write_bit(unsigned char bitval) {
 
  __delay_us(1);
  DQ=OUTPUT; //DQ w stan niski
 
  __delay_us(10);
  if(bitval==1)
   DQ =1; // DQ w stan wysoki jeżeli zapisujemy jedynkę
  __delay_us(50); // stan zapisywanego bitu na magistrali przez całą szczelinę czasową
  DQ=INPUT; // zwolnienie DQ (stan wysoki)
 
}
// READ_BIT – odczytanie bitu z magistrali 1-wire
unsigned char read_bit(void) {
  unsigned char retval;
  __delay_us(1);
  DQ=OUTPUT;//DQ=0 – start szczeliny czasowej
  __delay_us(2);
  DQ=INPUT; // zwolnienie magistrali
  __delay_us(10);
  retval=DQPORT;//odczytaj stan linii magistrali
  __delay_us(48); // dokończenie szceliny czasowej odczytu (min 60usek)   
  return(retval);
}

Do realizacji magistrali 1-wire wykorzystano możliwości portów w mikrokontrolerach Microchipa. Kierunek przesyłania danych na linii portu jest określony przez zapisanie odpowiedniego bitu w rejestrze SFR TRISx. Jeżeli na przykład bit TRISB4 dla linii portu RB4 jest wyzerowany, to linia jest wyjściowa, a jeżeli ustawiony to linia jest wejściowa. Drugi rejestr PORT odpowiada za odczytywanie stanu linii ustawionej jako wejściowa, lub zapisywanie stanu linii ustawionej jako wyjściowa. Załóżmy, że do rejestru PORTB4 linii RB4 zapiszemy zero, a linia będzie podciągnięta do plusa zasilania przez rezystor. Po wpisaniu do TRISB zera linia staje się wyjściową i pojawi się na niej stan niski, bo do PORTB4 zostało wpisane zero. Kiedy do TRISB4 wpiszemy jedynkę, to linia staje się wejściową i rezystor wymusza na niej stan wysoki. Manipulowanie stanem jest realizowane przez zapisywanie rejestru TRISB4. Inicjalizacja portów została na listingu 11.

List. 11. Inicjalizacja linii DQ i wyszukiwanie czujników

#define OUTPUT 0
#define INPUT 1
#define DQ TRISBbits.TRISB4
#define DQPORT PORTBbits.RB4
void DS18B20Init(void)
{

  DQ=OUTPUT;
  DQPORT = 0;//zapisanie do RB4 stanu niskiego
  DQ=INPUT;
  DS_reset();   //zerowanie magistrali
  FindDevices(); //procedura wyszukiwania czujników temperatury 
}

Procedura Find devices (listing 12) wyszukuje czujniki na magistrali i tworzy tablicę FoundROM z odczytanymi numerami seryjnymi DS18B20.

List. 12. Wyszukiwanie czujników

// FIND DEVICES
void FindDevices(void) {
  unsigned char m;
  if(!DS_reset()) { // początek wyszukiwania, kiedy wykryto presence
    if(First()) {
      numROMs=0;
      do {
        numROMs++;
        for(m=0;m<8;m++) {
          FoundROM[numROMs][m]=ROM[m]; //Identifies ROM
        }
       
      }
      while (Next()&&(numROMs<MaxROMs)); //Continues until no additional devices are found
    }
  }   
}

Procedury First i Next używane do wyszukiwania zostały pokazane na listingach 13 i 14.

List. 13. Wyszukiwanie pierwszego czujnika na magistrali

//wyszukiwanie pierwszego czujnika na magistrali
unsigned char First(void) {
  lastDiscrep = 0;
  doneFlag = FALSE;
  return Next();
}

List. 14. Wyszukiwanie kolejnych czujników

// NEXT
//wyszukiwanie kolejnych czujników na magistrali
//jeżeli nie ma czujników, to funkcja zwraca FALSE
unsigned char Next(void) {
  unsigned char m = 1;
  unsigned char n = 0;
  unsigned char k = 1;
  unsigned char x = 0;
  unsigned char discrepMarker = 0;
  unsigned char g;
  unsigned char nxt;
  int flag;
 
  nxt = FALSE;
  dowcrc = 0;
  flag = DS_reset(); // reset the 1-Wire
  if(flag||doneFlag) { //nie ma czujnika – powrót z FALSE
    lastDiscrep = 0; //
    return FALSE;
  }
  write_byte(0xF0); // wyślij komendę SearchROM
  do { // dla wszyskich bajtów
    x = 0;
    if(read_bit()==1) x = 2;
     __delay_us(120);
    if(read_bit()==1) x |= 1;
    if(x ==3) // nie ma czujników na magistrali 1-wire
      break;
    else {
      if(x>0)
        g = x>>1;
      else {
                if(m<lastDiscrep)
          g = ((ROM[n]&k)>0);
        else
          g = (m==lastDiscrep);
        if (g==0) discrepMarker = m;
      }
      if(g==1)
        ROM[n] |= k;
      else
        ROM[n] &= ~k;
      write_bit(g);
      m++;
      k = k<<1;
      if(k==0) {
        ow_crc(ROM[n]);
        n++; k++;
      }
    }
  }
  while(n<8);
  if(m<65||dowcrc)
    lastDiscrep=0;  else {
    lastDiscrep = discrepMarker;
    doneFlag = (lastDiscrep==0);
    nxt = TRUE; //
  }
 
  return nxt;
}
Absolwent Wydziału Elektroniki Politechniki Wrocławskiej, współpracownik miesięcznika Elektronika Praktyczna, autor książek o mikrokontrolerach Microchip i wyświetlaczach graficznych, wydanych nakładem Wydawnictwa BTC. Zawodowo zajmuje się projektowaniem zaawansowanych systemów mikroprocesorowych.