Programar como un electrónico

Programando hoy en día las llamadas librerías nos quitan un montón de trabajo, el mejor ejemplo de esto es programar en el Arduino IDE. En él ni siquiera te tienes que preocupar de importar las librerías más usadas, y puedes cambiar de placa sin tener que cambiar ni un poquito del código que has escrito, es casi magia.

Si no sabes de qué te estoy hablando, no pasa nada, se explicará según avance este hilo.

Pero hubo un tiempo, no muy lejano, en el que las cosas eran más complejas y se programaban con ensamblador. Este es el lenguaje de programación más puro (por describirlo de alguna forma) al que te vas a enfrentar cuando programes algo, aunque estoy seguro de que no lo tendrás que usar porque, como digo, es cosa del pasado. Sin embargo está bien, estuvo bien por mi parte aprender eso, porque da una visión mucho más cercana de lo que es en realidad un sistema de este tipo, un microcontrolador, un procesador, su memoria, por dónde van los datos... Que parece innecesario, pero luego hasta los ordenadores se manejan con esto en el fondo. Y eso es lo que quiero transmitir.

Como decía, el ensamblador es el lenguaje de programación más básico, lo que le transmites al programador con lo que escribes no es más que mover unos bits aquí y allá, hacer operaciones lógicas básicas, todo funciona leyendo y escribiendo registros.

¿Qué es un registro?

Bueno, no me voy a poner ahora a explicar qué tiene un ordenador por dentro (RAM, CPU, almacenamiento, buses de datos), pero sí extender esto a los microcontroladores.

En primer lugar, el programa se escribe en la memoria permanente del micro, ahora por lo general son memorias tipo FLASH, ahí se almacena en unos y ceros todo el programa y es el único lugar donde se mantienen los datos inalterados aunque se apague y se encienda. A no ser que el micro tenga otra memoria para datos del usuario, por ejemplo una EEPROM, que ahí también son permanentes, hasta que los borres.

En los micros la CPU es un concepto un poco abstracto, ya que está desperdigada por el resto de componentes. Se encarga de leer la memoria de programa, mover los datos y llevarlos a la unidad aritmético-lógica (ALU), el lugar donde se realizan operaciones matemáticas o lógicas muy simples, como sumar, multiplicar o hacer AND u OR.

Hay memoria RAM, está por ahí para guardar las variables mientras corre el programa, esas variables que se borran tras el apagado.

Hay vida más allá de estos tres componentes, hay lo que se llama registros. Un registro es como otra memoria que se borra al apagar el micro, y está hecha de la misma forma, de una serie de bits. Además tiene una dirección, a la que la CPU puede ir para leer o escribir los datos. Es igual que una memoria simple, pero los registros tienen funciones especiales. Vamos a ver un ejemplo.

El micro está leyendo el programa en la FLASH, y el programa le dice que escriba dos variables en la RAM, que son dos números en binario 110 (6) y 010 (2), y quiere que esas dos variables se sumen. El proceso no es nada simple.

En primer lugar va a coger uno de esos números y lo va a poner en un registro auxiliar (el registro W por ejemplo en el PIC), para ello tiene que pasar por la ALU sin realizar ninguna operación.

En el siguiente paso va a coger el otro número y lo va a poner en la ALU a la vez que el primer número, que estaba en el registro auxiliar, también entra en la ALU por el otro lado. La ALU tiene la instrucción de sumar y obtiene 1000 (8) que se guarda en, ojo cuidado, el registro auxiliar. Una vez terminada la operación hay un registro de estado (STATUS en el PIC), que tiene un bit que se pone a 1 cuando la ALU ha terminado. Se mira este bit y como ya lo hemos visto, hay que decirle que se ponga a 0 para que avise la próxima vez.

Ya sabemos que el resultado está preparado, ahora se puede mover a la RAM para guardar el resultado o incluso enviarlo a un registro de los periféricos, por ejemplo un pin de salida digital, si se envía al registro del puerto digital (p.ej. PORTB), esto haría que (con otras configuraciones previas) el pin correspondiente al bit número 4 de ese registro se pusiese en 1, y encendería... No sé, digamos que se enciende el LED, que siempre queda bonito.

