środa, 3 kwietnia 2019

[13] Atmega - Sterownik przekaźników

W tym poście chciałbym opisać prosty sterownik przekaźników z obsługą pamięci EEPROM, wyświetlacza oraz zegara RTC.



Wyświetlacz I2C HD44780:


Na samym początku należy uruchomić interfejs I2C:

  1. void I2C_Init(void)
  2. {
  3.   TWSR = 0;                        
  4.   TWBR = ((F_CPU/SCL_CLOCK)-16)/2;
  5. }

Będzie on odpowiedzialny za obsługę wyświetlacza oraz zegara RTC.

Sterowanie wyświetlaczem jest takie jak dla bezpośredniego podłączenia układu HD44780. Różnica polega jedynie na tym, że dane są przesyłane przez ekspander portów PCF8574 zamiast bezpośrednio na porty.

Wyświetlacz służy do wyświetlania godziny, daty wraz z dniem tygodnia, temperatury oraz danych konfiguracyjnych dla odpowiedniego przekaźnika.

Obsługa wyświetlacza jest dosyć standardowa, dlatego nie będę umieszczał tutaj poszczególnych fragmentów kodów. Całość można pobrać z linku na dole strony.

Obsługa UART:


UART odpowiada za przesyłanie informacji z ustawieniami urządzenia czy sposobem sterowania.

Inicjalizacja interfejsu:

  1. void USART_INIT(USART_PORT port, BAUDRATE baud)
  2. {
  3.     uint8_t presc=0;
  4.    
  5.     if(CLOCK_TYPE == 0)       // 16MHz
  6.     {
  7.         if(baud==BaudRate_4800)         {   presc=207;  }   //0.2%
  8.         else if(baud==BaudRate_9600)    {   presc=103;  }   //0.2%
  9.         else if(baud==BaudRate_14400)   {   presc=68;   }   //0.6%
  10.         else if(baud==BaudRate_19200)   {   presc=51;   }   //0.2%
  11.         else if(baud==BaudRate_28800)   {   presc=34;   }   //-0.8%
  12.         else if(baud==BaudRate_38400)   {   presc=25;   }   //0.2%
  13.         else if(baud==BaudRate_57600)   {   presc=16;   }   //2.1%
  14.         else if(baud==BaudRate_115200)  {   presc=8;    }   //-3.5%
  15.         else if(baud==BaudRate_230400)  {   presc=3;    }   //8.5%
  16.     }
  17.     else if(CLOCK_TYPE == 1)  //8MHz
  18.     {
  19.         if(baud==BaudRate_4800)         {   presc=103;  }   //0.2%
  20.         else if(baud==BaudRate_9600)    {   presc=51;   }   //0.2%
  21.         else if(baud==BaudRate_14400)   {   presc=34;   }   //-0.8%
  22.         else if(baud==BaudRate_19200)   {   presc=25;   }   //0.2%
  23.         else if(baud==BaudRate_28800)   {   presc=16;   }   //2.1%
  24.         else if(baud==BaudRate_38400)   {   presc=12;   }   //0.2%
  25.         else if(baud==BaudRate_57600)   {   presc=8;    }   //-7%
  26.         else if(baud==BaudRate_115200)  {   presc=3;    }   //8.5%
  27.         else if(baud==BaudRate_230400)  {   presc=1;    }   //0%
  28.     }
  29.     else if(CLOCK_TYPE == 2) //1MHz
  30.     {
  31.         if(baud==BaudRate_4800)         {   presc=12;   }   //0.2%
  32.         else if(baud==BaudRate_9600)    {   presc=6;    }   //-7%
  33.         else if(baud==BaudRate_14400)   {   presc=3;    }   //8.5%
  34.         else if(baud==BaudRate_19200)   {   presc=2;    }   //8.5%
  35.     }
  36.    
  37.     if(port==USART_PORT_0)
  38.     {
  39.         //Ustawienie baudrate
  40.         UBRR0H=0x00;
  41.         UBRR0L=presc;
  42.         //---------------------------
  43.         UCSR0A=0x00;
  44.         //---------------------------
  45.         UCSR0B=0x00;
  46.         UCSR0B|=0x80 | 0x10 | 0x08;     //Rx przerwania on, rx enable, tx enalbe
  47.         //---------------------------
  48.         UCSR0C=0x00;                    // tryb asynchroniczny, bez przystosci, 1 bit stopu
  49.         UCSR0C|=0x04 | 0x02;            // 8 date bits
  50.         //---------------------------
  51.     }
  52.     sei();
  53. }

Przesłanie pojedynczego znaku:

  1. static void Stat_Send_Char(USART_PORT port, char data)
  2. {
  3.     if(port==USART_PORT_0)
  4.     {
  5.         while(!(UCSR0A & 0x20));
  6.         UDR0=data;
  7.     }
  8. }

