Rozdział 5. Interfejsy
Rozdział 4. Konfiguracja STM32 | OpenDiag - STM32 CAN Tool | Rozdział 6. Protokoły
To jest rozdział, który będzie później naszym "kontraktem". Dzięki niemu każdy moduł będzie wiedział co może zrobić, ale nie będzie wiedział jak robi to niższa warstwa.
Uważam, że tutaj warto odejść od podejścia "wszystko jest globalne" i zaprojektować API od początku.
Rozdział 5. Interfejsy
5.1 Cel
Interfejsy definiują sposób komunikacji pomiędzy modułami oprogramowania.
Ich zadaniem jest:
- ukrycie implementacji,
- oddzielenie warstw programu,
- umożliwienie wymiany implementacji bez zmian w kodzie wyższych warstw.
Każdy moduł udostępnia wyłącznie własny plik nagłówkowy (*.h).
5.2 Zasada zależności
Każdy moduł zna tylko interfejs modułu niżej.
ELM
↓
CAN API
↓
CAN Driver
↓
HAL
Parser ELM nie może wywoływać funkcji HAL.
5.3 Podstawowe interfejsy
Na początku projektu przewidujemy następujące interfejsy:
ICan
IUsb
IUart
ILogger
IConfig
ITimer
W przyszłości mogą dojść:
IStorage
IEthernet
IBluetooth
IUpdate
5.4 Interfejs CAN
Najważniejszy interfejs projektu.
Powinien umożliwiać:
- inicjalizację portu,
- konfigurację szybkości,
- wysłanie ramki,
- odbiór ramki,
- konfigurację filtrów,
- zmianę trybu pracy.
Parser ELM nie powinien wiedzieć czy pracuje z bxCAN czy FDCAN.
Powinien wywołać jedynie:
CAN_Open()
CAN_Send()
CAN_Read()
CAN_Close()
5.5 Interfejs USB
Warstwa USB udostępnia wyłącznie:
USB_Open()
USB_Write()
USB_Read()
USB_Close()
Bez wiedzy czy jest to CDC, WinUSB czy Ethernet.
5.6 Interfejs UART
Służy wyłącznie do debugowania.
UART_Open()
UART_Write()
UART_Read()
Na początku będzie wykorzystywany przez Logger.
5.7 Logger
Każdy moduł może wysłać komunikat diagnostyczny.
Przykład logiczny:
LOG_INFO()
LOG_WARNING()
LOG_ERROR()
LOG_DEBUG()
Logger decyduje gdzie komunikat zostanie wysłany.
Na początku: ST-Link UART.
Później:
USB
Ethernet
plik
5.8 Konfiguracja
Interfejs konfiguracji umożliwia pobranie ustawień projektu.
Przykład:
Config_GetCanSpeed()
Config_GetDeviceName()
Config_GetUsbMode()
Implementacja może pochodzić:
- z pliku,
- z Flash,
- z EEPROM.
5.9 Timer
Nie chcemy używać HAL_Delay() poza BSP.
Dlatego powstaje interfejs:
Timer_GetTick()
Timer_Delay()
Timer_Start()
Timer_Stop()
Dzięki temu później można przejść na FreeRTOS bez zmian wyższych warstw.
5.10 Kolejki
Interfejsy nie powinny przesyłać pojedynczych bajtów.
Komunikacja odbywa się przez:
USB RX Queue
USB TX Queue
CAN RX Queue
CAN TX Queue
Każdy moduł odbiera kompletne komunikaty.
5.11 Struktury danych
Od początku definiujemy wspólne typy.
Przykład:
CanFrame
UsbPacket
LogMessage
Zamiast przekazywać tablice bajtów.
5.12 Obsługa błędów
Każdy interfejs zwraca wspólny typ wyniku.
Np.
OK
TIMEOUT
BUSY
ERROR
INVALID_PARAMETER
Dzięki temu wszystkie moduły zachowują się w taki sam sposób.
5.13 Wersjonowanie interfejsów
Każdy interfejs powinien umożliwiać sprawdzenie wersji.
Przyda się przy późniejszym podziale projektu na biblioteki.
Co chciałbym zmienić względem wcześniejszych propozycji
Po przemyśleniu zrezygnowałbym z interfejsów typu ICan, IUsb w stylu obiektowym.
W projekcie pisanym w C są one mało naturalne i często prowadzą do niepotrzebnych wskaźników do funkcji.
Zamiast tego proponuję prostsze podejście:
Core/
can/
can.h
can.c
usb/
usb.h
usb.c
logger/
logger.h
logger.c
Każdy moduł eksportuje własne API, np.:
can_init();
can_send();
can_receive();
usb_init();
usb_write();
logger_info();
logger_error();
Moja propozycja organizacyjna
Do tej pory opisaliśmy projekt "od góry". Od następnego rozdziału proponuję zejść poziom niżej i zacząć definiować konkretne moduły (CAN, USB, ELM, parser AT itd.) wraz z ich odpowiedzialnością i publicznym API. Dzięki temu, gdy rozpoczniemy implementację, każdy plik .c i .h będzie już miał jasno określoną rolę.