STM32 L4 HAL_FLASH Escribe en una función pero no en otra

Estoy trabajando con una placa de evaluación STM32 L476RG en un proyecto. El objetivo actualmente es utilizar la biblioteca de almacenamiento masivo de STM para escribir datos en flash a través de USB. Tengo la biblioteca USB funcionando correctamente (simplemente leyendo y escribiendo datos desde/a la RAM para empezar), pero encontré un problema extraño al usar flash.

Si escribo en la memoria flash en la función STORAGE_Init_FS (usando HAL_FLASH_Program(....)), puedo escribir en la memoria flash perfectamente. Borro la página, le escribo datos arbitrarios y aparece en la vista de memoria del depurador.

Sin embargo, si trato de hacer exactamente lo mismo en STORAGE_Write_FS (intenté con línea por línea, el mismo código), entonces la operación flash devuelve un error y nunca escribe ningún dato.

Aquí están las porciones de código relevantes:

{
  /* USER CODE BEGIN 2 */
    USB_Flash_Init();
  return (USBD_OK);
  /* USER CODE END 2 */
}

HAL_StatusTypeDef USB_Flash_Init() {
    __HAL_RCC_SYSCFG_CLK_ENABLE();
    __HAL_RCC_FLASH_CLK_ENABLE();
    /* Clear flash flags */
    HAL_FLASH_Unlock();
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
    HAL_FLASH_Lock();

    HAL_FLASH_Unlock();
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, 0x8080000, 0x0001);
    HAL_FLASH_Lock();
    return HAL_OK;
}

En el código anterior, los datos 0x0001 se escriben correctamente en la dirección de memoria 0x8080000. (La llamada de borrado no está ahí, pero la página ha sido borrada).

Sin embargo, en la siguiente función,

int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 7 */
    __HAL_RCC_SYSCFG_CLK_ENABLE();
    __HAL_RCC_FLASH_CLK_ENABLE();
    HAL_FLASH_Unlock();
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
    HAL_FLASH_Lock();
    HAL_FLASH_Unlock();
    FLASH_PageErase(0, FLASH_BANK_2);
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, 0x8080000, 0x0001);
    FLASH_PageErase(0, FLASH_BANK_2);
    HAL_FLASH_Lock();
  return (USBD_OK);
  /* USER CODE END 7 */
}

Los datos se borran correctamente (ver la dirección de memoria a través del depurador), pero luego nunca se escriben, permanecen 0xFFFFFFFF. Sé que algunas de las cosas de inicialización ya se habían hecho, pero quería estar seguro de que no era la causa, así que lo incluí allí también. Lo único en lo que realmente puedo pensar sería que la pila podría no tener suficiente espacio (poco probable con la gran cantidad de memoria que tiene). Para verificar que esta no era la causa, intenté sacar mi búfer de 2k (que no se muestra aquí) fuera de la función para que ya no fuera una variable local y aumentar los tamaños de pila y montón a 0x4096 (excesivo) en la RAM .ld.

Ninguno cambió el comportamiento. Cualquiera con el que he hablado ha estado perdido en cuanto a la causa de esto. ¿Qué podría estar haciendo mal?

Recibo dos errores cuando compruebo HAL_FLASH_CheckError() (puede que no sea el nombre exacto de la función), 0xA0, que corresponde a HAL_FLASH_ERROR_PGS y HAL_FLASH_ERROR_PGA, secuencia de programación y errores de alineación de programación, respectivamente. No estoy seguro de que estos errores sean relevantes, considerando que las llamadas a funciones reales y la configuración son idénticas, solo el alcance es diferente.

Si alguien tiene algún consejo, ¡me encantaría escucharlo! Con suerte, solo me falta algún registro de control o bit en alguna parte.

