Jag har skrivit en hel del program till större applikationer och tror för tillfället på följande struktur för att få en dynamisk uppsättning objekt att leka med.
Det kommer bli mycket kod, är inte alltid effektivt men fördelarna är att man får ett väldigt tydligt api att arbeta med samt att man har möjlighet att "byta" drivrutin" i runtime om man så vill. Det är ganska lätt att porta om kod och bygga om moduler t.ex om du kommer på att du vill prata paralellt med den grafiska display eller om du vill kunna koppla in både en text och en grafisk display på samma kretskort.
Själva grund-uppbyggnaden på hur jag löser det hela är följande:
* Öppnar en SPI port och skaffar ett "handtag" av typ "ReadWriteApi.
* Öppnar en Paralell port och skaffar ett "handtag"av typ "ReadWriteApi"
* Öppnar t.ex din grafiska display med hjälp av en init-funktion där jag slänger in SPI porten som inparameter.
* Öppnar text-displayen med hjälp av en init-funktion där jag släner in Paralell-porten som inparameter.
ReadWriteApi:et skulle kunna se ut som följande i en .h fil
Kod: Markera allt
// prototype for common "write" function
typedef uint16 (*comAdrApiWriteFnPtr)( void *pDrv, uint16 addr, uint8 *buffer, uint16 numData );
// prototype for common "read" function
typedef uint16 (*comAdrApiReadFnPtr)( void *pDrv, uint16 addr, uint8 *buffer, uint16 numData);
#define RW_API_MEMBERS \
comAdrApiWriteFnPtr Write; \
comAdrApiReadFnPtr Read;
typedef struct __RWApi
{
RW_API_MEMBERS
} RWApiBaseT;
// help macro for functions to get a more readable code when casting pointers.
#define ComAdrApiWrite( pdrv, addr, buffer, numData ) ((RWApiBaseT *)(pdrv))->Write( pdrv, addr, buffer, numData )
#define ComAdrApiRead( pdrv,addr, buffer, numData ) ((RWApiBaseT *)(pdrv))->Read( pdrv, addr, buffer, numData )
Om du vill använda APIet i till t.ex din SPI port skulle du kunna skriva följande:
spi.h
Kod: Markera allt
typedef struct __SpiCom
{
RW_API_MEMBERS
uint8 spiNbr;
}SpiComT;
spiInit( SpiComT *pThis, uartNbr);
spi.c
Kod: Markera allt
// static gör att funktionen blir "privat" för enbart filen "spi.c" vilket gör att även andra filer kan ha funktioner med samma namn utan att de "krockar"
static uint16 spiRead( void *pDrv, uint16 addr, uint8 *buffer, uint16 numData )
{
// some implementation.
}
static uint16 spiWrite( void *pDrv, uint16 addr, uint8 *buffer, uint16 numData)
{
SpiComT *pThis = pDrv;
switch(pThis->SpiNbr)
{
case SPI_NR_1: // do something
break;
case SPI_NR_2: // do something elese
break
default:
}
}
spiInit( SpiComT *pThis, spiNbr)
{
pThis->spiNbr = spiNbr;
pThis->Write = spiWrite;
pThis->Read = spiRead;
}
Det som händer är att alla "medlemma i "RW_API_MEMBERS" kommer läggas först i din strukten"SpiComT" och därefter läggs alla privata variabler som är specifika för just den drivrutinen. t.ex paralell porten borde inte behöva några parametrar alls medan en UART kanske även vill hålla koll på baud-raten beroende på applikation eller annat konstigt som man kommer på. Den applikation som vill använda SPI porten eller UART porten kör en "cast" på strukten till "RWApiBaseT" där read och write funktionen alltid ligger på samma "adress-offset" från grund-adressen.
De delar som är hårdvaruspecifika och alltid fasta brukar jag lägga i en separat fil som heter t.ex spi_config.h så att man lät kan kopiera och modifiera och är specifik för just det projektet/kretskortet du arbetar med.
Om man bygger vidare på detta sätt skulle man även kunna göra ett "terminalApi" som har hand om text-hantering så att man har sitt api för "printf" eller "moveto" kommandon oberoende av om man kör en grafisk eller text display.
Det finns möjlighet att "ärva" från andra api-er t.ex om man vill göra ett lite mer avancerat API för just grafiska displayen då man vill rita linjer eller t.ex cirklar. Det är viktigt att om man börjar ge sig på detta på olika klasser så måste dessa alltid hamna i rätt ordning i strukten. Alltså att t.ex ett GraphApi alltid har ett TERMINAL_MEMBERS först i strukten innan "GRAPHICAL_MEMBERS" läggs in.
Ett vidare exempel på en main-funktion:
Kod: Markera allt
int main(void)
{
char textString[] = "hejsan hej";
SpiComT spiHandle;
UartComT uartHandle;
Terminal terminalHandle;
spiInit( &spiHandle, SPI_NR_1);
// exempel på hur man skriver en sträng till spi-porten med hjälp av hjälp-macrot.
ComAdrApiWrite( &spiHandle,
0x1234,
textString,
sizeof( textString ) );
uartInit( &uartHandle, 19200, UART_2);
graphDisplayInit( &terminalHandle, &spiHandle);
ApplicationInit( &teminalHandle, uartHandle );
while(1)
{
spiExe(); // hanterar SPI, tex bit-banginig.
uart(Exe(); // hanterar Uart,
graphExe(); // hanterar grafiska displayen
ApplicationExe(); // hanterar applicationen.
}
}
Koden jag skrivit är inte kompilerad och säkert full av fel, men hoppas du hänger med på principen

Det är lätt att virra bort sig i funktions-pekar träsket men får du till det så finns det många fördelar. T.ex så har jag bytt ut de hårdvarunära delarna i mina projekt så att applikationerna kan kompileras och debuggas i t.ex visual-studio innan man kör vidare och testar på riktig hårdvara.
Lycka till!
