Buscar en Google

martes, 5 de abril de 2011

Incrementos y decrementos


Post-incrementos y post-decrementos


Volviendo al caso del contador, hay una forma aún más corta de escribirlo, y es la siguiente:

char A;

paso 1:   A = 1;

paso 2:   A++;

paso 3: Volver al paso 2

Vemos ahora que se ha cambiado la instrucción del paso 2 por A++. Esto significa que es equivalente hacer:

A = A + 1
A += 1
A++

A los fines prácticos. Es exactamente lo mismo elegir un caso u otro para nuestro programa. Sin embargo, el uso del operador post-incremento que es así como se llama cuando colocamos la instrucción A++ tiene otras ventajas.

La más visible de todas, es que en los microcontroladores existe normalmente una instrucción que se llama incremento. Por lo tanto, el hecho de colocar una línea tal como A++ significa para el microcontrolador una sola instrucción de código. Diferente es cuando se hace A+=1 o A = A +1. Ya que normalmente, el compilador toma el valor de A y le suma el valor 1. Para después copiar el resultado en la variable A.

Pero hay otra ventaja de utilizar post-incrementos. Podemos realizar en una misma línea algo como ésto:

B = 10 + A++;

¿Que es esto que les acabo de poner? A simple vista no hay ninguna ventaja, pero veamos lo que hace el compilador. Supongamos que la variable A cuando pasa el micro por la  línea que les acabo de mostrar, vale por ejemplo 10. Entonces, el compilador toma el valor de A y le suma 10. El resultado de la suma será 20 que será guardado en la variable B. Sin embargo, inmediatamente después de haber leído el compilador el valor de la variable A le hace un post-incremento. Esto es, al finalizar la ejecución de la línea, la variable A terminará con el valor 11.

Si nos gustan las cosas complicadas también podemos escribir algo totalmente válido en C:

B = 10 + A++ + A;

¿Se ve muy raro, no es cierto? Sin embargo no es inválido. El C lo permite. Analicemos que hace. Supongamos que de nuevo la variable A vale 10 al ingresar en la línea. Entonces el compilador resuelve el primer término de la suma, ésto es, en una variable temporal hará 10 + 10= 20. Pero inmediatamente de haber tomado el valor de la variable A por primera vez, la incrementa, por lo tanto cuando evalúa el segundo término, la variable A vale ahora 11. Entonces, la variable temporal que iba a obtener el resultado evaluará en un segundo paso: 20 + 11 = 31. O sea, la variable A fue modificada en el medio de la evaluación de la operación aritmética y a su vez el resultado dependió de esa modificación.





De la misma manera que hicimos A++, podemos hacer A--.

A = A - 1
A -= 1
A--

Donde ahora, si ponemos una línea de código que dice A-- significa que la variable A se debe decrementar en un valor. O lo que es lo mismo, restar 1 al valor de la variable A y almacenar el resultado en la misma variable. De igual manera que con el post-incremento, tiene las mismas características. Si bien, no todos los microcontroladores poseen una instrucción decremento, la mayoría si, por lo tanto para el compilador es una conversión directa entre la instrucción A--  y una instrucción de decremento.  También puede utilizase dentro de operaciones aritméticas, o dentro de otras instrucciones el operador post-decremento, que hará que se decremente el valor de la variable A ni bien se terminó de obtener el valor para evaluarlo en la fórmula o instrucción donde deba ser utilizado.

Pre-incrementos y pre-decrementos


De forma similar a las operaciones de post-incremento y/o post-decrementos, donde se incrementa o decrementa la variable afectada, después de haber evaluado el valor. Es posible indicarle al compilador, que primero incremente o decremente una variable cualquiera, y después tome el valor para procesar la operación donde está siendo afectada. Para indicar un pre-incremento o un pre-decremento, se anteponen dos signos ++ a la variable o dos signos -- para el caso de que sea una operación de decremento.

Si la operación consiste en implementar un simple contador, tal como lo hemos visto en el programa anterior, no hay diferencias entre elegir un pre-incremento o un post-incremento. Ya que el resultado será el mismo.

++A;
--A;

Sin embargo, si lo utilizamos dentro de una operación aritmética, o dentro de una función, la operación cambia. Veamos un ejemplo.

