Un compilador BASIC para microcontroladores PIC

Uno de los compiladores de lenguaje BASIC para microcontroladores PIC que está atrayendo legiones de usuarios es MikroBasic. Si bien ya lleva un tiempo en el mercado, a partir de la versión 5.00 se ha masificado. Afortunadamente, el hobbysta no necesita aprender el lenguaje Assembler ni conocer a fondo oscuras herramientas de programación para dotar de software a sus proyectos con microcontroladores.

La empresa mikroElectrónica distribuye una serie de compiladores para microcontroladores, entre los que se destacan mikroC y mikroBasic. La característica más destacada de estos compiladores, y en particular del que nos ocupará en esta serie de artículos, es la inclusión de un IDE (entorno de desarrollo integrado o, en inglés, Integrated Development Environment) que hace muy cómoda la programación, ya que resalta la sintaxis del lenguaje, proporciona acceso muy rápido a la excelente ayuda incluida, estadísticas sobre el uso de recursos del microcontrolador y muchas ventajas más.

Además, mikroElectrónica nos permite descargar una versión gratuita del compilador que, a pesar de estar limitado en la generación de código a 2Kb., es más que suficiente para muchos proyectos y, sobre todo, sirve perfectamente para que podamos aprender el lenguaje. Probablemente mikroBasic sea el entorno que soporta más modelos de micros y además dispone de un enorme grupo de librerías, divididas en comunicaciones RS-232, RS-485 e I2C; teclados PS/2, conexiones USB, interfaz para LCD, y un larguísimo etc.

Nos proponemos a lo largo de estos artículos profundizar en el set de instrucciones de mikroBasic.

MikroBasic - Primera Parte
IDE de mikroBasic, uno de sus puntos fuertes.

Estructura de un programa en mikroBasic

Respecto de la organización interna del programa, debemos saber que es necesario que las partes que lo componen (funciones, rutinas, etc.) sigan ciertas reglas en su escritura. No es necesario que todas estén presentes. Las secciones obligatorias son “program” y el bloque “main-end”. Las demás, opcionales, solo las usaremos si las necesitamos.

program <nombre del programa>
include <incluir otros modulos>
'******************************************************** Declaraciones (globales, disponibles en todo el programa): '********************************************************
'Constantes
const ...
'variables
dim ...
' Simbolos
symbol ...
' Procedimientos
sub procedure nombre_del_procedimiento(...)
<declaraciones locales> ...
end sub
' Funciones
sub function Nombre_de_la_funcion(...)
< declaraciones locales> ...
end sub
'********************************************************
'* Programa principal:
'********************************************************
main:
' Aquí escribimos nuestro código
end.

Ahora, empecemos a ver sus instrucciones más elementales:

Comentarios

Se puede (¡y se debe!) comentar el código usando el apóstrofo "'". Los comentarios pueden ir solos en una línea, o a continuación de una instrucción cualquiera. No se permiten comentarios de más de una línea:

' Esto es un comentario
A = 10
' y esto también….

Estos comentarios son los que nos permitirán realizar modificaciones o mejoras en nuestro código sin necesidad de perder horas intentando comprender lo que escribimos tiempo atrás.

Identificadores

Los llamados “identificadores” son en realidad los nombres de nuestras variables, procedimientos, funciones, constantes, etc. En mikroBasic los nombres de identificadores pueden contener letras desde la “a” hasta “z” y desde la “A” hasta “Z”, el guión bajo (“_”) y los dígitos del “0” al “9”. El primer carácter no puede ser un dígito. Además, los nombres de los identificadores no son “case-sensitive”, es decir que “Total”, “total” y “TOTAL” son nombres de la misma variable, y podemos usar cualquiera de ellos por igual.

Correctos:

Temperatura
aux_12
rele_activado

Incorrectos:

3boton 'Comienza con un digito
Aux.12 'Contiene un punto
_salto 'Comienza con un guión bajo
Humedad% 'Contiene un carácter invalido (%)

Variables