¿Cuál es el camino por el que la ejecución llega a cada punto? Intente poner la función en su propio archivo y llamarla al principio de main() y también en cualquier punto en el que esté tratando con los datos reales. Con suerte, no hace falta decir que no puede hacer esto en un contexto de interrupción. Asegúrese de que la sincronización de su flash sea adecuada para el reloj del sistema que necesite para USB...
Borra la página (FLASH_PageErase) después de programar (HAL_FLASH_Program)... Si se trata de un error de "extracción de código", mire la latencia de flash y prefetch y compañía.
@ChrisStratton El camino que lleva a la llamada es a través de algunas capas de bibliotecas STM para llegar a cada función. Creo que tiene algo que ver con el lugar desde donde se llama, considerando que funciona en una función pero no en otra. Flash está escribiendo bien en main() y donde sea que lo intente, es solo en esta función de escritura USB que falla. Editar: lo siento, presionar enter envió el comentario. ¿Por qué no se puede hacer esto en un contexto de interrupción? Ambas funciones que he demostrado se llaman en una interrupción USB administrada por las bibliotecas de STM. No tengo control sobre cuándo se llama esto.
Solo para estar seguro, no es un error de reloj ya que obtuve toda la comunicación USB funcionando perfectamente al escribir en la RAM. @rom1nux De hecho, lo borre después en ese ejemplo, pero el código real que trata con los bloques de datos USB y todo no lo hizo, y lo estoy revisando con el depurador y viendo cómo cambia la dirección de la memoria (o no, en el problemático uno). Quitar este borrado adicional no causa una diferencia en el comportamiento.
Para que quede claro, soy consciente de que generalmente es una mala práctica hacer "demasiado" en una interrupción y desea mantener la lógica de interrupción simple para evitar la interrupción del flujo del programa principal. La programación Flash ciertamente califica. Pero en este punto, literalmente no hay nada más ejecutándose (bucle while vacío), y cuando esté en uso real para almacenamiento masivo USB, el dispositivo estará en un modo de descarga de firmware donde se detiene la ejecución normal.

Respuestas (2)

Parece que el problema real es que FLASH_PECR_ERASE(o al menos eso es lo que se llama en el L0) se deja establecido por las típicas rutinas de borrado de flash. Esto hace que los intentos posteriores del programa sean una mezcla extraña y quizás inválida de programa y operación de borrado. Como se menciona en los comentarios a continuación, he visto esto anteriormente en un STM32L0, y parece ser un problema con el STM32L4 aquí también.

Siguen ideas originales y aparentemente irrelevantes

Flash está escribiendo bien en main() y donde sea que lo intente, es solo en esta función de escritura USB que falla.

¿Por qué no se puede hacer esto en un contexto de interrupción? Ambas funciones que he demostrado se llaman en una interrupción USB administrada por las bibliotecas de STM.

Tomando al pie de la letra la creencia (quizás incorrecta) de que esto se está llamando desde un contexto de interrupción, definitivamente no puede escribir en flash desde una interrupción por medios ordinarios. Escribir en la memoria flash es un proceso muy complejo, una desviación real de la operación ordinaria de un chip que se supone que los chips casi nunca realizan, y que también consume mucho tiempo.

Deberá almacenar en caché la intención de realizar una escritura (guardar el deseo y los datos), realizarla más tarde desde el ciclo principal y luego poner en cola una respuesta de éxito o falla para enviar de vuelta a través de USB (es decir, aceptar los datos inmediatamente, pero no responda si la escritura funcionó o no hasta que la haya completado). Recuerde establecer comunicación entre la interrupción y el ciclo principal a través de banderas atómicas seguras, o use una sección crítica apropiadamente breve para hacer una copia de cualquier cosa no atómica. Probablemente necesitará bloquear más modificaciones del búfer de datos desde la interrupción hasta que haya terminado la escritura, si no puede hacerlo en el nivel del búfer USB, es posible que necesite DMA desde un búfer USB en otra parte de la RAM y luego escribir desde allí.

Los ejemplos de almacenamiento masivo USB son muy comunes. Probablemente pueda aprender cosas útiles sobre cómo hacer el desacoplamiento necesario de un ejemplo respaldado por flash SPI en lugar de interno o de uno para un STM32 diferente si el bloque funcional USB tiene similitudes significativas en su arquitectura con respecto al almacenamiento en búfer, etc.

Si realmente se llama desde una interrupción, es un código de ejemplo desafortunado STORAGE_Write_FS(). Puede ser que haya partes de la arquitectura del ejemplo que aún no se entiendan lo suficiente; si dan un ejemplo respaldado por almacenamiento no volátil real (en lugar de falsificarlo con un poco de RAM), sería necesario un examen detallado de eso. llave.

