Tutorial XC8

35
[Tutorial XC8] Introducción Aquí se presenta un tutorial del compilador XC8 que pretende proporcionar los conocimientos básicos para comenzar a desarrollar con el mismo, para ello daremos una introducción de cómo es un microcontrolador PIC y su funcionamiento, los lenguajes de programación y la ventaja de usar C, como crear un proyecto, la estructura de un programa en C y lo necesario para ir creando ejemplos. Espero sea de utilidad Licencia La misma se expresa al pie de cada articulo. Indice Introducción al microcontrolador El lenguaje de programación Comenzando a desarrollar en C Nuestro primer proyecto en un PIC18F4550 y XC8 Operadores, variables y estructura for Estructura if y arreglos de variables Introducción al microcontrolador Un microcontrolador es un dispositivo electrónico capaz de ejecutar una secuencia de comandos previamente programados. Estos comandos son proyectados por el usuario utilizando algún lenguaje de programación y luego grabados en la memoria del microcontrolador. Los microcontroladores PIC de gama baja poseen arquitectura Harvard, esto quiere decir que utilizan dos memorias distintas, una para almacenar las instrucciones y otra para manejar los datos. Entonces está compuesto principalmente por un procesador (CPU), memoria RAM, memoria ROM y buses de comunicaciones (cada tipo de memoria tiene un bus de datos, uno de direcciones y uno de control). Adicionalmente también dispone de puertos de entrada y salida, y diversos periféricos que nos facilitan el desarrollo tales como osciladores, temporizadores/contadores, módulos de comunicación serial y paralela, comparadores analógicos, conversores analógicos a digital, memoria eeprom, etc.

description

uso del compilador xc8

Transcript of Tutorial XC8

[Tutorial XC8] Introducción

Aquí se presenta un tutorial del compilador XC8 que pretende proporcionar los conocimientos básicos para

comenzar a desarrollar con el mismo, para ello daremos una introducción de cómo es un microcontrolador PIC y su

funcionamiento, los lenguajes de programación y la ventaja de usar C, como crear un proyecto, la estructura de un

programa en C y lo necesario para ir creando ejemplos. Espero sea de utilidad 

Licencia

La misma se expresa al pie de cada articulo.

Indice

Introducción al microcontrolador

El lenguaje de programación

Comenzando a desarrollar en C

Nuestro primer proyecto en un PIC18F4550 y XC8

Operadores, variables y estructura for

Estructura if y arreglos de variables

Introducción al microcontrolador

Un microcontrolador es un dispositivo electrónico capaz de ejecutar una secuencia de comandos previamente

programados. Estos comandos son proyectados por el usuario utilizando algún lenguaje de programación y luego

grabados en la memoria del microcontrolador.

Los microcontroladores PIC de gama baja poseen arquitectura Harvard, esto quiere decir que utilizan dos memorias

distintas, una para almacenar las instrucciones y otra para manejar los datos. Entonces está compuesto

principalmente por un procesador (CPU), memoria RAM, memoria ROM y buses de comunicaciones (cada tipo de

memoria tiene un bus de datos, uno de direcciones y uno de control). Adicionalmente también dispone de puertos de

entrada y salida, y diversos periféricos que nos facilitan el desarrollo tales como osciladores,

temporizadores/contadores, módulos de comunicación serial y paralela, comparadores analógicos, conversores

analógicos a digital, memoria eeprom, etc.

Pero vayamos por partes, así entenderemos como es su funcionamiento, igualmente cabe aclarar que seremos

concisos, sin describir profundamente.

La memoria ROM es del tipo no volátil, esto significa que los datos almacenados no se pierden aunque no esté

energizada. Se destina principalmente a contener la lista de instrucciones que conforman la aplicación, por ello suele

llamarse memoria de programa y en microcontroladores de la actualidad ronda desde los 512 bytes a 128 kbytes,

correspondientes a los de gama baja de Microchip. Son del tipo Flash, de bajo consumo, que se pueden escribir y

leer, y de gran densidad de almacenamiento. Además tienen la ventaja que permiten ser reprogramadas en circuito,

sin necesidad de extraer el circuito integrado de la tarjeta.

La memoria RAM, memoria volátil, es la destinada a guardar las variables y datos temporales que serán utilizados

por el procesador para  realizar cálculos u otro tipo de operaciones lógicas. El espacio de direcciones de memoria

RAM se divide en dos sectores: registros de propósito general (GPR), espacio destinado para crear variables por el

usuario y variables propias del compilador; y registros de funciones especiales (SFR), espacio que contiene bits de

configuración y control de los periféricos del microcontrolador. En la actualidad se pueden encontrar

microcontroladores con memoria RAM de unos 32 bytes hasta 4 kbytes.

Un registro es una pequeña porción de la memoria y su tamaño se mide generalmente en bits (8-bits, 16-bits, 32-

bits). Está representado por un numero que denominamos dirección de memoria, pero dentro del lenguaje de

programación existe la posibilidad de asignarle un nombre, de esta manera es más sencillo de manejar.

El CPU o unidad central de procesamiento es el encargado de direccionar la memoria ROM, decodificar la

instrucción  y ejecutar la operación que implica. El primer paso es leer la instrucción de la memoria, la posición es

controlada por un contador de programa (PC) que almacena un número que identifica la posición actual a ejecutar.

La instrucción que el CPU lee desde la memoria es usada para determinar qué operación debe hacer el CPU, en este

paso hay una decodificación, en donde la instrucción es dividida en partes que tienen significado para otras unidades

del CPU. Después de los pasos de lectura y decodificación, es llevado a cabo el paso de la ejecución de la instrucción.

Durante este paso, varias unidades del CPU son conectadas de tal manera que ellas pueden realizar la operación

deseada. Si, por ejemplo, una operación de adición fue solicitada, una unidad aritmético lógica (ALU) será conectada

a un conjunto de entradas (números a ser sumados) y un conjunto de salidas (suma). Luego el paso final,