Al igual que en otros dialectos Basic, las variables son objetos cuyo valor puede cambiar con la ejecución del programa. Cada variable tiene un nombre único, que se ajusta a las reglas definidas más arriba para los identificadores. Deben declararse con dim antes de ser usadas, y su ámbito de validez varía de acuerdo a la sección en que se declaran (ver la ayuda para más datos sobre esto). La sintaxis de dim es:

dim lista de variables as tipo

La lista de tipos disponibles incluye Byte (8 bits, con valores de 0 a 255), Word (2 bytes, o 16 bits, con valores de 0 a 65535) y Longint (con valores desde -2147483648 a 2147483647). No son los únicos: la lista completa de tipos se puede consultar en la documentación del compilador. Algunos ejemplos válidos de dim son:

dim i, j, k as byte
dim contar, temp as word
dim cadena as longint[100]
MikroBasic - Primera Parte
La opción “code explorer” permite analizar nuestro programa.

Bucles

Tenemos 3 formas de lograr que un grupo de instrucciones se repitan:

FOR – NEXT

Se repite mientras variable sea menor que valor_final. En cada ciclo variable se incrementa en incremento o en 1 si step no está presente.

for variable = valor_inicial to valor_final [step incremento]
 Instrucciones a repetir
next variable

Ejemplo:

s = 0
for i = 0 to n
 s = s + a[i] * b[i]
next i

WHILE – WEND

Mientras que la expresión sea verdadera, las instrucciones a repetir se ejecutarán. Si dentro de esas instrucciones no existe alguna que modifique el resultado de la expresión, el ciclo se repetirá eternamente. Como la expresión se evalúa al comenzar el ciclo, puede ocurrir que si al comenzar el programa la expresión es falsa las instrucciones a repetir no se ejecuten nunca.

while expresión
 Instrucciones a repetir
wend

Ejemplo 1:

s = 0
i = 0
while i < n
 s = s + a[i] * b[i]
 i = i + 1
wend

Ejemplo 2: Un ciclo sin fin.

while TRUE
 Instrucciones a repetir
wend

DO – LOOP

En este caso, las instrucciones a repetir se repiten mientras la expresión sea verdadera. Al evaluarse al final del ciclo, el grupo de instrucciones a repetir se ejecutará al menos una vez.

do
 Instrucciones a repetir
loop until expresión

Ejemplo: Producto escalar entre dos vectores.

s = 0
i = 0
...
do
 s = s + a[i] * b[i]
 i = i + 1
loop until i = n

Cualquiera de estas tres formas de crear un bucle puede ser interrumpida si dentro de las instrucciones a repetir se coloca la instrucción break. Al ejecutarla, el control del programa salta a la sentencia escrita inmediatamente a continuación de next, wend o loop until, según el caso.

Toma de decisiones

Existen dos instrucciones para la toma de decisiones dentro de un programa escrito en mikroBasic:

IF – THEN – ELSE – ENDIF

Las instrucciones a continuación del then se ejecutan si la expresión es verdadera, y las que después del else si es falsa.

if expresión then
 instrucciones
[else
 instrucciones]
end if

SELECT – CASE

Se utiliza para transferir el control a una rama del programa, basándose en una determinada condición. Consiste en una expresión y una lista de sus posibles valores. Las instrucciones a continuación del case else, si existen, se ejecutan en caso de que selector tome un valor para el que no exista una rama case disponible.

select case selector
 case valor_1
 instrucciones_1
 ...
 case valor_n
 instrucciones_n
 [case else
 Instrucciones por defecto]
end select

Saltos

También tenemos dos instrucciones que permiten saltos incondicionales:

GOTO

Generalmente desaconsejada, esta instrucción nos permite transferir el control a cualquier punto dentro del programa. A pesar de estar demostrado que es posible construir cualquier programa sin utilizar nunca esta instrucción, se utiliza con cierta frecuencia. El control del programa se transfiere a la línea que tenga la etiqueta utilizada en el goto:

for i = 0 to n
 for j = 0 to m
 ...
 if desastre
 goto Error
 end if
 ...
 next j
next i
.
.
.
Error: ' código para manejar el error. . .

GOSUB – RETURN

Funciona de manera similar a GOTO, pero con la ventaja de que el control del programa regresa a la instrucción posterior a GOSUB luego de ser ejecutado el return. Esto la hace ideal para crear subrutinas que pueden ser llamadas varias veces y/o desde distintos puntos del programa.

gosub mi_rutina
instrucciones
...
mi_rutina: 'código de la subrutina
...
Return
MikroBasic - Primera Parte
Display de LEDs programado en mikroBasic.

Funciones y procedimientos

Es muy recomendable, por cuestiones de claridad del código y sobre todo para facilitar su mantenimiento, que aquellos grupos de instrucciones que se utilicen más de una vez dentro del programa se escriban como funciones o procedimientos. mikroBasic permite el uso de ambas, y esta es la sintaxis que se debe respetar:

Funciones

sub function nombre_de_la_funcion(lista_de_parametros) as tipo
 [ declaraciones locales ]
 Cuerpo de la funcion
end sub

Procedimientos

sub procedure nombre_del_procedimiento(lista_de_parametros)
 [ declaraciones locales ]
 Cuerpo del procedimiento
end sub

Ejemplo: Una función para elevar un número x a una potencia n cualquiera.

sub function potencia(dim x, n as byte) as longint
dim i as byte
 i = 0
 resultado = 1
 if n > 0 then
 for i = 1 to n
 resultado = resultado*x
 next i
 end if
end sub

Desde el programa principal, si queremos calcular 2^5, debemos escribir algo como esto:

A = potencia (2,5)

Y A tomará el valor de 32 (2^5).

MikroBasic - Primera Parte
Permite colorear el código para facilitar la lectura.

Los puertos de E/S

Cada uno de estos puertos recibe un nombre, generalmente “puertoA”, “puertoB”, etc. En los microcontroladores PIC más pequeños, de 8 pines en total, solo se dispone de un puerto que tiene hasta 6 E/S (los otros dos pines del PIC se destinan a la alimentación del mismo). En los de 16 pines disponemos de dos puertos, el portA que tiene entre 5 y 8 E/S (dependiendo del modelo de PIC) y el portB que tiene 8 E/S.

Desde mikroBasic es posible leer cada uno de los 8 bits que componen cada puerto, o escribir en ellos un “1” o un “0”, además de elegir individualmente el comportamiento de cada pin, es decir, si va a ser un pin de entrada o de salida. En los PIC más complejos también se puede seleccionar si un pin determinado de un puerto va a ser una entrada analógica (que el PIC va a convertir internamente en un valor digital), etc.

Veamos cuáles son estas instrucciones mediante un caso práctico: supongamos que estamos usando un PIC 16F84A, que no posee conversores A/D, solo tiene dos puertos de E/S digitales, el puertoA con E/S y el puertoB con 8. Vamos a configurar todo el puertoA como entradas (supongamos que en ellas tendremos conectados pulsadores que, al operarlos, conecten el pin correspondiente a 5V) y el puertoB tendrá los primeros cinco pines (del 0 al 4) como salidas, conectados a LEDs que encenderán cuando escribamos un “1” en ellas y los demás (del 5 al 7) como entradas. El circuito eléctrico sería el de la figura que vemos al final de la página.

La instrucción TRISn es la que nos permite configurar los pines del puerto “n” como entradas y salidas. Podemos configurarlos individualmente, o bien configurar todo el puerto a la vez. Los pines que TRIS defina como “0” serán de salida, y los definidos como “1” se comportarán como entradas. Veamos algunos ejemplos:

TRISB = 0 : Configura TODOS los pines del puertoB como salidas.
TRISB = %11110000 : Configura los pines del 4 al 7 como entradas y los demás como salidas. El “%” indica que el numero a continuación esta en binario.
TRISB = 240 : Hace lo mismo que la anterior: 240 en decimal es lo mismo que 11110000 en binario. (128 + 64 + 32 + 16 + 0 + 0 + 0 + 0 = 240)
TRISA = 255 : Configura todo el puertoA como entradas. Si el puerto solo tiene 5 bits conectados a pines físicos, los otros 3 bits se ignoran.

Para el caso de nuestro ejemplo, el programa quedaría así:

program Ejemplo1

main: 'Este es el cuerpo del programa

 TRISA = 255 ' Todos los pines del puertoA como entradas…
 TRISB = %11100000 ' PuertoB tiene 3 pines como entradas y 5 como salidas

end. 'Fin del programa

Obviamente, si compilamos este programa y lo cargamos en el microcontrolador, no veremos que encienda ningún LED ni ninguna otra cosa, puesto que lo único que hemos hecho es configurar los puertos, pero no hemos escrito nada en ellos.

Ahora, veamos cómo hacer para que los LEDs enciendan a medida que accionemos los pulsadores. Al conectar el circuito a la alimentación, los 5 LEDs estarán apagados. Al ir presionando los pulsadores, el LED correspondiente se encenderá, y quedará en ese estado. Si se pulsa cualquiera de los tres pulsadores conectados a los pines del puertoB, todos los LEDs que estén encendidos se apagarán. Veamos el programa:

program Ejemplo2

main: 'Este es el cuerpo del programa

 TRISA = 255 ' Todos los pines del puertoA como entradas…
 TRISB = %11100000 ' PuertoB tiene 3 pines como entradas y 5 como salidas
 PORTB = %00000000 ' Apago los LEDs conectados al puertoB

 while TRUE ' Comienzo un bucle infinito
 if PORTA.0 = 1 then ' Si se presiona el pulsador 1
 PORTB.0 = 1 ' Enciendo el LED 1
 End if

 if PORTA.1 = 1 then ' Si se presiona el pulsador 2
 PORTB.1 = 1 ' Enciendo el LED 2
 End if

 if PORTA.2 = 1 then ' Si se presiona el pulsador 3
 PORTB.2 = 1 ' Enciendo el LED 3
 End if

 if PORTA.3 = 1 then ' Si se presiona el pulsador 4
 PORTB.3 = 1 ' Enciendo el LED 4
 End if

 if PORTA.4 = 1 then ' Si se presiona el pulsador 5
 PORTB.4 = 1 ' Enciendo el LED 5
 End if

 if PORTB.5 = 1 OR PORTB.6 = 1 OR PORTB.7 = 1 then ' Si se presiona algún pulsador del puertoB
 PORTB = %00000000 ' Apago los LEDs conectados al puertoB
 End if

 delay_ms(100) ' Espero 100 milisegundos antes de
 wend ' repetir el bucle.

end. 'Fin del programa

El programa anterior se puede compilar y cargar en un 16F84A mediante el programador visto oportunamente. Los que hayan armado el entrenador visto en Power User también pueden aprovecharlo y montar sobre el circuito correspondiente para probar el programa.

Es importante recalcar que el uso de comentarios, tal como se ve en el código anterior, permite la fácil comprensión del programa por terceros (¡e incluso por uno mismo!).

MikroBasic - Primera Parte
Circuito para probar el código de control de puertos.
MikroBasic - Primera Parte
Esta fuente es necesaria para alimentar al PIC.

Conclusión

Los ejemplos vistos, si bien muy simples ya que justamente esa era la idea al escribirlos, nos permiten vislumbrar el potencial de este lenguaje.

Es evidente que teniendo una idea acabada de la tarea que debe realizar nuestro circuito y construyendo un algoritmo adecuado, está en nuestras manos el poder realizar prácticamente cualquier aparato usando un PIC.

A lo largo de sucesivas notas veremos cómo controlar motores, displays LCD y todo lo imaginable mediante un PIC, y como no, mikroBasic. Más información>>

MikroBasic - Primera Parte
MikroBasic - Primera Parte