Supongamos que tenemos una operación en una línea de programa como la siguiente:

B = 10 + ++A;

Supongamos que la variable A vale inicialmente el valor 10. Entonces, el microcontrolador cuando llegue a ejecutar dicha línea, la primera tarea que realizará será el incremento de la variable A. En éste caso, como dijimos que valía 10, al incrementarla queda en 11. Entonces, luego para calcular el valor de la variable B, hace 10 + 11 = 21.

Notemos la diferencia cuando en vez de utilizar un pre-incremento, elegimos un post-incremento. Si la línea hubiese sido escrita de la siguiente manera:

B = 10 + A ++;

En éste caso, si la variable A originalmente hubiera tenido cargado el valor 10, el compilador evaluaría el valor de ésta como 10 para ejecutar la suma, por lo tanto B sería igual a 10 + 10, o sea 20. Y la variable A se incrementa inmediatamente después de haber sido evaluada. Terminando ésta en el valor 11. Para el caso del pre-incremento, la variable A también termina valiendo 11, pero en la fórmula entra el valor incrementado previamente a procesar la operación.

viernes, 1 de abril de 2011

Sintaxis de operaciones

Sintaxis de operaciones


Entremos ahora más en el mundo del lenguaje C. Hasta éste momento, los programas que fuimos viendo, estuvieron hechos de una manera si se quiere primitiva. Explícitamente les colocaba muchos pasos para realizar las diferentes tareas. Todo esto buscando que los programas que les iba presentando queden lo más claro posible.

Ahora, vamos a ver instrucciones que son del lenguaje C, que nos permiten ahorrar líneas de código, y que tienen su correlato con instrucciones soportadas dentro del microcontrolador. Aunque la mayoría de los compiladores tienen muy buenos algoritmos de optimización. Si utilizamos las operaciones con la sintaxis del C de manera más eficiente, ayudamos al compilador a tomar la decisión por el resultado más optimizado.

Recordemos el programita que les había presentado que implementaba un contador sencillo. El programa era el siguiente:

                        char A;
char B;

Paso 1 :           A=1;

Paso 2:            B = A + 1;
Paso 3:            A = B;

Paso 4:            Volver a ejecutar el paso 2.

Operadores de asignación


Veamos, que en nuestro programa ejemplo, había utilizado dos variables A y B. Donde B la utilizaba como variable temporal que tomaba el valor de la variable A y le sumaba el valor 1. Para luego asignarle el valor de la variable B nuevamente a la variable A.

El mismo programita lo podría haber reescrito de una manera diferente. Con menos código, pero que sea exactamente el mismo resultado.

char A;

Paso 1:             A=1;

Paso 2:            A = A + 1;

Paso 3:            Volver al paso 2

Como vemos, en éste último programa que les acabo de pasar, ya no utilizo más la variable B. Directamente le digo al compilador que tome el valor de la variable A, que le sume uno al valor de la variable A y que el resultado lo almacene dentro de la misma variable A.  Además de ahorrarnos una línea en el código que escribimos, nos ahorramos algo más valioso: Una posición de memoria RAM que se ocupaba para la variable B. Cuando hablamos de microcontroladores, una variable más o una variable menos es algo importante. Recordemos que en los PICs hay modelos que sólo tienen 20 bytes de RAM. Por lo tanto, la cantidad de variables que ocupemos tienen su implicancia.

Ahora, podemos volver a reescribir el código del programita, ahorrando un poco más todavía.




char A;

Paso 1:    A =1;
Paso 2: A+=1;

Paso 3: Volver al paso 2

Acá, en la línea 2, lo que acabo de hacer, es simplemente un ahorro cosmético si se quiere. Es exactamente lo mismo poner la línea:

A = A + 1;

Como también poner la línea

A += 1;

Cuando se antepone el signo del operador + al signo igual, significa para el compilador que tome el valor que está a la derecha del signo igual, y lo acumule en la variable A.

También por ejemplo podemos hacer lo siguiente:

A += 2;

El funcionamiento de ésta instrucción, es decirle al compilador que sume 2 al valor de la variable A y que el resultado se guarde nuevamente en la variable A. Es como poner la línea A=A+2.

De la misma manera, se pueden utilizar los otros operadores, -, *, /, &, etcétera.

