Modo de suspensión PIC32 con error de activación del perro guardián

Estoy en el proceso de intentar que un PIC32MX795F512L funcione con un menor consumo de energía. Estoy tratando de hacer que entre en modo de suspensión y luego me despierte el temporizador de vigilancia. Todas las cosas bastante estándar.

Mi código funciona en la primera activación del temporizador de vigilancia, pero la segunda activación no se activa como si estuviera en modo de suspensión, sino en modo de ejecución normal. Como resultado, no continúa la ejecución, sino que reinicia todo el chip.

En el PIC32, el temporizador de vigilancia, cuando está en modo de reposo o inactivo, provoca un NMI con el mismo vector que el reinicio. Luego verifica algunas banderas en un registro para ver si fue causado por el perro guardián en modo de suspensión, etc.

Mi código de inicio se ve así:

_reset:
    la      k0, RCON        # Load address of RCON register

    lw      k1, 0(k0)       # Get contents of the register
    and     k1, k1, 0x18    # We are only interested in 0x18
    sub     k1, 0x18        # Subtract 0x18
    beqz    k1, _ret_nmi    # and if the result is 0 (i.e., equal to 0x18) then branch.

    lw      k1, 0(k0)       # Same again but looking for 0x14.
    and     k1, k1, 0x14
    sub     k1, 0x14
    beqz    k1, _ret_nmi
    nop

    la      k0, _startup
    jr      k0                      # Jump to startup code
    nop

_ret_nmi:
    lw      k1, 0(k0)
    and     k1, 0xFFE3
    sw      zero, 0(k0)
    eret

Básicamente, busca 0x18 o 0x14 configurados en el registro RCON, luego borra esos bits y regresa de una interrupción si están configurados.

El modo de suspensión se ingresa configurando el bit SLEEP en OSCCON (que primero debe desbloquearse), y de acuerdo con el manual de ahorro de energía para PIC32 se hace así:

// Standard unlock sequence
SYSKEY = 0x0;
SYSKEY = 0xAA996655;
SYSKEY = 0x556699AA;
OSCCONSET = 0x10; // Enable sleep mode
SYSKEY = 0x0;

Luego habilita el temporizador de vigilancia, "patea al perro" como se conoce, y detiene la CPU con una waitinstrucción:

WDTCONSET = 1<<15; // Turn on
WDTCONSET = 0x01; // Kick the dog!
uint32_t i = disableInterrupts(); // We don't want any old interrupt waking us up
asm volatile("wait");
restoreInterrupts(i);

Eso hace que la CPU se detenga y luego el NMI se activa después de 1.024 segundos. La CPU se reinicia desde el vector de reinicio, el código de inicio verifica las banderas, encuentra que es un NMI y regresa de la interrupción y continúa con la siguiente línea de código.

La primera vez.

La segunda vez que el registro RCON contiene 0x10 en lugar de 0x18, actúa como un tiempo de espera para no patear al perro.

Al inspeccionar OSCCON después del tiempo de espera fallido, el bit SLEEP parece haberse reiniciado. Establecer el bit SLEEP cada vez que pasa por el bucle principal justo antes de dormir no tiene ningún efecto.

Sin embargo

Si hago exactamente lo mismo pero usando el modo inactivo en lugar del modo de suspensión, todo funciona perfectamente. La waitinstrucción continúa después de 1,024 segundos cada vez sin fallar.

Entonces, ¿por qué esto no funciona como debería en el modo de suspensión?

¿Hay algo obvio que me estoy perdiendo?

Actualizar

He intentado forzar el nivel de prioridad de la CPU para que sea el más bajo posible antes de dormir, pero no ha tenido ningún efecto. Este es el código que estoy usando para ello:

asm volatile("mfc0 $8, $12");
asm volatile("ins $8, $0, 10, 3");
asm volatile("mtc0 $8, $12");
asm volatile("wait");
descargo de responsabilidad: nunca he programado un PIC. Solo AVR y ARM. De acuerdo con la página 10-8 de este documento , el código que ha publicado coloca el dispositivo en modo inactivo. El código del modo de suspensión se encuentra en la página 10-4. Esto podría ser una pista: en la página 10-9 solo menciona el uso de eret para tratar el WDT como una interrupción para regresar solo durante el modo de suspensión. Tal vez si se enciende desde el modo inactivo, WDT no cuenta como una interrupción, por lo que ¿eret no será válido?
No, definitivamente es el modo de suspensión. La diferencia sutil entre los dos es que la suspensión se configura para el modo de suspensión (OSCCONSET = 0x10) y se borra para el modo inactivo (OSCCONCLR = 0x10). Todos los tiempos de espera de WDT en suspensión o inactividad son un NMI, como se detalla aquí 9.4.2: Cuando el módulo WDT expira en suspensión o inactividad, se genera una NMI. El NMI hace que la ejecución del código de la CPU salte al vector de reinicio del dispositivo. Aunque el NMI comparte el mismo vector que un reinicio de dispositivo, los registros y los periféricos no se reinician. Notarás que el código con él es lo que tengo.
... Y, el modo inactivo funciona bien, es el modo de suspensión que no lo hace. Consideraría algún problema de tiempo o algo con el reinicio del oscilador, pero: 9.6.4: Habrá un retraso de tiempo entre el evento WDT en el modo de suspensión y el comienzo de la ejecución del código. La duración de este retraso consiste en el tiempo de arranque del oscilador en uso y el retraso PWRT, si está habilitado.
La página 8-23 de esto establece: "Si el temporizador de proximidad está habilitado y la prioridad de interrupción pendiente es menor que la prioridad de proximidad temporal, el procesador no permanece en suspensión. Pasa a inactivo y luego se ejecuta, una vez que el TPT Si la prioridad de la solicitud de interrupción es menor o igual que el nivel de prioridad actual del procesador, el dispositivo cambiará al modo inactivo y el procesador permanecerá detenido". ¿Es por casualidad que el procesador cambia de reposo a inactivo pero luego permanece allí?
Como puede ver en mi código, todas las interrupciones están deshabilitadas durante el período de suspensión. Cuando eso no se hace, y uso el modo inactivo en lugar del modo de suspensión, termina waitcasi de inmediato debido a otras interrupciones. Con ellos desactivados se comporta. Sin embargo, dormir no, es lo mismo.
Sugiero que la NMI (interrupción no enmascarable) podría tener una prioridad más baja que la prioridad de la CPU cuando llamó al comando de espera. Esto haría que la CPU pasara de la suspensión a la inactividad en lugar de activarse. No parece haber una manera de aumentar la prioridad de NMI, pero puede disminuir la prioridad de la CPU antes de la llamada a esperar, como se sugiere aquí
Seguramente sería así para el primer NMI, así como para el segundo. Buscaré establecer la prioridad de la CPU, pero esa publicación no menciona cómo, ni qué parte de ese registro es la prioridad para establecer.
Mencionan esto: "La configuración de IPL está en CP0 Register 12, Select 0". Sin embargo, su problema fue que las puntas se quedaron detenidas. En su caso, dice que solo reinicia todo el chip; por lo que podría no aplicarse. Estoy tan confundido como tú, ya que no debería comportarse de esa manera. ¿Has probado el mismo código con un chip diferente?
Establecer IPL en CP0/12 no tiene ningún efecto (ver mi edición para el código). He probado con varios chips y sigue igual.
@EvangelosEm Lo acabo de resolver, y la respuesta es sorprendente por decir lo menos (ver más abajo).

Respuestas (1)

Ok, ahora tengo esto funcionando correctamente. Y así es como:

  1. Lea el manual.
  2. Tira el manual a la basura.
  3. Escriba un programa de trabajo en un IDE / compilador diferente.
  4. Desmonte el programa resultante y vea cómo se debe hacer.

XC32 incorpora un código para manejar el NMI en su controlador de reinicio por usted, y el código que genera no se parece a lo que el manual dice que debe hacer. En su lugar, examina el estado NMI de C0. Si era un NMI, haga un eret, de lo contrario, inicie el programa normalmente.

Aquí están las partes relevantes del programa desmontadas:

bfc00000:   401a6000    mfc0    k0,c0_status
bfc00004:   7f5a04c0    ext k0,k0,0x13,0x1
bfc00008:   13400005    beqz    k0,bfc00020 <_no_nmi>
bfc0000c:   00000000    nop
bfc00010:   3c1a9d00    lui k0,0x9d00
bfc00014:   275a02a8    addiu   k0,k0,680
bfc00018:   03400008    jr  k0 <_nmi_handler>
bfc0001c:   00000000    nop

9d0002a8 <_nmi_handler>:
9d0002a8:   401a6000    mfc0    k0,c0_status
9d0002ac:   3c1bffbf    lui k1,0xffbf
9d0002b0:   377bffff    ori k1,k1,0xffff
9d0002b4:   035bd024    and k0,k0,k1
9d0002b8:   409a6000    mtc0    k0,c0_status
9d0002bc:   42000018    eret

Como puede ver, no importa por qué sucedió el NMI, simplemente eretes independientemente.

Con ese estilo de hacerlo en mi rutina de inicio, ¡ahora funciona!