Espero que con esto más o menos ya tengas una idea de qué es un registro. Es eso, un lugar en las direcciones de memoria del micro donde cambiamos bits para que ocurran ciertas cosas. Ya estamos un poquito más cerca de entender qué hace un código en C.

Direcciones

Ahora hablemos de las direcciones, hemos dicho que los registros son lugares en la memoria y que la CPU puede dirigirse a ellos mediante su dirección. Esta viene dada en las hojas de características del micro, es decir, te lo chiva el fabricante, y en numeración hexadecimal, lo que no lo hace muy amigable pero sí algo más compacto. Y tu se lo transmites al programa previamente, aunque aún no lo sepas.

Continuando con el ejemplo, en ensamblador tu no le dices al programa "manda lo que hay en el registro auxiliar al puerto b", lo que dices realmente es "mueve lo que hay en el registro auxiliar al registro con dirección 0x06". En este caso no tienes que decir la dirección del registro auxiliar porque ya está implícita en la función de ensamblador, MOVWF. Al fin y al cabo casi todo lo que se mueve pasa por ese registro, así se escribe menos. Vamos a escribir esta línea en ensamblador:

MOVWF 0x06

Nota: no todo lo que se mueve en todos los micros pasa por ese registro, pero sí la absoluta mayoría. Los más actuales tienen un módulo llamado DMA (direct memory access) que evita precisamente eso, pasando directamente de un registro a otro o a la memoria, con lo que la CPU puede usar el registro auxiliar para otra cosa mientras tanto.

Librerías

Pero menudo lío, ¿no? Pues llegan al rescate las librerías. ¿Qué hacen las librerías? Predefinir las cosas para que no lo tengas que hacer tu, y con esto me refiero a que, con la tecnología tan avanzada que tenemos, podemos hacer lo siguiente:

MOVWF portb

¿Qué acaba de hacer esta librería? Asignar un nombre bonito a una dirección de memoria, para que no tengamos que recordar los numeritos, ha hecho algo tan simple como esto:

portb equ 0x06

Hagamos otro ejemplo, esta vez vamos a poner a 1 un pin del puerto b directamente, pero sin alterar el estado del resto de los pines como hemos hecho (ups) accidentalmente antes. Para ello el ensamblador tiene que hacer:

  • Leer el puerto b y llevarlo al registro auxiliar W
  • Operar con un número binario cuyo resultado cambie el bit exacto que queremos sin alterar el resto
  • Limpiar el bit de STATUS que indica que ha terminado la operación
  • Llevar el resultado del registro W al registro del puerto b

¿Qué has hecho tu en un código C con librerías? Si estamos en Arduino IDE por ejemplo:

digitalWrite(pin4, HIGH)

Pero la ejecución es exactamente la misma, y esto ya viene hecho de antes por otra persona entonces no tienes que preocuparte.

"Bueno no es para tanto"

Pues no, no es para tanto si hablamos de un micro de 8 bits como es el PIC o los más comunes Atmel de Arduino, en estos la dirección de un registro se puede describir mediante un número hexadecimal de 2 dígitos, por ejemplo el 0x06 que hemos visto antes.

PERO. Luego vienen los micros de 32 bits. Los ordenadores son de entre 32 y 64 bits, recordemos. En estos casos la dirección del registro se tiene que escribir con 8 dígitos. 0x00000000. Ya va empeorando el asunto, pero esto no es nada.

En un micro de arquitectura ARM, la configuración de cada uno de los pines alcanza tal grado de complejidad (resistencias pullup/down, multiplexores, funciones analógicas, pwm, comunicación, velocidad, etc etc etc) que los registros empiezan a tener registros dentro. Y sin embargo, gracias a las librerías, logramos compactarlo todo a una serie de instrucciones muy simples y fáciles de recordar, por ejemplo lo que expliqué aquí.

¿Pero entonces se usa para algo el ensamblador? No, el ensamblador se ha sustituído completamente por las librerías, pero sí que usamos de vez en cuando un sucedáneo. Ya no nos gusta escribir la función movwf, sino que podemos hacer algo como:

portb = 0x08

Y en ocasiones, se da que no existen librerías para lo que quieres o simplemente lo vas a hacer más rápido tu directamente que ponerte a buscarlo, por lo que sí que puede pasar que escribas direcciones a mano...

0x06 = 0x08

Como esto cambiaba todo el puerto voy a hacer algo que sólo cambia el pin 4:

0x06.4 = 1

¿Entonces qué es eso de programar como un electrónico?

La programación que se hace en los ordenadores es muchísimas veces independiente del hardware, es decir, vas a hacerlo funcionar en cualquier ordenador, y no importan sus registros, direcciones, ni su configuración porque eso ya lo han implementado previamente los fabricantes de cada uno de los componentes (drivers) y se ha agrupado todo bajo el sistema operativo para hacerlo funcionar sin que te enteres.

En electrónica programamos microcontroladores muy dependientes del hardware y sus periféricos. Cuando hacemos una placa con un micro, le añadimos una señal de reloj, esta señal tiene que ser tratada previamente para que llegue a todos los periféricos correctamente, y esto se hace a nivel de registros (pero con librerías). Nosotros somos el fabricante. Cuando añadimos las entradas y salidas, configuramos cada uno de los pines para que haga exactamente su tarea. Se trata de saber dónde están y a dónde van los bits que quieres manejar, más que poner una función y dejar que el sistema operativo se preocupe por tí.

Estos métodos tienen ciertas ventajas, un mayor control sobre lo que estás haciendo y muchas veces, en vez de usar una librería que lo hace todo, puedes encender únicamente los periféricos que necesitas, con lo que ahorras energía. Otra ventaja es que durante el diseño tienes libertad de colocar las cosas como mejor te convenga, sin tener que seguir los esquemas del fabricante.

Espero que os haya parecido interesante

Starter Kit de Arduino

Como vamos a hacer un cursillo de introducción a lo "básico" de Arduino, necesitábamos un kit o por lo menos tener las piezas en casa. Los organizadores pedían 25 euros por el cursillo pero por unos 35 más te traen éste:

http://www.aliexpress.com/snapshot/260781748.html

Ahora mismo su precio se sitúa por los 39'90 US$ (aprox. 30€) por pack, envío gratis como suele ocurrir con éstas páginas chinorris que, si bien el producto es bueno, el tiempo de llegada puede ser desesperante (20 a 40 días). Incluye:

  • Placa Arduino Uno Rev. 3
  • Mini placa de pruebas con base
  • Placa de pruebas de 830 puntos
  • Caja para componentes
  • 15 LEDs rojos, amarillos y verdes (5 de cada)
  • 2 Buzzers (uno pasivo)
  • 4 interruptores
  • 2 dígitos LED
  • 2 interruptores de vuelco "tilt switch"
  • 3 fotoresistores
  • 1 potenciometro
  • 1 sensor de llamas
  • 1 receptor infrarrojo
  • Resistencias de 220, 1K y 10K (5 de cada)
  • Sensor de temperatura LM35
  • 1 cable USB
  • 30 cables jumper
  • 1 porta pilas de 9V
  • 1 control remoto infrarrojo
  • Pantalla LCD 1602
  • 1 servomotor 9G
  • 1 motor paso a paso 5V + placa driver ULN2003
  • 1 74HC595 (shift register, para manejar matriz de LEDs)
  • 1 Matriz de LEDs 8x8

Como se ve, vale la pena y esperamos que el producto sea de gran calidad como ha llegado en otras ocasiones. Ellos se ocuparán de testearlo antes de dar el cursillo, que empezará, si no hay retrasos en el envío, el 28 de septiembre.

En todo caso para más info y mis agradecimientos a MakersVLC.

Habrán más ediciones si ésta resulta buena.

 

Manejo de bits en C

En ésto se incluye el LED que parpadea o varios LEDs que se encienden y se apagan con distintos tiempos. Todo depende de cómo se programe. Lo que voy a hacer va a ser escribir un programa y sus posibles variantes y explicarlo, así vamos a dar juego a todas las funciones que nos interesan. Como dije en la introducción vamos a seguir un orden preestablecido. Usaremos en ésta ocasión el micro más sencillo y que se usa para prácticas, el PIC16F84.

