Migrando de Arduino UNO a ESP8266: excepciones

Estoy portando un programa que tengo funcinando en varios Arduino UNO a un ESP8266 y me encuentro que no funciona, provoca una excepción 28 y un volcado de pila en cuanto introduzco datos…

Excepciones del ESP8266: https://arduino-esp8266.readthedocs.io/en/latest/exception_causes.html

Después de mucho revisar compruebo que el programa para Arduino no lo tengo demasiado bien escrito, pero no causa problemas. Eso es debido a que no tiene MMU (unidad de manejo de memoria). El ATMEGA328P es un micro muy sencillo y tiene toda la memoria expuesta. Si tengo un “puntero loco” siempre puedo leer el o escribir en la dirección de memoria a donde apunta.

Sin embargo, el ESP8266, tiene un micro Tensilica Xtensa, bastante más complejo, con MMU https://en.wikipedia.org/wiki/Memory_management_unit y excepciones. Si un puntero apunta a la zona de memoria incorrecta es muy facil que se genere una excepción al intentar leer o escribir datos.

El código mal programado es este:

char *SavePtr;

char * MySerialbuffer;

char *Token = strtok_r(MySerialBuffer, " ", &SavePtr);
if (strcmp(Token, "Read") == 0) // Primera palabra clave
{
Token = strtok_r(NULL, " ", &SavePtr);
while (*SavePtr == ' ') SavePtr++; // Elimina espacios extra
  if (strcmp(Token, "Data") == 0) // Segunda palabra clave
  {
  int Src = atoi(strtok_r(NULL, Space, &SavePtr)); // Primer parametro
  int Len = atoi(strtok_r(NULL, Space, &SavePtr)); // Segundo paametro
// etc...
}
}

En MySerialBuffer estan los comandos que recibo por el puero serie. El trozo de código anterior detecta si he escrito “Read Data 12 23” donde 12 y 23 serían dos parámetros numéricos… Pero ese código es un despropósito.

La función “Token = strtok_r(caden ade entrada, delimitadores, saveptr)” https://linux.die.net/man/3/strtok_r devolverá un puntero nulo (NULL) cuando no encuentre una cadena que terminada por los delimitadores. En un micro sin MMU eso no tiene trascendencia, excepto porque cuando leamos datos de *Token estaremos leyendo de la zona de memoria 0 que puede contener cualquier cosa. En mi caso como son comparaciones con palabras clave es casi imposible que en esa zona de memoria haya algo parecido a lo que busco, la comparación falla y ya esta.

Pero en el ESP8266, con el micro Xtensa y su MMU, leer datos de un puntero nulo genera una excepción y el programa termina en una detención halt.

La forma correcta de programar es ir comprobando que el puntero devuelto por strtok_r no es nulo:

char *Token = strtok_r(MySerialBuffer, " ", &SavePtr);  
if (Token==NULL) return 0; // No continuar
if (strcmp(Token, "Read") == 0) // Primera palabra clave
{
Token = strtok_r(NULL, " ", &SavePtr);
if (Token==NULL) return 0; // No continuar
// Elimina espacios extra, con seguridad
if (SavePtr!=NULL) while (*SavePtr == ' ') SavePtr++;
  if (strcmp(Token, "Data") == 0) // Segunda palabra clave
  {
Token = strtok_r(NULL, Space, &SavePtr);
  if (Token != NULL) Src = atoi(Token);
  else return 0; // Falta un parametro
  Token = strtok_r(NULL, Space, &SavePtr);
  if (Token != NULL) Len = atoi(Token);
  else return 0; // Falta un el otro parametro
// etc...
}
}

Es lo que pasa cuando uno se acostumbra demasiado a programar micros ulrasencillos, luego hay que volver a la realidad de las MMU, la alineaciones a página etc