Curso Programación MCUs PIC en lenguaje C - Edudevices
Curso Programación MCUs PIC en lenguaje C - Edudevices
Curso Programación MCUs PIC en lenguaje C - Edudevices
You also want an ePaper? Increase the reach of your titles
YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.
Apr<strong>en</strong>di<strong>en</strong>do a programar<br />
Microcontroladores <strong>PIC</strong> <strong>en</strong><br />
L<strong>en</strong>guaje C con CCS<br />
Los compiladores de l<strong>en</strong>guaje C hoy son ampliam<strong>en</strong>te utilizados para la creación<br />
de programas con microcontroladores <strong>PIC</strong>. El compilador que mejor soluciona las<br />
necesidades del programador <strong>en</strong>mascarando el hardware y simplificando la<br />
implem<strong>en</strong>tación de una aplicación es el fabricado por la compañía CCS.<br />
Por Andrés Raúl Bruno Saravia<br />
Entrega Nº 2.<br />
En nuestra <strong>en</strong>trega anterior apr<strong>en</strong>dimos a crear un proyecto d<strong>en</strong>tro del MPLAB<br />
para escribir nuestro primer código <strong>en</strong> l<strong>en</strong>guaje C, hoy crearemos nuestro primer<br />
código y apr<strong>en</strong>deremos la función que cumpl<strong>en</strong> sus elem<strong>en</strong>tos principales .<br />
Creando Nuestro Primer Código<br />
Ya que hemos apr<strong>en</strong>dido a crear nuestro primer proyecto, escribiremos nuestro primer<br />
código, el cual adicionaremos a nuestro proyecto. Para ello nos moveremos con el<br />
Mouse hasta el ícono de New File y haremos clic sobre el mismo:
Y se desplegará una nueva v<strong>en</strong>tana <strong>en</strong> la cual escribiremos el código de nuestro<br />
programa.<br />
Por ser este nuestro primer programa, simplem<strong>en</strong>te haremos titilar un LED con un<br />
tiempo de flashing de 500ms. La idea es introducirnos de a poco, y muchas veces querer<br />
com<strong>en</strong>zar con un código demasiado elaborado es complejo.<br />
#include<br />
#use delay(osc=4000000)<br />
//configura fusibles de configuración<br />
#fuses XT //Oscilador a cristal standar<br />
#fuses NOWDT //sin WatchDog Timer<br />
#fuses NOPROTECT //sin proteccion de memoria de programa<br />
#fuses NOPUT //sin PowerUp Timer<br />
#fuses NOBROWNOUT //sin brownout<br />
#fuses NOLVP //sin programación <strong>en</strong> baja t<strong>en</strong>sión<br />
//rutina principal<br />
void main(void)<br />
{ //abrimos la función principal<br />
Setup_adc_ports(NO_ANALOGS);//configuramos los puertos digitales<br />
while(1) //creamos un bucle infinito<br />
{ //abrimos el bucle<br />
output_high(PIN_B0); //pr<strong>en</strong>demos RB0<br />
delay_ms(500); //esperamos 500 ms<br />
output_low(PIN_B0); //borramos RB0<br />
delay_ms(500); //esperamos 500 ms<br />
} //cerramos el bucle<br />
} //cerramos la función principal<br />
Este código lo hemos escrito <strong>en</strong> lo que se conoce <strong>en</strong> la jerga técnica como “codigo <strong>en</strong> C<br />
nativo” ya que usamos las funciones de control de <strong>en</strong>trada / salida de CCS<br />
El código inicia siempre <strong>en</strong> lo que se conoce como “cabecera”, es decir el principio.<br />
En esta cabecera <strong>en</strong>contraremos instrucciones dirigidas al compilador y no al<br />
microcontrolador, dichas instrucciones se d<strong>en</strong>ominan “directivas”. Las directivas se<br />
difer<strong>en</strong>cian de las instrucciones <strong>en</strong> que:<br />
• Siempre se <strong>en</strong>cu<strong>en</strong>tran <strong>en</strong> la cabecera del programa<br />
• Todas comi<strong>en</strong>zan con el símbolo del numeral #<br />
En nuestro caso estas son las directivas:<br />
#include<br />
#use delay(crystal=4000000)<br />
//configura fusibles de configuración<br />
#fuses XT //Oscilador a cristal standar<br />
#fuses NOWDT //sin WatchDog Timer<br />
#fuses NOPROTECT //sin proteccion de memoria de programa<br />
#fuses NOPUT //sin PowerUp Timer<br />
#fuses NOBROWNOUT //sin brownout<br />
#fuses NOLVP //sin programación <strong>en</strong> baja t<strong>en</strong>sión<br />
La directiva : #include nos permite decirle al compilador para que<br />
microcontrolador hemos escrito el código.
Seguidam<strong>en</strong>te con la directiva #use delay(crystal=4000000) le indicamos a que<br />
frecu<strong>en</strong>cia esta funcionando nuestro oscilador.<br />
El ord<strong>en</strong> de las directivas es crucial ya que siempre primero debemos indicarle al<br />
compilador cual es el <strong>PIC</strong> que estamos usando y luego cual es la frecu<strong>en</strong>cia del<br />
oscilador. Posteriorm<strong>en</strong>te podemos agregar el resto de directivas. Si esto no se<br />
respeta podemos t<strong>en</strong>er errores <strong>en</strong> el proceso de compilación sobre todo cuando<br />
usamos alguna función propia del compilador (llamadas funciones embebidas)<br />
para manejar algún periférico, y que la misma necesita saber la frecu<strong>en</strong>cia del<br />
oscilador.<br />
La directiva puede t<strong>en</strong>er distintas formas ya que amolda la configuración interna a la<br />
frecu<strong>en</strong>cia que le indicamos. Así esta directiva nos permite activar multiplicadores y<br />
divisores internos, o accionar los osciladores internos cuando el microcontrolador los<br />
trae; por ejemplo:<br />
#use delay(crystal=4000000, clock=16000000)<br />
Le indica al compilador que t<strong>en</strong>emos un cristal externo de 4Mhz, y que la frecu<strong>en</strong>cia<br />
que llega a la CPU es de 16Mhz, por lo tanto el compilador configurará correctam<strong>en</strong>te<br />
el PLL de la CPU para alcanzar los 32Mhz. Otro ejemplo pero usando el reloj interno es<br />
el sigui<strong>en</strong>te:<br />
#use delay(internal=8000000, clock=16000000)<br />
Sin embargo esta directiva debe usarse con precaución ya que el clock que definimos<br />
nunca debe sobrepasar la máxima velocidad de procesami<strong>en</strong>to del <strong>PIC</strong> que se esté<br />
usando. Este parámetro se d<strong>en</strong>omina MIPS (Millones de Instrucciones por Segundo) y<br />
se obti<strong>en</strong>e dividi<strong>en</strong>do la frecu<strong>en</strong>cia de <strong>en</strong>trada por cuatro.<br />
MIPS = fosc/4<br />
La directiva #fuse xx nos permite activar o desactivar las características del nucleo,<br />
como ser el circuito de Watch Dog Timer, que reseta al microcontrolador ante un<br />
cuelgue del mismo, el Brown Out Detect, que resetea el microcontrolador ante un fallo<br />
de la alim<strong>en</strong>tación, el tipo de oscilador, etc.<br />
Las etiquetas usadas para activar o desactivar la propiedad, están incluidas <strong>en</strong> el archivo<br />
de cabecera y deb<strong>en</strong> ser consultadas siempre, ya que las mismas suel<strong>en</strong> cambiar <strong>en</strong>tre<br />
versiones del compilador o tipos de microcontroladores.<br />
En líneas g<strong>en</strong>erales podemos decir que anteponi<strong>en</strong>do la palabra NO al fusible de<br />
configuración (así se llama al seteo de las propiedades), se le informa al compilador que<br />
el fusible <strong>en</strong> cuestión está desactivado, mi<strong>en</strong>tras que colocando solo el nombre<br />
activamos la propiedad.<br />
Por otra parte para activar o desactivar los distintos fusibles se puede realizar <strong>en</strong> varias<br />
líneas (como <strong>en</strong> el ejemplo) o se pued<strong>en</strong> activar y desactivar <strong>en</strong> una sola línea separando<br />
cada fusible con comas:<br />
#fuse NOWDT,HS,NOPUT,NOLVP,NOMCLR,NOPROTECT,NOBROWNOUT<br />
La cabecera además puede incorporar redefinición de nombres de pines, definición de<br />
variables y constantes. Y prototipo de funciones. Esto será visto <strong>en</strong> nuestras próximas<br />
lecciones.
Finalizada la cabecera, continúa el código, que es el que se traducirá <strong>en</strong> instrucciones al<br />
microcontrolador luego del proceso de compilación. En nuestro caso nuestro código es<br />
bastante s<strong>en</strong>cillo:<br />
void main(void)<br />
{ //abrimos la función principal<br />
setup_adc_ports(NO_ANALOGS);//configuramos los puertos digitales<br />
while(1) //creamos un bucle infinito<br />
{ //abrimos el bucle<br />
output_high(PIN_B0); //pr<strong>en</strong>demos RB0<br />
delay_ms(500); //esperamos 500 ms<br />
output_low(PIN_B0); //borramos RB0<br />
delay_ms(500); //esperamos 500 ms<br />
} //cerramos el bucle<br />
} //cerramos la función principal<br />
Todo programa siempre inicia <strong>en</strong> una rutina principal. En el l<strong>en</strong>guaje C las rutinas se<br />
d<strong>en</strong>ominan funciones. Las funciones son un conjunto de s<strong>en</strong>t<strong>en</strong>cias u ord<strong>en</strong>es que<br />
realizan una operación determinada, como lo hac<strong>en</strong> las rutinas, sin embargo las<br />
funciones ti<strong>en</strong>e una característica extra; a ellas se les puede pasar valores de variables<br />
para que las proces<strong>en</strong>, y son capaces de devolvernos los resultados de dichos procesos.<br />
Básicam<strong>en</strong>te actúan como las funciones matemáticas.<br />
Todo programa C siempre inicia <strong>en</strong> la función principal, la cual se d<strong>en</strong>omina main.<br />
Dicho nombre no puede ser distinto, todo programa debe t<strong>en</strong>er una función main, de lo<br />
contrario el compilador nos indicará un error.<br />
La función <strong>en</strong>cierra una serie de s<strong>en</strong>t<strong>en</strong>cias, las cuales forman el bloque de dicha<br />
función. Dicho bloque inicia con una llave { y finaliza con otra llave } .<br />
Entre estas dos llaves se <strong>en</strong>cu<strong>en</strong>tran las s<strong>en</strong>t<strong>en</strong>cias y las estructuras lógicas.<br />
La función main es una función especial ya que no puede recibir ningún valor, ni<br />
tampoco puede devolver uno. Por lo tanto observe que va acompañada por dos palabras<br />
void lo cual <strong>en</strong> el l<strong>en</strong>guaje C significa vacío; es decir que no devuelve ningún valor<br />
(primer void) ni puede recibir ningún valor, (segundo void, el cual esta <strong>en</strong>cerrado <strong>en</strong>tre<br />
paréntesis).<br />
void main(void)<br />
{<br />
}<br />
Nuestro código<br />
Nuestro código lo hemos escrito <strong>en</strong> formato “CCS nativo”. Esto significa que hemos<br />
usado todas las funciones embebidas (incluidas d<strong>en</strong>tro) del compilador para simplificar<br />
la escritura del código y que nos ahorran mucho tiempo.<br />
Debe observarse que cada línea la hemos decalado (separado del orig<strong>en</strong>) por medio del<br />
TABULADOR; esto es una bu<strong>en</strong>a práctica para poder advertir a simple vista cuales<br />
s<strong>en</strong>t<strong>en</strong>cias son las que estas anidadas d<strong>en</strong>tro de cada bloque del programa principal.<br />
La primera línea o s<strong>en</strong>t<strong>en</strong>cia le indica al compilador que debe desactivar todos los<br />
puertos analógicos del <strong>PIC</strong> y que debe configurarlos como puertos digitales:<br />
setup_adc_ports(NO_ANALOGS);
Usamos la función embebida setup_adc_ports, la cual esta embebida <strong>en</strong> el<br />
compilador, y que se <strong>en</strong>carga básicam<strong>en</strong>te de setear que puertos van a trabajar como<br />
analógicos y que puertos van a trabajar como digitales. La función configura los bits<br />
PCFG o ANSEL dep<strong>en</strong>di<strong>en</strong>do con que <strong>PIC</strong> estemos trabajando y lo realiza de forma<br />
automática.<br />
Observe que hemos escrito <strong>en</strong>tre paréntesis NO_ANALOGS lo cual le dice al<br />
compilador que no hay puertos analógicos. Esta etiqueta la obt<strong>en</strong>emos del archivo de<br />
cabecera del procesador (16f887.h).<br />
Es importante resaltar que las etiquetas siempre van <strong>en</strong> mayusculas, mi<strong>en</strong>tras que<br />
las instrucciones se escrib<strong>en</strong> <strong>en</strong> minúscula.<br />
Es muy importante que se respete el ord<strong>en</strong> mayúscula-minúscula pues el<br />
compilador es s<strong>en</strong>sible a ello. Si escribimos una instrucción con mayúscula, NO LA<br />
IDENTIFICARÁ.<br />
Debe observarse también que las s<strong>en</strong>t<strong>en</strong>cias siempre terminan con un punto y coma.<br />
La sigui<strong>en</strong>te s<strong>en</strong>t<strong>en</strong>cia de nuestro código es una instrucción estructural: while<br />
El while es una instrucción condicional la cual determina la ejecución de una o mas<br />
instrucciones <strong>en</strong> tanto y <strong>en</strong> cuanto se cumpla una condición, la cual se <strong>en</strong>cierra <strong>en</strong>tre<br />
paréntesis.<br />
En programación, si una condición se cumple, se dice que es verdadera, y esto se<br />
simboliza con el número 1; por el contrario, si la condición no se cumple, es falsa y<br />
se simboliza con el número 0.<br />
En un while, lo que este d<strong>en</strong>tro del bloque del mismo, se ejecutará, siempre que la<br />
condición de verdadera, caso contrario no se ejecutará ninguna s<strong>en</strong>t<strong>en</strong>cia que se<br />
<strong>en</strong>cu<strong>en</strong>tre d<strong>en</strong>tro del while.<br />
En nuestro caso hemos forzado la condición 1, con lo cual el while se ejecutará<br />
eternam<strong>en</strong>te. Es decir que las s<strong>en</strong>t<strong>en</strong>cias <strong>en</strong>cerradas d<strong>en</strong>tro del bloque while (limitado<br />
por las llaves{}), se ejecutaran por siempre.<br />
Observe que d<strong>en</strong>tro del while usamos también funciones embebidas:<br />
output_high(PIN_B0);<br />
output_low(PIN_B0);<br />
Estas son funciones de salida de datos, se <strong>en</strong>cargan de poner <strong>en</strong> uno o <strong>en</strong> cero un puerto,<br />
el cual la misma función se <strong>en</strong>carga de configurar como salida, no debe hacerlo el<br />
programador. Entre paréntesis le indicamos el PIN a <strong>en</strong>c<strong>en</strong>der o apagar. En nuestro caso<br />
es el RB0, al cual CCS lo d<strong>en</strong>omina d<strong>en</strong>tro del archivo de cabecera del<br />
microcontrolador como PIN_B0.<br />
Este formato si bi<strong>en</strong> parece <strong>en</strong> principio raro porque no se adapta al usado <strong>en</strong> el data<br />
sheet, es práctico para los programadores NO ELECTRÓNICOS, y es el que ha<br />
adoptado CCS.<br />
Por ejemplo el RA0 CCS lo llama PIN_A0, y al RC6, PIN_C6, y así sucesivam<strong>en</strong>te.<br />
Otra de las s<strong>en</strong>t<strong>en</strong>cias usadas es<br />
delay_ms(500);
La cual es una función de tiempo que nos permite crear un tiempo de espera <strong>en</strong><br />
milisegundos. También existe el delay_us que nos permite crear un delay de<br />
microsegundos.<br />
La exactitud de la función delay dep<strong>en</strong>de de que hayamos definido correctam<strong>en</strong>te la<br />
frecu<strong>en</strong>cia de clock.<br />
De esta forma hemos hecho falshear un LED <strong>en</strong> nuestro primer código.<br />
Bu<strong>en</strong>o, esto es todo por ahora , <strong>en</strong> las próximas <strong>en</strong>tregas iremos descubri<strong>en</strong>do paso a<br />
paso las utilidades de la programación <strong>en</strong> l<strong>en</strong>guaje C y del <strong>en</strong>torno CCS.<br />
Continuará ......