Programando sistemas embebidos II tratamiento

Antes de la lectura de este post, recomiendo que leáis el post anterior: Programando sistemas embebidos I variables. En este post contaba, a parte de algunos criterios a la hora de definir las variables que vayamos a utilizar para realizar nuestro código, que estos consejos son tan válidos para sistemas embebidos con Emb. OS, RTOs o en caso de realizar la programación directamente del firmware mediante el compilador y el entorno de trabajo facilitado o recomendado por el fabricante del microprocesador.

divisiones y módulos

Algunos de estos consejos son obvios, pero el problema es el mismo que comentaba en el post anterior relacionado con la programación de sistemas embebidos: Las prisas o el hecho de ir probando código concreto puede hacer que se dejen grandes barbaridades en el código aunque reconozco que este es un caso que ni por prisas ni con ninguna otra excusa justifica el uso del operador módulo (%) ya sea en programación de embebidos o de software para PC.

Volvemos con las comparaciones, la gracia de las comparaciones es utilizar las instrucciones que puede ejecutar el micro de la forma más directa posible, en este sentido utilizar la división o el módulo para comprobar que se ha llegado a un valor de contaje es, como ya decía, una auténtica barbaridad dado que así como todos los micros tienen instrucciones para la comparación, la operación módulo en la condición de un if no sólo requiere el tiempo de ejecución de la propia comparación sino también el del propio cálculo.

NO?

if(ucAuxCounter % COUNT_FINISH_VALUE == 0)
   //Se cumple la condición
else
   //No se cumple la condición

SI?

if( ucAuxCounter >= COUNT_FINISH_VALUE)
   //Se cumple la condición
else
   //No se cumple la condición

Como en todo, existen excepciones y en este caso son las divisiones entre valores potencia de dos, pues la división entre un valor de este tipo produce única y exclusivamente un shift de los bits de la variable hacia la derecha dado que para realizar esta operación no es necesario ningún cambio de base pues el bus de datos del micro trabaja en binario. Tened en cuenta que si la división es entre dos variables por mucho que sepamos que el valor del dividendo es potencia de dos el compilador lo interpretará como una división convencional, para poder aprovechar el shiftado en la división la manera de avisar al procesador es que dicho dividendo sea una constante potencia de dos. En este último caso que diferencia existe? Lo de siempre, si aplicamos una división el programador que llegue después sabrá que lo que hacemos es una división entre 2,4,8,16,… Mientras que si hacemos directamente el shiftado puede llevar a confusión con la aplicación de algún tipo de máscara (aunque si el programador es un poco hábil el tiempo para ver la diferencia debería ser de segundos).

switch e if-else

Cuantas veces habré encontrado códigos con líneas y líneas de bloques if, else if, else if, else if,… Cuando la comparación por la que regimos el funcionamiento de la aplicación depende de dos valores o condiciones con un if y un else nos bastamos pues el micro sólo realizará una comparación, pero cuando se trata de muchos valores posibles que cuesta utilizar un switch? Ya no sólo desde un punto de vista de orden y estética del código, sino también de tiempo perdido en comparaciones.

Supongamos que tenemos una variable que según su valor nos determina cual es la siguiente acción secuencial a realizar. Si nos basamos en bloques if/else if y se cumple la condición del primer bloque, la ejecución del contenido de ese bloque se realizará consecutivamente sin retraso. Pero si el valor cumple con la última de las condiciones? El micro habrá perdido tiempo en realizar todas y cada una de las comparaciones anteriores retrasando así la ejecución del bloque siendo el retraso directamente proporcional al número de bloques o comparaciones; este sería el peor caso que podría pasar, pero puede pasar.  Así que para una evaluación de muchos valores el switch es la mejor opción ya que el retraso entre los diferentes casos será constante (recordad siempre de tener en cuenta el caso default para no tener sorpresas).

bucles