simplemente la obtención del resultado, escribiéndolo en un registro interno del CPU de acceso rápido, modificando

un registro de la memoria RAM o modificando el contador de programa para generar saltos o bucles, etc.

La siguiente animación intenta dar a comprender como es el funcionamiento, pero tener en cuenta que es muy

genérica:

Actualmente existen 3 tipos de arquitectura:

CISC (Computadores de juego de instrucciones complejo): Disponen de un conjunto amplio de instrucciones donde

muchas de ellas son complejas, permitiendo realizar operaciones sofisticadas y potentes, pero que ocupan varios

ciclos.

RISC (Computadores de juego de instrucciones reducido): En este caso el repertorio de instrucciones mínimo y muy

sencillo, y generalmente ocupa entre 1 o 2 ciclos. La ventaja de éstos es que la sencillez y rapidez de las

instrucciones permiten optimizar el hardware y el software del procesador.

SISC (Computadores de juego de instrucciones específicos): Se utiliza para microcontroladores destinados a

aplicaciones concretas, donde el set de instrucciones es reducido y específico para las tareas a desarrollar,

adaptándose a las necesidades de la aplicación prevista.

Los microcontroladores de gama baja de Microchip se basan en la arquitectura RISC, con 35 instrucciones para los

16F y 75 instrucciones para los 18F, en este último caso parecen muchas pero son simples y que ocupan pocos ciclos.

El PIC18F4550

Ahora tenemos un conocimiento general de que compone un microcontrolador y como es su funcionamiento, así que

pasaremos a uno puntual. En este tutorial nos basaremos en el microcontrolador PIC18F4550 de la familia PIC18 de

Microchip que tiene las siguientes características básicas:

Arquitectura RISC avanzada Harvard de 16-bits con 8-bits de datos.

Máxima frecuencia de operación 48 MHz, 12 MIPS.

Memoria de programa de 32 kBytes.

Memoria RAM de 2 kBytes.

Memoria EEPROM de 256 Bytes.

Multiplicador por hardware de 8×8

Pila de 32 niveles.

2 niveles de prioridad para las interrupciones.

20 fuentes de interrupción.

4 temporizadores.

2 comparadores analógicos.

13 canales de conversión analógica.

Comunicación UART, SPI, I2C, USB.

PLL y oscilador interno de 8 MHz.

En el mismo datasheet podemos encontrar el siguiente diagrama de bloques de su estructura interna:

Como hemos comentado anteriormente, en la memoria de programa existe una lista de instrucciones que el

microcontrolador ejecuta de forma secuencial. Estas instrucciones son números binarios de ancho establecido por la

arquitectura (12, 14, 16 bits, etc.) y que, como es más fácil de trabajar, se representan de forma hexadecimal.

Entonces a esta lista de instrucciones se la suele denominar código HEX.

Para generar este código HEX existen varios lenguajes de programación, tenemos de bajo nivel denominado

Assembler y de alto nivel entre los cuales podemos encontrar C, C++, Basic, Pascal, lenguaje de tipo visual, etc.

El lenguaje Assembler no es más que una traducción de las instrucciones en hexadecimal que “entiende” el

microcontrolador en abreviaturas más sencillas de recordar y manejar por un programador. Requiere tener mucho

conocimiento de la arquitectura y para proyectos grandes  es extremadamente compleja su utilización, además de

requerir mucho más tiempo de trabajo.

Los lenguajes de programación de alto nivel fueron creados con el propósito de superar estas desventajas, otorgando

un mayor entendimiento de cómo es el flujo del programa, generando sentencias simples de entender para una

persona, ya no es necesario conocer en profundidad el conjunto de instrucciones de la arquitectura y además

proporcionan portabilidad. O sea, un programa (funciones, librerías) escrito en lenguaje de alto nivel en determinado

microcontrolador es mucho más sencillo portarlo a otro microcontrolador, ya sea de distintas prestaciones dentro de

la misma marca o un microcontrolador de otra empresa. (Principalmente C)

Algunas de las ventajas de usar C son:

Es un lenguaje simple y flexible.

Permite incorporar ASM dentro del código.

Proporciona facilidades para realizar programas modulares y/o utilizar código o librerías existentes.

Sencillo de corregir errores.

Es un lenguaje utilizado en todas las arquitecturas, lo que permite la portabilidad.

Desventajas:

Se tiene poco control en el HEX generado.

A diferencia de un programa realizado en Assembler, es menos eficiente ocupando más espacio y siendo más

lento.

Algunas sentencias pueden ser complicadas de entender.

XC8 es un nuevo compilador que ha presentado Microchip para las familias de microcontroladores de 8-bits (PIC10,

PIC12, PIC16, PIC18) que reúne de forma integral a los antiguos compiladores Hitech PICC, Hitech PICC18 y MPLAB

C18. Es un compilador multiplataforma, compatible con Windows, Linux y Mac. Hay diferentes opciones de descarga

según la necesidad, Pro, Estándar y Free con distintos niveles de optimización.

XC8 puede utilizarse por medio de línea de comandos o integrarse a un entorno de desarrollo, como MPLAB IDE o

MPLAB X. Se encarga de realizar la compilación de los archivos fuente del proyecto y de realizar el enlace de éstos

con librerías pre compiladas para la generación del HEX.

El compilador “lee” lo escrito por el programador e intenta interpretarlo según las reglas del lenguaje y la sintaxis

de C. Si no encuentra errores, produce un archivo intermedio llamado objeto (con la extensión obj). En caso de tener

un archivo fuente escrito en asm, este es ensamblado para así también generar un archivo objeto.

Luego el paso siguiente para la generación del HEX es el enlazado (link en ingles). Este toma los archivos objetos del

proceso de compilación y ensamblado, toma archivos objetos de librerías pre compiladas según la necesidad del

proyecto y genera el HEX que puede ser grabado en el microcontrolador. Adicionalmente a éste se generan otros

archivos que permiten realizar depuraciones (simulaciones) del proyecto, como los archivos cof.

