Drivers en multi-tasca

Quan fem servir un dispositiu (I2C, SPI, etc.) en un entorn multi-tasca com és FreeRTOS podem tenir el problema de dos o més tasques accedint simultàniament a un mateix recurs (el mòdul hardware del microcontrolador). És per això que cal escriure els drivers per accedir a dispositius d’una manera especial quan treballem en entorns multi-tasca.

Ens podem imaginar què passaria si dues tasques intentessin accedir al bus I2C alhora? Què passaria quan una estigues llegint pel bus i, pel que fos, quedés suspesa i la següent tasca a executar-se comences una transferència d’escriptura pel mateix bus? Segurament es corromprien totes dues transferències o s’estarien fent transferències errònies al sistema.

El que volem evitar és que dues o més tasques facin ús alhora del recurs compartit. Per tant, caldrà establir un control d’accés de manera que fins que una tasca no ha acabat de fer servir el recurs l’altra tasca s’ha d’estar esperant.

Hi ha diferents maneres de fer això, aquí farem servir la més senzilla i estesa, que és escriure un wrapper (embolcall) que protegeixi les funcions del driver i que seran les que farem servir a les nostres tasques. Aquest wrapper contindrà totes les funcions necessàries i les protegirà amb un mutex (veieu Fent servir Mutex en aquest mateix curs). Aquest mutex ens servirà per controlar l’accés a les parts compartides, que seran les pròpies crides al driver del més baix nivell.

Veiem-ho amb un exemple fent un wrapper al driver d’I2C de Silicon Labs que tenim a una aplicació completa (codi al github aquí). En el cas que tinguéssim un sistema on hi hagués més d’un dispositiu Slave connectat el bus, que hi accedeixen dues tasques diferents, podriem trobar-nos amb el problema que comentàvem d’accés múltiple. Per tant, ens cal protegir els accessos amb el mutex tal com hem comentat.

El primer que caldrà és definir una funció d’inicialització del wrapper I2C, que podria ser així:

/****************************/
/*   Fitxer I2C_Wrapper.h   */
/****************************/
typedef struct I2C_Handle_t* I2C_WrapperHandler_t;

I2C_WrapperHandler_t I2C_initialize(void);

bool I2C_WriteRegister(I2C_WrapperHandler_t handlr, uint8_t addr, uint8_t reg, uint8_t data);

bool I2C_ReadRegister(I2C_WrapperHandler_t handlr, uint8_t addr, uint8_t reg, uint8_t *val);



/****************************/
/*   Fitxer I2C_Wrapper.c   */
/****************************/
struct I2C_Handle_t {
   SemaphoreHandle_t mutex;
};

static struct I2C_Handle_t i2c_hdnl = {0};

I2C_WrapperHandler_t wrapper_I2C_Init() {

  if (i2c_hdnl.mutex == NULL) {
    i2c_hdnl.mutex = xSemaphoreCreateMutex();
  
    I2C_Init (...);
  }

  return &i2c_hdnl;
}

La funció tant sols crea un mutex i inicialitza el driver de la biblioteca emlib d’I2C del fabricant. El mutex el retorna com un tipus handler de l’I2C (I2C_WrapperHandler_t) i serà el primer paràmetre que caldrà passar a la resta de crides a les funcions del wrapper.

Així, podem modificar les dues funcions per accedir al bus I2C i que provin d’accedir al mutex, les modificacions podrien quedar així:

bool wrapper_I2C_ReadReg(I2C_WrapperHandler_t handlr, uint8_t address, uint8_t reg, uint8_t *data) {

  xSemaphoreTake (handlr->mutex, portMAX_DELAY);
  ...
  I2C_Transfer( ... );
  ...
  xSemaphoreGive (handlr->mutex);

}

bool wrapper_I2C_WriteReg(I2C_WrapperHandler_t handlr, uint8_t address, uint8_t reg, uint8_t data) {
  xSemaphoreTake (handlr->mutex, portMAX_DELAY);
  ...
  I2C_Transfer ( ... );
  ...
  xSemaphoreGive (handlr->mutex);
}

Així, amb aquests canvis el que tenim ara és que una funció d’accés al bus I2C no es col·lissonarà amb una altra, ja que abans d’intentar accedir-hi haurà d’agafar el mutex. Si no ho aconsegueix, la funció es queda esperant-lo un temps infinit (es bloqueja la funció i la tasca que l’hagi cridada). Quan estarà disponible el mutex? Doncs quan una altra funció d’una altra tasca acabi el seu accés i alliberi el bus.

Cal veure també que el tipus del handler (I2C_WrapperHandler_t) és l’únic tipus que és públic del mòdul i així amaguem l’implementació de l’estructura del handler. En aquest cas el handler és una estructura amb només un mutex, però si més endavant cal afegir-hi més informació no farà que canviï el tipus del handler que fan servir els diferents mòduls.

Aquesta és una bona pràctica per amagar l’implementació de la definició i deixant independent una de l’altra i donant-los la llibertat de canviar l’estructura sense haver de canviar res del codi que fa servir la biblioteca.

També cal veure que el handler és, de fet, un apuntador a una estructura. Això també és una pràctica comuna, ja que és molt més ràpid i eficient passar com a paràmetre un apuntador (que no deixa de ser un tipus de 32 bits) que no pas passar tota l’estructura sencera (que poden ser força camps i molt costosa de passar, copiar, etc.).

Un exemple de canvi a l’estructura del handler podria ser afegir el timeout que volem per provar d’accedir al mutex associat, de manera que a la funció I2C_initialize() se li passés el timeout desitjat i es guardes a l’estructura handler. Els canvis serien els següents (remarcats al codi):

struct I2C_Handle_t {
   SemaphoreHandle_t mutex;
   TickType_t timeout;
};

I2C_WrapperHandler_t I2C_initialize(TickType_t timeout) {
  ...
  i2c_hdnl.mutex = xSemaphoreCreateMutex();
  i2c_hdnl.timeout = timeout;
  ...
}

bool I2C_WriteRegister(I2C_WrapperHandler_t handlr, uint8_t addr, uint8_t reg, uint8_t data) {
  ...
  xSemaphoreTake(handlr->mutex, handlr->timeout);
  ...
}
...

Aquesta canvis només implicarien afegir el paràmetre de timeout a la crida d’inicialització de l’I2C i cap altre canvi per part dels mòduls que facin servir aquesta biblioteca.

Un altre canvi que es podria afegir és en el cas que tinguem més d’un perifèric del mateix tipus (és a dir, 3 SPIs, o 2 I2C, o…) caldria llavors passar quin dels perifèrics volem inicialitzar i fer servir. Per tant, una opció seria passar com a paràmetre a la funció I2C_initialize() quin dels perifèrics I2C es vol inicialitzar. El handler que tornés hauria de ser diferent en funció del perifèric a treballar i quin és s’hauria de guardar a l’estructura oculta.

Deixa un comentari

Fill in your details below or click an icon to log in:

WordPress.com Logo

Esteu comentant fent servir el compte WordPress.com. Log Out /  Canvia )

Google photo

Esteu comentant fent servir el compte Google. Log Out /  Canvia )

Twitter picture

Esteu comentant fent servir el compte Twitter. Log Out /  Canvia )

Facebook photo

Esteu comentant fent servir el compte Facebook. Log Out /  Canvia )

S'està connectant a %s

Aquest lloc utilitza Akismet per reduir els comentaris brossa. Apreneu com es processen les dades dels comentaris.