// Declaración de librerias, constantes y fuses

#include <16f84.h> //Hacemos saber que vamos a usar éste uC
#FUSES XT,NOWDT    //Utilizaremos un osc. de 4Mhz y sin el WD timer
#use delay(clock=4000000) //El tiempo de delay se hará según el osc. de 4Mhz

// Declaramos los bytes del puerto B y su estado(TRIS)

#byte portb=0x06
#byte trisb=getenv("SFR:TRISB")

//Declaramos los bits del puerto B que vamos a manejar uno a uno

#bit b0=0x06.0
#bit b1=0x06.1
//etc. si queremos más

// Pasamos a programar el principal //
// Ejemplo encendido y apagado de un pin
void main(){
  trisb=0x00; //Declaramos como salidas todo el puerto
  while(1){   //Ponemos el programa en bucle infinito
    b0=1; //encendemos el pin b0
    delay_ms(1000); //esperamos un segundo
    b0=0; //apagamos b0
    delay_ms(1000); //volvemos a esperar
  }  //Cerramos while
} //Cerramos main
// Con éste ejemplo podemos hacer parpadear una luz o controlar un
// motor por modulación de ancho de pulso si son tiempos pequeños

// Ahora vamos a hacer que parpadeen 2 pines a distinto tiempo
void main(){
  trisb=0x00;
  while(1){
    b0=1; b1=0;
    delay_ms(500);
    b0=0; b1=1;
    delay_ms(500);
  }
}
// Es muy sencillo gracias a las funciones por defecto en C

//Ahora os voy a enseñar a hacer que los pines roten, es decir que
// se enciendan y se apaguen cada vez uno de los 8, 
// sin tener que escribirlo todo a mano

int i,c; //Declaramos una variable de apoyo que será un byte y otra

void main(){
  trisb=0x00;
  portb=0x00;
  while(1){
    i=0b00000001;
    portb=i; //pasamos el byte i al puerto b encendiendo b0
    c=0; //Inicializamos c a 0
    while(c<7){  //mientras c no sea mayor que 7 vas a hacer...
      delay_ms(500);
      rotate_left(&i,1); //rotas el byte i a la izquierda
      portb=i; //y lo pasas a port b encendiendo el siguiente pin
      c++; //sumas uno a c
    }
    delay_ms(500);
  }
}

//Si la funcion rotate_left no os la compila es porque debe estar en
// la libreria <string.h> que se debe declarar al principio como
// #include <string.h>
//También podéis hacer que al final del puerto se de la vuelta y
// rote en el sentido contrario con rotate_right
//Pero éso os lo dejo a vosotros para que os calentéis la cabeza 

Y éste es el fin del programa, espero que os haya resultado interesante y hayáis aprendido un poco de él

Introducción a la programación en C

Mucho más sencillo y organizado que el ensamblador, que ya dejé atrás hace unos meses pero que no dejaré de utilizar este curso supongo... Hay que tener algo de experiencia en programación en C.

Sin embargo C tiene las mismas historias: necesita seguir un esquema y unas partes, con sus funciones, declaración de variables, puertos, etc. Por lo tanto haré esta introducción para tener la base a mano.

Necesitaréis un programa para compilar lo que hagáis, yo estoy utilizando PIC C Compiler. Recordar con cualquier compilador que hay que seleccionar el micro que se va a usar.

Declaración de librerías, fuses, puertos y constantes

 
#include <pic.h>

Donde pic es el numero del micro, por ejemplo 16F84A

#fuses ...

Aquí podemos poner varias cosas que queremos o no que nuestro micro use, separadas por comas:

- LP, XT, HS, RC (Usar cristal externo hasta 200 khz, de 4 a 10 Mhz, a partir de 8 Mhz o un oscilador resistencia-condensador)

- NOWDT (desactiva el watchdog timer)

- PWRT (delay al encender el micro para estabilizar tensión)

- CP (code protect, que no se pueda copiar el código de la memoria)