Przesłanie ciągu znaków:

  1. void USART_SEND(USART_PORT port_number, const char *buf, RX_END_ASCII end_sign)
  2. {
  3.     uint8_t n;
  4.     uint8_t c;
  5.    
  6.     for(n=0;n<=TX_BUFFER_LEN;n++)
  7.     {
  8.         c=buf[n];
  9.        
  10.         if(c==0)                                //Jesli natrafi na koniec bufora
  11.         {
  12.             if(end_sign==END_CR_ASCII)
  13.             {
  14.                 Stat_Send_Char(port_number, CHAR_CR);
  15.             }
  16.             else if(end_sign==END_LF_ASCII)
  17.             {
  18.                 Stat_Send_Char(port_number, CHAR_LF);
  19.             }
  20.             else if(end_sign==END_CRLF_ASCII)
  21.             {
  22.                 Stat_Send_Char(port_number, CHAR_CR);
  23.                 Stat_Send_Char(port_number, CHAR_LF);
  24.             }
  25.             break;
  26.         }
  27.         else
  28.         {
  29.             Stat_Send_Char(port_number, c);
  30.         }
  31.     }
  32. }

W przypadku przesyłania danych zawierających w ciągu 0x00 należy wykorzystać inną funkcję, która prześle ramkę danych o określonej długości:

  1. static void SEND_DATA_WITH_SPEC_SIZE(USART_PORT port_number, const char *buf, uint8_t sizeToSend)
  2. {
  3.     uint8_t n;
  4.     uint8_t c;
  5.        
  6.     for(uint8_t n=0; n<=sizeToSend ;n++)
  7.     {
  8.         c=buf[n];
  9.         Stat_Send_Char(port_number, c);
  10.     }
  11. }

Obsługa przerwania:

  1. ISR(USART_RX_vect)
  2. {
  3.     uint8_t recive_char;
  4.    
  5.     recive_char=UDR0;
  6.     if(position_0<RX_BUFFER_LEN)
  7.     {
  8.         buffer_0[position_0]=recive_char;   //Write data into Buffer
  9.         receiveStart = 1;
  10.         buffer_0[position_0+1]=0x00;        //clear next receive data in buffer
  11.         position_0++;                       //Change position
  12.     }
  13.     else
  14.     {
  15.         receiveStart = 0x00;
  16.         position_0 = 0;
  17.         for(uint8_t i=0; i<RX_BUFFER_LEN; i++){
  18.             buffer_0[i] = 0x00;
  19.         }
  20.     }
  21. }

Obsługa odbieranych ramek danych:

  1. void UART_MainProcedure(uint8_t *receiveBuffer)
  2. {
  3.     if(receiveStart == 0x01)
  4.     {
  5.         _delay_ms(70);
  6.        
  7.         if(*(receiveBuffer + 0) == SOH_START && *(receiveBuffer + 1) == 0x05 &&
  8.             *(receiveBuffer + 2) == 0xA5 && *(receiveBuffer + 3) == 0xA5)
  9.         {
  10.             uint8_t dataToSend[15] = {0x00};
  11.                
  12.             dataToSend[0] = 0x01;
  13.             dataToSend[1] = 0x0D;
  14.             dataToSend[2] = 0xA5;
  15.             dataToSend[3] = 0xA5;
  16.             dataToSend[4] = *(receiveBuffer + 4);
  17.            
  18.             ReadDataFromSelectedRelayStructSendToPC(*(receiveBuffer + 4), &dataToSend[5]);
  19.            
  20.             dataToSend[14] = 0x00;
  21.            
  22.             SEND_DATA_WITH_SPEC_SIZE(USART_PORT_0, (const char *)&dataToSend[0], 0x0D);
  23.         }
  24.         else if(*(receiveBuffer + 0) == SOH_START && *(receiveBuffer + 1) == 0x0D &&
  25.                 *(receiveBuffer + 2) == 0xA7 && *(receiveBuffer + 3) == 0xA7)
  26.         {
  27.             uint8_t dataToWrite[9] = {0x00};
  28.            
  29.             dataToWrite[0] = *(receiveBuffer + 5);
  30.             dataToWrite[1] = *(receiveBuffer + 6);
  31.             dataToWrite[2] = *(receiveBuffer + 7);
  32.             dataToWrite[3] = *(receiveBuffer + 8);
  33.             dataToWrite[4] = *(receiveBuffer + 9);
  34.             dataToWrite[5] = *(receiveBuffer + 10);
  35.             dataToWrite[6] = *(receiveBuffer + 11);
  36.             dataToWrite[7] = *(receiveBuffer + 12);
  37.            
  38.             WriteDataForSelectedRelayAndToStruct(*(receiveBuffer + 4), &dataToWrite[0]);
  39.            
  40.             uint8_t frameToSend[6] = {0x01, 0x05, 0xA7, 0xA7, *(receiveBuffer + 4), 0x00};
  41.                
  42.             USART_SEND(USART_PORT_0, (const char *)&frameToSend[0], CHAR_LF);
  43.         }
  44.         else if(*(receiveBuffer + 0) == SOH_START && *(receiveBuffer + 1) == 0x0B &&
  45.                     *(receiveBuffer + 2) == 0xA1 && *(receiveBuffer + 3) == 0xA1)
  46.         {
  47.             //0x01 0x0A 0xA1 0xA1 <godzina> <minuta> <sekunda> <dzien> <miesiac> <rok> <DOW>
  48.             DS3231_setTime(*(receiveBuffer + 4), *(receiveBuffer + 5), *(receiveBuffer + 6), pm, _24_hour_format);
  49.             DS3231_setDate(*(receiveBuffer + 10), *(receiveBuffer + 7), *(receiveBuffer + 8), *(receiveBuffer + 9));
  50.            
  51.             uint8_t frameToSend[6] = {0x01, 0x05, 0xA1, 0xA1, 0xA5, 0x00};
  52.                        
  53.             USART_SEND(USART_PORT_0, (const char *)&frameToSend[0], CHAR_LF);
  54.         }
  55.        
  56.         receiveStart = 0;
  57.         position_0 = 0;
  58.         for(uint8_t i=0; i<RX_BUFFER_LEN; i++){ buffer_0[i] = 0x00; }
  59.     }
  60. }

