Contenido>Indice>Intro CursoC51

INTERRUPTS, USING, REGISTERBANKS, NOAREGS EN C51



Las interrupciones juegan un papel importante en la mayoría de las aplicaciones del 8051, y afortunadamente C51 permite escribir las funciones de interrupción enteramente en C. Aunque es perfectamente posible escribir código que funcione, respetando el estándar ANSI C, si se quiere mejorar la eficacia del código es conveniente comprender la utilidad de los siguientes controles:

El atributo básico de las funciones de interrupción

Para que una función de interrupción pueda ser alcanzada es necesario generar el vector de interrupción adecuado. El compilador C51 lo hace de forma automática, basándose en el argumento de la palabra interrupt. Posteriormente, el linker impide que las variables locales de las funciones de interrupción se solapen con las del programa principal, creando secciones especiales en RAM. Ejemplo:

/* Rutina de atención a la interrupción por desbordamiento del Timer 0*/

timer0_int() interrupt 1
{
unsigned char temp1 ;
unsigned char temp2 ;

/* Sentencias C ejecutables ; */
}

Tomando como ejemplo la función de atención a la interrupción por desbordamiento del timer 0, y suponiendo que esta no utilice registros:

Código de entrada a la función timer0_int

void timer0_int(void) interrupt 1 
{
RSEG ?PR?timer0_int?TIMER0
USING 0	
timer0_int:
; LINEA DE CODIGO FUENTE # 2

Si la función de interrupción llama a su vez a otra función llamada "sys_interp", el código de entrada cambia a:

Código de entrada a la función timer0_int que llama a otra función

; void timer0_int(void) interrupt 1 
{
RSEG ?PR?timer0_int?TIMER0
USING 0
timer0_int:
PUSH ACC
PUSH B	
PUSH DPH
PUSH DPL
PUSH PSW
PUSH AR0
PUSH AR1
PUSH AR2
PUSH AR3
PUSH AR4
PUSH AR5
PUSH AR6
PUSH AR7

 

Ahora, al entrar a la función de interrupción se guardan en la pila todos los registros, ya que C51 supone que la función llamada (sys_interp) puede hacer uso de los mismos. Si se observa la entrada a sys_interp, se hace evidente un truco importante del compilador:

Código de entrada a sys_interp()

; unsigned char sys_interp(unsigned char x_value,
RSEG ?PR?_sys_interp?INTERP
 USING 0
_sys_interp:
MOV y_value?10,R5
MOV map_base?10,R2
MOV map_base?10+01H,R3
;--Variable 'x_value?10' assigned to Register 'R1' --	
MOV R1,AR7

 

Puede observarse la forma tan eficiente para mover el contenido de R7 a R1, utilizando AR7. Este tipo de direccionamiento absoluto de registros proporciona un código muy rápido.

El truco del direccionamiento absoluto de registros en detalle

El 8051 no dispone de la instrucción MOV Reg,Reg, por ello Keil utiliza el truco de considerar un registro como una dirección data absoluta:

Simulación de la instrucción MOV Reg,Reg:

En el banco de registros 0 - MOV R0,AR7, es idéntica a - MOV R0,07H.

Este truco solo puede utilizarse cuando el compilador conoce el banco de registros que se está utilizando. Cuando se utiliza el control USING, pueden surgir problemas. Véanse las siguientes secciones...

El control USING

El control using n ordena al compilador que utilice el banco de registros n. De esta forma las rutinas de interrupción con exigencias de tiempo muy críticas pueden cambiar de "contexto", con mayor rapidez que empilando los registros. Además las funciones de interrupción de igual prioridad pueden compartir el mismo banco de registros, al no existir el riesgo de que se interrumpan entre ellas.

Direcciones de base de los bancos de registros del 8051

Los registros R0...R7 ocupan direcciones consecutivas en la RAM interna del 8051, siendo la dirección base, o dirección correspondiente al registro R0, variable en función del banco de registros que se encuentre activo. Así la dirección base puede ser: 0x00, 0x08, 0x10, o 0x18 según que el banco de registros activo sea el: 0, 1, 2, o 3.

Si a una función de interrupción se le añade el control "USING 1", se sustituye el empilado de los registros por la instrucción "MOV PSW,#08H" que conmuta el banco de registros. El tiempo de entrada a la interrupción disminuye considerablemente, pero puede fallar el direccionamiento absoluto de registros si no se tiene cuidado. Si la función de interrupción no hace uso de registros, y no llama a ninguna otra función, el optimizador elimina el código de banco de registros.

Código de entrada a timer0_int con USING

Con USING 1
; void timer0_int(void) interrupt 1 using 1 {
RSEG ?PR?timer0_int?TIMER0
USING 1 <--- Nuevo banco de registros
timer0_int:
PUSH ACC
PUSH B
PUSH DPH
PUSH DPL
PUSH PSW
MOV PSW,#08H

Código de entrada a sys_interp()

Usando todavía el banco de registros 0

; unsigned char sys_interp(unsigned char x_value,
RSEG ?PR?_sys_interp?INTERP	
USING 0
_sys_interp:	
MOV y_value?10,R5	
MOV map_base?10,R2	
MOV map_base?10+01H,R3;
--Variable 'x_value?10' assigned to Register 'R1' --	
MOV R1,AR7      <----- FALLA!!!!

 

El direccionamiento absoluto de registros supone que el banco de registros activo es el 0, y el programa falla.

Notas sobre llamadas a funciones desde las interrupciones

C51 utiliza un cierto grado de inteligencia al entrar en las funciones de interrupción. Además de sustituir el RET del final de la función por un RETI, automáticamente empila los registros que utilice la función. 

Sin embargo, hay algunos aspectos a considerar:

Cuando usar el control USING

El #pragma NOAREGS

Direccionamiento absoluto de registros con C51.

Ya se ha comentado que el 8051 no dispone de la instrucción MOV Reg,Reg, por lo que C51 utiliza MOV R1,AR7 donde AR7 es la dirección absoluta del registro R7 en uso. Para que la sustitución funcione correctamente el compilador debe conocer el banco de registros que está utilizando. Si una función se llama desde una interrupción que utiliza el control USING, hay dos posibilidades:

Las funciones compiladas con NOAREGS sirven siempre, independientemente del banco de registros que se utilice, aunque pueden ser más lentas que las que utilicen un banco de registros definido.

El control REGISTERBANK como alternativa a NOAREGS

El control #pragma REGISTERBANK(n) informa a C51 sobre el banco de registros utilizado, haciendo posible el direccionamiento absoluto de registros.

EJEMPLO:
/* Rutina de atención a la interrupción por desbordamiento del Timer 0 */
timer0_int() interrupt 1 USING 1 {
unsigned char temp1 ;
unsigned char temp2 ;
/* Sentencias C ejecutables ; */
}

 

Función llamada por timer0_int:

#pragma SAVE // Recuerda el banco de registros actual
#pragma REGISTERBANK(1)	// Informa a C51 sobre el nuevo banco de registros
void sys_interp(char x) {// Función llamada desde interrupción con "using 1"	
/* Código */	
 }
#pragma RESTORE // Repone el banco de registros original

 

Al utilizar #pragma REGISTERBANK(1) con sys_interp() se restaura el direccionamiento absoluto de registros ya que C51 conoce el banco de registros utilizado.

Nota: Utilícese siempre el control REGISTERBANK(n) para las funciones llamadas desde interrupciones con USING n.

Código de entrada de sys_interp() con REGISTERBANK(n)

; unsigned char sys_interp(unsigned char x_value,	
RSEG ?PR?_sys_interp?INTERP	
USING 1
_sys_interp:	
MOV y_value?10,R5	
MOV map_base?10,R2	
MOV map_base?10+01H,R3;--
Variable 'x_value?10' assigned to Register 'R1' --	
MOV R1,AR7

 

Resumen de USING y REGISTERBANK

Expresado en pseudo-código

if(interrupt routine = USING 1){
las funciones llamadas desde aquí deben usar #pragma REGISTERBANK(1)
}

Nota: las funciones llamadas por la interrupción, sólo pueden llamadas desde funciones que utilicen el banco de registros 1.

Re-entrancia en C51 - La solución definitiva

A veces una misma función se llama desde interrupciones y desde el programa principal. Para que la llamada desde dos lugares diferentes no tenga consecuencias desastrosas, la función llamada debe ser re-entrante. El usuario puede especificar funciones re-entrantes utilizando el atributo reentrant en la definición de las mismas. El aviso del linker "MULTIPLE CALL TO SEGMENT" es el primer signo de que se está tratando de utilizar una función de forma re-entrante.

La razón por la cual una función no re-entrante no puede ser llamada a la vez desde el programa principal y desde una interrupción, es que C51 asigna lugares de almacenamiento en RAM, para las variables locales y para los parámetros de las funciones ordinarias.

El valor asignado a ?C_IBP en el fichero startup.a51, le dice a C51 el lugar donde debe colocar la pila artificial para las funciones re-entrantes. Cada vez que se llama a una función re-entrante, los parámetros de entrada a la misma se llevan desde los registros al área de RAM que comienza en la dirección indicada por ?C_IBP. De igual forma, cualquier variable local utilizada por una función re-entrante se coloca en esta pila especial.

Cuando, antes de main( ),  se ejecuta startup.a51, la línea:

IF IBPSTACK <> 0
EXTRN DATA (?C_IBP)
MOV ?C_IBP,#LOW IBPSTACKTOP
ENDIF

inicializa ?C_IBP al valor que se le haya asignado anteriormente a IBPSTACKTOP. A medida que se "empilan" variables locales en la pila re-entrante, se decrementa ?C_IBP. Así, si se produce una interrupción que llama a la función de nuevo, las variables locales se guardan en el valor actual de ?C_IBP, sin destruir los valores anteriores.

Obtención de una variable local con offset 2 desde el valor actual de la pila re-entrante:

MOV R0,?C_IBP ;	Obtener la base de la pila re-entrante
MOV A,@R0 
ADD A,#002    ; Añadir el offset
MOV A,@R0     ; Obtener la variable local por direccionamiento indirecto
MOV R7,A      ;	Guardar su valor
...

?C_IBP actúa como un puntero a la base de la pila re-entrante, utilizado para acceder a las variables locales de las funciones re-entrantes. Al salir de la función se restaura el valor de ?C_IBP a su valor inicial, sumando el tamaño de las variables locales y parámetros utilizados. Esto representa una sobrecarga de trabajo, que indica que la re-entrancia solo debe utilizarse cuando sea absolutamente necesaria.

Si se añade el atributo reentrant a la función sys_interp(), todavía se necesita el control NOAREGS ya que se ha cambiado el banco de registros con USING 1. De echo, cualquier función re-entrante debe llevar el atributo NOAREG para hacerla totalmente independiente del banco de registros.

Código de entrada de sys_interp()

; unsigned char interp_sub(unsigned char x,	
RSEG ?PR?_?interp_sub?INTERP	
USING 0
_?interp_sub:	
DEC ?C_IBP	
DEC ?C_IBP	
MOV R0,?C_IBP	
XCH A,@R0	
MOV A,R2	
XCH A,@R0	
INC R0	
XCH A,@R0	
MOV A,R3	
XCH A,@R0	
DEC ?C_IBP	
MOV R0,?C_IBP	
XCH A,@R0	
MOV A,R5	
XCH A,@R0	
DEC ?C_IBP	
MOV R0,?C_IBP	
XCH A,@R0	
MOV A,R7	
XCH A,@R0	
DEC ?C_IBP ;	

Código de salida de sys_interp()

?C0009:	
MOV A,?C_IBP	
 ADD A,#010H   <-- Restaura ?C_IBP a su valor original
position	
MOV ?C_IBP,A	
RET ;
END OF _?sys_interp

 

Resumen de controles para funciones de interrupción

Utilizando las siguientes combinaciones de controles se evitan los avisos del linker y el código potencialmente peligroso.

Atributo de función de Interrupc. |   Atributo de la función llamada: 
----------------------------------|-----------------------------------
                                  |    "no re-entrante"
No USING                          |    no requiere ningún atributo
----------------------------------------------------------------------
USING n                           |    USING n
                                  |    o
                                  |    #pragma REGISTERBANK(n)
                                  |    o
                                  |    #pragma NOAREGS
----------------------------------------------------------------------

Re-entrancia y funciones de librería

La mayoría de las funciones de libreria de C51 son re-entrantes y pueden utilizarse libremente desde el programa principal y desde las interrupciones. Sin embargo, algunas de las funciones de mayor tamaño, tales como  printf(), scanf() etc. no son re-entrantes. Si se utiliza una función no re-entrante de manera re-entrante, se obtiene el aviso "MULTIPLE CALL TO SEGMENT", como cabía esperar.

Las funciones de librería "ocultas" que se encargan de las operaciones con enteros (multiplicación, división, etc.) son todas re-entrantes, lo cual hace que se puedan realizar libremente divisiones de 16 bits en las rutinas de interrupción o en el programa principal.

En cualquier caso, cuando se utilicen funciones de librería de forma re-entrante, es conveniente consultar el manual de C51 para obtener detalles del carácter de esas funciones. 


   Contenido>Indice>Intro CursoC51