1 Lenguaje C para Microcontroladores Parte 2 Prof. Ing. Julián R. Camargo
2 Funciones La función es el corazón de un programa en C, corresponde a lo que en lenguaje ensamblador se llama un procedimiento o una subrutina invocado mediante las instrucciones BSR o JSR. El objetivo de una función es proveer un mecanismo que permita invocar una secuencia de código, el cual puede ser llamado varias veces en un programa, facilitando el entendimiento del código creando además código reusable, evitando tener que incluir dicho código cada vez que requiere su ejecución. La forma general de una función es: retorno Nombre_Funcion(argumento1, argumento2,,,,, argumentoN){ Cuerpo de la Funcion }
3 En donde: retorno: Es el valor que retorna la función después de invocada. Si la función no retorna valor alguno, deberá tener la palabra reservada void. Nombre_Funcion: Es el nombre de la función, la define el programador. argumento1, argumento2,,, argumentoN: son las variables separadas por coma, que se entregan a la función, sobre las cuales se realizara alguna operación. En caso de no requerir argumentos, la función deberá tener en los argumentos: (void).Una función puede en teoría recibir cualquier número de argumentos, normalmente estos se entregan a la función en los registros de trabajo del microcontrolador, y en caso de ser estos insuficientes se entregan en el stack. En la práctica y específicamente para sistemas de 8 bits, no se recomiendan funciones que reciban más de 4 argumentos, debido a que cada argumento generara código adicional antes de hacer el llamado a la función.
4 El retorno se refiere al resultado que entrega la función al final de su ejecución, también se le denomina el resultado de la función, este se entrega mediante la palabra reservada return seguido de su valor, este solo puede ser uno y solo uno de los tipos de variables estándar: char, int, long, float, acompañado de uno, o mas de los modificadores estándar de las variables. unsigned int Funcion3(void){ unsigned int vInt; return vInt; }
5 En el caso que una función no necesite retornar un valor, su prototipo debe indicar explícitamente la palabra void en su retorno, para indicar que no cuenta con retorno, con esto al hacerse el llamado externo no se espera ningún valor de regreso no se requiriéndose de la palabra return en el cuerpo de la función. void Funcion(void); Si un prototipo de función se declara sin retorno o sin argumento, el C asumirá que tanto el retorno como su argumento son de tipo int. Aunque esto no genera un error en el compilador, si puede generar un Warning que podría llegar a ocasionar confusiones o datos erróneos de entrega o retorno. Funcion(); Este prototipo es asumido por el ANSI C como: int Funcion(int valor);
6 Sentencias de Control Sentencia “if.. else” La forma general de esta sentencia es: A if( ?){ B Bloque de Programa VERDADERA }else{ C Bloque de Programa FALSA } D
7 Una vez que el control del programa llega a la sentencia if, punto A, evalúa la condición para examinar si es verdadera (diferente de cero) o es falsa (cero). Si la evaluación de la condición resulta ser verdadera el control pasara al punto B, y ejecutará el bloque de programa de programa VERDADERA, una vez terminado pasa al punto D para continuar con la ejecución del programa desde este punto. Si por el contrario al evaluar la condición resulta ser falsa, el control del programa pasara al punto C para ejecutar el bloque de programa FALSA y al terminar al punto D de ejecución. El bloque de programa FALSA es opcional con lo que puede existir un if sin else en el cual solo se ejecutará el bloque de programa VERDADERA si la condición es diferente de cero, o no hará nada si la condición es falsa.
8 Una vez que el control del programa llega a la sentencia if, punto A, evalúa la condición para examinar si es verdadera (diferente de cero) o es falsa (cero). Si la evaluación de la condición resulta ser verdadera el control pasara al punto B, y ejecutará el bloque de programa de programa VERDADERA, una vez terminado pasa al punto D para continuar con la ejecución del programa desde este punto. Si por el contrario al evaluar la condición resulta ser falsa, el control del programa pasara al punto C para ejecutar el bloque de programa FALSA y al terminar al punto D de ejecución. El bloque de programa FALSA es opcional con lo que puede existir un if sin else en el cual solo se ejecutará el bloque de programa VERDADERA si la condición es diferente de cero, o no hará nada si la condición es falsa.
9 Sentencia “for” La sentencia for es una de las mas completas y útiles en la programación estructurada, permite agrupar un código de programa, el cual se ejecuta de forma repetitiva con base en ciertas condiciones dadas. Su estructura es: A for( ; ; ){ B Bloque de Programa FR } D
10 En la sentencia for existen 3 partes principales: La que solo se ejecuta una vez: al iniciar el for, normalmente es una sentencia de asignación que se utiliza para inicializar una variable de control del bucle. La es una expresión relacional que determina cuando finaliza el bucle. El define como cambia la variable de control cada vez que termina el bucle. Las 3 partes deben estar separadas por punto y coma y todas son opcionales, es decir que puede existir un for(;;) sin, sin en cuyo caso se asumirá VERDADERA, y sin.
11 Una vez el control del programa llega al punto A, ejecuta la, a continuación evaluará la, si esta resulta ser verdadera ejecuta el Bloque de Programa FR, una vez terminado, ejecuta el y evalúa la nuevamente, si persiste verdadera nuevamente ejecuta el Bloque de Programa FR y así sucesivamente hasta que en una evaluación de la esta resulte ser falsa, pasando al punto D para continuar con otra sentencia. Variaciones de la Sentencia “for” La sentencia for tiene muchas variaciones y dependiendo del uso que se busque puede tener o no alguna de sus partes, así el bucle infinito de ejecución es el siguiente: for (;;){ Bloque de Programa } Este código ejecutará el Bloque de Programa una y otra vez, debido a que la condición que pudiera sacarlo no existe y en cuyo caso de define como VERDADERA
12 Una variación adicional permite utilizar el separador coma para permitir dos a mas variables de control del bucle, así por ejemplo las variables i y j controlan el siguiente bucle y ambas son inicializadas dentro de la sentencia for. unsigned char i,j; for( i =0, j=0 ; (i+j) < 55 ; i++){ Bloque de Programa } Pueden también existir sentencias for sin bloque de programa, en algunos casos se utilizan para generar pequeños retardos, como seria: unsigned int i; for(i=0; i< 10000; i++); La cual contará de 0 a 10000 sin ejecutar bloque de programa alguno.
13 Ejemplo Escribir un programa en C que realice operaciones con dos variables tipo int, vble1 y vble2, dependiendo del estado de las teclas INPUT-1 y de INPUT-2, de la siguiente forma: 1. Si la tecla INPUT-1 se presiona, el código deberá hacer la suma de vble1 y vble2 y actualizar el resultado de la variable result. 2. Si la tecla INPUT-2 se presiona, la variable result deberá contener la operación vble1-vble2. 3. Si estando presionada INPUT-1, se presiona además INPUT-2, el valor de la variable result, deberá ser la multiplicación de vble1 y vble2. En todos los casos las salidas OUT-2 y OUT-1 deberán indicar el estado de los bits de mayor y menor peso de result, respectivamente. Si el resultado de la operación result en algún caso es cero, deberá indicarlo con la salida auditiva Buzzer BUZZ.
14 #include /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ #define Disable_COP() CONFIG1_COPD = 1 //Desh. COP #define Buzzer_On() PTD_PTD0 = 1 #define Buzzer_Off() PTD_PTD0= 0 #define OUT_1_On() PTC_PTC3 = 1 #define OUT_1_Off() PTC_PTC3 = 0 #define OUT_2_On() PTC_PTC2 = 1 #define OUT_2_Off() PTC_PTC2 = 0 #define INPUT_1() (!PTD_PTD1) #define INPUT_2() (!PTD_PTD2) int vble1,vble2; long result;
15 void main(void) { Disable_COP(); DDRC_DDRC2 = 1; DDRC_DDRC3 = 1; DDRD_DDRD0 = 1; vble1 = 54; vble2 = 23; for(;;) { if(INPUT_1() && !INPUT_2()){ result = (long)vble1+(long)vble2; } if(INPUT_2() && !INPUT_1()){ result = (long)vble1-(long)vble2; }
16 if(INPUT_1() && INPUT_2()){ result = (long)vble1*(long)vble2; } if(result & 0x01){ OUT_1_On(); }else{ OUT_1_Off(); } if(result &0x80000000){ OUT_2_On(); }else{ OUT_2_Off(); }
17 if(result == 0){ Buzzer_On(); }else{ Buzzer_Off(); } } /* loop forever */ }
18 Sentencia “do.. while” La secuencia do.. while suele usarse cuando se quiere dejar el programa ejecutando una secuencia de líneas en un ciclo, mientras no se cumpla una condición especifica. La forma de la sentencia es: A do{ B Bloque de Programa DWH }while( ); D Al llegar el control del programa al punto A, pasa de inmediato a ejecutar el Bloque de Programa DWH, ejecutándolo completamente. Al terminar evalúa la, si es verdadera, volverá al punto B para ejecutar de nuevo el Bloque de Programa DWH y así hasta que cuando la resulte ser falsa.
19 Nótese que el Bloque de Programa DWH se ejecuta como mínimo una vez, independiente del estado de la, de hecho, si al llegar al punto A la condición es falsa, el Bloque de Programa DWH se ejecuta una vez. También téngase en cuenta que la condición solo tendrá posibilidad de cambiar su estado en el Bloque de Programa DWH o en una interrupción (siempre y cuando las interrupciones estén habilitadas). Sin embargo, la condición solo es evaluada al terminar el Bloque de Programa DWH, si en algún momento la condición cambia a falsa, y nuevamente a verdadera, al evaluar la condición solo tomara el valor presente, y nunca valores pasados.
20 Sentencia “while” La secuencia while es muy similar a la do.. while, se utiliza de manera similar en secciones de código que requieren esperar una condición o que se ejecuten cierto número de veces si en la condición se pone un contador. Su diferencia con el do..while radica en que primero se hace la evaluación de la condición y luego la decisión si el bloque de programa se ejecuta o no. Su estructura es: A while( ){ B Bloque de Programa WH } D
21 Una vez el programa llegue al punto A evaluara la condición, si esta resulta ser verdadera pasará el control al punto B y ejecutar el Bloque de Programa WH, terminado nuevamente pasará a evaluar la condición y si esta persiste verdadera, nuevamente ejecutará el Bloque de Programa WH, de esta forma hasta que en la evaluación de la condición esta resulte ser falsa, en cuyo caso no ejecutará el bloque de programa y saltara directamente al punto D. Nótese que si al llegar al punto A, la condición es falsa la primera vez, no ejecutará el Bloque de Programa WH ni una sola vez y pasará al punto D.
22 Ejemplo Escribir una función en C que reciba como argumento una variable de tipo unsigned char, y retorne el valor de su factorial tipo unsigned long. #include /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ #define Disable_COP() CONFIG1_COPD = 1 //Desh. COP unsigned long Factorial(unsigned char var){ unsigned long result; unsigned char num; result = 1; num = var; if(num != 0){
23 while(num != 1){ result =(unsigned long)num*result; num--; } return result; }else{ return 1; // caso trivial variable es 0, su factorial es 1 } unsigned long factResult; void main(void) { Disable_COP(); factResult = Factorial(11); factResult = Factorial(1); for(;;) { } /* loop forever */ }
24 Sentencia “switch” La sentencia switch es una de las más importantes y que más utilizadas, debido a la estructura vertical que tiene y su capacidad de poder dividir un módulo en estados. La sentencia switch nace como una forma sencilla de tener if anidados, los cuales pueden ser un poco confusos cuando se habla de mas de 3 if’s anidados, entre los cuales deberá existir por lo menos un tabulador, haciendo que el código se extienda de manera horizontal lo que dificulta su lectura y entendimiento.
25 La estructura de la sentencia “switch” es: A switch( ){ case VALOR_1: Bloque de Programa V_1 break; case VALOR_2: Bloque de Programa V_2 break; case VALOR_3: Bloque de Programa V_3 break; default: Bloque de Programa V_Df break; } D
26 Al llegar el control del programa al punto A, toma la variable y la compara con el VALOR_1, si ambos valores son diferentes, pasara a comparar el valor de la variables con el del case VALOR_2, VALOR_3, hasta que encuentre coincidencia, en el momento que encuentre igualdad, iniciará la ejecución del bloque de programa V_N que coincide hasta que encuentre un la palabra reservada break, en cuyo caso se irá directo al punto D terminando la sentencia switch. En teoría se pueden tener tantos “cases” como se quiera, o soporte el tipo de la en cuestión, si al llegar al switch, la variable NO coincide con ninguno de los VALOR_1 … VALOR_N, se ejecutará el bloque de programa V_Df como bloque “default”. La estructura switch solo exige el switch y un solo case, tanto la palabra “break”, como la palabra “default” son opcionales para que no genere error de sintaxis, sin embargo exige especial atención cuando se suprimen estas palabras.
27 Ejemplo Escribir un programa en C que realiza diferentes acciones, dependiendo de la tecla presionada. En la siguiente tabla se describen las teclas, sus pines en el microcontrolador y su acción respectiva. NOMBRE TECLA PIN en MCU ACCION KEY_ENTER PTD1 (INPUT_1) Enciende temporalmente OUT_1 KEY_ESCAPE PTD2 (INPUT_2) Enciende temporalmente OUT_1 y BUZZ KEY_INC PTD3 (INPUT_3) Enciende OUT_2 KEY_DEC PTD4 (INPUT_4) Apaga OUT_2
28 #include /* for EnableInterrupts macro */ #include "derivative.h" /* include peripheral declarations */ #define DisableWatchdog() CONFIG1_COPD = 1 #define INPUT_1() !PTD_PTD1 #define INPUT_2() !PTD_PTD2 #define INPUT_3() !PTD_PTD3 #define INPUT_4() !PTD_PTD4 #define OUT_1_On() PTC_PTC3 = 1 #define OUT_1_Off() PTC_PTC3 = 0 #define OUT_2_On() PTC_PTC2 = 1 #define OUT_2_Off() PTC_PTC2 = 0
29 #define Buzz_On() PTD_PTD0 = 1 #define Buzz_Off() PTD_PTD0 = 0 // declaracion de rutina de retardo void Delay(unsigned long nroIteraciones){ unsigned long i; for(i=0;i
30 void Key_Init(void){ DDRD_DDRD0 = 1; DDRC_DDRC2 = 1; DDRC_DDRC3 = 1; } char Key_Read(void){ if(INPUT_1()) return KEY_ENTER; if(INPUT_2()) return KEY_ESC; if(INPUT_3()) return KEY_INC; if(INPUT_4()) return KEY_DEC; return KEY_INVAL; }
31 void Key_Run(void){ char nroTecla; nroTecla = Key_Read(); switch(nroTecla){ case KEY_ESC: Buzz_On(); case KEY_ENTER: OUT_1_On(); Delay(3000); OUT_1_Off(); Buzz_Off(); Delay(3000); break; case KEY_INC: OUT_2_On(); break;
32 case KEY_DEC: OUT_2_Off(); break; default: break; } void main(void) { DisableWatchdog(); Key_Init(); for(;;) { Key_Run(); } /* loop forever */ }
33 Sentencia “goto” Se usa en algunos casos donde la velocidad de ejecución es crítica, debido a que puede con determinada evaluación, ir directamente a algún lugar del programa (adelante o atrás) con mucha velocidad y obviando evaluaciones intermedias. La sentencia goto requiere de una etiqueta la cual no es mas que un marcador dentro del código el cual define una posición del PC (program counter) dentro del mapa de memoria del procesador. Esta sentencia es equivalente a un BRA (salto sencillo) o un JMP (salto largo) en lenguaje ensamblador, con la diferencia que en C la etiqueta deberá estar en la misma función en la que se utiliza el goto.
34 Mezcla de C con Lenguaje Ensamblador - Ejecutar procedimientos muy optimizados en velocidad de ejecución. - Manejar características del microcontrolador que por medio de lenguaje C no es posible: por ejemplo existe la instrucción BIH (branch if IRQ High) y BIL (branch if IRQ Low), las cuales realizan lectura directa del pin IRQ y toman una decisión de salto. No existe en C una directiva o sentencia propia que lo realice, y en este caso es necesario hacerlo por la vía del lenguaje ensamblador. - Invocar instrucciones específicas de ensamblador: Instrucciones para manejo del bajo consumo de energía (STOP, WAIT) o llamar la interrupción de software (SWI). - Generar pequeños lapsos de tiempo muy breves: En C la medida de tiempo por ejecución de código no existe, porque las instrucciones generadas pueden variar, dependiendo del compilador, del tipo de optimización, etc. Mientras que en código de máquina y por el hecho de estar intacta su generación, se puede medir de forma precisa el tiempo que un procedimiento se tarda en ejecutar.
35 El código en ensamblador escrito no será optimizado (haga lo que haga), y tampoco verifica si interfiere con el código en C; al mismo tiempo el código ensamblador, puede interactuar con las variables (leerlas y/o modificarlas) y funciones declaradas en el proyecto. Al incluir el código deberá ponerse especial énfasis en el manejo del stack pointer (SP), su alteración podría provocar malfuncionamiento en el proyecto una vez se abandone la sección, de tal forma que debe garantizarse la entrega del SP en la misma dirección en la que fue recibido al iniciar la sección en ensamblador, tener en cuenta que instrucciones que envíen datos al stack (ejemplo: PSHA, PSHX..) alteran la posición del stack pointer, por lo que deben tener su correspondiente instrucción paralela (ejemplo: PULA, PULX …), al terminar el procedimiento.
36 Existen 3 formas de incluir lenguaje ensamblador dentro de un proyecto en C, y su uso depende de la cantidad de código que se requiera insertar: 1: Usado cuando se requiere insertar pocas líneas de lenguaje ensamblador, (normalmente una línea), y tiene la siguiente sintaxis: asm(“ “); Donde: es cualquier instrucción del procesador usado con su respectivo modo de direccionamiento.
37 2: Usando cuando se requiere insertar un código intermedio de lenguaje ensamblador. La sintaxis es: __asm __asm __asm...................................... __asm Donde: es una instrucción completa con su respectivo direccionamiento.
38 3: Se usa cuando se requiere insertar un bloque de lenguaje ensamblador conteniendo varias instrucciones secuenciales. Su sintaxis es la siguiente: #asm …. #endasm Donde: es una instrucción completa con su respectivo direccionamiento. #asm y #endasm: determinan respectivamente el inicio y fin de la sección en código ensamblador, ambos deben iniciar en la columna 1 del editor.
39 Manejo de Interrupciones en C Todo sistema por sencillo que sea, maneja interrupciones de diferente tipo, los compiladores usan diferente sintaxis de tal forma que no hay una regla general que los cubra a todos, sin embargo el concepto de función de interrupción ISR ( Interrupt Service Routine) si es general, y se refiere a una función que el software no invoca, sino que es el procesador quien al detectar una interrupción (o excepción), cambia el contexto hacia esta función. Esta ejecutará su código completo y retornará con una instrucción diferente a la de retorno de función normal, debido a que tiene que recuperar del stack no solo la dirección de retorno sino el contenido cada uno de los registros del modelo de programación.
40 En cada interrupción que se definan se deberán tener en cuenta sin excepción los siguientes 6 puntos: 1.Las funciones de interrupción o ISR, son diferenciadas de las funciones o subrutinas normales anteponiéndole la palabra reservada interrupt, la cual le indica al compilador que la función en cuestión es una ISR y lo prepara para generar un retorno de interrupción (RTI) en lugar de un retorno de subrutina normal (RTS). Además el compilador prepara el código generado para conservar el modelo de programación que no es guardado en el stack por el cambio de contexto de la CPU (para el caso del HC(S)08 generar las instrucciones PSHH y PULH al comienzo y fin de la función de interrupción para guardar y recuperar el registro H respectivamente al inicio y fin de la función de interrupción).
41 2. Una vez se genera una interrupción el procesador tomará una y solo una dirección de la tabla de vectores de interrupción, en la cual está la dirección de la función a la que deberá ir una vez se cambie el contexto (PC = Vector de Interrupción), este vector se genera una vez se realiza la definición de la función mediante un número NroVector que sigue a la palabra reservada interrupt, dicho número se cuenta a partir del vector de RESET, el cual es CERO, hasta llegar al vector al que corresponde la interrupción que se quiere invocar. 3. Por el hecho de no ser invocadas desde el programa, la ISR no recibe ni retorna argumentos. Se recomienda acompañar el nombre de la función con la terminación ISR, para indicar explícitamente que se trata de una función de interrupción. El prototipo de una función de interrupción se verá de la siguiente forma general: interrupt NroVector void Funcion_ISR(void);
42 4. En términos generales el código de programa de la interrupción, deberá ser un código muy corto, lo mas resumido posible a fin de evitar demoras en el retorno de la ISR, debido a que el cambio de contexto desde el programa principal hacia la interrupción deshabilita todas las interrupciones para evitar anidación (bandera I en el CCR en 1. Por consiguiente, cualquier interrupción que ocurra durante la ejecución del código de la interrupción, quedara pendiente y solo será atendida al retorno de esta, inclusive si la interrupción es de mayor prioridad a la que se esta llevando a cabo. 5. En cada ISR debe borrarse la bandera correspondiente a la interrupción que genera el llamado a la ISR, a fin de no ocasionar el llamado consecutivo a la misma ISR ante la misma interrupción.
43 6. Recordar que las variables en RAM que se usen dentro de una ISR, deberán tener el modificador “volatile”, que asegura que su código redundante u obvio, no será optimizado. Finalmente la implementación de una ISR se vera de la siguiente forma general: interrupt NroVector void Funcion_ISR(void){ … código de programa de la interrupción … código de programa borrado de bandera de interrupción }