Tutaj właściwie odbieramy trzy rodzaje ramek: ustawienie czasu na urządzeniu, odczytanie oraz ustawienie konfiguracji przekaźników.

Ramki danych nie są zbytnio skomplikowane. Zrezygnowałem w nich z sprawdzania sumy kontrolnej, ponieważ ryzyko wprowadzenia błędnych ustawień jest mało prawdopodobne.

Ogólny opis ramki: <Bit startu> <Długość> <Kod rozkazu> <Kod rozkazu> <Dane>

Odczyt danych dla jednego wyjścia: 0x01 0x05 0xA5 0xA5 <nr przekaźnika>

Zapis daty i godziny: 0x01 0x0A 0xA1 0xA1 <godzina> <minuta> <sekunda> <dzien> <miesiac> <rok> <DOW>

Zapis ustawień przekaźnika: 0x01 0x0D 0xA7 0xA7 <nr przekaźnika> <dzień tyg ON> <czas_dzialania_sec> <czas_dzialania_msec> <godzinaStart> <minutaStart> <sekundaStart> <godzinaStop> <minutaStop>

Odczyt czasu uznałem za niepotrzebny, ponieważ dane z datą i godziną będą wyświetlane na wyświetlaczu.

Zegar RTC:


Służy on do załączenia przekaźników w odpowiednim czasie. Moduł zegara został wyposażony w baterię podtrzymującą jego działanie po zaniku zasilania. Wobec tego nie ma potrzeby przejmować się błędnym odczytem czasu. 

W związku z tym, że przekaźniki mają być uruchamiane na czasy krótsze niż jedna sekunda to przygotowałem dodatkowo obsługę timera, którego zadaniem będzie zliczanie krótszych odcinków czasu. Tak aby wyłączyć przekaźnik z możliwie najdokładniejszym czasie.

  1. void InitTimer2()
  2. {
  3.     OCR2A = 0x3E;
  4.     TCCR2A |= (1 << WGM21);
  5.     TIMSK2 |= (1 << OCIE2A);
  6.     TCCR2B |= (1 << CS12);
  7.     sei();
  8. }

Przerwanie od timera 2 uruchamiane jest co 200 ms.

W obsłudze przerwania sprawdzam czy ustawiono uruchamianie przekaźnika.

Dodatkowo zegar posiada możliwość odczytu temperatury, z czego również skorzystałem w projekcie. Informacja o temperaturze jest wyświetlana na wyświetlaczu.

Funkcja inicjalizująca zegar DS3231:

  1. void DS3231_init()
  2. {
  3.     DS3231_WriteDataToAddress(controlREG, 0x00);
  4.     DS3231_WriteDataToAddress(statusREG, 0x08);
  5. }

Ustawienie czasu:

  1. void  DS3231_setTime(unsigned char hourToSet, unsigned char minuteToSet, unsigned char secondToSet, unsigned char amPmStateSet, unsigned charhourFormat)
  2. {
  3.     unsigned char writeValue = 0;
  4.    
  5.     DS3231_WriteDataToAddress(secondREG, (decimalToBcdConversion(secondToSet)));
  6.     DS3231_WriteDataToAddress(minuteREG, (decimalToBcdConversion(minuteToSet)));
  7.    
  8.     if(hourFormat == 1)
  9.     {
  10.         if(amPmStateSet == 1) {
  11.             writeValue = 0x60;
  12.         }
  13.         else {
  14.             writeValue = 0x40;
  15.         }
  16.         DS3231_WriteDataToAddress(hourREG, ((writeValue | (0x1F & (decimalToBcdConversion(hourToSet))))));
  17.     }
  18.     else
  19.     {
  20.         DS3231_WriteDataToAddress(hourREG, (0x3F & (decimalToBcdConversion(hourToSet))));
  21.     }
  22. }

Ustawienie daty:

  1. void DS3231_setDate(uint8_t daySet, uint8_t dateSet, uint8_t monthSet, uint8_t yearSet)
  2. {
  3.     DS3231_WriteDataToAddress(dayREG, (decimalToBcdConversion(daySet)));
  4.     DS3231_WriteDataToAddress(dateREG, (decimalToBcdConversion(dateSet)));
  5.     DS3231_WriteDataToAddress(monthREG, (decimalToBcdConversion(monthSet)));
  6.     DS3231_WriteDataToAddress(yearREG, (decimalToBcdConversion(yearSet)));
  7. }

