TODOPIC
Microcontroladores PIC => Lenguaje Basic para microcontroladores PIC => Mensaje iniciado por: marcunni24 en 21 de Octubre de 2013, 23:50:22
-
Buenas noches a todos!
Estoy trabajando en un proyecto en Proton IDE 2.0.0.8 + Proteus 8, y la verdad que me tiene mal ya, je.
El PIC que utilizo es un 16F876A, y lo estoy programando en BASIC.
El proyecto consiste en hacer un contador, que posea 6 displays 7-segmentos para mostrar el valor de cuenta. (Así me lo pidieron, no es algo sobre lo que pueda decidir). Además, debe hacer algunas funciones extra, como levantar una salida al llegar a determinado valor seteado.
Para esto escribí un programa bastante sencillo, que ejecuta un bucle principal para hacer el multiplexado, y se encarga de modificar el valor de la cuenta mediante interrupciones.
Mientras el código del programa no superaba determinada cantidad de líneas, andaba "relativamente" bien. Con esto quiero decir que el programa ejecutaba las funciones que debía, pero al visualizarlo en el Proteus, los LEDs se veían parpadear. (Digamos que el multiplexado no era tan efectivo. Pero al intentar disminuír el tiempo de Delay entre bucle y bucle, directamente no hacía nada. Sólo mostraba 000000 todo el tiempo). Por esto, creí que el problema estaba en que el Proteus no se aguantaba la simulación, y lo dejé ahí.
Ahora bien, el problema que me surgió fue al aumentar la cantidad de líneas de código. Al superar las 2000 (aprox.), comienza a arrojar fallas del tipo:
STACK OVERFLOW.
En uno de los hilos del foro leí que una recomendación era colocar el segmento de manejo de interrupciones inmediatamente después de las variables. Lo hice y me apareció:
[PIC 16CORE]PC=0x014D. Stack underflow executing RETFIE Instruction
También leí que puede deberse a que haya muchas operaciones en el segmento de las interrupciones, y que eso produce el desbordamiento de la pila. Probé sacar todas las operaciones, sólo levantar una flag que utilizara después, durante el bucle principal... pero no. Siguió teniendo los mismos problemas.
No termino de entender la causa del problema. Si es algo en la forma de atender las interrupciones, o a lo mejor el código.
Les agradecería algún comentario sobre la forma de resolver las interrupciones y el bucle principal. Sólo para saber si es factible, o si de entrada ya viene mal encarado. Si esto está bien, entonces tendré que revisar el resto del código.
A continuación les dejo lo más importante del código. (Si quieren el archivo completo, lo subo, pero en principio no quiero aburrirlos con 600 líneas de potenciales errores).
Gracias desde ya por tomarse el tiempo en leer esto.
Device=16F876A
Config XT_OSC, PWRTE_ON, CPD_OFF, WDT_OFF, BODEN_OFF, LVP_OFF
;XT_OSC - oscilador exterior
;PWRTE_ON - Reset de inicio conectado
;CPD_OFF - Codigo de proteccion eeprom desactivado
;WDT_OFF - Perro guardian desconectado
;BODEN_OFF - No actua el reset por bajada de tension
;LVP_OFF - programacion de bajo voltaje desconectado
Xtal=4
All_Digital = TRUE
INTCON= %10011000 'Habilito las interrupciones globales, y en particular la interrupción por Hardware (RB0)
CMCON = 7 'Desactivo los comparadores A/D
TRISA = %000000 '1=ENTRADA 0=SALIDA
TRISB = %11110111 'PORTB son todas entradas, excepto RB3, que es la salida del relé
TRISC = %00000000 'PORTC son todas salidas
OPTION_REG = %01101000
Symbol INTF = INTCON.1 ' RB0 External Interrupt Flag.
Symbol INTE = INTCON.4 ' RB0 External Interrupt Enable.
Symbol GIE = INTCON.7 ' Global Interrupt Enable.
Symbol INTEDG = OPTION_REG.6 ' Flag = 0 int. por flanco bajada. Flag = 1 int. por flanco subida.
Symbol RBIF = INTCON.0 ' RB Port CHANGE Interrupt Enable
On_Interrupt GoTo InterrupEncoder ' Interrupción por Hardware (es la más rápida).
GIE = 1 ' Activa interrupciones generales.
INTE = 1 ' Activa la interrupción externa RB0/INT.
INTEDG = 1 ' Hace que las interrupciones se habiliten por flanco de subida.
All_Digital = TRUE
Bucle_General:
If presionado==0 Then 'Mientras no haya interrupciones
'--------------------------- Inicio Bucle Display cuenta------------------------------------------------
Digito= Dig Contador1,i 'tomo el valor i-ésimo de la variable Contador1
'Convierto de decimal a 7-seg, dígitos ánodo común
Select Digito
Case 0
PORTC=$C0
Case 1
PORTC=$F9
Case 2
PORTC=$A4
Case 3
PORTC=$B0
Case 4
PORTC=$99
Case 5
PORTC=$92
Case 6
PORTC=$82
Case 7
PORTC=$F8
Case 8
PORTC=$80
Case 9
PORTC=$90
End Select
Select i 'Multiplexo los transistores
Case 0
PORTA=%011111
If Contador1<0 Then
PORTC=$BF
End If
Break
Case 1
PORTA=%101111
Case 2
PORTA=%110111
Case 3
PORTA=%111011
Case 4
PORTA=%111101
Case 5
PORTA=%111110
End Select
If temporizaMODO=1 Then 'Seccion para contar que MODO esté presionado durante x tiempo
If PORTB.4==1 Then ' sin por ello dejar de mostrar el display
tiempo=tiempo+1
Else
tiempo=0
temporizaMODO=0
End If
If tiempo==20 Then
GoTo Configuracion
End If
End If
i=i+1
If i==6 Then 'Cuando mira el 6to dígito, recomienza
i=0
End If
If Contador1>setpoint Then 'Prende el relé si se supera la cuenta
PORTB.3=1
Else
PORTB.3=0
End If
If PORTB.2==1 Then
GoTo Contador
End If
DelayMS 5
'--------------------------- Fin Bucle Display cuenta------------------------------------------------
Y la foto del circuito:
http://imageshack.us/photo/my-images/12/dz61.jpg/
Saludos!
-
Estuve mirando un poco más el problema. A lo mejor este thread debería ir en el subforo Proteus (hasta ayer no lo había visto... :oops:). Si es así, por favor alguien avíseme, así lo posteo donde va. Resumo un poco lo que concluí hasta ahora:
- -Cuando la rutina de manejo de interrupciones la pongo al final de todo, no tira el problema de "Stack underflow executing RETFIE Instruction", el cual SÍ tira cuando muevo la misma rutina, y la pongo arriba de todo, inmediatamente a continuación de la declaración de variables. No termino de entender la causa de esto, pero por lo menos no molesta en las simulaciones.
-Al ejecutar el código con "pocas líneas" (<2000 'program words'), funciona correctamente. (Al menos, no tira errores al simularlo).
-Cuando uso el código completo, y lo simulo, me genera una seguidilla de errores:
1) Varios "Stack overflow pushing return address of interrupt"
2) seguidos por algunos Stack overflow executing CALL instruction
3) y finalmente algunos Stack UNDERflow esecuting RETURN instruction.
Estos últimos se repiten varias veces.
Lo que me queda claro... es que hay stack overflow.... :(
Esta es la rutina de manejo de interrupciones:
'Manejo de interrupciones
InterrupEncoder:
Context Save ' Salva el contexto de los registros antes de operar con la interrupción.
If INTF==1 Then'Si la interrupción fue por pulso en el encoder
'--------------------------- Interrupción de Encoder ------------------------------------------------
If PORTB.0 = 1 Then ' Si RB0 se puso en 1 (flanco de subida),
INTEDG = 0 ' entonces activar la siguiente interrupción por flanco de bajada.
If PORTB.1 = 1 Then ' Si RB1 está en 1,
Inc Contador1 ' entonces incrementar el contador .
EndIf
Else ' Si RB0 se puso en 0 (flanco de bajada),
INTEDG = 1 ' entonces activar la siguiente interrupción por flanco de subida.
If PORTB.1 = 1 Then ' Si RB1 está en 1,
Dec Contador1 ' entonces decrementar el contador .
EndIf
INTF = 0 ' Borra el "flag" de la interrupción RB0/INT para poder permitir la sig interrupción cuando pase.
EndIf
'--------------------------- Interrupción de Encoder ------------------------------------------------
'--------------------------- Decodificador de Switches -------------------------------------------
ElseIf RBIF==1 Then 'Si la interrupción se produjo por algún otro de los pulsadores.
'Averiguo si se seteó algún valor.
PuertoBactual=PORTB
PinCambiado=PuertoBactual^PuertoBanterior
If PinCambiado.4==1 Then 'Esto implementado con los ELSE if, establece las jerarquías
secuencia=4 'Secuencia de Modo
ElseIf PinCambiado.5==1 Then
secuencia=5
ElseIf PinCambiado.6==1 Or PinCambiado.7==1 Then
secuencia=6
End If
presionado=1 'Seteo variable estableciendo que algo se presionó
RBIF=0 'Borro la flag de interrupción
PuertoBanterior=PuertoBactual 'Guardo el valor anterior del puerto
'--------------------------- Decodificador de Switches -------------------------------------------
End If
Retfie
'Context Restore ' Restablece el contexto de los registros tal como estaban antes de la interrupción.
' Resume
La idea es utilizar dos tipos de interrupciones:
INTF (pulsos en RB0, por parte de un encoder)
RBIF (pulsos en RB7 a RB4, por parte de diferentes pulsadores)
Dependiendo el pulsador, debe levantar una variable que haga algo diferente en cada caso.
Quisiera su opinión sobre la forma en que está manejado esto. No termino de entender por qué se produce el stack overflow, dependiendo de la cantidad de líneas del programa. Cualquier sugerencia es bienvenida.
Desde ya, gracias por leerlo!
Saludos
Martín
-
Estuve investigando un poco y encontré lo siguiente, a lo mejor le sirve a alguien para una situación similar:
Mi programa estaba escrito con varios GoTos. Esta instrucción, utilizada como:
GOTO k,
simplemente carga la constante k (de 9 bits), en el registro PC (el Program Counter, que direcciona).
El problema que aparece con esto es que la constante k es de sólo 9 bits, mientras que el registro PC es de 11 bits. Los dos bits faltantes, (bits 9 y 10 del PC) son tomados respectivamente de los bits de selección de página PA0 y PA1 de la palabra de estado (STATUS). (Por lo menos para los 16F8x, no sé para otros PICS)
Esta "forma rara" de direccionar hace que la memoria de programa aparezca como dividida en páginas de 512 posiciones. Entonces uno debe tener en cuenta que antes de ejecutar una instrucción GOTO es posible que haya que programar los bits PA0 y PA1.
Estos 11 bits del PC, te permiten direccionar una memoria de programa de 2K. Yo creo que acá estaba mi problema. Al momento de sobrepasar los 2k de líneas de código, el programa cargaba una dirección mayor a la que podía manejar en el PC, por lo tanto desbordaba el puntero, y me indicaba a cualquier lado. :?
Lo que hice hasta ahora fue sacarle todas las instrucciones posibles a las interrupciones (prácticamente lo único que hacen es levantar flags). Por otro lado, disminuí bastante las líneas de código, utilizando subrutinas (con GOSUB y RETURN en lugar de GOTO).
Por lo pronto el programa me funciona. Lo que sucede es que al "ahorrar espacio", nuevamente tengo menos de 2k de program words usadas.
Me parece una solución bastante mediocre. Probablemente, lo que tendría que hacer es:
Conociendo las direcciones exactas en las que arranca cada sección, programar los bits SATUS.0 y STATUS.1 para que al hacer el salto vaya a donde debe.
Si alguien tuvo una experiencia similar (la lógica me dice que debe ser un problema bastante común, por lo menos con los PICS 16F8x.... no?), le agradezco cualquier comentario o sugerencia.
Saludos!
Martín
-
Hola.
Este tema se ha tratado varias veces en el foro y hay un tutorial de Leon_PIC en donde tratamos este tema.
El datasheet de tu micro lo dice en la sección 2.3:
All PIC16F87XA devices are capable of addressing a continuous 8K word block of program memory. The CALL and GOTO instructions provide only 11 bits of address to allow branching within any 2K program memory page. When doing a CALL or GOTO instruction, the upper 2 bits of the address are provided by PCLATH<4:3>. When doing a CALL or GOTO instruction, the user must ensure that the page select bits are programmed so that the desired program memory page is addressed. If a return from a CALL instruction (or interrupt) is executed, the entire 13-bit PC is popped off the stack. Therefore, manipulation of the PCLATH<4:3> bits is not required for the RETURN instructions (which POPs the address from the stack).
Cada página tiene un tamaño de 2048 (0x800) instrucciones. Si estás programando en BASIC, debería ser el compilador quien se encargue de generar el código correcto para que no tengas problemas.
Para que quede claro, GOTO k permite 11 bits para cargar la dirección destino. Los otros 2 bits restantes provienen de los bits PCLATH<4:3>. No tienen nada que ver los bits de selección de página del registro STATUS. No confundamos. El PCLATH está relacionado a la memoria de programa(FLASH), mientras que los bits STATUS<RP1:RP0> están relacionados a la memoria RAM.
La memoria de programa está paginada cada 2KWords (2048 instrucciones de programa).
La dirección final (13 bits) que se carga al hacer un CALL o GOTO provienen de:
los 2 bits de mayor peso provienen del valor actual de los bits PCLATH<4:3> y los 11 de menor peso se cargan desde la instrucción GOTO/CALL.
Y qué pasa con el RETURN/RETFIE/RETLW también tengo que tener respecial cuidado antes de retornar de una subrutina?
NO. El STACK de estos micros es de 8 niveles y cada uno de 13 bits. Es decir que cuando haces un CALL, se guardan TODOS los 13 bits donde debe retornar el PC cuando se ejecute el RETURN/RETFIE/RETLW.
Ahora, en cuanto a la memoria RAM:
Está seccionada en BANCOS, cada uno de 0x80 registros (128 Bytes).
La posición de memoria sobre la cual se trabaja (9 bits) provienen de:
los 2 bits de mayor peso provienen de STATUS<RP1:RP0> y los 7 de menor peso se cargan desde el valor de la instrucción mísma.
Si por ejemplo, hago:
BCF STATUS,RP1
BSF STATUS,RP0
MOVF 0x20,W
estaré leyendo la dirección 0xA0 y copiando su valor a W.
La fórmula para calcular la dirección final de memoria a afectar es:
((RP1 * 2) + RP0) * 0x80 + dirección cargada en la instrucción.
En el ejemplo anterior sería entonces:
((0 * 2) + 1) * 0x80 + 0x20 = 0xA0
Eso es para direccionamiento DIRECTO.
Para direccionamiento indirecto, mediante los registros INDF y FSR:
Como el registro FSR es quien contiene la posición de memoria a leer/escribir indirectamente, y los registros del uC son de 8 bits, en este caso el FSR puede contener un valor entre 0 y 255 (un total de acceso de 256 bytes). Nuevamente estos micros poseen más de 256 bytes de RAM, por lo que aparece un registro de apoyo como bit de mayor peso ( bit 8 ) para poder direccionar entre los bancos de memoria. Dicho bit es el STATUS,IRP.
Entonces la posición de memoria a acceder indirectamente proviene de:
el bit de mayor peso proviene del valor del bit STATUS,IRP y los 8 bits de menor peso del valor de FSR.
Ejemplo:
bcf STATUS,IRP
movlw 0x20
movwf FSR
movf INDF,W
W contendrá el valor de la posición de memoria 0x20.
Ahora, si hiciese:
bsf STATUS,IRP
movlw 0x20
movwf FSR
movf INDF,W
W en este caso contendrá el valor de la posición de memoria 0x120.
Saludos!
-
Gracias, BrunoF! Muy clara tu respuesta!
Sí, ya sospechaba que debía ser un tema trillado, pero no sabía cómo buscarlo, ya que creía que el problema era con las interrupciones.
Tenés razón, estaba mezclando los tantos con lo de la ram y la flash.
Ahora, una consulta. Por lo que estoy viendo, mi compilador NO se está encargando de realizar este direccionamiento.
Nunca había tenido un problema así, pero tampoco había programado antes en BASIC.
Por eso, consulto: ¿Hay algo que se deba configurar al inicio, o alguna otra cosa que posteriormente pueda afectar este funcionamiento? A lo mejor metí la pata de alguna manera, o me faltó alguna palabra de configuración...
Gracias de nuevo por tu respuesta.
Saludos
Martín
-
Hola,
no programé nunca un PIC en BASIC así que sinceramente no puedo decirte con certeza. Sí encontré esto:
7.5. Life After 2K
Yes, there is life after 2K using the PICBASIC PRO™ Compiler.
PICmicro MCUs have a segmented code space. PICmicro MCU instructions in 14-bit core parts such as Call and Goto only have enough bits within them to address 2K of program space. To get to code outside the 2K boundary, the PCLATH register must be set before each Call or Goto.
PBP automatically sets these PCLATH bits for you. There are a few restrictions imposed, however. The PICBASIC PRO™ library must fit entirely into page 0 of code space. Normally this is not an issue as the library is the first thing in a PICBASIC PRO™ program and the entire library is smaller than 2K. However, attention must be payed to this issue if additional libraries are used.
Assembly language interrupt handlers must also fit into page 0 of code space. Putting them at the beginning of the PICBASIC PRO™ program should make this work. See the upcoming section on assembly language for more information.
The addition of instructions to set the PCLATH bits does add overhead to the produced code. PBP will set the PCLATH bits before any Call or Goto instruction on 12-bit core PICmicro MCUs with more than 512 words of code space, 14-bit core devices with more than 2K of code space and PIC17Cxxx devices with more than 8K of code space.
There are specific PICBASIC PRO™ instructions to assist with the 2K issues.
BRANCHL was created to allow branching to labels that may be on the other side of a 2K boundary. If the PICmicro has 2K or less of program space, BRANCH should be used as it takes up less space than BRANCHL. If the microcontroller has more than 2K of code space, and you cannot be certain that BRANCH will always act within the same page, use BRANCHL.
The assembler may issue a warning that a page boundary has been crossed. This is normal and is there to suggest that you check for any BRANCHes that may cross a page boundary.
Fuente: http://melabs.com/resources/pbpmanual/7_0.htm
Que puede te sea útil, ya que parece mencionar el uso de una isntrucción especial para que el compilador te genere los valores correctos del PCLATH antes de saltar.
Saludos.
-
Qué grande! Por lo que estoy leyendo, es exactamente lo que pasa.
Muchas gracias por tu ayuda con esto!! ((:-)) ((:-))
Cuando tenga alguna novedad, lo posteo acá.
Saludos!
Martín
-
Estimados:
El manual que comenta Bruno pertenece al Pic Basic Pro y Marcunni24 comenta que compiló con Proton que es un compilador basic totalmente distinto ,asi mismo ,el manejo de los stack hasta donde recuerdo ambos compiladores lo hacen automáticamente .
Si tengo un rato veo de compilar con proton a ver que me dice.-
Saludos
Jorge
-
Jorge, buenas tardes
Gracias por tu respuesta. Lo que decís es cierto, ese manual era para el PBP, y no para el Protón. Sin embargo, llego a la conclusión de que el problema es el mismo. Al pasar las 2k líneas de código, y ejecutar un salto, el compilador no está pudiendo direccionar al banco correspondiente (modificando los bits de PCLATH), por lo que el programa va a donde no debe.
Hasta ahora le escapé al problema provisionalmente, acortando el código, usando subrutinas, manejando las interrupciones al inicio del programa, y demás. Ahora la stack funciona correctamente, sin over o underflows.
Pero me parece que cuando vuelva a superar los 2048, va a reaparecer el problema. No sé si esto es un problema del compilador, o si existe algún Fusible especial que se deba configurar al inicio.
Gracias por los datos!
Saludos
Martín
-
Hola!
Puedo garantizarte que fusibles no hay. Es así la arquitectura de esa familia de micros. Debería haber alguna solución simular para Protón. Te pido disculpas por la confusión, pero no utilizo Basic para programar.
Saludos!