Entonces tenemos las siguientes posibilidades:

Operación
Equivalente
A += B
A = A + B
A -= B
A = A - B
A *= B
A = A * B
A /= B
A = A / B
A &= B
A = A & B
A |= B
A = A | B
A ^= B
A = A ^ B
A << = B
A = A << B
A >>= B
A = A >> B
A &&= B
A = A && B
A ||= B
A = A || B

Si bien la diferencia es mínima. Es muy común que los programas escritos en C tomen la operación con la forma descripta en la columna de la izquierda de la tabla. Ambas formas de escribir las operaciones funcionan correctamente en C. Según el tipo de compilador que estén usando serán compiladas iguales o nó. Para ello, deben probar ambas operaciones escritas de diferentes maneras y observar el código resultante en assembler para ver las diferencias. A modo práctico, normalmente se usa la forma de la columna de la izquierda ya que expresando de ésa manera las operaciones, posiblemente el compilador tomará la opción de menor cantidad de código.  La forma de escribir los operadores de la manera que están en la columna de la izquierda es lo que se conoce como operadores de asignación.

Sintaxis de operaciones


Sintaxis de operaciones


Entremos ahora más en el mundo del lenguaje C. Hasta éste momento, los programas que fuimos viendo, estuvieron hechos de una manera si se quiere primitiva. Explícitamente les colocaba muchos pasos para realizar las diferentes tareas. Todo esto buscando que los programas que les iba presentando queden lo más claro posible.

Ahora, vamos a ver instrucciones que son del lenguaje C, que nos permiten ahorrar líneas de código, y que tienen su correlato con instrucciones soportadas dentro del microcontrolador. Aunque la mayoría de los compiladores tienen muy buenos algoritmos de optimización. Si utilizamos las operaciones con la sintaxis del C de manera más eficiente, ayudamos al compilador a tomar la decisión por el resultado más optimizado.

Recordemos el programita que les había presentado que implementaba un contador sencillo. El programa era el siguiente:

                        char A;
char B;

Paso 1 :           A=1;

Paso 2:            B = A + 1;
Paso 3:            A = B;

Paso 4:            Volver a ejecutar el paso 2.

Operadores de asignación


Veamos, que en nuestro programa ejemplo, había utilizado dos variables A y B. Donde B la utilizaba como variable temporal que tomaba el valor de la variable A y le sumaba el valor 1. Para luego asignarle el valor de la variable B nuevamente a la variable A.

El mismo programita lo podría haber reescrito de una manera diferente. Con menos código, pero que sea exactamente el mismo resultado.

char A;

Paso 1:             A=1;

Paso 2:            A = A + 1;

Paso 3:            Volver al paso 2

Como vemos, en éste último programa que les acabo de pasar, ya no utilizo más la variable B. Directamente le digo al compilador que tome el valor de la variable A, que le sume uno al valor de la variable A y que el resultado lo almacene dentro de la misma variable A.  Además de ahorrarnos una línea en el código que escribimos, nos ahorramos algo más valioso: Una posición de memoria RAM que se ocupaba para la variable B. Cuando hablamos de microcontroladores, una variable más o una variable menos es algo importante. Recordemos que en los PICs hay modelos que sólo tienen 20 bytes de RAM. Por lo tanto, la cantidad de variables que ocupemos tienen su implicancia.

Ahora, podemos volver a reescribir el código del programita, ahorrando un poco más todavía.



char A;

Paso 1:    A =1;
Paso 2: A+=1;

Paso 3: Volver al paso 2

Acá, en la línea 2, lo que acabo de hacer, es simplemente un ahorro cosmético si se quiere. Es exactamente lo mismo poner la línea:

A = A + 1;

Como también poner la línea

A += 1;

Cuando se antepone el signo del operador + al signo igual, significa para el compilador que tome el valor que está a la derecha del signo igual, y lo acumule en la variable A.

También por ejemplo podemos hacer lo siguiente:

A += 2;

El funcionamiento de ésta instrucción, es decirle al compilador que sume 2 al valor de la variable A y que el resultado se guarde nuevamente en la variable A. Es como poner la línea A=A+2.

De la misma manera, se pueden utilizar los otros operadores, -, *, /, &, etcétera.