Odczyt godziny:

  1. void DS3231_readTime(uint8_t *hour, uint8_t *miutes, uint8_t *seconds, uint8_t *hourFormat12, uint8_t hour_format)
  2. {
  3.     unsigned char temporaryValue = 0;
  4.    
  5.     temporaryValue = DS3231_ReadDataFromAddress(secondREG);
  6.     *seconds = bcdToDecimalConversion(temporaryValue);
  7.     temporaryValue = DS3231_ReadDataFromAddress(minuteREG);
  8.     *miutes = bcdToDecimalConversion(temporaryValue);
  9.    
  10.     if(hour_format == _12_hour_format)
  11.     {
  12.         temporaryValue = DS3231_ReadDataFromAddress(hourREG);
  13.         temporaryValue &= 0x20;
  14.         *hourFormat12 = (unsigned char)(temporaryValue >> 5);
  15.         temporaryValue = (0x1F & DS3231_ReadDataFromAddress(hourREG));
  16.         *hour = bcdToDecimalConversion(temporaryValue);
  17.     }
  18.     else
  19.     {
  20.         temporaryValue = (0x3F & DS3231_ReadDataFromAddress(hourREG));
  21.         *hour = bcdToDecimalConversion(temporaryValue);
  22.     }
  23. }

Odczyt daty:

  1. void DS3231_readDate(uint8_t *daySet, uint8_t *dateSet, uint8_t *monthSet, uint8_t *yearSet)
  2. {
  3.     unsigned char temporaryValue = 0;
  4.    
  5.     temporaryValue = DS3231_ReadDataFromAddress(yearREG);
  6.     *yearSet = bcdToDecimalConversion(temporaryValue);
  7.     temporaryValue = (0x1F & DS3231_ReadDataFromAddress(monthREG));
  8.     *monthSet = bcdToDecimalConversion(temporaryValue);
  9.     temporaryValue = (0x3F & DS3231_ReadDataFromAddress(dateREG));
  10.     *dateSet = bcdToDecimalConversion(temporaryValue);
  11.     temporaryValue = (0x07 & DS3231_ReadDataFromAddress(dayREG));
  12.     *daySet = bcdToDecimalConversion(temporaryValue);
  13. }

Odczytanie temperatury z urządzenia:

  1. float readTemperature()
  2. {
  3.     float readTemp = 0.0;
  4.     unsigned char lowByte = 0;
  5.     signed char highByte = 0;
  6.    
  7.     lowByte = DS3231_ReadDataFromAddress(tempLSBREG);
  8.     highByte = DS3231_ReadDataFromAddress(tempMSBREG);
  9.    
  10.     lowByte >>= 6;
  11.     lowByte &= 0x03;
  12.    
  13.     readTemp = ((float)lowByte);
  14.     readTemp *= 0.25;
  15.     readTemp += highByte;
  16.    
  17.     return readTemp;
  18. }

Odczyt danych z podanego rejestru:

  1. static unsigned char DS3231_ReadDataFromAddress(unsigned char address)
  2. {
  3.     unsigned char value = 0;
  4.    
  5.     i2c_start_wait(DS3231_Write_addr);
  6.     i2c_write(address);
  7.    
  8.     i2c_rep_start(DS3231_Read_addr);
  9.     value = i2c_readNak();
  10.     i2c_stop();
  11.     return value;
  12. }

Zapis danych do danego rejestru:

  1. static void DS3231_WriteDataToAddress(unsigned char address, unsigned char value)
  2. {
  3.     i2c_start_wait(DS3231_Write_addr);
  4.     i2c_write(address);
  5.     i2c_write(value);
  6.     i2c_stop();
  7. }

Dla układu DS3231 możliwa jest także konfiguracja alarmów:

Ustawienie alarmu 1:

  1. void DS3231_setAlarm1(uint8_t alarmDay, uint8_t alarmHour, uint8_t alarmMinutes, uint8_t alarmSecondDS3231_alarm1_start_condition_enum_t alarm1Mode)
  2. {
  3.     alarmSecond = decimalToBcdConversion(alarmSecond);
  4.     alarmMinutes = decimalToBcdConversion(alarmMinutes);
  5.     alarmHour = decimalToBcdConversion(alarmHour);
  6.     alarmDay = decimalToBcdConversion(alarmDay);
  7.    
  8.     if(alarm1Mode == DS3231_EVERY_SECOND)
  9.     {
  10.         alarmSecond |= 0b10000000;
  11.         alarmMinutes |= 0b10000000;
  12.         alarmHour |= 0b10000000;
  13.         alarmDay |= 0b10000000;
  14.     }
  15.     else if(DS3231_MATCH_S)
  16.     {
  17.         alarmSecond &= 0b01111111;
  18.         alarmMinutes |= 0b10000000;
  19.         alarmHour |= 0b10000000;
  20.         alarmDay |= 0b10000000;
  21.     }
  22.     else if(DS3231_MATCH_M_S)
  23.     {
  24.         alarmSecond &= 0b01111111;
  25.         alarmMinutes &= 0b01111111;
  26.         alarmHour |= 0b10000000;
  27.         alarmDay |= 0b10000000;
  28.     }
  29.     else if(DS3231_MATCH_H_M_S)
  30.     {
  31.         alarmSecond &= 0b01111111;
  32.         alarmMinutes &= 0b01111111;
  33.         alarmHour &= 0b01111111;
  34.         alarmDay |= 0b10000000;
  35.     }
  36.     else if(DS3231_MATCH_DT_H_M_S)
  37.     {            
  38.         alarmSecond &= 0b01111111;
  39.         alarmMinutes &= 0b01111111;
  40.         alarmHour &= 0b01111111;
  41.         alarmDay &= 0b01111111;
  42.     }
  43.     else if(DS3231_MATCH_DY_H_M_S)
  44.     {
  45.         alarmSecond &= 0b01111111;
  46.         alarmMinutes &= 0b01111111;
  47.         alarmHour &= 0b01111111;
  48.         alarmDay &= 0b01111111;
  49.         alarmDay |= 0b01000000;
  50.     }
  51.    
  52.     DS3231_WriteDataToAddress(alarm1secREG, alarmSecond);
  53.     DS3231_WriteDataToAddress(alarm1minREG, alarmMinutes);
  54.     DS3231_WriteDataToAddress(alarm1hrREG, alarmHour);
  55.     DS3231_WriteDataToAddress(alarm1dateREG, alarmDay);
  56. }

