LENGUAJE C PARA SISTEMAS DEDICADOS
FUNDAMENTOS
Computadora Se dispone de un S.O. El S.O. inicia y configura los periféricos. El S.O. brinda al usuario subrutinas para utilizar los periféricos ( system calls ).
Microcontrolador No posee un S.O. El firmware debe iniciar los periféricos. El usuario debe crear sus propias subrutinas para utilizar los periféricos.
En lenguaje C para sistemas dedicados no podemos hacer: printf( Hola Mundo );
Llamada a la función main A pesar que main es el punto de entrada al programa, el firmware realiza tareas previas a main. Entre ellas. Cargar registros de configuración. Limpiar zonas de RAM. Cargar el puntero de pila SP.
Código en ensamblador ORG $EE00 configuración de los periféricos ricos. BRA $
Código en C ORG $EE00 código de inicialización CALL/JMP main void { } main(void) código escrito por el usuario while(1) { }
Recursos
Arquitecturas de memoria
Modelo tiny ( Freescale ) MOV PORTA,PORTB
Modelo small ( Freescale ) LDA PORTA STA var1
Pasaje de parámetros Al igual que en un computadora, la pila se utiliza para el pasaje de parámetros: int funcion(char a, int b, float c,.)
Ejemplo de pasaje de parámetros usando la pila void main(void) { int dunga, donga; dunga = 3; donga = 5; dunga = suma(dunga, donga); } donga = resta(dunga, donga); int sum(int s1, int s2) { return s1 + s2; } int resta(int r1, int r2) { return r1 r2; }
Pasaje de parámetros usando la pila Ventajas: Al retornar de la función se libera el espacio de memoria. Desventajas: Llamados anidados a funciones pueden limitar la RAM libre.
Uso del heap Se hace declarando variables estáticas externas, mas conocidas como VARIABLES GLOBALES.
Ejemplo de pasaje de parámetros usando el heap iint dunga, donga; void main(void) { dunga = 3; donga = 5; suma(); resta(); } int { } suma(void) dunga = dunga + donga; int { } resta(void) dunga = dunga - donga;
Pasaje de parámetros usando el heap Ventajas: Pueden anidarse los llamados a funciones sin que crezca la pila enormemente. Desventajas: La memoria usada en el heap no puede liberarse.
Uso de las funciones recursivas void factorial(int n) { if(n == 1) return 1; else return factorial(n-1) * n; } factorial(8);
Variables
Tipos de datos básicos En PC: char: 1 byte int: 2 ó 4 bytes float: 4 bytes En microcontroladores: char: 1 byte int: 2 bytes float: 4 bytes
Variantes de los tipos básicos Se admiten los modificadores signed y unsigned. También los modificadores short y long. El float puede no estar contemplado en versiones gratuitas de los compiladores.
Operaciones básicas Aritméticas Suma + Resta Multiplicación * División / Resto % Lógicas AND & OR XOR ^
No necesariamente existe en ensamblador una instrucción para realizar cierta operación aritmética. Ej: Los PIC 16F no cuentan con instrucciones de multiplicación. En ese caso el compilador debe generar un algoritmo en assembler que ejecute esa operación.
Alternativas al tipo float En general queremos representar cifras del tipo: 20,3 mv Se trata de un número decimal de punto fijo. Tampoco se dispone de representación en punto fijo.
Solución: Trabajar las mangintudes multiplicadas x10, x100, La cifra 20,3 se almacena como 203. Así puede contenerse en un tipo int.
Manejo de bits como variables Es válida aplicar una asignación a un bit. Son válidas las operaciones lógicas entre bits, & y ^. Ejemplos: Freescale: PTAD_PTAD4 = 1; PIC: PORTBbits.RB4 = 0;
Volatile Código en C void findecuenta(void) { if(time == 100) { time = 0; } } Posible compilación en ensamblador: findecuenta: LDA time CMP #!100 BNE CLR time
Volatile Si time no cambia antes del if, la compilación anterior es válida. Si time proviene del módulo timer puede modificarse entre que es cargada y el if.
Solución Código en C: volatile int time; void findecuenta(void) { if(time == 100) { time = 0; } } Compilación en ensamblador: findecuenta: LDA time CMP #!100 BNE CLR time
Reseña de punteros Se declaran y usan de la forma convencional: int *p; char *p; int hola; p= &hola; *p = 3; ( hola = 3 )
Punteros a RAM int int hola; *p; p = &hola; *p = 3; p apunta a una posición en RAM.
Punteros a ROM char char *p; texto[20] = Hola mundo ; p = texto; p apunta a una posición en ROM (FLASH).
Arquitectura Von Neumann ( Freescale ) La RAM y ROM están en un mismo mapa. Pueden accederse por una única clase de punteros.
Arquitectura Harvard ( PIC ) La RAM y ROM están en mapas diferentes. Se deben indicar a que mapa apunta el puntero.
Punteros en micros Hardvard Punteros a RAM: char char p = &hola; hola; *p; Punteros a ROM: const char rom char rom *p; p = texto; texto[20] = me duermo ;
Entrada y salida
En la computadora Acceso al I/O de entrada: a = getch(); Acceso al I/O de salida: printf( Hola mundo );
Sistema dedicado Crear bibliotecas de funciones: LCD_printf(char *texto); LCD_Dato(char caracter); LCD_gotoxy(char x, char y);
sprintf resuelve todo Genera textos. Convierte datos int a BCD. Convierte datos float a BCD. Permite generar salidas en formatos muy complejos. sprintf(texto, Chau ); sprintf(texto, %d, a); sprintf(texto, %f, d); sprintf(texto, V = %02.1f mv, voltios);
Uso de los puertos de E/S Acceso a pines individuales: struct { byte PTAD0:1 byte PTAD1:1 byte PTAD2:1 byte PTAD3:1 byte PTAD4:1 byte PTAD5:1 byte PTAD6:1 byte PTAD7:1 }Bits; Bits.PTAD0 = 1;
Mas sobre puertos Acceso a todo el puerto: typedef { byte struct { }Bits; }PTADSTR; union Byte; byte byte byte byte byte byte byte byte PTAD0:1 PTAD1:1 PTAD2:1 PTAD3:1 PTAD4:1 PTAD5:1 PTAD6:1 PTAD7:1 PTADSTR.Byte = 0xff; PTADSTR.Bits.PTAD0 = 1; PTAD_PTAD0 = 1;
Manejo de periféricos En PC usando system calls. En microcontroladores: Freescale: Asistentes de configuración ( Processor Expert ) Funciones de biblioteca. En PIC: Bibliotecas.
Processor Expert Permite configurar cualquier módulo del microcontrolador.
Processor Expert Pueden configurarse todos los parámetros del módulo.
Processor Expert Genera el código de inicialización Genera funciones para el manejo de módulo. Genera vectores de interrupción.
Bibliotecas para PIC A/D Comparador EEPROM I2C PWM
Temporización
Demoras cortas Intercalando código en ensamblador: unsigned char contador_1; asm { LDA #$32 STA contador_1 dem_100us_1: NOP NOP NOP T total = 50 x 2µs = 100µs T int = 10 ciclos x 200ns = 2µs DBNZ contador_1,dem_100us_1 }
Demoras largas Intercalando código en ensamblador: unsigned char contador_1; unsigned char contador_2; unsigned char contador_3; for(contador_3 = 0; contador_3 < 100; contador_3++) for(contador_2 = 0; contador_2 < 20; contador_2++) { asm { LDA #$C8 STA contador_1 dem_1s_1: NOP NOP NOP DBNZ contador_1,dem_1s_1 } } T total = 100 x 10ms = 1s T ext = 20 x 500µs = 10ms T int = 250 x 2µs = 500µs
Demoras largas Módulo timer con interrupciones: void main(void) { TI1_SetPeriodSec(1); } ISR(TI1_Interrupt) { (void)tpm1c0sc; TPM1C0SC = 0x80; } (código del usuario)
Código bloqueante Durante el llamado a una función el programa no continúa su ejecución hasta retornar de ella. Ej: void main(void) { demora_seg(1); }
Código no bloqueante El programa puede continuar con otras tareas mientras una función no entregue los resultados deseados. Ej: void main(void) { } ISR(TI1_Interrupt) { (void)tpm1c0sc; TPM1C0SC = 0x80; } (código del usuario)
Timer tick de la PC Produce interrupciones cada 1ms. El usuario puede generar eventos por timer tick. El usuario no debe atender la interrupción. void fastcall TForm1::Timer1Timer(TObject *Sender) { ( código del usuario ) }
Eventos de timer con microcontroladores La velocidad del timer tick es determinada por el firmware. Pueden generarse eventos por timer tick. El usuario no debe atender la interrupción. void TI1_OnInterrupt(void) { ( código del usuario ) }
Interrupciones
Arquitectura PIC Existen 1 ó 2 vectores de interrupción. El usuario debe determinar con un if que periférico disparó la interrupción. #pragma code low_vector=0x18 void interrupt_at_low_vector(void) { _asm GOTO low_isr _endasm } #pragma code #pragma interruptlow low_isr void low_isr(void) { }
Arquitectura Freescale El Processor Expert resuelve buena parte del código. Existen vectores independientes para cada evento y periférico. El usuario no debe atender una interrupción. El Processor Expert genera el código que lo hace.
Evento de conversión A/D word AD1_OutV; ISR(AD1_Interrupt) { ((TWREG*)(&AD1_OutV))->b.high = ADC1RH; ((TWREG*)(&AD1_OutV))->b.low = ADC1RL; OutFlg = TRUE; AD1_OnEnd(); ModeFlg = STOP; } void AD1_OnEnd(void) { unsigned int medicion; }
CHAU!