Entonces tenemos las siguientes posibilidades:

Operación
Equivalente
A += B
A = A + B
A -= B
A = A - B
A *= B
A = A * B
A /= B
A = A / B
A &= B
A = A & B
A |= B
A = A | B
A ^= B
A = A ^ B
A << = B
A = A << B
A >>= B
A = A >> B
A &&= B
A = A && B
A ||= B
A = A || B

Si bien la diferencia es mínima. Es muy común que los programas escritos en C tomen la operación con la forma descripta en la columna de la izquierda de la tabla. Ambas formas de escribir las operaciones funcionan correctamente en C. Según el tipo de compilador que estén usando serán compiladas iguales o nó. Para ello, deben probar ambas operaciones escritas de diferentes maneras y observar el código resultante en assembler para ver las diferencias. A modo práctico, normalmente se usa la forma de la columna de la izquierda ya que expresando de ésa manera las operaciones, posiblemente el compilador tomará la opción de menor cantidad de código.  La forma de escribir los operadores de la manera que están en la columna de la izquierda es lo que se conoce como operadores de asignación.

domingo, 27 de marzo de 2011

Desplazamientos


Desplazamientos


En el lenguaje C, nos está quedando una operación que se puede realizar con las variables y que es muy interesante e importante conocer. La operación que está faltando es el desplazamiento. Esta operación, junto con las operaciones aritméticas y lógicas; componen la familia básica de herramientas que posee el C; y que además también en la mayoría de los microcontroladores tienen implementadas por hardware. Con el uso de estas 3 operaciones básicas, me atrevo a decir que se resuelven el 90% de los casos de programas.

La operación que nos toca en éste apartado es el desplazamiento. El desplazamiento consiste en cambiar de posición los bits de una variable de algún tipo. Por ejemplo, un desplazamiento hacia la izquierda significa hacer que el bit 7 cargue lo que tiene el bit 6, luego el bit 6 carga el valor que tiene el bit 5, y así sucesivamente hasta llegar al bit 1. El bit 0, por definición del C tomará el valor 0.

El signo que se utiliza para realizar desplazamientos es el << cuando se indica que se debe realizar un desplazamiento hacia la izquierda. Y el >> cuando se quiere desplazar hacia la derecha. La variable que queda a la izquierda del doble signo “<<” o “>>” es la variable que se va a desplazar. Mientras que el valor o variable que quedan del lado derecho del signo, debe indicar la cantidad de desplazamientos que se deben realizar. El valor que está colocado a la derecha, nunca debe ser mayor a la cantidad de bits que tiene la variable o el valor que se está desplazando, puesto que si se intenta desplazar más veces que la cantidad de bit que tiene el valor original, el resultado será obligatoriamente 0, ya que se van completando los bits agregados siempre con el valor 0.

Podemos ver en forma de gráfico como se produce el desplazamiento:

Supongamos que hacemos el siguiente programita:

char A;
char B;

A = 0x55;
B = A << 1;

Este programita, indica que la variable B tiene que tener como resultado el valor de la variable A, desplazado una vez hacia la izquierda.


Numero de bit
7
6
5
4
3
2
1
0
A=0x55
0
1
0
1
0
1
0
1

A << 1 = 0xAA
1
0
1
0
1
0
1
0
Se agrega un cero


Podemos observar que al producir un desplazamiento, se agrega un cero en el bit 0 del resultado del desplazamiento. Y el bit 7 de la variable A original se descarta.

Lo interesante de ésta operación, es que cuando es un único desplazamiento. Se resuelve en un sólo paso dentro del microcontrolador. En algunos modelos de microcontroladores, y muchos DSP, tienen la posibilidad de realizar más de un desplazamiento al valor, y aún así tomar solo un ciclo del procesador.

Si lo que deseamos es realizar una mayor cantidad de desplazamientos, podemos por ejemplo escribir la línea como A << 4. Donde lo que significa que se rotará 4 veces hacia la izquierda el valor de la variable A
Numero de bit
7
6
5
4
3
2
1
0
A=0x55
0
1
0
1
0
1
0
1

A << 4 = 0x50
0
1
0
1
0
0
0
0
Se agregan 4 ceros

