Docsity
Docsity

Prepara tus exámenes
Prepara tus exámenes

Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity


Consigue puntos base para descargar
Consigue puntos base para descargar

Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium


Orientación Universidad
Orientación Universidad


programacion de microcontroladores , Apuntes de Informática

lenguaje C para microcontroladores

Tipo: Apuntes

2016/2017

Subido el 06/09/2017

agustin-palacios
agustin-palacios 🇦🇷

5

(1)

2 documentos

1 / 42

Toggle sidebar

Esta página no es visible en la vista previa

¡No te pierdas las partes importantes!

bg1
Lenguaje C orientado a µControladores
Preparado por jhuircan. Ver preliminar 1.0
1
Lenguaje C orientado a
Microcontroladores
Rev.1.0
2011
Preparado por: Juan Ignacio Huircán
Dado que el documento es una primera revisión no todas las
fuentes salvo:
Microchip, PIC16F87XA Data Sheet 28/40/44-Pin Enhanced Flash
Microcontrollers
B Knudsen, C Compiler for the PICmicro Devices Version 3.2
User's Manual
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28
pf29
pf2a

Vista previa parcial del texto

¡Descarga programacion de microcontroladores y más Apuntes en PDF de Informática solo en Docsity!

Lenguaje C orientado a

Microcontroladores

Rev. 1.

Preparado por: Juan Ignacio Huircán

Dado que el documento es una primera revisión no todas las

fuentes salvo:

Microchip, PIC16F87XA Data Sheet 28/40/44-Pin Enhanced Flash

Microcontrollers

B Knudsen, C Compiler for the PICmicro Devices Version 3.

User's Manual

Lenguaje C orientado a Microcontroladores

Review

Un programa codificado en lenguaje C resulta muy útil en la aplicación de mControladores, dado que su compilación es bastante eficiente y óptima acercándose a la codificación de lenguaje de máquina. Lo descriptivo de la sintaxis permite elaborar de mejor forma los algoritmos olvidándose de los molestos push y pop usados en el lenguaje de máquina cuando se usan saltos a subrutinas. En la primera parte se plantean elementos genéricos de lenguaje C, pero siempre mirando las posibles aplicaciones con mControladores.

Elementos básicos

