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ę.