Este último caso, si lo que se está utilizando es un microcontrolador PIC o un Texas MSP430xxx. Necesariamente ocupará como mínimo 4 ciclos de trabajo. Ya que ninguno de los dos posee un desplazamiento mayor a una vez, implementada por hardware.

De la misma manera que se realizan los desplazamientos hacia la izquierda, se pueden realizar desplazamientos hacia la derecha. Por ejemplo:

A >> 1

Significa que se tome el valor de la variable A y se le aplique un desplazamiento de sus valores hacia los bits de la derecha:

Numero de bit
7
6
5
4
3
2
1
0
A=0x55
0
1
0
1
0
1
0
1

A >> 1 = 0x2A
0
0
1
0
1
0
1
0

El funcionamiento consiste en hacer que el bit 0 del resultado cargue el valor del bit 1, luego el bit 1 toma el valor del bit 2, y así sucesivamente hasta llegar al bit 6. El bit 7 siempre se completa con 0.

De la misma manera que los desplazamientos a la izquierda, es posible realizar mayor cantidad de desplazamientos a la derecha. Por ejemplo:

A >> 3

Numero de bit
7
6
5
4
3
2
1
0
A=0x55
0
1
0
1
0
1
0
1
A >> 1 = 0x0A
0
0
0
0
1
0
1
0


Ahora bien. ¿Para qué querría alguien usar estas operaciones?

Respondiendo a esa comprensible pregunta, volvamos a las bases, cuando hablamos de números binarios y números decimales.

Veamos que sucede en nuestro sistema decimal cuando multiplicamos un número por 10, o por 100, o por 1000.

Supongamos que tenemos un número 123 en decimal y lo multiplicamos por 10. En forma mental ¿qué hacemos?¿como realizamos una multiplicación de un número por 10?

Simplemente agregamos un cero a la derecha y el número se multiplicó por 10:

123 x 10 = 1230

Cuando multiplicamos un número por 100, le agregamos mentalmente dos ceros a la derecha. Por ejemplo:

123 x 100 = 12300
Ahora pasemos al mundo binario, el mundo de nuestros microcontroladores. No voy a multiplicar por 10 en decimal, sino por 2, por 4, y por 8. Notemos que en binario el 2 es igual a 10. El 4 en binario se representa por 100. El 8 en binario se representa por 1000.

Supongamos que tenemos el valor 6 y lo multiplicamos por 2.

6 x 2 = 12

Ahora veamos los mismos valores en binario: 6 es 110. 2 es 10.  Y el 12 mágicamente es 1100

110 (6) x 10 (2) = 1100 (12)

O sea, al multiplicar un valor cualquiera por 2, que en binario es 10. Si representamos el valor del número que queremos multiplicar por 2 en binario, la multiplicación consiste en agregar un cero a la derecha del número, y desplazar todos los otros dígitos una posición hacia la izquierda.

Si repetimos el proceso para la multiplicación x 4, vamos a ver que se cumple la misma regla, pero ahora agregando dos ceros a la derecha, y desplazando dos veces el valor hacia la izquierda. Notemos que 2x2 = 4. Por lo tanto, es lo mismo multiplicar un valor por 4, que multiplicarlo dos veces consecutivas por 2. O sea, agregar dos veces consecutivas un cero a la derecha.

6 x 4 = 24

110 (6) x 100 (4) = 11000 (24)

Lo mismo sucede para multiplicar por 8, por 16, por 32, y por cualquier potencia de 2. O sea, si tenemos que realizar una multiplicación de una potencia  de 2 en un programa hecho en C. Se puede resolver de dos maneras diferentes.

Por ejemplo, vamos a resolver la multiplicación de dos valores por 8. Una de la soluciones es usar la operación aritmética multiplicación y la otra usando las rotaciones.

char A;
char B;
char C;

B = A * 8;
C = A << 3;

¿Cuando conviene una forma y cuando la otra?

Recordemos que una multiplicación, en un microcontrolador que no posee multiplicador por hardware, requería de muchos pasos de procesamiento. Suponiendo que el multiplicando y el multiplicador son de 8 bits, vimos que como mínimo se requerían de 16 pasos de procesamiento. Si utilizamos el desplazamiento, para la misma multiplicación de un número por 8 sólo se usan 3 pasos. O sea, si nuestro microcontrolador trabaja a 5 MIPS, cuando se utiliza la multiplicación normal, podremos como máximo lograr 312500 operaciones por segundo. En el otro caso, podremos  llegar a las 1666666 operaciones por segundo.

No se puede utilizar el desplazamiento cuando el valor es negativo ya que la información con el signo no se respeta. Para ese caso, debe hacerse un truco que consiste en preguntar previamente si el valor era positivo o negativo. Convertir el valor a positivo, desplazarlo y luego convertirlo nuevamente en negativo.

De la misma manera que desplazar hacia la izquierda implica una multiplicación por una potencia de 2. El desplazamiento hacia la derecha implica la división en una potencia de 2. De nuevo la gran ventaja de usar desplazamientos en vez de una división, cuando el divisor es una potencia de 2 es que la cantidad de ciclos de procesamiento que se utilizan son mucho menores. Así, con esta herramienta se puede lograr que un microcontrolador de los más humildes, realice tareas que son impensadas si lo intentamos programar en la forma más natural que se nos ocurriría normalmente que es usando multiplicaciones y divisiones.

Para poder usar estas herramientas como ayudas matemáticas, tenemos que acostumbrarnos al uso de las potencias de 2 como base para toda nuestra programación. Tenemos que sacarnos de la cabeza el sistema decimal y adoptar el binario y/o hexadecimal. Así, cuando hagamos por ejemplo un acumulador para tomar un valor promedio, no pensemos en usar 10 muestras y luego dividir por 10 para tener el promedio. Pensemos siempre en usar un número potencia de 2 para tomar las muestras, por ejemplo 8 o 16, y después el valor promedio se saca dividiendo por 8 o por 16, que es lo mismo que hacer una rotación del acumulador 3 veces hacia la derecha para el caso de la división por 8, o 4 veces a la derecha para el caso de la división por 16.

Incluso los compiladores mas avanzados, tienen opciones de optimización. Cuando colocamos la operación A = B / 8. Ya conoce el truco, y automáticamente en vez de hacer una operación división estándar, aplica desplazamientos hacia la derecha. Lo único que complica también en éste caso es cuando la variable B es del tipo signed. El compilador tiene que respetarlo, y eso implica mayor cantidad de código al momento de compilarlo. Por ello, es importante conocer que tipo de variable estamos utilizando, si es con signo o nó, si los valores que vamos a utilizar son siempre positivos o pueden aparecer valores negativos.

Si lo que les tira más en su carrera de la programación van a ser los DSP, o van a apuntar a tener equipos donde estén ingresando datos a una tasa de velocidad relativamente importante para un microcontrolador (que puede ser del orden de las 10000 muestras por segundo), y a esas muestras hay que aplicarle algún filtro digital, donde hay que acumular valores y efectuar multiplicaciones, van a tomarle mucho cariño al uso de los desplazamientos que le van a ayudar enormemente a encontrar la solución. En mi experiencia, he hecho cosas con PIC que para muchos eruditos en la programación y manejo de microcontroladores es totalmente imposible. Sin embargo, usando las técnicas y trucos que les estoy dando se pueden implementar. Conclusión, a lo mejor con un microcontrolador económico se pueden realizar tareas que a simple vista parecen muy difíciles o imposibles. Dichas tareas sin dudas que con un microcontrolador más grande o DSP se realizan sin dificultad. Pero, cuando hay que hacer producciones en masa, el precio del microcontrolador que elijan será determinante en el precio del producto final. Por lo tanto, hay que analizar muy bien como implementar la solución para que el resultado sea el óptimo. Si no hay restricciones de microcontrolador y pueden elegir alguno bien potente elijan ese. Ahora, si existe una imposición que se deben reducir los costos al máximo, van a tener que recurrir a herramientas como éstas para poder meter en lugares mas chicos las soluciones. Tengamos en cuenta que cuando trabajamos en el mundo de los microcontroladores es totalmente diferente al mundo de las PC. En las PC cada vez se hacen programas más pesados total cada vez las computadoras son más potentes. En los microcontroladores lo que se busca principalmente es la reducción de costos. Si bien cada día aparece un microcontrolador nuevo más potente, debemos estar atentos a lo que nos ofrece la tecnología y cual es nuestro objetivo de costo.