> Definitivamente no puedes escribir a flash desde una interrupción por medios ordinarios. Escribir en la memoria flash es un proceso muy complejo, una desviación real de la operación ordinaria de un chip que se supone que los chips casi nunca realizan, y que también consume mucho tiempo. Entonces, ¿por qué funciona en una llamada de interrupción y no en la otra? Tengo problemas para ver por qué no se puede hacer desde una interrupción, una interrupción es simplemente otra función con un punto de acceso no estándar, ¿no? Entendería si hubiera algún límite de tiempo en la interrupción, o si hubiera otro código crítico ejecutándose.
Particularmente, esto es desconcertante cuando funciona en una interrupción y no en otra. Podría almacenar los datos en el búfer y realizar la escritura en main(), pero necesitaría almacenar en el búfer suficiente memoria para la solicitud de escritura máxima que el host podría realizar, lo que daría como resultado un búfer de al menos kilobytes (no sé el máximo exacto aquí) que se mantiene constantemente en la memoria y no se usa excepto en este caso especial. Buscaré ejemplos con placas similares (parecía que había algunas para la serie F4, pero usan una biblioteca ligeramente diferente de la L4, he estado trabajando con un video STM de entrenamiento USB
Actualización: he encontrado algunos ejemplos de personas que hacen lo mismo, utilizando el flash interno como un MSC USB. Están haciendo lo que estoy haciendo, simplemente llamando a la función de programación flash en la función Storage_Write_FS. También seguí adelante y cambié Storage_Write_FS para establecer un indicador, que le dice a main() que realice la escritura deseada (en realidad, todavía no se trata de datos USB, todavía escribe 1 en una dirección codificada), y sorprendentemente está generando el mismo problema. ¡El mismo código de error al pasar por la programación flash, y el flash nunca se actualiza! Muy curioso.
Estoy bastante perplejo en cuanto a cómo programaría bien el flash en USB_Flash_Init() (simplemente llamado por STORAGE_Init_FS, activado por la interrupción del comando OS), pero está fallando no solo en STORAGE_Write_FS() sino también en main( ) . ¡Cualquier consejo o idea sería muy apreciada!
No está claro que su creencia de que algo de esto se está llamando en un contexto de interrupción sea realmente correcta; sería bastante desafortunado si lo fuera. Sin embargo, puede ser que la escritura flash deba protegerse contra el disparo de otras interrupciones. Antes debería echar un vistazo a las fuentes de cargadores de arranque basados ​​en USB. En algunas arquitecturas de hardware anteriores, no se podía escribir en un banco flash mientras se ejecutaba desde él (solía tener que cargar un código auxiliar en la RAM para hacer la escritura real o colocarlo en otro banco flash), tuve la sensación de que el problema se solucionó en los actuales pero quizás imperfectamente.
Bueno, main() simplemente ejecuta un ciclo while vacío, por lo que no veo cómo podría llegar a las funciones de inicio/escritura USB que no sea a través de una interrupción de alguna forma. Echaré un vistazo al seguimiento de la pila completa y me aseguraré de que nada esté interactuando o protegiendo el flash de ninguna manera. La razón particular por la que elegí la dirección de memoria con la que hice escribir fue porque es el inicio del Banco 2 de flash, donde el programa principal se ejecuta en el Banco 1. Este microcontrolador permite la ejecución mientras parpadea si está parpadeando en un banco diferente al uno desde el que se está ejecutando.
El bucle main() vacío suena como una arquitectura muy desafortunada. Esto no usa un RTOS, ¿verdad? Cualquiera que sea la realidad, edite su pregunta para vincular al código fuente si ya está en línea, para colocarlo en un repositorio público y vincularlo.
El bucle principal vacío se debe a que estoy trabajando para producir la mínima funcionalidad posible absoluta para esta sección del proyecto, luego planeo integrarlo en el proyecto mucho más grande una vez que tenga el USB MSC funcionando como deseo. Solo una prueba de concepto para usar el flash como almacenamiento masivo. Estoy pensando que es muy probable que me esté perdiendo algo con el flash en este punto, y que funcione en casos separados es más una coincidencia que otra cosa. Voy a profundizar un poco más en la documentación flash, luego, si todavía estoy perplejo, publicaré el código fuente. ¡Gracias por su ayuda hasta ahora!
Creo que he reducido el problema a la escritura flash. Parece que la primera escritura flash siempre tiene éxito, pero cuando luego borro la página (para volver a escribir), la escritura flash falla. Reiniciar el programa es la única forma de que vuelva a escribir después de borrar la página. Muy curioso..
Eso suena inquietantemente como una experiencia que tuve en un STM32L0 hace un tiempo, creo que descubrí que tenía que agregar un código adicional para borrar FLASH_PECR_ERASE antes de que la escritura volviera a funcionar. O algo así. Puede valer la pena revisar el manual y no asumir que el código provisto enterrado en las bibliotecas es correcto, sino compararlo.
¡Ese es exactamente el problema! Descubrí el miércoles pasado que el problema era mi uso de FLASH_PageErase() que no parece restablecer los bits de control adecuados. Cambié a usar HAL_FLASHEx_Erase() (que estaba evitando porque es bastante desordenado) y todo funciona bien. Solo usaré la función de borrado HAL. ¡Gracias por tu ayuda!

Actualización: el problema era el uso de la función FLASH_PageErase(), que no restablece los bits de control adecuados para que el flash permita volver a escribir. Simplemente coincidió con el orden de las llamadas de función que no estaba borrando la página hasta la segunda llamada.

Estoy seguro de que revisar la documentación arrojará los bits relevantes que deben restablecerse, pero en mi caso simplemente cambié a la función HAL_FLASHEx_Erase. Dejo esto aquí en caso de que alguien se encuentre con un problema similar.