Mostrando entradas con la etiqueta PIC. Mostrar todas las entradas
Mostrando entradas con la etiqueta PIC. Mostrar todas las entradas

sábado, 4 de mayo de 2013

¿Quieres aprender a diseñar tus propios robots?

Aquellos que estamos empezando en el mundo de los concursos de robótica, solemos tener el problema de afrontar los problemas de la construcción del robot desde cero. Al no tener experiencia es común cometer errores tontos que cuando llegamos a pista nos damos cuenta que nos van a impedir hacer nada. En mi caso, el primero robot con el que me presenté a un concurso tenía el principal problema de controlar mal los motores y cuando me di cuenta de ello ya era demasiado tarde.

Hoy quiero dejaros la idea, de que si queréis comenzar a construir vuestro robot, lo mejor es que comencéis cuanto antes y que no os rindáis  Si vais a una competición seguramente descubriréis mil fallos de vuestro robot que lo hacen totalmente inservible al lado de la mayoría de rivales, pero esto no tiene que echaros atrás, lo importante es descubrir estos defectos y corregirlos para mejorar.

Otra cosa importante es no estar solo, buscad a alguien con vuestra misma motivación, leed foros, blogs, leed a gente que hable de estas cosas, de como construirlas y como mejorarlas  No dudéis en preguntar en foros o blogs, seguro que están encantados de ayudaros, yo me ofrezco humildemente a responder todas aquellas dudas que sea capaz. Y sobre todo, aprender de los mejores, es decir de aquellos que ya tienen experiencia y saben de lo que hablan, aquellos que ya se han llevado mil palos porque así os evitareis dároslos vosotros también.

Hoy os traigo un vídeo, de los que creo que han sido los mejores durante mucho tiempo en carreras de robots velocistas. Es un vídeo muy interesante sobre la ponencia que dan Daniel Alvarez y Alberto Calvo en las jornadas de la robótica en Valladolid. Daniel y Alberto han sido campeones de velocistas los últimos años en casi todas las pruebas en las que se han presentado en España. Yo he tenido la suerte de competir con ellos en una edición de campus party y verlos en directo, aunque no se si se puede llamar competir a lo que hice, ya que mi robot tenía un error garrafal de diseño, que fue no utilizar el freno eléctrico en los motores, algo que os contaré el día que consiga un poco de tiempo para hacer una entrada sobre el control de motores. El caso es que podéis aprender mucho de la experiencia de Daniel y Alberto y seguro que os despierta alguna idea. No pretendo que os deprimáis  porque cuando veáis el nivel de estos caballeros os vais a asustar, pero es lo que hay. Hay que avanzar poco a poco, se pueden hacer robots mas sencillos e igualmente competitivos, pero no esta mal saber lo que se esta haciendo y tomar ideas, para no dar palos de ciego.



Como me dijo una vez un profesor, hay que aprender de los que saben mas que tu y si es posible copiar lo que hicieron para poder entenderlo realmente, comprender porque lo hicieron así y una vez conseguido eso, descubrir lo que puedes aportar tu y mejorarlo.

Un saludo a todos y por si me leen algún día, aprovecho para mandar un saludo a Daniel y a Alberto y dales mi enhorabuena por todo su trabajo, y también agradecerles que nos ilustren a todos con charlas como esta, es una verdadera gozada poder escucharles contar su experiencia.

martes, 20 de marzo de 2012

Programando PIC con C++: Switch VS if {...} else if {..}

Cuando se programa microcontroladores en C++. no hay que perder de vista la arquitectura con la que se está trabajando. El código que se escriba en C++ condicionará el código ensamblador que generará el compilador empleado. Un claro ejemplo de esto es el caso de la estructura switch frente a una estructura if else if. Mucha gente pensará que los siguientes códigos en C++ son equivalentes:

void main(void)
{
 int a=0;
 while(1){
  a++;
  if(a==0){
   a=1;
  }else if(a==1){
   a=2;
  }else if(a==2){
   a=3;
  }else if (a==3){
   a=4;
  }else{
   a=0;
  }
 }
}







void main(void)
{
 int a=0;
 while(1){
  a++;
  switch(a){
   case 0:
    a=1;
   break;
   case 1:
    a=2;
   break;
   case 2:
    a=3;
   break;
   case 3:
    a=4;
   break;
   default:   
    a=0;
   break;
  }
 }
}

No obstante el código que realmente genera el compilador no es el mismo. Por lo general el código que se genera para un switch es, en termino medio, más eficiente que en el caso de los if else. Cuando se trabaja con sistemas de tiempo real que deben interaccionar con el entorno, hay que tener en cuenta que se deben realizar ciclos con las siguientes fases :
  1. Lectura de sensores
  2. Ejecución del código y toma de decisiones
  3. Actuación (transmisión de las decisiones a los actuadores)