Odczytanie ustawionych danych dla alarmu:

  1. DS3231_ConfigAlarmType_Struct DS3231_getAlarm1(void)
  2. {
  3.     uint8_t values[4] = {0x00, 0x00, 0x00, 0x00};
  4.     DS3231_ConfigAlarmType_Struct DS3231_AlarmTime = {
  5.         .day = 0,
  6.         .hour = 0,
  7.         .minute = 0,
  8.         .second = 0
  9.     };
  10.     values[0] = DS3231_ReadDataFromAddress(alarm1secREG);
  11.     values[1] = DS3231_ReadDataFromAddress(alarm1minREG);
  12.     values[2] = DS3231_ReadDataFromAddress(alarm1hrREG);
  13.     values[3] = DS3231_ReadDataFromAddress(alarm1dateREG);
  14.    
  15.     values[0] = bcdToDecimalConversion(values[0] & 0b01111111);
  16.     values[1] = bcdToDecimalConversion(values[1] & 0b01111111);
  17.     values[2] = bcdToDecimalConversion(values[2] & 0b01111111);
  18.     values[3] = bcdToDecimalConversion(values[3] & 0b01111111);
  19.        
  20.     DS3231_AlarmTime.second = values[0];
  21.     DS3231_AlarmTime.minute = values[1];
  22.     DS3231_AlarmTime.hour = values[2];
  23.     DS3231_AlarmTime.day = values[3];
  24.    
  25.     return DS3231_AlarmTime;
  26. }

Sprawdzenie ustawionego trybu alarmu:

  1. DS3231_alarm1_start_condition_enum_t DS3231_getAlarm1Type(void)
  2. {
  3.     uint8_t readValue[4] = {0x00, 0x00, 0x00, 0x00};
  4.     uint8_t returnAlarmMode = 0x00;
  5.    
  6.     readValue[0] = DS3231_ReadDataFromAddress(alarm1secREG);
  7.     readValue[1] = DS3231_ReadDataFromAddress(alarm1minREG);
  8.     readValue[2] = DS3231_ReadDataFromAddress(alarm1hrREG);
  9.     readValue[3] = DS3231_ReadDataFromAddress(alarm1dateREG);
  10.    
  11.     readValue[0] = bcdToDecimalConversion(readValue[0]);
  12.     readValue[1] = bcdToDecimalConversion(readValue[1]);
  13.     readValue[2] = bcdToDecimalConversion(readValue[2]);
  14.     readValue[3] = bcdToDecimalConversion(readValue[3]);
  15.    
  16.     returnAlarmMode |= ((readValue[0] & 0b01000000) >> 6);
  17.     returnAlarmMode |= ((readValue[1] & 0b01000000) >> 5);
  18.     returnAlarmMode |= ((readValue[2] & 0b01000000) >> 4);
  19.     returnAlarmMode |= ((readValue[3] & 0b01000000) >> 3);
  20.     returnAlarmMode |= ((readValue[3] & 0b00100000) >> 1);
  21.    
  22.     return (DS3231_alarm1_start_condition_enum_t)returnAlarmMode;
  23. }

Uruchomienie bądź wyłączenie alarmu odbywa się w rejestrze kontrolnym:

  1. void DS3231_EnableAlarm1(uint8_t enableDisableAlarm)
  2. {
  3.     uint8_t statusRegisterValue = 0;
  4.    
  5.     statusRegisterValue = DS3231_ReadDataFromAddress(controlREG);
  6.    
  7.     if(enableDisableAlarm == 1)
  8.     {
  9.         statusRegisterValue |= 0b00000001;
  10.     }
  11.     else
  12.     {
  13.          statusRegisterValue &= 0b11111110;
  14.     }
  15.    
  16.     DS3231_WriteDataToAddress(controlREG, statusRegisterValue);
  17. }