Éstos son los básicos y más necesarios pero hay bastantes más que se especifican en la datasheet de cada micro.

#byte x=y (nombre=dirección)

Para nombrar una dirección de un byte. Así se nombrarán puertos, por ejemplo PORT_A suele estar en la dirección 0x05: #byte PORT_A=0x05

Para evitar problemas que dan a veces simuladores y micros también declararemos aquí el estado del puerto (E o S), de ésta forma:

#byte TRIS_A=getenv("SFR:TRISA");

También podemos declarar bits (patillas) mediante:

#bit x=y.z (nombre=puerto.numero)

Declaración de funciones, variables, interrupciones y programación

Llegados a este punto lo que hacemos es declarar las variables (int, char, float...) y las funciones que programaremos después. Ésto no es necesario si no se van a usar.

Después de declararlas programamos la interrupción poniendo #Nombredeinterrupcion y en la linea siguiente programando la función de interrupción que se llevará a cabo. Por lo general el nombre de la interrupción ya está definido por las posibilidades del micro, por ejemplo int_ext (RB0/INT) o int_rb (RB4-RB7) dependiendo de donde cambie el estado del pin.

A continuación programamos las funciones que hemos declarado antes de las interrupciones.

Main, cuerpo del programa

Aquí programamos lo que queremos que el micro haga con todo lo que hemos declarado antes. Se empieza por un "void main() {" y se cierra } al final. Las funciones las iré explicando poco a poco según ponga ejemplos.

Sin duda hay mucho más que se puede hacer, puerto esclavo con conexion a ordenador, LCD, lectura analógica, y otras utilidades, pero éste es el cuerpo básico de un programa y se puede sacar mucho jugo de 4 funciones cualquiera. Ánimo y a por ello.

 

 

PIC16F84, información básica

Este microcontrolador ha sido reemplazado por otros más modernos y con mayores prestaciones. Ver PIC16F628A.

Ésta es la información básica necesaria para entender al 16F84. Es el más usado cuando se trata de entrenarse en microcontroladores.

El sujeto en cuestión es éste menda:

Cuyas patillas son:

Para que nos entendamos, GND es la conexión a tierra, +VE es por donde pondremos la alimentación del micro, 5V. MCLR es la patilla del reset, cuya utilidad se explicará al simular, y también por donde se aplica la tensión de 12V para programar el micro. OSC1 y OSC2 son la entrada y salida del oscilador, cuyo valor recomendado son 4Mhz.

Éste micro dispone de 5 patillas de interrupción. De la RB4 a la RB7, se activan en rbinte y su bandera es rbintf, también hay interrupción en el pin RB0, activada en inte y con bandera en intf.

En total contamos con 8 E/S e el puerto B y 5 en el puerto A.

En memoria EEPROM, tenemos suficiente para grabar un programa con 2048 líneas.

Es importante conocer mínimamente el mapa de memoria del microcontrolador para no perder tiempo en revisar la datasheet en cosas básicas, de todas formas, aquí se encuentra toda la información, mapas de memoria y sus bits internos:

http://ww1.microchip.com/downloads/en/devicedoc/35007b.pdf

En Status (0x03) se encuentra el bit que selecciona el banco de memoria en el que nos estamos moviendo, RP0 (5), y Z (2), una variable que nos indicará si una operación lógica es verdadera o falsa,

PORTA y PORTB (0x05 y 0x06), en el banco 0 de memoria, es donde indicamos si cada pin está en high o low (hay tensión en él o no).

TRISA y TRISB (0x05 y 0x06), en el banco de memoria 1, es donde indicaremos al principio del programa si cada pin es de entrada o salida.

INTCON (0x0B) es donde se indican las interrupciones. Cada bit tiene una función, por ejemplo, el bit 7 (GIE) habilita las interrupciones en general, y luego se debe ir indicando en los otros bits qué interrupciones queremos.

Entre 0x0C y 0x4F se encuentra la memoria RAM de uso general, donde almacenaremos variables y constantes que queramos utilizar. Hay que tener en cuenta que ésta memoria se borra al resetear el micro.