Este código debería ejecutarse con unos tiempos de ciclo lo mas constantes que sea posible, es decir, que la en el tiempo de ejecución de diferentes ciclos no haya grandes diferencias. Si se utilizan las estructuras if else, intuitivamente se puede sospechar que no será la misma cantidad de comprobaciones las que se harán para ejecutar el primer if que si se ejecuta el último. Esto se debe a que para que se ejecute el último caso primero de deben haber descartado todos los anteriores. El problema es que se está haciendo ineficiente la ejecución de acciones para los últimos casos de la estructura, y además sin que esto aporte ninguna ventaja. Este tipo de códigos se pueden construir mediante estructuras switch como se muestra en el ejemplo y esta creará un código bastante más eficiente y con unos costes temporales menos extremos, los mejores y los peores casos tendrán costes temporales similares.

Para ilustrar los ejemplos veamos el código ensamblador que se genera en cada uno de ellos para el PIC16F876A con el compilador SDCC. Para entender el código, se recomienda leer la entrada anterior, pero en todo caso al menos se habrá de tener en cuenta las siguientes indicaciones:

  • el registro r0x1000 se refiere a la parte baja del valor de a y r0x1001 a la parte alta de la misma variable.
  • La variable a es una variable entera de 16 bits, 8 por registro.
  • Cuando se habla de L(a) y H(a) en los comentarios se refiere a la parte baja y alta de la variable a respectivamente.
  • La instrucción BTFSC <reg>,<num> se salta la instrucción contigua cuando el bit <num> del registro <reg> está a 0 (clear)
  • La instrucción BTFSS <reg>,<num> se salta la instrucción contigua cuando el bit <num> del registro <reg> está a 1 (set) 
  • W es el registro acumulador de la ALU
Código 1: estructura if else if

_main ;Function start
; 2 exit points
; .line 3; ------ int a=0;
 BANKSEL r0x1000 
 CLRF r0x1000 ;se borra la parte baja de a
 CLRF r0x1001 ;se borra la parte alta de a
_00118_DS_
; .line 5; ------ a++;
 BANKSEL r0x1000
 INCF r0x1000,F ;incremento de a
 BTFSC STATUS,2 ;si el resultado es cero
   ;se ejecutará la siguiente linea que 
 INCF r0x1001,F  ;incrementa la parte alta