Wyłączenie alarmu 1:

  1. void DS3231_ClearAlarm1(void)
  2. {
  3.     uint8_t controlRegisterValue = 0;
  4.    
  5.     controlRegisterValue = DS3231_ReadDataFromAddress(controlREG);
  6.     controlRegisterValue &= 0b11111110;
  7.    
  8.     DS3231_WriteDataToAddress(controlREG, controlRegisterValue);
  9. }

Sprawdzenie czy wyzwalanie alarmu jest aktywne:

  1. uint8_t DS3231_CheckIfAlarmIsOn(void)
  2. {
  3.     uint8_t controlRegisterValue = 0;
  4.    
  5.     controlRegisterValue = DS3231_ReadDataFromAddress(controlREG);
  6.     controlRegisterValue &= 0b00000001;
  7.    
  8.     return controlRegisterValue;
  9. }

Ustawianie i kontrola alarmu drugiego jest podobna do alarmu pierwszego, wobec tego nie będę wklejał kodu do sterowania.

W celu odmierzania mniejszych ilości czasu obsługiwany jest Timer 2 skonfigurowany na wyzwalanie przerwań co 1ms. Ustawianie przekaźnika skonfigurowane w programie jest do 1 dziesiątej sekundy.

Inicjalizacja licznika:

  1. void InitTimer2()
  2. {
  3.     cli();
  4.     OCR2A = 0x3E;
  5.     TCCR2A |= (1 << WGM21);
  6.     TIMSK2 |= (1 << OCIE2A);
  7.     TCCR2B |= (1 << CS22) | (1 << CS20);
  8.     sei();
  9. }

W przerwaniu od licznika sprawdzamy stany poszczególnych przekaźników:

  1. ISR (TIMER2_COMPA_vect){
  2.     UpdateCounterAndCloseRelay(&RelayState_TypDef[0]);
  3.     UpdateCounterAndCloseRelay(&RelayState_TypDef[1]);
  4.     UpdateCounterAndCloseRelay(&RelayState_TypDef[2]);
  5.     UpdateCounterAndCloseRelay(&RelayState_TypDef[3]);
  6.     UpdateCounterAndCloseRelay(&RelayState_TypDef[4]);
  7.     UpdateCounterAndCloseRelay(&RelayState_TypDef[5]);
  8.     UpdateCounterAndCloseRelay(&RelayState_TypDef[6]);
  9.     UpdateCounterAndCloseRelay(&RelayState_TypDef[7]);
  10.     UpdateCounterAndCloseRelay(&RelayState_TypDef[8]);
  11.     UpdateCounterAndCloseRelay(&RelayState_TypDef[9]);
  12.     UpdateCounterAndCloseRelay(&RelayState_TypDef[10]);
  13.     UpdateCounterAndCloseRelay(&RelayState_TypDef[11]);
  14.     UpdateCounterAndCloseRelay(&RelayState_TypDef[12]);
  15.     UpdateCounterAndCloseRelay(&RelayState_TypDef[13]);                        
  16. }


Pamięć EEPROM:

Do zapamiętywania danych wykorzystam pamięć wewnętrzną mikrokontrolera Atmega328p.

Każda informacja z ustawieniem przekaźnika zapisana jest na osobnym bajcie danych. Przykładowa struktura pamięci dla pierwszego przekaźnika:

  1. #define EEPROM_RELAY1_WEEKDAYON_OFFSET      0x01
  2. #define EEPROM_RELAY1_WORKTIME_OFFSET       0x02
  3. #define EEPROM_RELAY1_WORKTIMEMM_OFFSET     0x03
  4. #define EEPROM_RELAY1_HOURSTART_OFFSET      0x04   
  5. #define EEPROM_RELAY1_MINUTESTART_OFFSET    0x05   
  6. #define EEPROM_RELAY1_SECONDSTART_OFFSET    0x06   
  7. #define EEPROM_RELAY1_HOURSTOP_OFFSET       0x07   
  8. #define EEPROM_RELAY1_MINUTESTOP_OFFSET     0x08

Odczyt pojedynczego bajtu danych:

  1. uint8_t eepromReadByteNoInter(uint16_t addresToRead)
  2. {
  3.     /* disable interrupts */
  4.     cli();
  5.        
  6.     /* wait till operation are finished */
  7.     while(EECR & (1<<EEPE)) { ; }
  8.    
  9.     /* writes data into EEDR */
  10.     EEAR = addresToRead;
  11.    
  12.     /* start read sequence */
  13.     EECR |= (1<<EERE);
  14.    
  15.     /* wait till operation are finished */
  16.     while(EECR & (1<<EEPE)) { ; }
  17.    
  18.     /* disable interrupts */
  19.     sei();
  20.    
  21.     return EEDR;
  22. }

Odczyt większej ilości danych:

  1. void readMultiBytes(uint16_t startAddress, uint8_t *dataBuffer, uint16_t numberBytesToWrite)
  2. {
  3.     uint16_t i = 0;
  4.    
  5.     while(!=  numberBytesToWrite){
  6.         *(dataBuffer + i) = eepromReadByteNoInter(startAddress);
  7.         ++startAddress;
  8.         ++i;
  9.     }
  10. }