Esto se puede ver en la siguiente imagen incluida en el manual de usuario de XC8:

En este tutorial utilizaremos como entorno de desarrollo MPLAB X, el cual permite integrar XC8 y los pasos

indicados anteriormente los realiza de forma automática. Para ello crea archivos makefile que contienen los

comandos y argumentos a ejecutar para generar el HEX. O sea, nosotros presionaremos Build All y si todo está

correcto obtendremos los archivos objeto, el archivo cof y el archivo HEX listo para grabar en el microcontrolador.

Creando un proyecto en MPLAB X y XC8

Para comenzar a desarrollar con XC8 y MPLAB X es necesario que ambos estén instalados en la PC. Los podemos

bajar directamente desde www.microchip.com, en este caso usaremos MPLAB X v1.2 y XC8 v1.1. El siguiente vídeo

muestra los pasos para crear un proyecto y comenzar a trabajar:

Una vez creado el proyecto ya podemos comenzar a desarrollar nuestro programa en C. El archivo principal, o sea el

que contiene la función principal (main) generalmente tiene la siguiente estructura:

/* Archivo: main.cAutor: SukyFecha: 20/08/12*/ Cabecera, información sobre el archivo pero sin importancia para el proceso de compilación.

#include <xc.h>Inclusión de archivos cabecera con información necesaria para las funciones implementadas en el proyecto. Para ello se hace uso de directivas de preprocesador.

#pragma config PLLDIV=5#pragma config CPUDIV=OSC1_PLL2#pragma config USBDIV=2#pragma config FOSC=HSPLL_HS

En los microcontroladores PIC el HEX puede contener la configuración de los fusibles. Estos generalmente se colocan en el archivo fuente principal.

unsigned char Variable1, Variable2;

Declaración de variables globales, pueden ser utilizadas por cualquiera de las función que se encuentran en el archivo fuente.

void vFuncion1(void);void vFuncion2(unsigned char Data); Declaración del prototipo de las funciones implementadas en el archivo fuente.

void main(void){….

vFuncion1();

….

while(1){….

vFuncion2(45);

….

}

}

Función principal que el 99% de las veces incluye el ciclo infinito.

void vFuncion1(void){}

void vFuncion2(unsigned char Data){}

Definición de las funciones implementadas.

Los comentarios

Es una buena práctica comentar el código fuente que vamos desarrollando, ya sea para uno mismo que después de

un tiempo retoma el proyecto o para otra persona que lo desea modificar o ampliar. Tenemos dos maneras de