Rizando el rizo, los bucles se usan a “mansalva” en programación tanto software como firmware. Puesto a numerar posibles maneras de optimizar el código y algo en lo que a priori no se tiene porqué caer: los bucles en los que se decrementa la variable de contaje ocupan menos instrucciones que los bucles en los que esta se incrementa. El motivo es sencillo, para realizar la comparación de la variable de contaje, en el segundo caso se realiza con 0, algo que a nivel de ejecución incorporan todos los microprocesadores en una sola instrucción: la comparación con 0 y salto; por el contrario si la comparación no es con 0 el proceso consta de dos instrucciones, la que compara el valor de la variable con la constante de valor de contaje máximo y posteriormente la instrucción de comparación con 0 del registro de flags y salto.

Os pego aquí un ejemplo que he encontrado por ahí:

CÓDIGO?

void test_incrementing_for_loop( void ){
   unsigned int i;
   for(i=0; i<100;i++){
      foo(i);
   }
}
void test_decrementing_for_loop( void ){
   unsigned int i;
   for(i=100; i; i--){
      foo(i);
   }
}

RESULTADO?

00000000 < test_decrementing_for_loop >:
   0:   e92d4010        push    {r4, lr}
   4:   e3a00064        mov     r0, #100        ; 0x64
   8:   ebfffffe        bl      0
   c:   e3a04063        mov     r4, #99 ; 0x63
  10:   e1a00004        mov     r0, r4
  14:   ebfffffe        bl      0
  18:   e2544001        subs    r4, r4, #1      ; 0x1
  1c:   1afffffb        bne     10
  20:   e8bd8010        pop     {r4, pc}

00000024 < test_incrementing_for_loop >:
  24:   e92d4010        push    {r4, lr}
  28:   e3a00000        mov     r0, #0  ; 0x0
  2c:   ebfffffe        bl      0
  30:   e3a04001        mov     r4, #1  ; 0x1
  34:   e1a00004        mov     r0, r4
  38:   e2844001        add     r4, r4, #1      ; 0x1
  3c:   ebfffffe        bl      0
  40:   e3540064        cmp     r4, #100        ; 0x64
  44:   1afffffa        bne     34
  48:   e8bd8010        pop     {r4, pc}

valor de retorno

Una manera de saber si una función se ha realizado de forma correcta o no es hacer que esta devuelva un valor correspondiente a un resultado satisfactorio y otro para resultado no satisfactorio (en funciones asíncronas también se devuelve muchas veces una tercera posibilidad correspondiente a que la ejecución aún está por terminar). Pues bien, supongamos que hacemos una función que cuando termina de forma satisfactoria devuelve 0 y -1 en caso contrario. Tan sencillo como utilizar la instrucción nativa del micro de comparación con 0:

if(MiFuncion()==0){
   //Acción resultante de un resultado OK de MiFuncion();
}else{
   //Acción resultante de un resultado KO de MiFuncion();
}

evaluación aplazada

Tal y como el compilador evalúa el código nos podemos permitir hacer el post-incremento:

uiVariableDestino = uiArrayValores[ucCounter++]

En el caso anterior uiVariableDestino valdrá uiArrayValores[ucCounter] y posteriormente ucCounter incrementará su valor valiendo ucCounter+1. La evaluación aplazada o diferida (traducción literal de Lazy Evaluation) consiste en aprovechar esta evaluación del código de izquierda a derecha en una sentencia if-else donde exista más de una condición. Aprovechando este orden en la evaluación del código, deberíamos poner la condición con menos carga del procesador como primera, de esta manera sólo se evaluará la condición con más carga sólo si la primera y más simple se cumple. Lo bueno es que esto nos sirve tanto para las condiciones relacionadas de forma lógica con los operadores && (AND) y || (OR).

if( (CONDICION_SENCILLA) || (CONDICION_COMPLEJA) ) //Sólo evaluará la compleja si la sencilla no se cumple.
if( (CONDICION_SENCILLA) && (CONDICION_COMPLEJA) )//Sólo evaluará la compleja si la primera no se cumple.
Tagged with: , , , , , , ,
Posted in Blog Técnico, Hardware Firmware, Sistemas Embebidos

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

*


*

enlaces patrocinados