Programación en C de microcontroladores PIC de gama media. Para el desarrollo de programas para microcontroladores se tiene 2 alternativas en cuanto al tipo de lenguaje de programación: • Lenguaje de bajo nivel (ensamblador) • Lenguaje de alto nivel (BASIC, C, …) El uso del lenguaje ensamblador tiene 3 ventajas fundamentales: • Aprovechamiento más eficiente de los recursos del microcontrolador. • Depende del programador el obtener un código optimizado tanto en número de instrucciones como en velocidad de ejecución. • Los microcontroladores PIC cuentan sólo con 35 instrucciones las cuales pueden llegar a dominarse son relativa facilidad. En ocasiones el desarrollo de programas en lenguaje ensamblador se hace especialmente tedioso y requiere demasiado esfuerzo y tiempo. Este es el caso de los programas con cierta complejidad en los que, por ejemplo, se realizan determinado cálculos matemáticos. Una alternativa al lenguaje ensamblador es el uso de lenguajes de alto nivel para la programación de los microcontroladores. En estos apuntes se describen algunas particularidades de la programación en lenguaje C del los PIC de gama media y el uso del compilador PICC de HI-TECH. Tipos de datos Tipo Tamaño (bits) aritmética Bit 1 Bolean Char 8 Con o sin signo Unsingned char 8 Entera sin signo Short 16 Entera con signo Unsigned short 16 Entera sin signo Int 16 Entera con signo Unsigned int 16 Entera sin signo Long 32 Entera con signo Unsigned long 32 Entera sin signo Flota 24 real Double 24 o 32 Real Formato de representación numérica (Radix): Radix Formato Ejemplo Binario 0bnúmero 0b00001011 Octal 0número 0562 Decimal número 185 Hexadecimal 0xnúmero 0xF3
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Programación en C de microcontroladores PIC de gama media. Para el desarrollo de programas para microcontroladores se tiene 2 alternativas en cuanto al tipo de lenguaje de programación:
• Lenguaje de bajo nivel (ensamblador) • Lenguaje de alto nivel (BASIC, C, …)
El uso del lenguaje ensamblador tiene 3 ventajas fundamentales: • Aprovechamiento más eficiente de los recursos del microcontrolador. • Depende del programador el obtener un código optimizado tanto en número de
instrucciones como en velocidad de ejecución. • Los microcontroladores PIC cuentan sólo con 35 instrucciones las cuales pueden
llegar a dominarse son relativa facilidad. En ocasiones el desarrollo de programas en lenguaje ensamblador se hace especialmente tedioso y requiere demasiado esfuerzo y tiempo. Este es el caso de los programas con cierta complejidad en los que, por ejemplo, se realizan determinado cálculos matemáticos. Una alternativa al lenguaje ensamblador es el uso de lenguajes de alto nivel para la programación de los microcontroladores. En estos apuntes se describen algunas particularidades de la programación en lenguaje C del los PIC de gama media y el uso del compilador PICC de HI-TECH.
Tipos de datos
Tipo Tamaño (bits) aritmética Bit 1 Bolean Char 8 Con o sin signo Unsingned char 8 Entera sin signo Short 16 Entera con signo Unsigned short 16 Entera sin signo Int 16 Entera con signo Unsigned int 16 Entera sin signo Long 32 Entera con signo Unsigned long 32 Entera sin signo Flota 24 real Double 24 o 32 Real
Tipo bit: Las variables de tipo bit sólo puede almacenar los valores “0” ó “1”. Con ellas se puede hacer un uso eficiente de la memoria al permitir definir banderas que no consumen gran cantidad de memoria RAM. Para declarar una variable tipo bit se utiliza la palabra “bit”, por ejemplos:
bit init_flag; static bit fin_flag;
Las variables tipo bit no pueden ser parámetro se una función, pero una función si puede retornar un valor en una variables de tipo bit. No se pueden definir punteros a variables de tipo bit ni inicializar estáticamente este tipo de variables. Para que una variable tipo bit tenga un valor determinado se ha de inicializar explícitamente en el código del programa. Cuando se asigna una variable de otro tipo a una tipo bit sólo se asigna el bit menos significativo. Para asignar a una varible tipo bit el valor “0” ó “1” dependiendo si en valor de una variable es cero o no se debe usar lo siguiente: Bit_varible = otra_varible ¡= 0 A una variable de tipo bit se le puede asociar una dirección absoluta. Por ejemplos, para declarar una variable de tipo bit que sea el bit 13 de una variable de tipo int (16 bits): int var_nombre; bit var_nombre @ 13; Otro ejemplo: Definir una variable PD de tipo bit que permita acceder al bit 27 del registro STATUS: static unsigned char STATUS @ 0x03; static bit PD @ (unsigned)&STATUS*8+3; Esta última forma es la que se utiliza en los archivos de cabecera (.h) para definir los bits de los distintos registros. Esto permite hacer referencia a estos bits directamente. Por ejemplo: En “pic1684.h” el RB5 está definido como el bit 5 del registro PORTB,
static volatile bit RB5 @ (unsigned)&PORTB*8+5; el GIE está definido como el bit 7 de INCONT static volatile bit GIE @ (unsigned)&INTCON*8+7; el RP0 el 5 bit del registro STATUS:
static volatile bit RP0 @ (unsigned)&STATUS*8+5;
Tipo chart Pude ser sin signo (por defecto) o consigno (opción de compilación–SIGNED_CHAR al PIC C). El tipo char es el más pequeño (8 bit) para la representación de enteros. En los PIC este tipo de dato puede ser usado para almacenar enteros, para almacenar código ASCII o para acceder a las localizaciones de entrada/salida.
El tipo de dato “unsigned char” es el que de forma natural manejan las instrucciones del PIC, por lo que se aconseja su uso siempre que sea posible ya que esto maximiza el rendimiento y minimiza el tamaño del código obtenido durante la compilación.
Enteros de 16 bits (int, short) Representación “little endian”: el byte menos significativo en la dirección más baja
Byte 1 Byte 0 16 bit
Enteros de 32 bit (long) Representación “little endian”
32 bit
Coma flotante (float, double) El tipo “float” siempre se representa en un formato de 24 bit. En el caso del tipo “double” el formato de 24 bit se usa por defecto pero puede usarce un formado de 32 bits mediante la opción de compilación –D32 al compilador PICC. Formatos de coma flotante:
donde:
• “Sign”: bit de signo: (-1)^sign • “Exponent”: almacena el exponente con un exceso de 127 (exponente 0 se
almacena como 127). • “Mantissa”: mantissa
El valor del número representado se calcula como
Valor = (-1)^sign * 2^(biased_exponent-127) * 1.mantissa Un valor de 0 se indica con un exponent igual a cero. Ejemplos:
El procedimiento para el cálculo en coma flotante se presenta mediante el ejemplo 1 de la tabla anterior:
• El bit de signo es cero. • El biased_exponent es 251, por lo tanto el exponente es 251-127 = 124. • Coger el número binario a la derecha del punto en la mantisa. Convertirlo a
decimal y dividirlo por 2^23 donde 23 es en número de bits que representan la mantisa. Esto da 0.302447676659.
El valor del número en coma flotante es: (-1)^0*2^124*1.302447676659 = 2.77000e+37
Variables absolutas Una variable global o estática puede ser localizada en una dirección absoluta si en su declaración se usa el operador @ seguido de la dirección que se desea asignar a la variable. Ejemplo: volatile unsigned char Portvar @ 0x06 es la declaración de la variable llamada charPortvar en la dirección 0x06. La línea de ensamblador que genera el compilador para la declaración anterior es: _Portvar equ 0x06 Ni el compilador ni el enlazador realizan un chequeo para comprobar el solapamiento de variables absolutas en la memoria. Esto es total responsabilidad del programador. Esta forma de “localización” de variables se utiliza en los archivos de definición de símbolos (.h) para asociar identificadores de C con las direcciones los registros del PIC.
Estructuras y uniones Se soportan los tipos struct y union de cualquier tamaño a partir de 1 byte, así como punteros a estos tipos. Pueden se parámetros de entrada o salida de funciones
Campos de tipo bit en una estructura Los campos de tipo bit se localizan comenzando por el LSB de la palabra en la que ellos serán almacenados. Los campos de tipo bits se almacenan en palabras de 8 bits. Cuando un campo de tipo bit se declara dentro de una estructura, este se localiza dentro de una unidad de 8 bits. Cuando el número de estos campos excede de 8 una nueva unidad de 8 bit se añade a la estructura. Ejemplos de declaración de estructuras tipo bit: struct{ unsigned hi : 1; unsigned dummy : 6; unsigned lo : 1;
} foo @ 0x10 hi es un campo de 1 bit, dummy de 6 bit y lo de 1 bit que se localizan de forma consecutiva dentro de la localización de memoria 0x10.
En la estructura siguiente entre los campos de tipo bit hi y lo se dejan 6 bits sin referencia: es el resultado de no poner nombre en la declaración de un campo. struct{ unsigned hi : 1; unsigned : 6; unsigned lo : 1;
} foo @ 0x10
Cadenas de caracteres Una cadena constante siempre se localiza en la memoria ROM y es accesible sólo mediante un puntero constante. En el siguiente ejemplo la cadena “Hola a todos” se almacena en la ROM :
#define HOLA “Hola a todos” …. SendBuff (HOLA);
Un arreglo no constante inicializado con una cadena se almacena en la RAM, por ejemplo: char saludo[] = “Hola a todos”; produce un arreglo en la RAM el cual se inicializa con la cadena “Hola a todos” (copiado desde la ROM). Si el arreglo es constante, entonces se crea en la ROM. Para pasar una cadena constante como parámetro de una función, o asignar esta a un puntero, este ha de ser un const char *, por ejemplo: void SendBuff(const char * ptr)
Modificadores de tipo: const y volatile Const: dice al compilador que el dato es de tipo constante. Los objetos declarados constantes se almacenan en un módulo especial en la ROM. Volatile: dice al compilador que el objeto no tiene garantía de mantener su valor entre accesos sucesivos. El compilador tiene en cuenta esto para evitar referencias redundantes a objetos declarados volatile y así evitar errores en la ejecución de programa. Todos los puertos de entrada/salida se declaran en los archivos .h como variables volatile. Ejemplo, declaración de PORTA en PIC1687.h:
static volatile unsigned char PORTA @ 0x05; También deben declararse volatile las variables que pueden ser modificadas por la rutina de interrupción.
Modificadores de tipo especiales Los modificadores persistent, bank1, bank2, bank3 permiten al usuario ubicar las variables static y extern en espacios de direcciones particulares. Estos modificadores también se pueden aplicar a los punteros. No pueden ser usados con las variables de tipo auto1 : si se usan en variables locales a una función deben ser combinados con el modificador static. Por ejemplo, no es correcta la forma: void func(void)
{ persistent int intvar; …. } porque intvar es de la clase auto. Para declarar intvar como una variable persistent: static near int intvar;
Persistent Una variable persistent no pierde su valor al apagar el microcontrolador. Estas variables se almacenan en zonas de memoria no volátil (nvram).
bank1, bank2, bank3 Se usan para especificar la localización de las variables en los bancos de memoria RAM. Las variables por defecto se localizan en el bank0. Ejemplos: Define un unsigned char en el banco 1: static bank1 unsigned char cuenta; Se asigna un puntero al unsigned char del banco 1: bank1 unsigned char * prt_cuenta; Se asigna un puntero del banco 3 a la variable cuenta en el banco 1: static bank1 unsigned char * bank3 prt3_cuenta;
Punteros El formato usado por los punteros depende de PIC. Para los microcontroladores de gama media, como el 16f877 las características son las siguientes:
Punteros a RAM: Son de 8 bits, apuntan a la RAM usando el registro índice FSR. Sólo se tiene acceso 256 localizaciones, por tanto únicamente se tiene acceso a los bancos bank0 y bank1 de memoria.
1 Tipo que por defecto tienen las variables locales a una función. Más adelante se verá en detalle.
Punteros a los bancos bank2 y bank3: Son punteros a RAM mediante los cuales se accede los bancos bank2 y bank3
Punteros a constantes: Son de 16 bits. Con ellos se accede a la RAM o a la ROM. Si el MSB es 1, el puntero apounta a la RAM (a cualquiera de los bancos). Estos punteros permiten la lectura de pero no la escitura en la RAM. Si el MSB es 0 el puntero accede al espacio de memoria ROM.
Punteros a funciones: La función se llama usando la dirección asignada al puntero.
Combinando los modificadores de tipo y los punteros Los modificadores persistent, volatile, const pueden ser aplicados a los punteros para controlar el comportamiento de los objetos apuntado. Cuando se usan estos modificadores en la declaración de un puntero se debe de tener cuidado para evitar la confución entre aplicar el modificador al objeto apuntado o al puntero. Como regla se puede seguir la siguiente: si el modificador está a la izquierda del * en la declaración del puntero, estonces se aplica al objeto apuntado. Si el modificador está a la derecha de * entonces se aplica al puntero. Ejemplo: La siguiente línea declara un puntero a un char volatile: volatile char * nptr; La siguiente línea declara un puntero volatile a una variable tipo char: char * volatile nptr; La siguiente línea declara un puntero volatile a una variable tipo char volatile: volatile char * volatile nptr;
Puntero a constante Se usan para acceder a objetos que han sido declarados usando en modificador const. Los punteros a constante se comportan de forma similae al rerto de punteros excepto que no está permitido la escritura a traver de estos punteros, esto es lógico ya que estos referecian a valores constantes. De esta manera, dada la declaración: const char * cptr; la siguiente línea es correcta: ch = *cptr; pero es incorrecto: *cptr = ch; En los microcontroladores de gama media los punteros constante puede acceder a la ROM o a la RAM.
Manejo de interrupciones El compilador permite el manejo de las interrupciones sin necesidad de escribir código en ensamblador. El modificador interrupt puede ser aplicado a una función para indicar al compilador que se trata de una rutina de atención a interrupción. El compilador procesa este tipo de función de forma diferente a las otras funciones que no tiene es modificador interrupt, generando el código para salvar y restrestaurar el valor de los registros usador y retornar de la función con una instrucción RETFIE, en vez de con un RETURN o RETLW. Una función de tipo interrup puede ser declarada de tipo interrrupt void y no tener parámetros. La llamada a una función interrup no se produce desde el código del programa, sino por la ocurrencia de una interrupción por por hardware. La función de atención a interrupción puede si puede llamar a otras funciones, sujeta a ciertas limitaciones.
Función interrupt en microcontroladore se gama media En los microcontroladores de gama media sólo existe un vector de interrupciones, por lo tanto en un programa sólo se puede definir una función de tipo interrupt. El vector de interrupciones apuntará automáticamente a esta función. Ejemplo de función interrupt : float cuenta; void interrupt inc_cuenta(void) { ++cuenta; }
Salvado del contexto Cuando se produce una interrupción el procesaror sólo salva en la pila el PC . El resto de registros (contexto) debe ser salvado por programa. El compilador determina los registros que son usados por la función de interrupción y los salva de manera automática. Si la funciónde interrupció llama a alguna otra función que ha sido definida antes del código de la función interrupt, entonces cualquier registro usado por esta función también será salvado de manera automática. Si dentro de una función interrupt se añaden líneas de código en ensamblador, los registros usados por estas líneas no son salvados de manera automática por el compilador. En este caso es reponsabilidad de l programador el savado de estos registros en la pila.
Recuperación de contexto
Todos los registros salvados en la pila durante la llamada a la función de atención a interrupción son automaticamente recuperados cuando finaliza dicha función y antes de retornar al punto del programa donde se produjo la interrupción.
Habilitación de interrupciones Existen dos macros para habilitar y deshabilitar las interrupciones: ei( ) habilita todas las interrupciones. di( ) deshabilita todas las interrupciones. En los PIC de gama media estas macros afectan al bit GIE del registro INTCON. Las macros debe ser usadas una vez activados los bits de mascara de las insterrupciones que se quieren habilitar. Por ejemplo: ADIE = 1; // habilita la interrupción por fin de conversión A/D PEIE = 1; // habilita todas las interrupciones de perisféricos ei(); //habilita todas las interrupciones …. di(); //deshabilita todas las interrupciones
Mezclando código C y ensamblador Existen diferentes técnicas para introducir fragmento de código en ensamblador dentro de un programa en C.
Funciones externas en ensamblador Una función puede ser escrita en lenguaje ensamblador, salvada en un fichero .as, ensamblada con el ensablador (ASPIC) y combinada con una imagen binaria usando un enlazador. Esta técnica permite el paso de parámetros, ya sean de entrada o salida, entre código C y ensamblador. Para acceder a una función externa desde un programa en C se ha de usar la declaración extern de la función. En los PIC de gama media el acumulador W es usado para el paso de argumentos a las funciones y también para almacenar los valores devueltos por la funciones. Por tanto este registro debe ser preservado por cualquier rutina en lenguaje ensamblador que sea llamada.
Accediendo a objetos en C desde dentro de código en ensamblador
Desde código ensamblador se puede acceder a los objetos gloables de C usando su nombre precedido por el carácter subrayado ( _ ) . Por ejemplo a el objeto cuenta, definido globalmente en un módulo de C:
int cuenta;
se puede acceder desde ensamblador como sigue: global _cuenta; movfw _ cuenta;
Si la rerefencia al objeto en C desde ensamblador se realiza en un módulo de programa diferente, entonces se debe usar la directiva global de ensamblador, como en las líneas de código anteriores. Si la referencia en ensamblador al objeto en C se realiza desde otro módulo, entonces en C se debe declarar al objeto como extern. Por ejemplo:
extern int cuenta;
#asm, #endasm y asm()
Las directivas #asm y #endasm permiten añadir un fragmento de código ensamblador dentro del código en C.
La directiva asm() permite introducir una instrucción en ensamblador dentro del programa en C. Ejemplo:
Las directivas #asm y #endasm no obedecen a las reglas de control e flujo de C. Por lo tanto no se pueden usar dentro de instrucciones de control de flujo como if, while, etc. En caso necesario usar sólo asm(“ ”), la cual interactúa correctamente con las instrucciones de control de flujo de C.
Variables locales C soporta dos clases de viables locales a una función: las variables auto y las static.
Variables auto Es el tipo por defecto de las variables locales. Al menos que explícitamente se declare que una variable es de la clase static, esta será auto. A diferencia de los parámetros de la función, estas variables no son localizadas en memoria en el mismo orden en que han sido declaradas. La mayoría de los modificadores de tipo no pueden ser usados para esta clase de variables, a excepción de const y volatile. Todas las variables auto so localizadas en el banco 0. Los modificadores de banco (bankx) no pueden ser usados en este tipo de variables.
Variables static Las variables static no inicializadas mantienen su valor entre llamadas a la función en la que han sido declaradas. Además a ellas se puede acceder para leer o modificar su contenido desde otras funciones por medio de punteros. La inicialización de una variables static solo se realiza una vez durante la ejecución de programa. Por tanto es preferible su uso al de variables auto inicializadas, cuya inicialización se realiza cada vez que el bloque (función) donde se define es ejecutado.
Bibliotecas estándares (BE) Se definen funciones para diferentes microcontroladores. El nombre de las BE tiene el siguiente formato:
donde:
• “ Processor Type”: es “pic” • “Processor Range” es 2 para los de gama baja, 3 para los de media y 7 para los
de gama alta. • “n” es el número de bancos de memoria ROM. • “m” es el número de bancos de memoria RAM. • “Double Type”: es “-” para la representación 24 bits y “d” para representación
en 32 bits. • “Library Type”: es “c” para bibliotecas estándares, “l” para librerías que
contienen funciones relacionadas con “printf” con soporte adicional para tipo de datos tipo long, es “f” para librerías que contienen funciones relacionadas con “printf” con soporte adicional para tipo de datos tipo long y float.
Por ejemplo, el fichero con nombre PIC401DC.LIB contiene la librería de tipo estandar (C) con soporte para el tipo de datos double de 32 bits (D) de un PIC de gama media (4) con 1 banco de memoria RAM y ningún banco de memoria ROM. En estas bibliotecas existen funciones para:
Una descripción detallada de las funciones disponibles y su uso se puede consultar en la guía de usuario del compilador PIC C (User’s guide PIC ANSI C compiler). Para hacer uso de las funciones disponibles en las BE se ha de incluir el archivo .h donde se declara las funciones a usar. Ejemplo: archivo en que se define una función para el cálculo del cos-1(x). La primera línea del programa incluye el fichero math.h donde se declara la función asin().
#include <math.h>
#define PI 3.14159265358979 #define TWO_PI 6.28318530717958 #define HALF_PI 1.570796326794895