Zapis pojedynczego bajtu danych:

  1. void eepromWriteByteNoInter(uint16_t addresToWrite, uint8_t dataToWrite)
  2. {
  3.     /* disable interrupts */
  4.     cli();
  5.    
  6.     /* wait till operation are finished */
  7.     while(EECR & (1<<EEPE)) { ; }
  8.        
  9.     /* writes data into EEDR and adres into EEAR */
  10.     EEAR = addresToWrite;
  11.     EEDR = dataToWrite;
  12.    
  13.     /* set bits */
  14.     EECR |= (1<<EEMPE);
  15.     EECR |= (1<<EEPE);
  16.    
  17.     /* wait till operation is finished,
  18.         used here to e*/
  19.     while(EECR & (1<<EEPE)) { ; }
  20.    
  21.     sei();
  22. }

Zapis większej ilości danych:

  1. void writeMultiBytes(uint16_t startAddress, uint8_t *dataBuffer, uint16_t numberBytesToWrite)
  2. {
  3.     uint16_t i = 0;
  4.    
  5.     while(!= numberBytesToWrite){
  6.         eepromWriteByteNoInter(startAddress, *dataBuffer);
  7.         ++startAddress;
  8.         ++dataBuffer;                        
  9.         ++i;       
  10.     }  
  11. }

Dane z ustawieniami przekaźników odczytywane są po każdym resecie urządzenia bądź podczas odczytywania informacji programem sterującym. Zapis danych odbywa się po wprowadzeniu ustawień przez program narzędziowy.

Obsługa przycisku:


Przycisk ma za zadanie przechodzenie pomiędzy poszczególnymi ustawieniami przekaźników. Dzięki niemu można przeglądnąć ustawienia jakie zostały wprowadzone do urządzenia bez konieczności podłączenia urządzenia do komputera.

Ze względu na rozmiary wyświetlacza dane dla jednego przekaźnika powinno udać się zawrzeć w 40 znakach (dwie linie wyświetlacza):

X D-xx ON- HH:MM:SS
OffH:HH:MM WT-HH:MM

Gdzie: 
X - numer przekaźnika,
D - dzień załączenia (wartość podawana w postaci zawartości pamięci komórki dzień tygodnia),
ON - godzina załączenia,
OffH - godzina wyłączenia,
WT - czas działania w przypadku ustawienia sekundowego.

Sterowanie przekaźnikami:

Tutaj jest dosyć prosta sprawa. Samo sterowanie odbywa się poprzez zwarcie odpowiedniego pinu do masy.

Dane dla przekaźników przechowywane są w strukturze:

  1. typedef struct relayState_TypedefStruct{
  2.     uint8_t relayNumber;
  3.     //---------------------
  4.     uint8_t weekDayOn;
  5.     //---------------------
  6.     uint8_t workTime_seconds;
  7.     uint8_t workTime_miliseconds;
  8.     //---------------------
  9.     uint8_t hourStart;
  10.     uint8_t minuteStart;
  11.     uint8_t secondStart;
  12.     //---------------------
  13.     uint8_t hourStop;
  14.     uint8_t minuteStop;
  15.     //---------------------
  16.     uint16_t const_MaxCountValueForWorkTime;
  17.     volatile uint16_t onCounterForWorkTimeOn;
  18.     volatile uint8_t startFlagForOnCounterWorkTime;
  19.     volatile uint8_t relayOnOffStatus;
  20.     //---------------------
  21.     void (*OnFunction)(void);
  22.     void (*OffFunction)(void);
  23. }RelayState_Typedef;

Do struktury nie dodawałem danych dotyczących numerów portów oraz pinów, ponieważ każdy z tych elementów jest stały dla tego projektu i nie było sensu rozpisywać tego osobno.

Decyzję o załączeniu/wyłączeniu przekaźnika podejmuję sprawdzając czas RTC i porównując go z danymi konfiguracji odczytanymi z pamięci EEPROM. Do tego celu przetwarzam czas załączenia oraz czas aktualny na sekundy:

  1. uint32_t DS3231_ConvertTimeToSeconds(uint16_t days, uint8_t hours, uint8_t minutes, uint8_t seconds)
  2. {
  3.     return ((hours) * 60 + minutes) * 60 + seconds;
  4. }

Funkcja odpowiedzialna za sprawdzanie czasu załączania wykonywana jest co sekundę gdy nastąpi zmiana sekundy dla zegara RTC. Sprawdzanie nie jest wykonywane częściej ponieważ mogło by występować zbyt częste włączanie/wyłączenie przekaźników gdy licznik znajdował by się na krawędzi zmiany.

  1. static void ConfigRelayToEnableDisable(RelayState_Typedef *RelayPtr)
  2. {
  3.     uint8_t WeekDayBitPosition = ConvertDayOfWeekToEEpromPosValue(RtcTime_TypeStruct.dow);
  4.     uint32_t actualTimeInSeconds = DS3231_ConvertTimeToSeconds(RtcTime_TypeStruct.hour,
  5.                                                 RtcTime_TypeStruct.minutes, RtcTime_TypeStruct.seconds);
  6.    
  7.     if(CHECK_BIT(RelayPtr->weekDayOn, WeekDayBitPosition)) //Sprawdź czy dzień tygodnia ten sam do odpalenia
  8.     {
  9.         uint32_t startRelayValue = DS3231_ConvertTimeToSeconds(RelayPtr->hourStart,
  10.                                             RelayPtr->minuteStart, RelayPtr->secondStart);
  11.         uint32_t endTimeValue = 0;
  12.        
  13.         if(RelayPtr->const_MaxCountValueForWorkTime == 0)   //Przekaźnik ustawiony na czas załączenia
  14.         {
  15.             endTimeValue = DS3231_ConvertTimeToSeconds(RelayPtr->hourStop, RelayPtr->minuteStop, 1);
  16.            
  17.             if((startRelayValue <= actualTimeInSeconds) && (endTimeValue >= actualTimeInSeconds) &&
  18.                     (RelayPtr->relayOnOffStatus == 0))
  19.             {
  20.                 RelayPtr->OnFunction();
  21.                 RelayPtr->relayOnOffStatus = 1;
  22.             }
  23.             else if((RelayPtr->relayOnOffStatus == 1) && (endTimeValue <= actualTimeInSeconds))
  24.             {
  25.                 RelayPtr->OffFunction();
  26.                 RelayPtr->relayOnOffStatus = 0;
  27.             }
  28.         }
  29.         else //Przekaźnik ustawiony na czas załączenia w sekundach i mikrosekundach
  30.         {
  31.             if((startRelayValue == actualTimeInSeconds) && (RelayPtr->relayOnOffStatus == 0))
  32.             {
  33.                 RelayPtr->OnFunction();
  34.                 RelayPtr->relayOnOffStatus = 1;
  35.                 RelayPtr->startFlagForOnCounterWorkTime = 1;
  36.             }
  37.         }
  38.     }
  39. }

W funkcji głównej uruchamiany jest przekaźnik czy to do działania dłuższego, bądź do ustawienia w sekundach i mili sekundach. Dla ustawienia dłuższego czasu włączenie oraz wyłączenie przekaźników następuje w tej funkcji. Odliczanie odbywa się na podstawie obliczeń czasu z zegara RTC. W przypadku załączenia tylko na krótki odcinek czasu funkcja jest odpowiedzialna tylko za włączenie przekaźnika w zadanym czasie. Po jego uruchomieniu czas obliczany zliczanie przerwań wygenerowanych przez timer 2. Wywołanie przerwania następuje co 1ms.

  1. void UpdateCounterAndCloseRelay(RelayState_Typedef *RelayState_TypDefPtr)
  2. {
  3.     if(RelayState_TypDefPtr->startFlagForOnCounterWorkTime == 1)
  4.     {
  5.         if(RelayState_TypDefPtr->onCounterForWorkTimeOn <  RelayState_TypDefPtr->const_MaxCountValueForWorkTime)
  6.         {
  7.             RelayState_TypDefPtr->onCounterForWorkTimeOn++;
  8.         }
  9.        
  10.         if(RelayState_TypDefPtr->onCounterForWorkTimeOn >= RelayState_TypDefPtr->const_MaxCountValueForWorkTime)
  11.         {
  12.             RelayState_TypDefPtr->startFlagForOnCounterWorkTime = 0;
  13.             RelayState_TypDefPtr->onCounterForWorkTimeOn = 0;
  14.             RelayState_TypDefPtr->relayOnOffStatus = 0;
  15.             RelayState_TypDefPtr->OffFunction();
  16.         }
  17.     }
  18. }

Obsługa przycisku:


Przycisk w programie został użyty do wyświetlania zapisanych ustawień dla przekaźników. Naciśnięcie przycisku pozwala na przejście do następnego przekaźnika i odświeżeniu tekstu na dwóch ostatnich liniach wyświetlacza.

Przycisk podłączony jest bezpośrednio pod pin D2:

  1. void InitBtnInterrupt()
  2. {
  3.     DDRD &= ~(1 << DDD2);
  4.     PORTD |= (1 << PORTD2);
  5.    
  6.     EICRA |= (1 << ISC00);
  7.     EIMSK |= (1 << INT0);
  8.     sei();
  9. }

W przerwaniu zwiększamy licznik ustawionych przekaźników. Gdy przekroczymy licznik zdefiniowanych przekaźników to wracamy do początku:

  1. ISR(INT0_vect)
  2. {
  3.     if(btnInterruptEnable == 0)
  4.     {
  5.         btnInterruptEnable = 1;
  6.         relayCounterForDisplaySettings++;
  7.        
  8.         if(relayCounterForDisplaySettings == 15)
  9.         {
  10.             relayCounterForDisplaySettings = 1;
  11.         }
  12.     }
  13. }

Program komunikacyjny:


Program do wgrywania ustawień został przygotowany w języku C#.

Ustawienia ostatnio wgrywane są zapisywane w celu kopii w bibliotece SQLite. Nazwy poszczególnych przekaźników są wykorzystywane tylko dla użytkownika i te dane wprowadzane są tylko do bazy danych. Komunikacja z płytą odbywa się poprzez interfejs UART.



Cały kod jest do pobrania z dysku Google pod tym linkiem. Projekt został zaktualizowany do wersji V1_1 poprawiający błędy obsługi czasu od zegara RTC oraz ustawiania dnia tygodnia.