Comentario , este permite la documentación del código y se usa de acuerdo a la siguiente sintaxis /* Este es un comentario / o // Este es un comentario Inicio y fin de bloque, permite agrupar un número de instrucciones las que pueden ser ejecutadas con cierta prioridad. Se usa { para iniciar bloque y } para finalizar bloque. { // inicio de bloque // instrucciones } // final de bloque Identificador , es el nombre que se le da a una variable o función por lo general asociado al tipo de dato que ha de contener la variable o al tipo de procedimiento que ha de realizar la función. Tipo , es una palabra reservada definida que indica el tipo de variable que se ha de definir y su alcance numérico, esto de acuerdo a la Tabla 1. Tabla 1. Tipo Código Tipo de compilador C char Entero de 8 bit Estándar int Entero de 16 bit Estándar long Entero de 32 bit Estándar float Real de codificado en 32 bit Estándar double Real codificado en 64 bit Estándar uns16 Entero sin signo 16 bit CC5x () uns32 Entero sin signo 32 bit CC5x () () Ver manual de CC5X compiler.

Instrucciones básicas

Estos son los elementos más básicos para la generación de un programa, se considera: asignación, operaciones matemáticas, operaciones lógicas.

Asignaciones

Consiste en asignarle un valor a una determinada variable. En el ejemplo se declaran dos variables de 8 bits y luego se le asignan los valores respectivos. char i, j; // Declaración de variables void main() { i=10; j=20; // Aquí van otras instrucciones } Los valores asignados a las variables tipo char o int pueden tener distintos formatos ya sea decimal, octal, hexadecimal o binario. Esto es muy útil dado que en el más bajo nivel siempre se trabaja en binario.

Independencia de la representación

A nivel de un μControlador, todas las instrucciones de trabajan en binario, por lo tanto, los datos también, es decir, se puede representar un valor en un formato numérico, ya sea hexadecimal, decimal o tipo carácter, pero a bajo nivel es siempre binario. Por ejemplo, sea la variable dato, la cual se le asigna el valor

  1. Este valor será almacenado en memoria en formato binario como 00110001, lo que representado en formato hexadecimal corresponde al 31H. void main() { char dato; dato=49; }

dato 0 0 1 1 0 0 0

Fig.2. Almacenamiento del dato en memoria. En el caso de trabajar con 16 bit, el formato hexadecimal quedará 0x0031.

Formatos de asignación

La asignación puede ser hecha en decimal, hexadecimal, binario o carácter (si es que se conoce). La Tabla 2, indica el formato binario para el compilador CC5X. Tabla 2. Decimal Binario(*) hexadecimal ASCII 10 0b00001010 0x0a 0x0A LF 123 0b01111101 0x7D 0x7d ‘}’ 49 0b 00110001 0x31 ‘1’ 65 0b01000001 0x41 ‘A’ Note que el número codificado hexadecimal comienza con 0x, es decir 31H será 0x31 y el binario comienza con 0b, es decir, 00110001 será 0b00110001 (este segundo formato corresponde al entregado por el

compilador CC5X). El caso de las variables tipo char se tiene el siguiente ejemplo, se inicializa una variable con el valor 1 ASCII, sin embargo esto se puede realizar de varias formas. char c; void main() { c=0x31; // en hexa c=49; // en decimal c=’1’; // en caracter } int d; long x; void main() { d=0xA031; // en hexa x=49000L; // en long }

Operaciones Matemáticas

Se tienen la suma, resta multiplicación y división, división entera más las operaciones de incremento y decremento. char i,j, k; void main() { i=10; j=20; k=i+j; //suma de 2 enteros k=i-j; // resta de 2 enteros k=i*j; // multiplicación de 2 enteros k=i%j; // Division entera (debe dar el resto) k++; // incremento k=k+ k--; // decremento k=k-1; }

Operaciones Lógicas

Se realizan con variables enteras y afectan directamente al bit del dato, se tienen los operadores lógicos y los desplazamientos a la Izquierda y a la derecha. char i,j, k; void main() { k=i&j; // operación AND entre i y j bit a bit k=i|j; // operación OR entre i y j bit a bit k=~i; // operación negación de i asignado a k k=i<<1; // i desplazado en un bit a izquierda k=i<<2; // i desplazado en 2 bit a izquierda k=i>>1; // i desplazado en un bit a la derecha k=i<<2; // i desplazado en 2 bit a la derecha }

Sentencias de control

Permiten definir las funciones a ejecutar dependiendo de ciertas condiciones que se deben evaluar. Estas condiciones pueden ser tipo lógicas , tipo mayor , menor o igual o ambas. Por lo general se agrupan entre paréntesis para definir la prioridad de la evaluación de la condición. Tabla 3. Operador de condición Descripción == Igual != Distinto

< Mayor, menor = =< Mayo o igual, Menor o igual && Operador Y || Operador O

void main() { i=1; // Solo mientras i mayor que cero hace lo que viene a cont. while(i) // Puede usarse while(1) { k=40; j=20; h=k+j; } } Para salir de un ciclo donde no existe posibilidad de que la condición sea alterada, se puede usar la

sentencia break.

char i,j,k,h; void main() { i=0; while(1) { i++; if(i==5)break; // Sale cuando i= } } La sentencia do-while se evalúa la condición al final, por ejemplo “Hágalo mientras se cumpla la condición”. void main() { char i; i=10; do { i--; }while(i!=5); // Sale cuando i= }

Sentencia switch - case

Esta instrucción se usa cuando se deben seleccionar entre varias opciones tipo numéricas. Es básicamente un selector. char c; void main() { // se le debe asignar el valor a la var. c switch(c) { case 1: // debe saltar a función break; case 2: // debe saltar a función break; case 3: // debe saltar a función break; } }

Sentencia for

Permite el uso de los clásicos ciclos for. El formato es el siguiente: for(expr_1;expr_2;expr_3)instrucción; for(expr_1;expr_2;expr_3) { instrucción_1; ... instrucción_n; } Tabla 4. Descripción expr_1 (^) Condición de comienzo, pueden haber varias inicializaciones separadas por coma. expr_2 (^) Se efectuará el bucle mientras se cumpla esta condición, se evalúa la condición antes de entrar al bucle. expr_3 (^) Indica lo que hay que hacer cada vez que se termina el bucle, puede haber varias instrucciones separadas por una coma. El código fuente muestra que la variable i se inicializa en cero, una vez realizadas las instrucciones dentro del bloque, en este caso, hacer j igual a cero, se ejecuta la instrucción incremento en uno para la variable i. char i,j; void main() { for(i=0;i<10;i++) // inc. i desde 0 mientras sea menor que 10 { j=10; // lo repite tantas veces de acuerdo a la condicion } }

void main() { char sensor; sensor=lee_sensores(); // salta a la funcion y retorna el valor en la variable sensor } char lee_sensores() { char x; // Instrucciones // Se debe asignar el valor de x en alguna parte return(x); // devuelve el valor de x tipo char } Se repite el procedimiento, pero esta vez se omite la declaración, pero se escribe la variable antes del main(). char lee_sensores() { char x; // Instrucciones // Se debe asignar el valor de x en alguna parte return(x); // devuelve el valor de x tipo char } void main() { char sensor; sensor=lee_sensores(); // salta a la funcion y retorna el valor en la variable sensor } Para el paso de los argumentos, estos son definidos entre los paréntesis de la función considerando el tipo de la variable requerida. La siguiente función recibe un parámetro tipo char por valor y no retorna valor. void activa_motor(char num_motor) { // instrucciones } void main() { char sensor; activa_motor(1); // pasa el valor 1 haciendo ref al num de motor } El siguiente ejemplo muestra una función que recibe parámetros y retorna un valor. El valor retornado a su vez puede ser usado por una sentencia de control para tomar alguna decisión. char enviar(char dato) { char status; // codigo return (status); } void main() { char estado_com; while(1) { estado_com=enviar(0xaa); if(estado_com!=1) break; } }

Punteros

Los punteros son variables que almacenan la dirección de una variable, el contenido de dicha variable es accesado en forma indirecta. La variable tipo puntero se define en forma similar a una variable que almacena datos, pero se agrega un *. char *p; //p es un puntero a un char char i, *p; // i es un char y p es un puntero a char int j, *p; // j es una var entera y p es un puntero a entero ¿Qué quiere decir puntero a char o puntero a int?. Simple, cuando un puntero a char se incrementa, la dirección almacenada en la variable se incrementa en una posición de memoria en cambio en un puntero a int se incrementa de 2 posiciones. En el caso de que fuera a una variable tipo long, la dirección se incrementaría en 4 posiciones de memoria. Para saber la dirección de memoria de una variable y asignarla a un puntero se usa el operador & y el nombre de la variable o solo el nombre de la variable. char i, *p; // i es un char y p es un puntero a char void main() { p=&i; // p almacena la dirección de memoria donde esta i } Para saber el contenido de la variable apuntada por el puntero se usa el operado . char i, j, p; // i y j char y p es un puntero a char void main() { i=10; // i contiene el valor 10 p=&i; // p almacena la dirección de memoria donde esta i j=p; //El contenido de p es almacenado en j } i=10; p=&i j=p i 0200h 10 i 0200h 10 i 0200h 10 j 0201h j 0201h j 0201h 10 p 0202h p 0202h 0200 p 0202h 0200 Fig.3. Manejo de puntero. Uno de los usos de los punteros es para el paso de variables por referencia a una función, es decir, el valor de la variable que se pasa como argumento a la función cambia si es que en el interior de la función cambia. Esto ocurre a que en vez de pasar el valor del argumento a la función, se pasa su dirección. Esto se podrá realizar si se utiliza un puntero. El siguiente ejemplo es solo ilustrativo, ya que si se va a devolver un valor, puede usar la instrucción return(). void duplicar(char p) { p=(p)2; }

Arreglos y matrices

Los arreglos son vectores unidimensionales (llamados string o cadenas) o bidimensionales (Matrices). Su formato es el siguiente: tipo ident_arreglo[num_elementos]; tipo ident_arreglo[num_fila][num_col]; A continuación se muestran ejemplos de arreglos unidimensionales y bi-dimensionales char cadena[10];// define una var string llamada cadena de 10 char int datos[15]; // define una var string llamada datos de 15 int char cad[2][3]; // define una matriz cad de 2 filas y 3 col Los arreglos pueden contener números o caracteres, pueden ser usados para manejo de cadenas de caracteres o string. Es conveniente siempre inicializar con un valor los arreglos. El primer elemento del arreglo corresponderá al almacenado en la posición 0 y el n - ésimo elemento corresponderá al almacenado en la posición n-1 del arreglo. Cuando se definen los arreglos, estos ocupan zonas completas de memoria, con direcciones consecutivas.

Inicialización de arreglos

Los arreglos pueden inicializarse elemento a elemento si es que es pequeño, o a través de la inicialización usando la cadena completa. char data[5],i; void main() { for(i=0;i<5;i++) data[i]=0; // rellena con 0s } Los arreglos pueden ser inicializados con mensajes, estos son formados por cadenas de caracteres llamados cadenas o string. Habitualmente, se usan las comillas para establecer o limitar el mensaje. // Esta inicialización corresponde al Turbo C char msg[10]={"HOLA"}; // C estándar char x[7]={"Juan\xd"}; char c[]={"popo"}; En los compiladores C estándar, cada cadena termina con el carácter 0x00 a continuación del último carácter. De esta forma en el ejemplo de la cadena msg, el elemento 5 de la cadena debe llevar un carácter 0x00. H O L A 0x msg[0] msg[1] msg[2] msg[3] msg[4] 0x48 0x4F 0x4C 0x41 0x00 ¿? ¿? ¿? ¿? Las funciones más usadas en el manejo de cadenas son la copia de cadenas o la concatenación de cadenas. Las cadenas comúnmente pueden ser usadas con caracteres de control tales como el 0x0d (CR) o el 0x0a (LF). Estos caracteres pueden ser incorporados al final de la cadena en forma directa en la posición correspondiente o de la siguiente forma: char msg[10]={ "HOLA\xd\xa"};

0x48 0x4F 0x4C 0x41 0x0d 0x0a 0x00 0x00 ¿? Arreglos y punteros Los punteros pueden ser inicializados con la dirección inicial del arreglo, de esta forma se tiene char cad[10], *pcad; //Formas equivalentes pcad=&cad[0]; pcad=&cad; pcad=cad; Los punteros pueden usarse para apuntar al primer elemento de una cadena y se pueden inicializar de acuerdo al siguiente ejemplo. char *p="chao"; Pasando cadenas a funciones Habitualmente se utilizan punteros, debido a que se pasa la dirección del primer elemento de la cadena, evidentemente, el puntero debe ser del mismo tipo del dato apuntado, de tal forma que cuando se incremente el puntero, se incremente en una posición dentro del arreglo. En C estándar se tiene que se asigna un puntero al primer elemento de la cadena, esto permite pasar filas completas. El siguiente ejemplo permite traspasar un mensaje ASCII a una cadena. Este ejemplo compila en C estándar y CC5X. void copy_msg(char *pdest, const char porig) { char x; while(1) { x=porig; pdest=x; if(porig==0x00) break; pdest++; porig++; } } void main() { char msg[8]; copy_msg(msg,”HOLA”); // Copia en la variable msg la cadena HOLA } Copiando del contenido de una cadena en otra, está codificado para CC5X void copy_str(char *pd, char po) { char x; while(1) { x=po; pd=x; if(po==0x00) break; pd++; po++; } }

El μControlador y el compilador CC5x

Arquitectura del μC

Para las aplicaciones se utilizará un μC basado en la familia 16F87Xa de microchip. Este μC contiene registros, módulos ADC, comunicación serial, temporizadores y manejo de interrupciones. Además posee un memoria de programa de entre 4 y 8Kbytes, su reloj puede ser de entre 4 y 20MHz. (ver data μC16F87xA). Un esquema simplificado es el de la Fig. 4. Fig. 4. Esquema simple del μC 16F87XA. Es bastante sencillo de hacer funcionar, solo basta un par de capacitores, el cristal más un resistor, de acuerdo a la Fig. 5. En dicho circuito, el terminal 28 correspondiente al bit más significativo del puerto B, se usará como monitor del funcionamiento. La tarea básica a implementar será poner en 1 y en 0 alternadamente el bit mencionado de tal forma de visualizarlo mediante un instrumento. TIMER0 TIMER 1 TIMER 2 10bit-ADC EEPROM (^) CCP1,2 USART (^) Synchronous Serial Port FLASH Memoria De Programas RAM File Registers PORTA PORTB PORTC CPU PCL REF COMPARADOR Voltaje

Fig. 5. Esquema de para funcionamiento básico.

Entorno de desarrollo

El entorno de desarrollo MPLAB permite el trabajo con el μC incorporando al editor y herramientas de simulación y un compilador proporcionado por otro fabricante, en este caso el compilador C CC5x desarrollado por B. Knudsen (ver www.bknudsen.com). Al trabajar con el compilador CC5x, se debe incorporar el archivo cabecera 16F873a.h, esto automáticamente permite configurar el entorno de trabajo, quedando definido todos los “recursos” de la CPU, registros y módulos con los nombres identificados en la referencia técnica del μC (ver tutorial). Se sugiere revisar el archivo de cabecera y contrastarlo con los nombres de los registros y direcciones asociadas descritas en el manual. Los registros de μC vienen definidos en el archivo de cabecera, estos pueden ser accesados directamente al igual que también sus bits individuales. En algunos casos los registros ya tienen definidos algunos bits internos usando otros identificadores, esto no es incompatible entre sí. Como ejemplo se tiene el caso del puerto B, llamado PORTB por el compilador CC5X, el cual está definido en el archivo de cabecera como una variable entera de 8 bits. También es posible encontrar el nombre de algunos bits definidos como variables tipo bit, ya sean banderas (Flags) o bits de habilitación de periféricos. Un ejemplo es RCIF ( R e C eive I nterrupt F lag) que está definido como una variable tipo bit o el PEIE ( PE riferical I nterrupt E nable). Definiciones de bit y uso de registros En el caso del PORTB, se accesan los bit recurriendo a una definición de campo de bits. De esta forma PORTB.0 corresponde al bit menos significativo del puerto y el PORTB.7 corresponderá al bit más significativo. Así se tiene que el PORTB.x, donde x representa la posición del bit dentro de la variable entera. Esto puede ser extendido a las variables enteras de 16 y de 32 bits. void main() { char dato; uns16 x; dato.2=1; // se setea el segundo bit menos significativo de la variable x.15=0; // reset al bit menos significativo de la var. x } 20 9 10 8 19 5V 5V 1K 22pF 22pF XTAL - 20MHz 28 Reset 1 16F873A

void funcion(const char *str) { // codigo } void main() { char tab[20]; // string en RAM const char ctab[] = “A string”; funcion(“Hello”); // Pasando el string explicito funcion(&tab[i]); // Usando una direccion de la cadena funcion(ctab); // Pasando la direccion del primer elemento }

Manejo de Puertos de E/S

El μC posee al menos 3 puertos que pueden ser usados como entradas-salidas digitales o como entradas análogas (si corresponde). Los Puertos son básicamente registros de 8 bits basados en Flip-Flops Tipo D, que pueden ser usados como entrada o salida. Los nombres de los puertos corresponden a los especificados en la data del μC, así se tiene el PORTB de 8 bits, el PORTC de 8 bit y el PORTA de 5 bits. Dichos bits se configuran como entrada o salida a través de un registro llamado TRIS. Para el caso del Puerto A será TRISA, para el puerto B, se usa el TRISB, etc. Los puertos señalados poseen más de una función. Estos puertos del μC se usan como si fueran variables enteras de 8 bit y pueden accesarse nivel de bit o en forma de bytes. Primero se debe especificar si el bit es de entrada o salida, esto se hace definiendo el bit correspondiente como salida, esto se hace con el registro TRISx (x es el puerto A, B o C) mediante el set o reset de dicho registro. Cada bit del registro TRISx maneja la entrada o salida del bit correspondiente del PORTx. TRISB.0=0; // Bit 0 del puerto B se define como salida TRISC.7=1; // Bit 7 del puerto C se define como entrada Puede definirse todo el puerto como entrada o salida o mezcla de ambas situaciones usando un byte completo sobre el registro TRIS correspondiente. TRISB=0x00; // los 8 bit del PORTB como salida TRISC=0xFF; // los 8 bits del PORTC como entrada TRISB=0x0f; // Los 4 bit más significativos de entrada, // los 4 bit menos significativos como salida La Fig. 6 muestra un diagrama funcional del terminal PORTB.0, el cual es controlado por el bit 0 del TRISB (TRISB.0). Fig.6. Esquema funcional del PORTB.0. Q Q D PORTB. TRISB Q D

Programa básico de Test

El programa permite verificar el funcionamiento del μC a través de la generación de un pulso en un terminal del dispositivo. Considerando el bit 7 de puerto B, se tiene. void main() { TRISB.7=0; PORTB.7=1; while (1) { if(PORTB.7==1) PORTB.7=0; else PORTB.7=1; } } El terminal deberá ser inspeccionado a través de una sonda lógica. Si no se dispone de ella, entonces puede usarse un retardo, el cual en conjunto con un LED permitirá visualizar los cambios de estado. void ret(uns16 r) { while(r>0) { r--;} } void main() { TRISB.7=0; PORTB.7=1; while (1) { if(PORTB.7==1) PORTB.7=0; else PORTB.7=1; ret(5000); } } Fig. 7. Circuito de prueba PORTB.7. El siguiente código permite enviar un byte completo hacia el exterior del μC, primero envía un 0xAA (en binario 0b10101010) que prende bit por medio después de un retardo envía un 0x55 (0b01010101), negando los bit en la salida. Para poder visualizar se recomienda la configuración de la Fig.8. 20 9 10 8 19

5V

5V

1K

22pF 22pF XTAL - 20MHz 28 220 W Reset 1 16F873A