; .line 6; ------ if(a==0){
 MOVF r0x1000,W ;se pone H(a) de a en W
 IORWF r0x1001,W ; OR entre W y la L(a)
 BTFSS STATUS,2 ;si el resultado no es cero
 GOTO _00115_DS_ ;se salta a esta etiqueta
; .line 9; ------ a=1;
 MOVLW 0x01
 MOVWF r0x1000
 CLRF r0x1001
 GOTO _00118_DS_
_00115_DS_
; .line 10; ---- }else if(a==1){
 BANKSEL r0x1000
 MOVF r0x1000,W
 XORLW 0x01      ;XOR entre L(a) y 0x01
 BTFSS STATUS,2  ;si el reslutado no es 0
 GOTO _00112_DS_ ;se salta a la etiqueta
 MOVF r0x1001,W  ;de lo contrario se
 XORLW 0x00 ;se comprueba la parte alta 
 BTFSS STATUS,2  ;si H(a) no es cero
 GOTO _00112_DS_ ;se salta a la etiqueta
; .line 11; ---- a=2;
 MOVLW 0x02
 MOVWF r0x1000
 CLRF r0x1001
 GOTO _00118_DS_
_00112_DS_

; .line 12; ---- }else if(a==2){
 BANKSEL r0x1000
 MOVF r0x1000,W
 XORLW 0x02
 BTFSS STATUS,2
 GOTO _00109_DS_
 MOVF r0x1001,W
 XORLW 0x00
 BTFSS STATUS,2
 GOTO _00109_DS_
; .line 13; ---- a=3;
 MOVLW 0x03
 MOVWF r0x1000
 CLRF r0x1001
 GOTO _00118_DS_
_00109_DS_
; .line 14; ---- }else if(a==3){
 BANKSEL r0x1000
 MOVF r0x1000,W
 XORLW 0x03
 BTFSS STATUS,2
 GOTO _00106_DS_
 MOVF r0x1001,W
 XORLW 0x00
 BTFSS STATUS,2
 GOTO _00106_DS_
; .line 15; ---- a=4;
 MOVLW 0x04
 MOVWF r0x1000
 CLRF r0x1001
 GOTO _00118_DS_
_00106_DS_
; .line 17; ---- a=0;
 BANKSEL r0x1000
 CLRF r0x1000
 CLRF r0x1001
 GOTO _00118_DS_
 RETURN 


Cómo se puede observar en el código anterior, hasta que se ejecuta el caso por defecto, se han tenido que realizar unos 4 saltos condicionales. Teniendo en cuenta el coste de ciclos de cada una de las operaciones se tiene un mejor caso (a=0) de 11 ciclos hasta que se llega a ejecutar el código de dicha opción. El conteo de los ciclos se incluye a continuación de forma ilustrativa. Nótese que en el caso de las instrucciones de salto condicional BTFSC y BTFSS valen 2 ciclos ya que el mejor caso se considera que no se cumplen, también derivado de este hecho la operación contigua a estas tiene un coste 0 ya que en su lugar se ha ejecutado una operación nop es decir un ciclo vacío.

 (1) BANKSEL r0x1000 
 (1) CLRF r0x1000 ;se borra la parte baja de a
 (1) CLRF r0x1001 ;se borra la parte alta de a
_00118_DS_
; .line 5; ------ a++;
 (1) BANKSEL r0x1000
 (1) INCF r0x1000,F ;incremento de a
 (2) BTFSC STATUS,2 ;si el resultado es cero
   ;se ejecutará la siguiente linea que 
 (0) INCF r0x1001,F  ;incrementa la parte alta
; .line 6; ------ if(a==0){
 (1) MOVF r0x1000,W ;se pone H(a) de a en W
 (1) IORWF r0x1001,W ; OR entre W y la L(a)
 (2) BTFSS STATUS,2 ;si el resultado no es cero
 (0) GOTO _00115_DS_ ;se salta a esta etiqueta

Si se considera el peor caso, es decir, en el que se debe ejecutar el caso por defecto pasando por el camino más largo, se tiene un total de 42 ciclos ya que cada comparación (excepto la de a==0) tiene un coste de 10 ciclos de ejecución en su peor caso y la de a==0 tarda un ciclo más en el caso en que se ejecute el GOTO que tiene un coste de 2 ciclos, aunque el BTFSS pase a costar 1 ciclo.

Codigo 2 : Estructura switch

_main ;Function start
; 2 exit points
; .line ---- int a=0;
 BANKSEL r0x1000
 CLRF r0x1000
 CLRF r0x1001
_00112_DS_
; .line 7; "ejemplo1-2.c" a++;
 BANKSEL r0x1000
 INCF r0x1000,F
 BTFSC STATUS,2
 INCF r0x1001,F
;---------------------------
;Hasta aquí es igual que antes
;---------------------------

A partir de aquí se muestra a la derecha el caso default y a la izquierda la continuación del código que ejecutaría el caso general.

;signed compare: 
;left < lit(0x0=0), size=2, mask=ffff
; .line 8; "ejemplo1-2.c" switch(a){
 BSF STATUS,0    ;se borra el flag C
 BTFSS r0x1001,7 ;se mira signo de a
        ;si el signo de a es negativo
 BCF STATUS,0    ;se activa el flag C
 BTFSC STATUS,0  ;si el flag C está activo
 GOTO _00109_DS_ ;se manda al caso default
;genSkipc:3083: created from rifx:0x286044
;swapping arguments (AOP_TYPEs 1/2)
;signed compare:
; left >= lit(0x4=4), size=2, mask=ffff
 MOVF r0x1001,W  ;Se carga H(a) en W
 ADDLW 0x80  ;se le suma dos veces 0x80
 ADDLW 0x80  ;con esto se elimina el signo
 BTFSS STATUS,2 ;si el resultado no es cero
 GOTO _00119_DS_ ;se pasa al caso default
 MOVLW 0x04      ; se carga un 4 en W
 SUBWF r0x1000,W ;se resta L(a)
_00119_DS_
 BTFSC STATUS,0  ;si el flag C está activo
 GOTO _00109_DS_ ;se va al default
;Hasta aquí sólo se ha analizado el default
; y se puede comprobar un mejor caso de 
;13 ciclos (salir por el primero GOTO)
; y un peor caso de 22 ciclos
; saliendo por el último

;genSkipc:3083: created from rifx:0x286044
 MOVLW HIGH(_00120_DS_);se carga la parte H
 MOVWF PCLATH ;de la direc. 120 al PCLATCH
    ;esto establece la parte alta del valor
    ;del contador de programa (PC)
 MOVLW _00120_DS_ ;mueve la parte baja de 
 BANKSEL r0x1000  ;la misma dirección a W 
 ADDWF r0x1000,W  ;y se le suma a.
 BTFSC STATUS,0  ;Si hay accarreo
 INCF PCLATH,F  ;se incrementa el PCLATCH
 BANKSEL PCL   ;Con esto se ha establecido
 MOVWF PCL ;el nuevo valor para el contador 
    ; de programa. Este ahora estará
    ; apuntando a uno de los GOTO 
    ; siguientes. Cada GOTO representa
un case del switch.
_00120_DS_
 GOTO _00105_DS_
 GOTO _00106_DS_
 GOTO _00107_DS_
 GOTO _00108_DS_
    ; este código consume 11 ciclos incluyendo
    ;el último GOTO




Como se puede observar en el caso del switch, el peor caso ya no es el default sino cualquiera de los demás casos. Además todos tardarán la misma cantidad de ciclos en llegar al código que deben ejecutar que en este caso es de 7 (inicialización de a e incremento) 5+9 (comprobación del default) + 11 (caso general) = 32 ciclos. Nótese que esa cantidad de ciclos representa aproximadamente tres cuartas partes del peor caso del ejemplo inicial con la estructura if else, además este coste es constante para cualquiera de los diferentes casos del switch. Existan la cantidad de casos que existan este coste permanecerá constante siempre y cuando estos casos tengan valores consecutivos, en caso contrario se iría añadiendo complejidad a la parte que evalúa el default. En un caso general en el que se implementa una maquina de estados, esta solución es la solución idónea para programar dicho sistema ya que evitará problemas y proporcionará tiempos equivalentes para alcanzar los diferentes casos y proporcionar una respuesta adecuada, por lo que el funcionamiento global mejorará considerablemente. Además de esto, con maquinas de estados complejas, la ganancia al emplear una estructura switch frente al uso de una estructura if else crecerá linealmente.

Por último el resto del código para este caso, donde están las etiquetas de cada uno de los case del switch:

_00105_DS_
; .line 10; "ejemplo1-2.c" a=1;
 MOVLW 0x01
 BANKSEL r0x1000
 MOVWF r0x1000
 CLRF r0x1001
; .line 11; "ejemplo1-2.c" break;
 GOTO _00112_DS_
_00106_DS_
; .line 13; "ejemplo1-2.c" a=2;
 MOVLW 0x02
 BANKSEL r0x1000
 MOVWF r0x1000
 CLRF r0x1001
; .line 14; "ejemplo1-2.c" break;
 GOTO _00112_DS_
_00107_DS_
; .line 16; "ejemplo1-2.c" a=3;
 MOVLW 0x03
 BANKSEL r0x1000
 MOVWF r0x1000
 CLRF r0x1001
; .line 17; "ejemplo1-2.c" break;
 GOTO _00112_DS_
_00108_DS_
; .line 19; "ejemplo1-2.c" a=4;
 MOVLW 0x04
 BANKSEL r0x1000
 MOVWF r0x1000
 CLRF r0x1001
; .line 20; "ejemplo1-2.c" break;
 GOTO _00112_DS_
_00109_DS_
; .line 22; "ejemplo1-2.c" a=0;
 BANKSEL r0x1000
 CLRF r0x1000
 CLRF r0x1001
; .line 24; "ejemplo1-2.c" }
 GOTO _00112_DS_
 RETURN 

Programando PIC con C++: ¿Porque programar en C++?

Tradicionalmente, cuando se enseña programación de microcontroladores, se emplea como lenguaje de programación el ensamblador del microcontrolador seleccionado. Esto tiene particular interés, desde el punto de vista didáctico, ya que al utilizar las funcionalidades del microcontrolador en ensamblador se está obligado a entender perfectamente el funcionamiento del propio microcontrolador. Se podría decir que la cercanía del lenguaje a la arquitectura de la maquina fuerza a su comprensión para poder programarlo. Sin embargo, como es evidente para cualquier persona que haya programado en lenguajes de alto nivel, la programación en lenguajes de bajo nivel no es eficiente en cuanto a la generación de código. Algunas de las tareas más sencillas como la programación de una estructura for o un simple if pueden llegar a variar mucho de un microcontrolador a otro en función de su arquitectura, por lo que hasta para este tipo de funcionalidades tan elementales habría que estudiar el repertorio de instrucciones, con el riesgo de no crear el código más óptimo.

Los microcontroladores más típicos (arquitecturas de 8 a 16 bits) suelen ser maquinas con arquitectura RISC, es decir, computadoras con un reducido conjunto de instrucciones. El motivo de esto es que se persigue construir máquinas lo más sencillas que sea posible. Las arquitecturas RISC permiten crear máquinas con un pequeño conjunto de instrucciones atómicas que permiten crear cualquier programa. La sencillez de estas instrucciones, a su vez, las hace muy rápidas, requiriendo sólo de uno o dos ciclos para ser ejecutadas. Sin embargo, esto tiene ciertas implicaciones dependiendo del criterio con que el fabricante haya diseñado/seleccionado dicho repertorio de instrucciones. Según las instrucciones incorporadas y su funcionamiento, la creación de estructuras sencillas puede variar mucho y por tanto se ha de invertir un tiempo en dominar la arquitectura en cuestión antes de comenzar a programar. Si a esto se le añade los problemas comunes inherentes a la programación de microcontroladores como conocer el patillaje del microcontrolador, realizar la correcta configuración de los puertos y de los distintos periféricos, funcionamiento de los registros especiales, etc., cambiar de fabricante o de microcontrolador se convierte en una ardua tarea.

No obstante, el repertorio de instrucciones es muy limitado, para abordar un ejemplo se ha elegido para el caso, el repertorio de instrucciones de la familia PIC16F87X. Este repertorio de instrucciones consta de aproximadamente 35 instrucciones. Además de estas existe instrucciones como nop que realiza un ciclo sin operación y algunas otras con fines muy concretos, y que no nos interesan para el tema que abordamos. En las siguientes tablas se muestran las instrucciones más importantes. En ellas se puede ver sucesivamente 18 operaciones orientadas a byte, 4 orientadas a bit y 13 operaciones con literales e instrucciones de control.


Mnemonic Operands Description Cycles Status Affected
ADDWF f, d Add W and f 1 C,DC,Z
ANDWF f, d AND W with f 1 Z
CLRF f Clear f 1 Z
CLRW - Clear W 1 Z
COMF f, d Complement f 1 Z
DECF f, d Decrement f 1 Z
DECFSZ f, d Decrement f, Skip if 0 1(2) -
INCF f, d Increment f 1 Z
INCFSZ f, d Increment f, Skip if 0 1(2) -
IORWF f, d Inclusive OR W with f 1 Z
MOVF f, d Move f 1 Z
MOVWF f Move W to f 1 -
NOP - No Operation 1 -
RLF f, d Rotate Left f through Carry 1 C
RRF f, d Rotate Right f through Carry 1 C
SUBWF f, d Subtract W from f 1 C,DC,Z
SWAPF f, d Swap nibbles in f 1 -
XORWF f, d Exclusive OR W with f 1 Z


Mnemonic Operands Description Cycles Status Affected
BCF f, b Bit Clear f 1 -
BSF f, b Bit Set f 1 -
BTFSC f, b Bit Test f, Skip if Clear 1 (2) -
BTFSS f, b Bit Test f, Skip if Set 1 (2) -


Mnemonic Operands Description Cycles Status Affected
ADDLW k Add Literal and W 1 C,DC,Z
ANDLW k AND Literal with W 1 Z
CALL k Call Subroutine 2 -
CLRWDT - Clear Watchdog Timer 1 TO,PD
GOTO k Go to Address 2 -
IORLW k Inclusive OR Literal with W 1 Z
MOVLW k Move Literal to W 1 -
RETFIE - Return from Interrupt 2 -
RETLW k Return with Literal in W  2 -
RETURN - Return from Subroutine 2 -
SLEEP - Go into Standby mode 1 TO,PD
SUBLW k Subtract W from Literal 1 C,DC,Z
XORLW k Exclusive OR Literal with W 1 Z


Para más detalles acerca de dichas instrucciones se puede recurrir a la sección 15 del datasheet de los PIC de la familia 16F78X. Por poner un ejemplo, el código requerido para la programación de un if debería ser como el siguiente:

Código C++:

        .....
        if(a==0){
            f();
        }else{
           ....

Código Generado con compilador SDCC:

Ciclos   Instrucción
  (1)     BANKSEL    _a  ;Se selecciona el banco de memoria donde está la variable a
  (1)     MOVF    _a,W    ;Se mueve a al acumulador
  (1)     IORWF    (_a + 1),W  ;Se realiza una OR lógica entre (a+1) parte alta H(a) de a 
                                        ; y el acumulador parte baja de a L(a)
  (1,2)  BTFSS    STATUS,2    ;Se comprueba el estado del bit de Zero (si es 1 se salta la siguiente
                                               ; instrucción) si el bit es set (1 ciclo sino 2 ciclos)
  (2)     GOTO    _00110_DS_  ;Si la OR lógica entre H(a) y L(a) no es cero se salta al else del if
  (2)     CALL    _f         ; De lo contrario significa que a vale 0 y por lo tanto se ejecuta el interior del if


Como se puede comprobar en el código anterior, el funcionamiento de la comparación de la variable a con cero, se realiza de una forma muy dependiente de la maquina y que porbablemente se haya elegido por rendimiento frente a otras opciones.  En el mejor caso este código tarda 6 ciclos y en el peor caso el código tarda 7 ciclos. Sin embargo al realizar una comparación con uno se puede comprobar que el código generado no es el mismo:

Código C++:
        .....
        if(a==1){
            f();
        }else{
           ....

Código Generado con compilador SDCC:

 Ciclos   Instrucción
  (1)     BANKSEL    _a  ;Se selecciona el banco de memoria donde está la variable a
  (1)     MOVF    _a,W    ;Se mueve a al acumulador la parte baja de a
  (1)     XORLW    0x01  ;Se realiza una XOR entre el acumulador y el valor 1 de manera que si difiere en 
                                       ;algún bit el resultado será disinto de 0
  (1,2)  BTFSS    STATUS,2    ;Se comprueba el flag de cero Z 
                                                ;(si está activo se salta la siguiente instrucción)
  (2)     GOTO    _00112_DS_   ;Si es distinto de cero quiere decir que no vale 
                                                  ; uno y portanto sale del if con esta instrucción GOTO

           ;A continuación se realiza la misma opreación para la parte alta de a

  (1)     MOVF    (_a+1),W       ;Se mueve la parte alta de a al acumulador
  (1)     XORLW    0x00            ;Se realia una XOR entre el acumulador y cero
  (1,2)  BTFSS    STATUS,2      ;Se comprueba el estado del flag cero Z 
                                                  ;Si está activo se salta la siguiente  instrucción y ejecutaría el código del if
  (2)    GOTO    _00112_DS_  ;Si el flag de cero no está activo significa que la parte de a no vale cero
  (2)     CALL    _f         ; Este sería el código interno del if

Como se puede ver este último código se puede generalizar y se podría emplear para realizar la comparación también con cero simplemente sustituyendo el valor de la segunda instrucción por 0x00 al igual que se hace con la parte alta de la variable a. Sin embargo, el primer código que generó el compilador es más óptimo ya que este último tiene un mejor caso de 6 ciclos (correspondería al caso en que la parte baja de a no vale 1) y un peor caso de 11 ciclos (se ejecutarían las instrucciones 1,2,3,4x2,6,7,8x2 y 10). Al programar directamente en ensamblador, este tipo de optimizaciones corren por cuenta del programador y puede que se le ocurra realizar las tareas de esta forma pero también corre el riesgo de que no sea así. Además, por lo general estas optimizaciones dependen enormemente del microcontrolador que se está empleando por lo que requeriría de un profundo estudio para encontrar las técnicas más eficientes y una gran disciplina para ser metódico y hacer las cosas siempre de la misma forma, y no usar diferentes métodos según se le vayan ocurriendo.

En conclusión, al programar en C++ un programador tiene la ventaja de abstraerse de este tipo de detalles dejándolos para el compilador. En C++ las estructuras if, for o while siempre son de la misma forma y por lo tanto el programador no necesita preocuparse por estos detalles. Además, como se ha demostrado en muchos casos el compilador va a realizar códigos muy eficientes de manera automática. 

No obstante, cabe destacar que no es oro todo lo que reluce. Cuando se programa un microcontrolador en C++ hay que tener en cuenta ciertos detalles, que pueden pasar desapercibidos por un programador experto que desconozca la arquitectura sobre la que se está trabajando. La mayoría de estos detalles, se refieren a la forma de trabajar de un compilador. Es muy importante que el programador tenga conocimientos de bajo nivel sobre compiladores para que tenga en cuenta estos detalles, sin perder de vista que no todos los compiladores tienen porque generar el mismo código, pero que sí es frecuente que sigan unas ciertas pautas comunes a todos ellos. A groso modo, se puede adelantar que por regla general un switch es más eficiente que una serie de estructuras if else, que un if else if es mejor que varios if independientes consecutivos cuando los casos son excluyentes, que las llamadas a funciones consumen pila del microcontrolador y que en el caso del 16F876X esta pila está limitada a 8 niveles. En futuras entradas se comentarán estos y otros detalles acerca de la programación de este microcontrolador en C++ intentando que la mayoría de ellos se pueden extrapolar a otros casos.

lunes, 2 de enero de 2012

Comenzando por lo Básico (2): "Diseños basados en microcontrolador"

En la última entrada se introdujeron los conceptos que se emplearán en el blog para referir a diferentes tipos de equipo que comúnmente se pueden encontrar en la industria. Como se comentó, desde el punto de vista del control podemos encontrar, partiendo desde lo más sencillo a lo más complejo:

  • Electrónica pura
  • Diseños basados en microcontrolador
  • Electrónica Reconfigurable
  • PLCs
  • PCs industriales

Continuando con la descripción de estos equipos, hoy voy a describir los diseños basados en microcontrolador.

Diseños basados en microcontrolador: Cuando se habla de diseños basados en microcontrolador, normalmente nos referimos a placas electrónicas que incorporan para su funcionamiento un microcontrolador, de cualquier tipo o marca. La ventaja de este tipo de diseño, es su versatilidad, ya que un microcontrolador no es más que un minicomputador que integra la RAM, ROM, Memoria de programa y datos (flash), CPU, entradas/salidas digitales y analógicas y un conjunto de periféricos que podemos emplear en los programas, como son: Convertidores Analógico Digital, Temporizadores, Contadores, Dispositivos de interrupción externos, unidades de comunicación serie universales USART, unidades de comunicación UART de sistemas específicos como I2C, SPI, CAN, Ethernet,etc.

El uso más común de estos microncotroladores es centralizar el control del sistema recibiendo señales a través de las entradas digitales o analógicas, y empleando las salidas digitales y analógicas para mandar señales al resto del circuito de forma que estas decisiones se toman en función de la lógica programada en el microcontrolador. Por lo general, en las entradas del microncontrolador se conectan sensores que permiten obtener información acerca del entorno, o botones que permiten a un operador mandar señales de usuario al sistema, y las salidas se emplean para realizar una acción sobre sistemas de actuación como diodos leds, motores, displays, etc.

Estos microcontorladores pueden ser de tipos muy sencillos, básicos y baratos como los archiconocidos 16F84 ó su hermando mayor el 16F876A ambos de microchip. Se trata de microcontroladores de 8 btis, sencillos y fáciles de programar dada su arquitectura RISC cuyo repertorio de instrucciones (ensamblador) es muy reducido y por tanto fácil de manejar. Son válidos para la mayoría de los proyectos que se puedan plantear, por poner dos ejemplos de uso, las viejas tarjetas programables empleadas para piratear los dispositivos de Canal+ empleaban el microcontrolador 16F84 y muchos otros proyectos de electrónica sencilla como el robot para principiantes Skybot emplea el microcontrolador 16F876A. Microchip es una de las marcas lideres en el campo de los microcontroladores ya que tienen una amplia gama, cuyas especificaciones permiten encontrar uno que cubra los requisitos de una determinada aplicación, por un precio muy ajustado. Dada la enorme popularidad de estos microcontroladores y su bajo coste existe multitud de proyectos, esquemas y páginas dedicadas a este tema en la Web, aquí os dejo una página con recursos sobre microchip.

Otros de los microcontroladores de 8 bits más famosos del momento, son los pertenecientes a la familia AVR de ATEML. El uso de los microcontroladores ATMEGA 128/328/1280/2560 en las placas de prototipo ARDUINO han dado a conocer sus altas prestaciones y han popularizado el uso de los bootloaders. Las arquitecturas AVR como estas, están especialmente diseñada para ejecución eficiente de código C. Esto supone una gran ventaja sobre los PIC en diseños con un alto requerimiento de complejidad en el control, aunque los microcontroladores PIC pueden ser programados en C hay que tener cuidad con ciertos detalles. Entre otras cosas, hay que tener especial cuidad con la pila del microcontrolador, cuando se llama a una función o se está evaluando una sentencia if, se van apilando resultados intermedios, parámetros y direcciones de retorno en la pila. La pila de los microcontroladores PIC es de 8 niveles, por lo que el uso indiscriminado de estos mecanismos, puede dar lugar a que la pila se llene no pudiendo almacenar más valores y en consecuencia obtener comportamientos aparentemente ilógicos. Esto no ocurre con los microcontroladores ATMEGA de la familia AVR cuya arquitectura Hardvard les proporciona una memoria de datos y una memoria de programa independientes, de esta manera son capaces de utilizar una pila dinámica en la memoria de datos SRAM (RAM estática)

Además de las dos comentadas, existen muchas otras marcas que se dedican a fabricar microcontroladores como ST, Freescale antes perteneciente a Motorola, Intel, National Semiconductors, entre otros. También existen arquitecturas de microcontroladores de 16 bits y 32 bits que permiten realizar sistemas más complejos con mayor necesidad de cálculo, mayores requerimientos de velocidad o necesidad de periféricos avanzados como UART extra, CAN ó Ethernet. Estas arquitecturas son conocidas como ARM, en esta familia se incluyen microcontroladores como los Cortex-M3 ampliamente utilizados por su buen equilibrio entre simplicidad, coste y potencia, como ejemplos de uso podemos encontrar el robot uXbot y un proyecto imitador de ARDUINO llamado MAPLE y que ofrece una buena alternativa cuando ARDUINO no dá la talla. Las arquitecturas ARM de ATMEL ofrecen un excelente abanico de funcionalidades, por ejemplo el microcontrolador SAM7X512 empleado en el proyecto NetDuino, otro imitador de ARDUINO, en este caso incorpora un RunTime del Micro Framwork 4.1 de .NET lo que nos permite programarlo en C# a través del IDE de Microsoft Visual Studio Express.

Otro problema clásico en la programación de microcontroladores es la necesidad de una placa entrenadora. Estas llamadas placas entrenadoras son las tradicionalmente utilizadas para grabar los programas en las las memorias EPROM o EEPROM de los microcontroladores, además estas placas suelen ser caras y es complejo programar el microcontrolador ya que para ello hay que extraerlo de la placa de aplicación e introducirlo en la entrenadora cada vez. No obstante, para solucionar este problema se suelen emplear los conocidos bootloader, que no son más que unos programas que preparan lo básico en el microcontrolador para que esté listo para funcionar. Estos bootloader son grabados en los microcontroladores inicialmente y a partir de ese momento permiten grabar nuevos programas en el chip a través del puesto serie. Esta funcionalidad se debe a que en cada reinicio, el bootloader encuesta el puerto serie del microcontrolador, en caso de haber alguien a la escucha (software de programación), recoge los datos enviados por el software y los mete en una dirección de memoria del microcontrolador que usa como memoria de inicio de programa, en caso de que no haya datos disponibles en el serie o de que ya se haya terminado de grabar el programa salta a esa dirección de inicio y ejecuta el código que haya a partir de ella. De esta manera es posible programar a posteriori el microcontrolador a través del puerto serie de una forma muy simple y cómoda. En este principio es en el que se basan los diseños comentados anteriormente como ARDUINO, NetDuino, uXbot, MAPLE, etc. Para ello, proporcionan un IDE de desarrollo que permite escribir el código en lenguajes de alto nivel y permiten grabar los programas generados en chips que tenga cargado el correspondiente bootloader. De esta manera el problema se reduce a grabar un único programa mediante la tarjeta entrenadora, el bootloader, y el resto ya serán grabados empleando un protocolo serie a través de la USART.

A pesar de estas pequeñas cuestiones, los diseños basados en microcontrolador siguen siendo una alternativa segura y muy flexible con respecto a los diseños basados únicamente en componentes electrónicos no programables. Siguen ofreciendo una alta estabilidad, siempre que se haya verificado la parte electrónica y que se haya comprobado adecuadamente que no existen posibilidades de que la pila del microcontrolador se llene, lo que provocaría comportamientos anómalos. Aún así, la verificación de estos sistemas es compleja, dado que se habrá de tener en cuenta toda posible sucesión de eventos, incluyendo aquellos que pueden producirse de forma imprevista o asíncrona, como una interrupción interna provocada por un timer, o bien una interrupción externa provocada por las comunicaciones o por una entrada de interrupción. Una solución, para que no se apilen interrupciones solapadas puede ser inhibir las inrerrupciones cuando se está atendiendo una de ellos. Para supervisar que el comportamiento sea adecuado, los microcontroladores incorporan los Watchdog Timer que permiten reiniciar el sistema en caso de que quede en un estado anómalo.

En resumen, la electrónica basada en microcontrolador permite una corrección de errores mas flexible ya que en muchos casos se puede realizar a través de una actualización del software, y permite realizar operaciones mucho más complejas de una forma más sencilla. Como desventaja, requieren de una mayor formación ya que es necesario tener buenos conocimientos de electrónica, al igual que en el caso de los sistemas basados en electrónica pura, y además requiere conocimientos de programación y nociones de arquitectura de Computadores para poder sacar el máximo partido al microcontrolador. Sin ninguna duda, esta es la opción por la que se está decantando el mercado actualmente, por tratarse de una opción intermedia entre los PLC y la electrónica pura, son flexibles por ser programables como un PLC y económicos como los circuitos basados en electrónica pura.

Más adelante veremos en detalle algunas de las múltiples cuestiones que han aparecido hoy como las comunicaciones, los bootloader, el acople de sensores y actuadores o el uso de los periféricos de los microcontroladores.