środa, 20 grudnia 2023

STM32H7 - Interfejs w C na przykładzie dostępu do zewnętrznej pamięci flash

W tym poście chciałbym opisać sposób implementacji interfejsu w języku C na przykładzie funkcji obsługującej pamięć flash. 


Dzięki interfejsom można w znacznie łatwiejszy sposób implementować testy jednostkowe. Ponieważ wystarczy przygotować funkcję zastępującą ich obsługę przez mocki, które mogą zwracać wartości operacji. Bez konieczności wykonywania implementacji dostępu do pamięci flash. Co w przypadku testów jednostkowych nie ma znaczenia. Ponieważ zależy nam na przetestowaniu logiki działania programu a nie obsługi zewnętrznych peryferiów. 

Dodatkowo gdy potrzebujemy zaimplementować różne sterowniki, dla różnych kości to wystarczy dzięki interfejsowi nie będzie potrzeba modyfikować całego kodu, tylko zmienić przekazywane funkcje. Należy też zwrócić uwagę na oddzielenie odpowiedzialności części kodu od reszty. Dzięki interfejsom wykonujemy wyższą warstwę kodu, przez co uzyskujemy lepszą modularność i zrozumienie kodu.

Do interfejsu przenoszę następujące funkcje:

  1. uint8_t AT45DB_EraseChip(uint32_t CSPin);
  2. void AT45DB_SectorErase(uint8_t sector, uint32_t CSPin);
  3. void AT45DB_ErasePage(AT45DB_Size_typedef *ptr, uint16_t page);
  4. uint8_t AT45DB_WritePage(AT45DB_Size_typedef *ptr, uint8_t *dataToSave, uint16_t dataToSaveLength, uint16_t pageNumber, uint8_t bufferNumber);
  5. uint8_t AT45DB_ReadPage(AT45DB_Size_typedef *ptr, uint8_t* dataToRead, uint16_t dataToReadLength, uint16_t pageNumber);

Pomijam tu funkcję inicjalizującą, która jest wywoływana tylko przy uruchamianiu systemu. 

Następnie tworzę strukturę, która będzie zawierała wskaźniki na funkcję. Zgrupowanie wszystkich funkcji ułatwi ich późniejsze wywoływanie. Całą strukturę umieszczam w osobnym pliku aby łatwiej można było wyciąć zależności z głównymi funkcjami.

  1. #ifndef INC_FLASH_FLASH_I_C_
  2. #define INC_FLASH_FLASH_I_C_
  3.  
  4. #include "flash/flash_control.h"
  5.  
  6. typedef struct
  7. {
  8.     uint8_t (*AT_EraseChip)(uint32_t CSPin);
  9.     void (*AT_SectorErase)(uint8_t sector, uint32_t CSPin);
  10.     void (*AT_ErasePage)(AT45DB_Size_typedef *ptr, uint16_t page);
  11.     uint8_t (*AT_WritePage)(AT45DB_Size_typedef *ptr, uint8_t *dataToSave, uint16_t dataToSaveLength, uint16_t pageNumber, uint8_t bufferNumber);
  12.     uint8_t (*AT_ReadPage)(AT45DB_Size_typedef *ptr, uint8_t *dataToRead, uint16_t dataToReadLength, uint16_t pageNumber);
  13. } Flash_Driver_Interface;
  14.  
  15. extern const Flash_Driver_Interface *flash_interface_get(void);
  16.  
  17. #endif /* INC_FLASH_FLASH_I_C_ */

W głównym pliku umieszczam jedynie inicjalizację struktury, wraz z funkcją przekazującą wskaźnik do niej. 

  1. #include "flash/flash_i.h"
  2.  
  3. static const Flash_Driver_Interface flash_driver_interface =
  4. {
  5.     .AT_EraseChip = AT45DB_EraseChip,
  6.     .AT_SectorErase = AT45DB_SectorErase,
  7.     .AT_ErasePage = AT45DB_ErasePage,
  8.     .AT_WritePage = AT45DB_WritePage,
  9.     .AT_ReadPage = AT45DB_ReadPage,
  10. };
  11.  
  12. const Flash_Driver_Interface *flash_interface_get(void)
  13. {
  14.     return &flash_driver_interface;
  15. }

Proces wywołania wygląda następująco:

  1. flash_interface_get()->AT_ReadPage(&AT45DB_Size_t[FLASH_MEMMORY_WITH_PERMISSION], &readBuffer[0], 528, readPageNumber);