środa, 10 kwietnia 2024

STM32H7 - Logi z pracy układu

W tym poście chciałbym przedstawić sposób logowania danych z pracy systemu na mikrokontrolerze STM32H7. Sposób ten w identyczny sposób będzie działał na innych mikrokontrolerach.



Mechanizm testowałem w mikrokontrolerze STM32H725 z systemem FreeRtos. 

Do zapisu danych wykorzystam zewnętrzną pamięć flash.

W celu wykonania tych operacji należy przygotować kilka rzeczy. 

Na samym początku tworzymy tablicę przechowującą wiadomości jakie chcemy aby zostały przesłane 

  1. static const char* const STRING_PL[] = {
  2.       "",
  3.       "Uruchomienie aplikacji Kontrolera",
  4.       "Ustawiono czas",
  5.       "Wejście do ustawień w aplikacji",
  6.       "Wyjście z ustawień w aplikacji",
  7.       "Zmiana stanu wejscia: Kn=1; We=%u; Fn=%u; St=%u",
  8.       "Zmiana stanu wyjscia: Kn=1; Wy=%u; Fn=%u; St=%u",
  9.       "Alarm! Kn=1",
  10.       "Pozar! Kn=1",
  11.       "Awaria zasilania!",
  12.       "Logowanie administratora systemu.",
  13.       "Wylogowanie administratora systemu.",
  14.       "Wprowadzono zmiane loginu i hasla do WWW",
  15.       "Wprowadzono modyfykacje parametrow sieciowych kontrolera",
  16.       "Wprowadzono nową wartość licencji",
  17. //... 
  18.       ""
  19. };

Mając powyższą tablicę w pamięci nie musimy zapisywać pełnych ciągów tylko przypisujemy indeks do wiadomości, wraz z parametrami jakie chcemy umieścić w wiadomości. 

Kolejnym elementem jest przygotowanie typu wyliczeniowego, który będzie się odnosił do odpowiedniego ciągu znaków:

  1. typedef enum {
  2.    LOG_JEZYK_STRING_ID_FIRST = 0,
  3.    // identyfikatory dla wartości tekstowych dla parametru "zdarzenie - treść":
  4.    LOG_JEZYK_STRING_ID_ZDARZENIE_APPLICATION_START,
  5.    LOG_JEZYK_STRING_ID_ZDARZENIE_CLOCK_SET,
  6.    LOG_JEZYK_STRING_ID_ZDARZENIE_SETTINGS_ENTER,
  7.    LOG_JEZYK_STRING_ID_ZDARZENIE_SETTINGS_EXIT,
  8.    LOG_JEZYK_STRING_ID_ZDARZENIE_INPUT_STATE,
  9.    LOG_JEZYK_STRING_ID_ZDARZENIE_OUTPUT_STATE,
  10.    LOG_JEZYK_STRING_ID_ZDARZENIE_ALARM,
  11.    LOG_JEZYK_STRING_ID_ZDARZENIE_POZAR,
  12.    LOG_JEZYK_STRING_ID_ZDARZENIE_ZASILANIE,
  13.   LOG_JEZYK_STRING_ID_ZDARZENIE_ADMIN_LOGIN,
  14.    LOG_JEZYK_STRING_ID_ZDARZENIE_ADMIN_LOGOUT,
  15.    LOG_JEZYK_STRING_ID_ZMIANA_LOGINU_I_HASLA_WWW,
  16.    LOG_JEZYK_STRING_ID_MODYFIKACJA_PARAMETROW_SIECIOWYCH,
  17.    LOG_JEZYK_STRING_ID_WPROWADZONO_NOWA_LICENCJE,
  18.    LOG_JEZYK_STRING_ID_LAST
  19. } Log_StringId_Enum;

Teraz przygotowuję strukturę przechowującą dane jakie będą zapisane w pamięci:

  1. typedef enum {
  2.    LOG_DEBUG = 1,  
  3.    LOG_SYSTEM = 2,  
  4.    LOG_OSTRZEZ = 3  
  5. } LogType_Enum;

  1. typedef struct {
  2.    LogType_Enum Typ;
  3.    LogTime_t Czas;
  4.    Log_StringId_Enum KodZdarzenia;
  5.    uint8_t NumerWejscia;
  6.    uint8_t NumerWyjscia;
  7.    uint8_t FunkcjaWeWy;
  8.    uint8_t StanWeWy;
  9. } LogMsg_t;

W programie mam jeden wątek, który zajmuje się tylko obsługą pamięci flash. Podłączone jest kilka kości flash, komunikujących się przez jeden interfejs SPI. Pozwala to na uniknięcie ewentualnych problemów z nakładaniem się zapisów/odczytów z różnych wątków. 

Gdy pojawi się konieczność zapisania jakichś danych do logów, to wywołuje odpowiednią funkcję zapisującą informację do bufora kołowego, która uruchamia wątek zapisu danych do flash:

  1. void LOG_ZapisZmianaStanuWejscia(const uint8_t numerWejscia, const uint8_t Funckja, const uint8_t Stan)
  2. {
  3.     /* "Zmiana stanu wejscia: Kn=1; We=%u; Fn=%u; St=%u", */
  4.  
  5.     LogMsg_t log;
  6.     Log_ClearLogTypedef(&log);
  7.  
  8.     RTC_TimeTypeDef rtc_time;
  9.     RTC_DateTypeDef rtc_date;
  10.  
  11.     HAL_RTC_GetTime(&hrtc, &rtc_time, RTC_FORMAT_BIN);
  12.     HAL_RTC_GetDate(&hrtc, &rtc_date, RTC_FORMAT_BIN);
  13.  
  14.     log.Typ = LOG_SYSTEM;
  15.     log.Czas.Godzina = rtc_time.Hours;
  16.     log.Czas.Minuta = rtc_time.Minutes;
  17.     log.Czas.Sekunda = rtc_time.Seconds;
  18.     log.Czas.Dzien = rtc_date.Date;
  19.     log.Czas.Miesiac = rtc_date.Month;
  20.     log.Czas.Rok = rtc_date.Year;
  21.     log.KodZdarzenia = LOG_JEZYK_STRING_ID_ZDARZENIE_INPUT_STATE;
  22.     log.FunkcjaWeWy = Funckja;
  23.     log.NumerWejscia = numerWejscia;
  24. log.NumerWyjscia = 0;
  25.     log.StanWeWy = Stan;
  26.  
  27.     LogCircBuffer_PutDataToBuff(&log);
  28. }

  1. int8_t LogCircBuffer_PutDataToBuff(LogMsg_t *logPtr)
  2. {
  3.     if(LogCircBuff.lock != 0) { return -1; }
  4.  
  5.     uint8_t head_temp = LogCircBuff.head + 1;
  6.  
  7.     if (head_temp == Log_CIRC_BUFFER_SIZE ){ head_temp = 0; }
  8.     if (head_temp == LogCircBuff.tail) { return -1; }
  9.  
  10.     LogCircBuff.lock = 1;
  11.  
  12.     LogStruct_Array[head_temp].Typ = zdarzeniaPtr->Typ;
  13.     LogStruct_Array[head_temp].Czas.Dzien = zdarzeniaPtr->Czas.Dzien;
  14.     LogStruct_Array[head_temp].Czas.Godzina = zdarzeniaPtr->Czas.Godzina;
  15.     LogStruct_Array[head_temp].Czas.Miesiac = zdarzeniaPtr->Czas.Miesiac;
  16.     LogStruct_Array[head_temp].Czas.Minuta = zdarzeniaPtr->Czas.Minuta;
  17.     LogStruct_Array[head_temp].Czas.Rok = zdarzeniaPtr->Czas.Rok;
  18.     LogStruct_Array[head_temp].Czas.Sekunda = zdarzeniaPtr->Czas.Sekunda;
  19.     LogStruct_Array[head_temp].FunkcjaWeWy = zdarzeniaPtr->FunkcjaWeWy;
  20.     LogStruct_Array[head_temp].KodZdarzenia = zdarzeniaPtr->KodZdarzenia;
  21.     LogStruct_Array[head_temp].NumerWejscia = zdarzeniaPtr->NumerWejscia;
  22.     LogStruct_Array[head_temp].NumerWyjscia = zdarzeniaPtr->NumerWyjscia;
  23.     LogStruct_Array[head_temp].StanWeWy = zdarzeniaPtr->StanWeWy;
  24.  
  25.     ZdarzeniaCircBuff.head = head_temp;
  26.     ZdarzeniaCircBuff.lock = 0;
  27.  
  28.     return 0;
  29. }

Po dodaniu danych do bufora wykonuje ich zapis do pamięci flash:

  1. uint8_t Log_SaveDataInMemmory(LogMsg_t* logPtr)
  2. {
  3.     uint8_t buffer[LOG_FLASH_SIZE] = {0x00};
  4.     uint8_t opStatus = 0xFF;
  5.  
  6.     LogMsg_t log;
  7.     Log_ClearData(&log);
  8.  
  9.     log.Typ = logPtr->Typ;
  10.     log.Czas.Godzina = logPtr->Czas.Godzina;
  11.     log.Czas.Minuta = logPtr->Czas.Minuta;
  12.     log.Czas.Sekunda = logPtr->Czas.Sekunda;
  13.     log.Czas.Dzien = logPtr->Czas.Dzien;
  14.     log.Czas.Miesiac = logPtr->Czas.Miesiac;
  15.     log.Czas.Rok = logPtr->Czas.Rok;
  16.     log.KodZdarzenia = log.KodZdarzenia
  17.     log.FunkcjaWeWy = log.FunkcjaWeWy;
  18.     log.NumerWejscia = log.NumerWejscia;
  19.     log.NumerWyjscia =  log.NumerWyjscia;
  20.     log.StanWeWy = log.StanWeWy;
  21.  
  22.     Log_ConvertIntoBuffer(&buffer[0], &log);
  23.     opStatus = Log_SaveLogInFlash(&buffer[0], &LogFlashPointerts);
  24.     return opStatus;
  25. }

Następnie odczytuje według pozycji wskaźników ostatnie nową wiadomość do przesłania.
Po odczycie wykonuje jej konwersje do postaci tekstowej:

  1. uint8_t PutDataIntoLogTxt(char* buffer, const unsigned char* format, LogMsg_t *logPtr)
  2. {
  3.     uint8_t length = 0;

  4.     switch(zdarzeniaPtr->KodZdarzenia)
  5.     {
  6.     case LOG_JEZYK_STRING_ID_FIRST:
  7.         /* NOTHING */
  8.         break;
  9.     case LOG_JEZYK_STRING_ID_ZDARZENIE_APPLICATION_START:
  10.         length = sprintf(buffer, (const char*)format);
  11.         break;
  12.     case LOG_JEZYK_STRING_ID_ZDARZENIE_CLOCK_SET:
  13.         length = sprintf(buffer, (const char*)format);
  14.         /* NOTHING */
  15.         break;
  16.     case LOG_JEZYK_STRING_ID_ZDARZENIE_SETTINGS_ENTER:
  17.         length = sprintf(buffer, (const char*)format);
  18.         /* NOTHING */
  19.         break;
  20.     case LOG_JEZYK_STRING_ID_ZDARZENIE_SETTINGS_EXIT:
  21.         length = sprintf(buffer, (const char*)format);
  22.         /* NOTHING */
  23.         break;
  24.     case LOG_JEZYK_STRING_ID_ZDARZENIE_INPUT_STATE:
  25.         //"Zmiana stanu wejscia: Kn=1; We=%u; Fn=%u; St=%u",
  26.         length = sprintf(buffer, (const char*)format, zdarzeniaPtr->NumerWejscia, zdarzeniaPtr->FunkcjaWeWy, zdarzeniaPtr->StanWeWy);
  27.         break;
  28.     case LOG_JEZYK_STRING_ID_ZDARZENIE_OUTPUT_STATE:
  29.         //"Zmiana stanu wyjscia: Kn=1; Wy=%u; Fn=%u; St=%u",
  30.         length = sprintf(buffer, (const char*)format, zdarzeniaPtr->NumerWyjscia, zdarzeniaPtr->FunkcjaWeWy, zdarzeniaPtr->StanWeWy);
  31.         break;
  32.     case LOG_JEZYK_STRING_ID_ZDARZENIE_ALARM:
  33.         //"Alarm! Kn=1",
  34.         length = sprintf(buffer, (const char*)format);
  35.         break;
  36.     case LOG_JEZYK_STRING_ID_ZDARZENIE_POZAR:
  37.         //"Pozar! Kn=1",
  38.         length = sprintf(buffer, (const char*)format);
  39.         break;
  40.     case LOG_JEZYK_STRING_ID_ZDARZENIE_ZASILANIE:
  41.         //"Awaria zasilania!",
  42.         length = sprintf(buffer, (const char*)format);
  43.         break;
  44.     case LOG_JEZYK_STRING_ID_ZDARZENIE_ADMIN_LOGIN:
  45.         //"Logowanie administratora systemu.",
  46.         length = sprintf(buffer, (const char*)format);
  47.         break;
  48.     case LOG_JEZYK_STRING_ID_ZDARZENIE_ADMIN_LOGOUT:
  49.         //"Wylogowanie administratora systemu.",
  50.         length = sprintf(buffer, (const char*)format);
  51.         break;
  52.     case LOG_JEZYK_STRING_ID_ZMIANA_LOGINU_I_HASLA_WWW:
  53.         //"Wprowadzono zmiane loginu i hasla do WWW",
  54.         length = sprintf(buffer, (const char*)format);
  55.         break;
  56.     case LOG_JEZYK_STRING_ID_MODYFIKACJA_PARAMETROW_SIECIOWYCH:
  57.         //"Wprowadzono modyfykacje parametrow sieciowych kontrolera",
  58.         length = sprintf(buffer, (const char*)format);
  59.         break;
  60.     case LOG_JEZYK_STRING_ID_WPROWADZONO_NOWA_LICENCJE:
  61.         //"Wprowadzono nową wartość licencji",
  62.         length = sprintf(buffer, (const char*)format);
  63.         break;
  64.     default:
  65.         break;
  66.     }
  67.  
  68.     return length;
  69. }
 
Tak przygotowany string może już zostać przesłany, czy to przez SSL czy np. z wykorzystanie interfejsu UART.