STM32L011 salta al gestor de arranque desde el código de usuario

Estoy tratando de hacerlo para que mi STM32L011 pueda saltar del código de usuario al cargador de arranque ST que permite reprogramar el flash a través de USART2. Esta es hipotéticamente la misma pregunta que STM32F091 Jump to Bootloader from application , pero no estoy teniendo suerte con las correcciones de desactivación de periféricos de esa pregunta. ST rehizo sus foros y rompió todos los enlaces antiguos a los hilos, por lo que me está costando mucho encontrar pistas sobre otros posibles problemas y estoy bastante perplejo en cuanto a dónde buscar a continuación.

Mi función de salto es la siguiente (modificada de la pregunta anterior para mi aplicación y usando las bibliotecas ST LL). El código de mi aplicación usa LPUART en PA2/PA3, que luego debería poder usar USART2 para el gestor de arranque. Se recibe un comando en el LPUART en el código de la aplicación del usuario, que llama a la función de salto. El código de la aplicación reconoce el comando correctamente según mi protocolo serial, pero luego el cargador de arranque USART no responde a ninguna utilidad intermitente. Envían el byte inicial 0x7F por AN2606 pero el cargador de arranque no devuelve el ACK.

#define STM32L01_SYSTEM_MEMORY  0x1FF00000

void jump_to_bootloader()
{
    // Disable global interrupts
    __disable_irq();

    // Disable interrupt requests from peripherals
    NVIC_DisableIRQ(SysTick_IRQn);
    NVIC_DisableIRQ(LPUART1_IRQn);
    NVIC_DisableIRQ(EXTI0_1_IRQn);
    NVIC_DisableIRQ(DMA1_Channel2_3_IRQn);
    NVIC_DisableIRQ(I2C1_IRQn);

    typedef void (*pFunction)(void);
    volatile uint32_t addr = STM32L01_SYSTEM_MEMORY;
    uint32_t jumpaddr = *(__IO uint32_t*) (STM32L01_SYSTEM_MEMORY + 4);
    pFunction jump_to_application;

    // Application-specific shutdown of HSE and switch to MSI
    clock_HSE_to_MSI();

    // Reset ALL peripherals
    LL_AHB1_GRP1_ForceReset(LL_AHB1_GRP1_PERIPH_ALL);
    LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_ALL);
    LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_ALL);
    LL_mDelay(5);

    LL_AHB1_GRP1_ReleaseReset(LL_AHB1_GRP1_PERIPH_ALL);
    LL_APB1_GRP1_ReleaseReset(LL_APB1_GRP1_PERIPH_ALL);
    LL_APB2_GRP1_ReleaseReset(LL_APB2_GRP1_PERIPH_ALL);
    LL_mDelay(5);

    // Fully reset RCC to power-up state
    LL_RCC_DeInit();

    // Zero out SysTick
    SysTick->CTRL = 0;
    SysTick->LOAD = 0;
    SysTick->VAL = 0;


    // Remap memory to system flash
    SYSCFG->CFGR1 = 0x01;

    jump_to_application = (pFunction)jumpaddr;

    /* Initialize user application's Stack Pointer */
    __set_MSP(*(__IO uint32_t*) addr);


    jump_to_application();
}

He descartado estos problemas:

  1. Los pines de control en mi transceptor MAX3221 RS232 (NEN, NFORCEOFF, FORCEON) están en los estados habilitados adecuados, por lo que si USART2 está hablando, debería ver algo en mi analizador lógico que está conectado al lado RS232. Desafortunadamente, la necesidad de controlar los pines GPIO en el momento del arranque para configurar estos pines de control significa que no puedo hacer la prueba "llamar a mi función de salto al comienzo de main()".
  2. La función jump_to_bootloader() se ejecuta completamente (no se cuelga en el interruptor del reloj, etc.). Veo que uno de los pines de control MAX3221 se eleva durante aproximadamente 160 us antes de volver a bajar, lo que he confirmado que sucede después de cualquier punto de la función en el que yo mismo podría cambiarlo así. Estoy operando bajo la suposición de que esto ocurre como parte del periférico GPIO que se inicializa nuevamente.
  3. He intentado varias permutaciones de incluir las llamadas de instrucción de barrera de memoria incluidas en la pregunta mencionada anteriormente sin ningún efecto. No he encontrado referencias a los que se incluyen en ningún otro código similar en línea.
  4. Estoy reiniciando todos los periféricos para asegurarme de que no tengo el mismo problema que la pregunta anterior. En teoría, esto debería anular la necesidad de intentar llamar a la función de salto al comienzo de main(). También estoy deshabilitando específicamente las solicitudes de interrupción utilizadas en mi aplicación para estar seguro.
  5. Nunca veo que el pin de reinicio del chip baje, así que no sospecho nada con el pin BOOT0 en este momento. El pin se mantiene bajo con una resistencia desplegable, por lo que si se produjera un reinicio, BOOT0 haría que el chip se reiniciara con el código de usuario en flash. Los bytes de opción de chip se establecen de la siguiente manera:

Bytes de opción

Teorías de trabajo:

  1. Algo podría estar saliendo mal donde estoy saltando. Por lo que puedo decir, tengo la dirección correcta de AN2606. Tengo problemas para confirmar esto cuando miro los registros con un depurador. Después de pasar por jump_to_bootloader(), el código parece colgarse, al menos según el depurador... No entiendo completamente si aún puedo confiar en el depurador después de salir de mi código de usuario. Los registros SP y MSP muestran direcciones SRAM (rango 0x20000000) y la PC está en flash (inicio 0x80000000).

Antes de __set_MSP():

Registrar valores

Después de llegar al final de la función de salto (los botones de paso del depurador ya no están habilitados):Registrar valores 2

Si luego hago clic en suspender la ejecución en el depurador, me devuelve a un punto de interrupción generado por Eclipse al comienzo de main(). No entiendo cómo podría ser este el caso, ya que el código de mi aplicación ya no funciona correctamente, con y sin el depurador presente.

  1. Algo está provocando un reinicio que no veo en el pin de hardware por alguna razón, lo que provoca un reinicio en el flash del usuario. Esto parece una pista falsa, pero sospecho por la forma en que el depurador me devolvió al inicio de main().
Una medida desesperada, pero que definitivamente funcionará y evitará todas estas batallas, es establecer un valor mágico en la RAM o en los registros de copia de seguridad de RTC, reiniciar con su propio código, ver ese indicador antes de cualquier configuración (es decir, idealmente en ensamblaje antes de C /C++ init) y corrige el puntero de la pila y salta al gestor de arranque de inmediato. No intentaría depurar a través de esto, solo hacerlo bien por diseño y ver que funcione por resultado.
@ChrisStratton: esta es en realidad la única forma confiable. Además, ST lo recomienda, o al menos el gurú de los foros ya desaparecidos citados por OP. Finalmente es como lo hice y funciona como debería. Así que realmente deberías publicar tu comentario como respuesta.
¿El cargador de arranque usa interrupciones? Intente tal vez cambiar el VTOR para que apunte al gestor de arranque antes de saltar.
No tuve suerte con el reinicio, el soporte de ST me hizo probar si la errata del chip del F042 enumerado en AN2606 posiblemente también se aplique al L011. "Debido al mecanismo de verificación vacío presente en este producto, no es posible saltar del código de usuario al gestor de arranque del sistema. Tal salto resultará en un salto de regreso al espacio flash del usuario".
Si habilita el paso de instrucciones, debería poder continuar paso a paso a través de la ROM del cargador de arranque, utilizando las instrucciones desmontadas como guía. He hecho esto antes para el F042 usando secuencias de comandos Python GDB para rastrear todos los valores de registro y lectura/escritura de memoria para ver cómo el gestor de arranque decide volver al flash del usuario: gist.github.com/devanlai/f9636f68c57fde2c5da912231f03c47e

Respuestas (1)

Podría intentar saltar a 0x1FF00FFE en lugar de 0x1FF00000. (dirección del gestor de arranque integrado según AN2606 Rev 35, p 26)

Mi comentario inicial estaba completamente equivocado. Pero de acuerdo con los comentarios anteriores, el iniciador del tema encontró lo que estaba mal. Debido al mecanismo de verificación vacío presentado en el dispositivo L011, no puede saltar a la memoria del sistema desde el código de usuario y permanecer allí. Encontré problemas similares en mi dispositivo L07x con mecanismo de banca dual (problema similar https://stackoverflow.com/questions/42020893/stm32l073rz-rev-z-iap-jump-to-bootloader-system-memory ). Resultó que el cargador de arranque vuelve al código de usuario sin importar qué, siempre que haya un código válido en uno de los bancos. Por supuesto, es posible omitirlo saltando a la dirección después del cheque vacío, pero es difícil encontrar la dirección correcta y no hay garantía de que STM no cambie esa dirección en la próxima impresión del chip.

El código de la pregunta lee el vector de inicio de la tabla de vectores de la ROM de arranque, no salta al inicio.