realizar los comentarios, cortos (comienzan con //) y multi-lineas (comienzan con /* y terminan con */). Un ejemplo:

/* Función que escribe en el puerto B.

Parámetro: Un byte a escribir

Retorna: Nada

*/

void vWritePortB(char Data){

TRISB=0×00;  // Seteamos el Puerto B como salida.

LATB=Data;   // Escribimos en el Puerto B el valor de Data.

}

Las directivas de preprocesador

El preprocesador es el primer programa que se ejecuta en el proceso de compilación y trata directivas especiales

como#include, #define, #if, etc. Algunas de las ventajas de utilizar directivas de preprocesador son las siguientes:

Los programas son más fáciles de leer.

Son más fáciles de modificar.

Permite generalizar para varias arquitecturas y compiladores.

#include

Esta directiva de preprocesador le indica al compilador que debe incluir un archivo fuente antes de continuar. Este

archivo fuente (.h) tendrá las declaraciones de las funciones que implementa dicha librería y/o macros útiles en el

proyecto. En el ejemplo mostrado incluimos xc.h, si buscamos que contiene encontraremos que incluye otros archivos

fuente dependiendo de que microcontrolador estemos usando y entre esos archivos fuente encontraremos macros

que nos ayudarán a crear nuestro código. Como por ejemplo:

#define  Nop()      asm(" nop")

#define Sleep()     asm(" sleep")

#define WRITETIMER0(x) ((void)(TMR0H=((x)>>8),TMR0L=((x)&0xFF)))

Además encontraremos la declaración de los puertos y pines del microcontrolador, registros y bits de configuración

de los periféricos, etc., por lo tanto no tendremos que saber cual es su dirección o número de bit para poder trabajar

con ellos.

El archivo a incluir se puede indicar mediante comillas (“”) o entre ángulos (<>). Con la primer opción el compilador

busca en la misma carpeta donde se encuentra el archivo que realiza el llamado o la dirección que indique el mismo y

entre ángulos busca en las carpetas definidas como contenedoras de archivos cabecera (Path include). Ejemplos:

#include <Timers.h>

#include “MiLibreria.h”

#include “../Include/MiLibreria.h”

En este último caso al colocar doble punto (..) el compilador sale de la carpeta donde se encuentra el archivo que

hace el llamado, busca la carpeta Include y dentro el archivo MiLibreria.h.

#define

Esta directiva permite crear un identificador para una secuencia de caracteres de la siguiente forma:

#define Identificador secuencia_de_caracteres

El preprocesador buscará los identificadores creados y los reemplazará por la secuencia de caracteres para su

posterior compilación. Esto es muy útil para crear código que sea más entendible a la lectura. Veamos ejemplos:

#define FALSO       0

#define VERDADERO   1

#define PI      3.141592

#define Deg2Rad(x)  (x*PI/180.0)

#define PIN_ENTRADA 1

#define PIN_SALIDA  0

#define ENCENDIDO   1

#define APAGADO 0

#define LED1        RB0

Tengamos en cuenta que podemos crear macros que se asemejen a funciones, la ventaja que otorga es que nos

ahorramos esos ciclos de llamada a dicha función pero ocupamos más código, dado que el preprocesador solo

reemplaza texto por texto.

#ifdef, #ifndef, #else, #elif y #endif

Estas directivas son utilizadas para realizar compilaciones condicionadas, por ejemplo para hacer una librería

generalizada para varias arquitecturas, para ser utilizada por varios compiladores o simplemente para seleccionar el

nivel de uso de cierto proyecto.

Por ejemplo podemos crear una definición para realizar o no debugger por puerto serial, y en código si está

habilitado compilar dichas sentencias y sino no:

#define USE_DEBUG

Ifdef USE_DEBUG

    printf(“Debug habilitado”);

#endif

O dependiendo de la arquitectura que se use incluir ciertos archivos fuente:

/* HI-TECH PICC / PICC-Lite compiler */

#if defined(__PICC__) || defined(__PICCLITE__)

#include <pic.h>

#endif

/* HI-TECH PICC-18 compiler */

#if defined(__PICC18__)

#include <pic18.h>

#endif

/* MPLAB C18 Compatibility Header */

#ifdef __18CXX

#include <pic18.h>

#endif

Puede que en un principio en sus programas no implemente tantas opciones y solo se enfoque en una arquitectura,

pero tener conocimiento ayudará a entender archivos fuentes que encontremos incluidos en el compilador (librerías)

y después con mucha más practica comenzar a implementarlos 

Su memoria de programa de de 32768 bytes, pero las instrucciones ocupan 2 bytes (excepto call, goto, movff, lsfr

que ocupan 4), por lo que se pueden almacenar hasta 16384 instrucciones como máximo. Las direcciones especiales

son0×00 que corresponde al reset del microcontrolador, 0×08 correspondiente a la interrupción de alta prioridad

y 0×18correspondiente a la interrupción de baja prioridad. Las prioridades de interrupciones pueden no habilitarse

y comportarse como un PIC16F, o habilitarse y cada fuente de interrupción (excepto la interrupción externa por RB0,

solo es de alta prioridad) tiene un bit que la configura como de alta prioridad o baja prioridad.

La memoria RAM es de 2048 bytes, 8 bancos de 256

bytes donde 4 de ellos son utilizados por el módulo USB en caso de utilizarlo. Además se tiene 160 bytes dedicados a

los registros de funciones especiales utilizados para la configuración de los módulos del microcontrolador.

Físicamente es un dispositivo de

40/44 pines donde, excepto los de alimentación, los pines tienen varias funciones según como se lo configure. Se

puede encontrar con los package TQFP, QFN o DIP. Este último, el más utilizado para el aprendizaje y los hobbystas,

tiene el siguiente pinout:

Hay mucho más por agregar respecto a este microcontrolador, pero no es la intensión de este tutorial, para

profundizar más es recomendable leer el datasheet.

Teniendo una idea general de cómo es la estructura de un archivo fuente de C pasaremos a realizar un primer

ejemplo como para llevarlo al hardware y ver su funcionamiento. Para trabajar nuestro microcontrolador necesita

una fuente de reloj, que provea una señal a frecuencia constante que permita ir leyendo y ejecutando las sentencias

secuencialmente. Generalmente los microcontroladores modernos ofrecen la posibilidad de recibir esta señal de reloj

desde varias fuentes, cristales externos, osciladores externos u osciladores internos, pero además permiten elevar la

frecuencia de trabajo internamente (PLL). Esto permite mayor flexibilidad y además al aumentar la frecuencia

ejecutar más instrucciones por segundo.

El microcontrolador PIC18F4550 que se utiliza en este tutorial incluye un PLL que permite multiplicar la frecuencia

de un cristal u oscilador externo, pero no así del oscilador interno.

A tener en cuenta, los microcontroladores de gama baja de Microchip necesita 4 ciclos de reloj para ejecutar una

instrucción simple, entonces tenemos que Fcy=Fosc/4. Un PIC18F4550 que puede trabajar hasta 48 MHz ejecuta

como máximo 12 millones de instrucciones por segundo, o sea 12 MIPS.

Para seleccionar qué tipo de fuente de reloj se utilizará y la configuración del PLL se utilizan los bits de

configuración del microcontrolador que solo pueden ser modificados al ser programados. Además de estas opciones

los bits de configuración permiten establecer otras características como por ejemplo, habilitar o no el reset, proteger

contra lectura ciertos sectores de programa, habilitar el perro guardian, etc. Para más detalles hay que estudiarse el

datasheet del microcontrolador específico, dado que en PICs pueden variar entre familias.

Para configurar el reloj y PLL del PIC18F4550 veamos el siguiente esquema extraído del datasheet:

Vamos a fijar como frecuencia de trabajo los 48 MHz máximos que soporta el microcontrolador, para ello vamos a

utilizar como fuente un cristal externo de 12 MHz. Para lograr los 48MHz es necesario habilitar el PLL, esto lo

hacemos mediante los bits FOSC3:FOSC0. Para un cristal de 12 MHz se debe colocar 111x = HS oscillator, PLL

enabled (HSPLL) y revisando la documentación que provee XC8 (doc/pic18_chipinfo.html) se debe

colocar FOSC=HSPLL_HS. Si observamos la figura anterior, al habilitar el PLL la frecuencia de entrada del mismo

debe ser 4 MHz, y para ello se provee de un divisor de frecuencia controlado por PLLDIV, en este caso los 12 MHz

los dividimos por 3, por lo tanto debemos hacer PLLDIV=3. La frecuencia de salida del PLL es de 96 MHz, los cuales

sirven de fuente de clock  para el módulo USB y también para el microcontrolador. Pero para el microcontrolador los

96 MHz los podemos dividir en 2, 3, 4 o 6! O sea, podemos obtener gran variedad de frecuencias. Como nos

establecimos como meta obtener 48 MHz debemos hacer CPUDIV=OSC1_PLL2. En resumen en los bit de

configuraciones establecemos:

#pragma config FOSC=HSPLL_HS, PLLDIV=3, CPUDIV=OSC1_PLL2

Estas configuraciones serían las principales para establecer el reloj de nuestro microcontrolador pero existen dos

fusibles de configuración que otorgan otras opciones de trabajo. Tenemos IESO, que permite cambiar la fuente de

clock cuando el microcontrolador está en funcionamiento y FCMEN que en caso de fallar la fuente de reloj principal

realiza el cambio a la fuente de reloj interna INTRC. Dentro del datasheet hay secciones donde podemos encontrar

más detalles para el uso de estos modos, aquí solo los estableceremos como OFF:

#pragma config IESO=OFF, FCMEN=OFF

Como comentamos anteriormente existen otros fusibles de configuración que dependen del microcontrolador

utilizado, aquí vamos a establecerlos de la siguiente manera, los detalles los podemos encontrar en la

sección Special Feactures of the CPU/Configuration Bits.

#pragma config PWRT=OFF, BOR=OFF, BORV=3, VREGEN=ON, WDT=OFF

#pragma config MCLRE=ON, XINST=OFF, LVP=OFF, PBADEN=OFF

Hay varios más, pero si no se configuran quedan como vienen por defecto. En MPLAB X podemos ver la

configuración  en Windows/PIC Memory Views/Configuration Bit.

Como primer ejemplo vamos hacer titilar un led, este ejemplo es el “Hola Mundo” en los microcontroladores. Para

ello vamos a necesitar una función que cree una demora y para ello XC8 dispone de __delay_us() y __delay_ms(), para

integrarlas debemos llamar a xc.h pero además previamente definir el valor de _XTAL_FREQ. A tener en cuenta,

tanto __delay_ms como __delay_us utilizan la función _delay(), y esta solo soporta hasta 197120 ciclos, o sea que para

48 MHz la máxima demora posible es 16.42 ms.

Para el control del led debemos establecer el pin a utilizar como salida y luego escribir en el pin el valor que

queramos establecer (0 o 1 lógico), para ello tenemos los registros TRIS y los registros LAT. En caso de necesitar

leer el valor que tiene un pin se utilizan los registros PORT. Esto es una mejora introducida a partir de la familia

PIC18, debido a que en familias anteriores para hacer operaciones sucesivas en puertos se necesitaba agregar

un nop entre ellas. Si por ejemplo queremos controlar nuestro led mediante el pin B0 debemos trabajar

con TRISB y LATB, XC8 para facilitarnos el desarrollo tiene definido el bit 0 de TRISB como TRISB0 o como

también es compatible con C18 se puede acceder mediante TRISBbits.TRISB0. Los mismo para el bit

0 de LATB, LATB0 o LATBbits.LATB0. Esto lo podemos ver en el archivo cabecera pic18F4450.h que se encuentra

en xc8\v1.00\include.

Entonces el código ejemplo seria de la siguiente forma:

/* 

 * File:   main.c

 * Author: Suky

 */

#define _XTAL_FREQ 48000000

#include <xc.h>

#pragma config FOSC=HSPLL_HS, PLLDIV=3, CPUDIV=OSC1_PLL2

#pragma config IESO=OFF, FCMEN=OFF

#pragma config PWRT=OFF, BOR=OFF, BORV=3, VREGEN=ON, WDT=OFF

#pragma config MCLRE=ON, XINST=OFF, LVP=OFF, PBADEN=OFF

void main(void) {

    TRISB0=0; // Lo establecemos como salida

    while(TRUE){

        // _delay(x) 197120 cycles max

        LATB0=0; // Apagamos led

        __delay_ms(10);

        LATB0=1; // Encendemos led

        __delay_ms(10);

    }

}

Nota: Tener en cuenta que la demora generada es solo de 10ms, en simulación se podrá visualizar el titilar del led

pero en una implementación real no, pronto lo mejoraremos 

Pero hagamos uso de los macros!!! El ejemplo quedaría así:

/* 

 * File:   main.c

 * Author: Suky

 */

#define _XTAL_FREQ 48000000

#include <xc.h>

#pragma config FOSC=HSPLL_HS, PLLDIV=3, CPUDIV=OSC1_PLL2

#pragma config IESO=OFF, FCMEN=OFF

#pragma config PWRT=OFF, BOR=OFF, BORV=3, VREGEN=ON, WDT=OFF

#pragma config MCLRE=ON, XINST=OFF, LVP=OFF, PBADEN=OFF

#define PIN_DIR_LED    TRISB0  

#define PIN_LED        LATB0

#define OUTPUT         0

#define INPUT         1

#define ON            1

#define OFF           0

void main(void) {

    PIN_DIR_LED=OUTPUT; 

    while(TRUE){

        // _delay(x) 197120 cycles max

        PIN_LED=OFF;

        __delay_ms(10);

        PIN_LED=ON;

        __delay_ms(10);

    }

}

Se dan cuenta que si quiero cambiar de pin para controlar el Led se hace mucho más sencillo? Y eso que solo es un

ejemplo muy sencillo!

En este ejemplo solo implementamos la función principal (void main(void)) que debe estar si o si en nuestro proyecto,

pues el compilador tiene una función que es llamada al ocurrir un reset (vector 0×00) la cual inicializa el puntero de

la pila (Stack en ingles), las variables inicializadas, etc. y luego llama a la función main().

Esta función puede contener el código para inicializar el hardware de la forma que nosotros queramos, o llamar a

funciones adicionales que hagan esta tarea y luego generalmente tiene el bucle infinito. Este bucle debe estar si o si,

sino al terminar de ejecutar las instrucciones el microcontrolador se resetea.  Dentro del bucle tendremos lo que

queremos que el microcontrolador ejecute periódicamente hasta ser reseteado. En C un bucle infinito se puede hacer

de varias formas, aquí usamos la sentencia while:

while(Condicion){Sentencias}

Esta sentencia o estructura de control de C permite ejecutar las sentencias que contiene de forma cíclica mientras la

condición sea verdadera. Tener en cuenta que primero se pregunta si la condición es cierta y luego ejecuta las

sentencias. Como la condición en nuestro caso es TRUE (1), se ejecuta de forma infinita.

Esto representado en un diagrama de flujo sería la siguiente:

Otra forma de implementarlo es usando la sentencia do-while:

do{Sentencias}while(Condicion);

En este caso las sentencias se ejecutan primero y luego se pregunta la condición, por lo que las sentencias se

ejecutan mínimo una vez.

El hardware mínimo para poder implementar el ejemplo seria el siguiente:

Operadores en C

Operador de asignación:   Permite asignar un valor al operando izquierdo en base al operando

derecho. El valor puede truncarse dependiendo del tipo de variable del operando izquierdo.

Veamos unos ejemplos.

unsigned char K; // Variable que puede tomar valores enteros entre 0 y 255.

float J; // Flotante de 24-bits

K=150; // k adquiere el valor 150.

K= 796; // k adquiere el valor 28. 796 necesita 16-bits para ser almacenado, pero como la variable es de 8-bits, los 8-

bits altos se pierden.

J=10;     // J adquiere el valor 10.0

J=95000.0; // J adquiere el valor 95000.0

J=95001.0; // J adquiere el valor 95002.0. Esto es debido a la resolución que se logra con 24-bits, si cambiamos a 32-

bits podremos almacenar 95001.0.

El truncamiento también ocurre cuando la asignación es mediante otra variable:

unsigned char K;

unsigned int A=796;

K=A; // K adquiere el valor 28.

Operadores de comparación: Permiten comparar dos operandos  y determinar si es falso o

verdadero. La siguiente tabla los describe:

Operado

r Descripción Ejemplo

== Retorna true si los operandos son iguales

k==0, k==’0’,

k==a

¡= Retorna true si los operandos son distintos

k!=0, k!=’0’, k!

=a

<

Retorna true si el operando izquierdo es menor al

operando derecho k<0, k<’0’, k<a

<=

Retorna true si el operando izquierdo es menor o igual

que el derecho

k<=0, k<=’0’,

k<=a

>

Retorna true si el operando izquierdo es mayor al

operando derecho k>0, k>’0’, k>a

>=

Retorna true si el operando izquierdo es mayor o igual

que el derecho

k>=0, k>=’0’,

k>=a

Operadores aritméticos: Básicamente, permiten realizar cálculos aritméticos como suma, resta,

división, etc..

Operado

r

Descripció

n Ejemplo

+ Suma A=5+4; // A adquiere el valor 9

- Resta A=20-8; // A adquiere el valor 12

*

Multiplicaci

ón A=15*10; // A adquiere el valor 150

/ División

A=15/6; // Si A en un entero adquiere el

valor 2

// Si A es un flotante adquiere el valor

2.5

++ Incremento A++; // Es igual a realizar A+1

– Decremento A–; // Es igual a realizar A-1

Operadores lógicos: Retornan un valor lógico, true o false, en base a las denominadas tablas de

verdad. Los operadores son:

Operado

r

Descripció

n Ejemplo

&& AND

(k<5) &&

(k>0)

|| OR

(k==3) ||

(k==15)

! NOT !(k<5)

Tablas de verdad:

AND

A B

Resultad

o

Fals

e

Fals

e False

Fals

e

Tru

e False

Tru

e

Fals

e False

Tru

e

Tru

e True

OR

A B

Resultad

o

Fals

e

Fals

e False

Fals

e

Tru

e True

Tru

e

Fals

e True

Tru

e

Tru

e True

NOT

A

Resultad

o

Fals

e True

Tru

e False

Operadores bitwise:   Estos operadores permiten actuar a nivel bit. Tenemos los siguientes

operadores:

Operado

r Descripción Ejemplo

&

AND, compara dos bits; si los dos son 1 el resultado

es 1, en otro caso el resultado será 0.

A=0xAA ->

0b10101010

B=0xF0 ->

0b11110000

C=A&B=0xA0 ->

0b10100000

|

OR, compara dos bits, si cualquiera de los dos bits es

1 el resultado es 1, en otro caso será 0.

A=0x0A ->

0b00001010

B=0xF0 ->

0b11110000

C=A|B=0xFA  ->

0b11111010

^

XOR, compara dos bits, si cualquiera de los dos bits

es 1 pero no ambos el resultado es 1, en otro caso

será 0.

A=0xFA ->

0b11111010

B=0xAF ->

0b10101111

C=A^B=0×55 ->

0b01010101

~

Complemento, retorna el complemento a 1 del

operando

A=0xAA ->

0b10101010

B=~A=0×55 ->

0b01010101

>>

Desplazamiento a derecha, desplaza a la derecha un

numero de bit especificado

A=0×37 ->

0b00110111

B=A>>2=0x0D ->

0b00001101

<<

Desplazamiento a izquierda, desplaza a la izquierda

un numero de bit especificado

A=0×37 ->

0b00110111

B=A<<1=0x6E ->

0b01101110

Por aquí un nuevo aporte del amigo Jukinch, gracias!!

El operador shift >> tiene diferente comportamiento dependiendo el tipo de dato al que se le va

a aplicar. Más precisamente si la variable fue creada con signo o sin signo.

EJEMPLO: creamos dos variables

unsigned char A=100;

signed char B=-100;

Valor de A= 100, valor decimal.

Valor de A en binario: 01100100.

Valor de A en binario luego de aplicarle shift >> 2:

Valor en binario: 00011001, en este caso el bit más significativo se indica con un cero y los bits

que se desplacen hacia la derecha del bit más significativo se completan con ceros 0.

Valor de B= -100, valor decimal

Valor de B en binario: 10011100

Valor de B en binario luego de aplicarle shift >> 2:

Valor de B en binario: 11100111, en este caso el bit más significativo se mantiene para indicar

el signo negativo y los bits que se desplacen hacia la derecha del bit más significativo se

completan con unos 1.

Variables en C

Una variable no es más que la asignación de un nombre a una dirección de memoria, la cual

dependiendo del tipo puede ocupar uno o más bytes. La sintaxis es la siguiente:

<Tipo de variable> <Nombre de la variable>;

Tenemos dos tipos de variables, variables globales que pueden ser utilizadas/modificadas por

cualquiera de las funciones dentro del mismo archivo fuente o variables locales que pueden ser

utilizadas solo en la función que ha sido declarada.

unsigned char VariableGlobal;

void vMiFuncion(void){

   unsigned char VariableLocal;

   VariableGlobal=10;

   VariableLocal=55;

}

void main(void){

   VariableGlobal=60;

}

En XC8 disponemos de los siguientes tipos de datos:

Los tipos bit y short long son tipos no estándar disponibles en XC8. En el caso de los tipos

flotantes se puede ver que dice 24 o 32 bits, esto depende de la configuración del proyecto que

por defecto viene establecido en 24-bits.

Adicionalmente al tipo se pueden implementar modificadores que darán información adicional

del modo en que serán utilizadas las variables. XC8 admite los modificadores estándar de ANSI

C y modificadores especiales que son útiles en sistemas embebidos en microcontroladores de 8-

bits.

Const: Indicara que la variable es solo de lectura y que no se modificará. Al definirse de esta

manera la variable se localiza en la memoria de programa. Puede indicarse la dirección

absoluta de donde ubicarla de la siguiente manera:

const char MiConstante @ 0×100=50;

Volatile: Indicará al compilador que no se asegura que se mantendrá su valor en accesos

sucesivos. Este tipo de modificador debe utilizarse en variables que son modificadas en

interrupciones y que son testeadas en el programa principal. Esto es para evitar que el

compilador al detectar que la variable no es modificada en dos puntos dados del código haga un

cacheo del valor de la variable. Ejemplo:

volatile bit MiBandera=0;

void interrupción(void){ // Evento impredecible generado asincrónicamente por el hardware.

   MiBandera=1;

}

void main(void){

   while(1){

     if(MiBandera==1){

        MiBandera=0;

     }

  }

}

Pesistent: Le indica al compilador que dicha variable no debe ser inicializada a 0 en el inicio.

Por default si a una variable no se le asigna un valor al declararla en el inicio es establecida en

0, al colocar persisten esto se evita y mantiene su valor al ocurrir un reset.

persisten char VariablePesistente;

Near: Le indica al compilador que la variable debe estar en el banco de acceso (PIC18), con lo

cual puede ser accedida sin importar el banco de memoria que este seleccionado. Esto reduce

el código y los tiempos de ejecución ya que no es necesario el cambio de banco.

near char VariableAcceso;

En la siguiente imagen podemos ver la diferencia entre el modo de acceso a una variable near y

otra ubicada en otros bancos:

near unsigned char k;

unsigned char j @ 0x100;

unsigned char i @ 0x200;

EEPROM: Le indica al compilador que la variable será ubicada en la memoria EEPROM. Tengan

en cuenta que se crea código adicional para acceder a esta memoria y que es mucho más lento.

Static: Le indica al compilador que le asigne una posición fija automática, pero que reserve su

espacio incluso en variables locales. Las variables locales estáticas sólo tiene alcance en la

función o bloque en el que se definen, pero a diferencia de las variables no-static, su memoria

se reserva para toda la duración del programa. Sirven para retener el valor de la variable en

llamadas sucesivas a dicha función.

Extern: Le indica al compilador que la variable declarada pertenece a otro módulo (otra función

u otro archivo fuente), por lo que no es necesario reservar memoria para ella.

Hemos dado un vistazo general de que es una variable y de qué modo se comportara

dependiendo de con que modificadores sea declarada. Ahora presentaremos la estructura de

control for, para de esta manera “mejorar” nuestro primer ejemplo.

Estructura de control for

Esta estructura se usa para ejecutar un bloque de código cierto número de veces. Posee un

valor de inicio, un valor final y un valor de incremento.

for(Valor inicial, condición, incremento){

Sentencias.

}

Ejemplo:

unsigned char k, a;

for(k=0;k<10;k++){

a=k;

}

En primer lugar k se establece en 0, luego se testea la condición, si es válida ejecuta el bloque

de código.

Luego se aplica el incremento establecido, se testea la condición y si es válida ejecuta el bloque

de código nuevamente. Esto se repite hasta que la condición no es válida. De esta manera k

toma los siguientes valores:

0,1,2,3,4,5,6,7,8,9,10.

En cambio a toma los siguientes valores:

0,1,2,3,4,5,6,7,8,9.

Otro ejemplo:

for(k=5;k<=17;k+=3){

a=k;

}

k=5,8,11,14,17.

a=5,8,11,14.

En nuestro primer ejemplo teníamos el inconveniente de que la demora máxima que podemos

generar a 48MHz es de 16.42 ms. Ahora podemos mediante el uso de variables y estructuras for

crear una demora mayor, por ejemplo ejecutando 30 veces una demora de 10ms. Tenemos

entonces:

Ahora sí, si lo ejecutamos en hardware real podremos ver el titular del led.

/*

* File:   main.c

* Author: Suky

*/

#define _XTAL_FREQ 48000000

#include

#pragma config FOSC=HSPLL_HS, PLLDIV=3, CPUDIV=OSC1_PLL2

#pragma config IESO=OFF, FCMEN=OFF

#pragma config PWRT=OFF, BOR=OFF, BORV=3, VREGEN=ON, WDT=OFF

#pragma config MCLRE=ON, XINST=OFF, LVP=OFF, PBADEN=OFF

#define PIN_DIR_LED    TRISB0  

#define PIN_LED        LATB0

#define OUTPUT         0

#define INPUT         1

#define ON            1

#define OFF           0

void main(void) {

Unsigned char k;

    PIN_DIR_LED=OUTPUT; 

    while(TRUE){

        // _delay(x) 197120 cycles max

        PIN_LED=OFF;

        for(k=0;k<30;k++){__delay_ms(10);}

        PIN_LED=ON;

       for(k=0;k<30;k++){__delay_ms(10);}

    }

}

Estructura de control if, if-else.

Estructura if: En todo algoritmo que desarrollemos seguramente deberemos tomar una

decisión de ejecutar o no cierto bloque dependiendo del valor de una variable o el

estado de un pin, por ejemplo. Para ello tenemos la estructura if la cual evalúa una

condición y si es verdadera ejecuta el bloque de código, la sintaxis sería la siguiente:if(<condición>){

    // Sentencias

}

En traducción sería, si la condición es verdadera ejecuta esto (Sentencias)

Ejemplos:

If(RB0==1){ // Es RB0 igual a 1?

    LATB1=1; // Si, coloco LATB1 a 1

}

if(MiVariable>=5){  // Es MiVariable mayor o igual a 5?

    LATC0=1;    // Si

    LATC1=1;

}

Estructura if-else: En este caso se agrega la instrucción else. Traduciendo nuevamente

sería, si la condición es verdadera ejecuta esto (Sentencias 1) y sino esto otro

(Sentencias 2)if(<condición>){

    // Sentencias 1

}else{

    // Sentencias 2

}

Ejemplos:

If(RB0==1){ // Es RB0 igual a 1?

    LATB1=1;    // Si

}else{          // No, entonces…

    LATB1=0;

}

if(MiVariable>=5){

    LATC0=1;

    LATC1=1;

}else{

    LATC0=1;

    LATC1=0;

}

Cuando se utilizan estructuras de control y operadores lógicos hay que tener en cuenta

la precedencia. Las expresiones vinculadas por && o || son evaluadas de izquierda a

derecha, y la evaluación se detiene tan pronto como se conoce el resultado verdadero o

falso. Ejemplo:if((k<10) && (++i==100)){

}

Primero se evalúa si k es menor a 10, si esta condición es falsa sin importar lo que

sucede con lo demás (por el &&) salta a la siguiente línea. Si en el diseño del algoritmo

se necesita que i se incremente de igual manera el resultado será incorrecto.

Arreglos de variables:

Los arreglos son una colección de variables del mismo tipo identificadas por un mismo

nombre. Son posiciones de memoria contiguas y cada elemento del arreglo puede ser

direccionado por medio de un índice que lo identifica. Los arreglos pueden ser de una o

más dimensiones. La sentencia sería la siguiente:

<tipo de variable> <Nombre del arreglo>[<Tamaño>];

<tipo de variable> <Nombre del arreglo>[<Tamaño1>][<Tamaño2>]…[<TamañoN>];

El primer elemento se lo identifica con el índice 0, por lo que si declaramos un arreglo

de 10 elementos el índice puede tomar los valores de 0 a 9.

Veamos algunos ejemplos:

unsigned char Vector[10];

unsigned int Matriz[5][5];

// Cargamos un valor a un elemento:

Vector[4]=150;

Matriz[2][4]=1024;

// Leemos un valor:

LATB=Vector[0];

LATC=Matriz[0][4];

Cuando declaramos el arreglo podemos cargar los valores iniciales de la siguiente

manera:

unsigned char Vector[10]={10,0x15,’A’,…};

unsigned int Matriz[5][5]={78,0x56,’1’,0b11,…};

Nota: Esto solo se puede hacer al declararse, en otro caso debe accederse al elemento

mediante un índice.

Cadenas.

Para manejar string (cadena de caracteres ascii) en C se deben crear arreglos de char

con suficiente espacio como para manejar los string a utilizar más un byte adicional

para almacenar el carácter nulo (0, ‘\0’), el cual indica final de string. Entonces si

sabemos que los string a utilizar dentro del algoritmo van a tener como máximo 10

caracteres, el arreglo a crear debe ser de 11 posiciones:

char string[11]=”Mi arreglo”;

Esto equivale a:

char string[11]=’M’,’i’,’ a’,’r ’,’r ’,’e ’,’g ’,’l ’,’o ’,’\0’;

Como C no maneja string, no podemos utilizar los operandos como para enteros o

flotantes, se deben utilizar funciones especificas para trabajar con ellos. C en su

biblioteca estándar incluye la librería string.h la cual dispone de funciones para

comparar strings, para concatenar strings, para copiar strings, para buscar caracteres

dentro del string, etc. Para más información leer el help de la misma.

Un ejemplo que vamos a realizar para ejercitar lo mostrado hasta el momento será

visualizar mediante un display de 7 segmentos la cuenta de pulsaciones que realicemos.

El hardware a utilizar sería el siguiente:

Código:/* 

 * File:   main.c

 * Author: Suky

 */

#define _XTAL_FREQ 48000000

#include <xc.h>

#pragma config FOSC=HSPLL_HS, PLLDIV=3, CPUDIV=OSC1_PLL2

#pragma config IESO=OFF, FCMEN=OFF

#pragma config PWRT=OFF, BOR=OFF, BORV=3, VREGEN=ON, WDT=OFF

#pragma config MCLRE=ON, XINST=OFF, LVP=OFF, PBADEN=OFF

#define PIN_DIR_SW          TRISB4

#define PIN_SW              RB4

#define PORT_DIR_DISPLAY    TRISD

#define PORT_DISPLAY        LATD

#define OUTPUT              0

#define INPUT               1

#define ON                  1

#define OFF                 0

const unsigned char Display7SegAC[10]={0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10};

void main(void) {

    unsigned char k,j;

    PIN_DIR_SW=INPUT;

    PORT_DIR_DISPLAY=0x00;

    // Comienza en 0

    PORT_DISPLAY=Display7SegAC[0]; // El display es de Anodo Comun, como el arreglo..

                                    // ..es para Catodo Comun se realiza el complemento xD

    k=0;

    while(TRUE){

        if(PIN_SW==0){

            __delay_ms(10); // Preguntamos, esperamos y volvemos a preguntar para evitar el efecto rebote..

            if(PIN_SW==0){  // .. de esta manera nos aseguramos que es una pulsación valida.

                if(++k==10){ // Primero se incrementa k, luego se compara..

                    k=0;

                }

                PORT_DISPLAY=Display7SegAC[k]; // Cargamos nuevo valor

                while(PIN_SW==0); // Esperamos a que se suelte

            }

        }

    }

}

Diagrama de flujo: