¿Tendrá un chip de memoria flash SPI los mismos problemas con las operaciones de escritura no atómica que la EEPROM interna de un dsPIC?

Hace un tiempo tuve algunos problemas intermitentes con la EEPROM interna de un dsPIC. De vez en cuando, se encontraría algún valor en la EEPROM puesto a cero en el encendido. Rastreé el problema hasta que el chip perdió energía después del paso de borrado del ciclo de escritura, pero antes de que se completara la escritura. Se trataba del momento del apagado en relación con la ejecución del firmware, que era (en funcionamiento normal) aleatorio. Resolví esto agregando una sección de búfer a mi EEPROM, para asegurarme de que un ciclo de escritura incompleto pudiera completarse al restaurar la energía. Tuve que convertir las escrituras de EEPROM en una operación atómica.

Ahora estoy usando un dsPIC diferente sin EEPROM interna, y estoy tratando de usar un chip de memoria flash externo para almacenar datos persistentes. Me pregunto si debería tener preocupaciones similares. ¿Debería preocuparme de que mi chip flash externo se apague a mitad de la escritura y pierda datos, y escriba una solución para esto en mi firmware como lo hice para la EEPROM interna? ¿O el propio chip garantiza operaciones de escritura atómica?

Para más detalles, mi técnica de almacenamiento en búfer define un área de memoria persistente que consta de tres campos: dirección para escribir, datos para escribir y un indicador LISTO. Una "escritura" consta de cuatro pasos: escribir en el búfer, establecer el indicador LISTO, escribir desde el búfer, borrar el indicador LISTO. En el encendido, comprueba el indicador LISTO. Si está configurado, ejecute lo que esté en el búfer. Esto funcionó bien en EEPROM, pero no estoy seguro si funcionará bien en flash.

La memoria flash es en realidad un poco peor que la EEPROM, porque dependiendo de lo que use (flash desnudo o flash con controlador; el suyo tiene un controlador interno) necesita borrar y escribir bloques más grandes que en EEPROM (que generalmente está organizado por palabra). Esto generalmente toma mucho más tiempo y le da la posibilidad de escrituras corruptas. Como puede leer en la hoja de datos, el borrado de un sector puede tardar hasta un segundo (!), mientras que las escrituras nuevas tardan 15 ms (en su chip). Así que diría que use lo que se ha recomendado antes: haga una verificación de apagón antes de escribir en flash.
FRAM evita este problema, a expensas del costo/disponibilidad.
Desafortunadamente, ¡el costo y la disponibilidad son mis principales preocupaciones!
@user36129: En los dispositivos EEPROM que he usado, si se borra un byte o una región más grande que contiene un bit en blanco, el bit permanecerá en blanco durante todo el proceso de borrado. En los dispositivos flash, los bits generalmente deberán programarse antes de que se borren. Esto no será observable si el borrado se completa, pero puede ser observable si se interrumpe.

Respuestas (4)

Nunca he oído hablar de un chip de memoria flash (o procesador con flash interno) que tenga suficiente almacenamiento de energía interno para completar un ciclo de escritura (o borrado) si se debe quitar la alimentación externa. En otras palabras, si no tiene control sobre cuándo se apaga su sistema, siempre debe crear un protocolo que pueda detectar y manejar cualquier operación de actualización flash individual que pueda haberse interrumpido.

Una forma de evitar esto es proporcionar el almacenamiento de energía necesario (por ejemplo, un condensador electrolítico) en su placa, de modo que pueda detectar una falla de energía externa y aún así completar cualquier operación de escritura/borrado que ya haya comenzado.

EDITAR: su concepto de búfer de escritura podría usarse con el flash externo, pero debe modificarse para tener en cuenta la granularidad de borrado más grande. De acuerdo con la hoja de datos , el tamaño mínimo de borrado es un "sector" (4K bytes).

Deberá reservar tres sectores para su búfer de escritura. Uno de estos sostendrá su bandera READY (llame a esto el wector WB_R). El segundo contendrá la dirección del sector del sector que se está actualizando (llámelo sector WB_A). El tercero contendrá los datos actualizados para ese sector (llámelo sector WB_D).

Para actualizar cualquier byte en particular (o un grupo de bytes en un solo sector), siga los siguientes pasos. Suponemos que WB_R ya está borrado.

  1. Borrar WB_A.
  2. Localice el sector flash que contiene el byte que desea cambiar (llámelo sector DEST).
  3. Escriba la dirección del sector de DEST en WB_A.
  4. Borrar WB_D.
  5. Copie el contenido de DEST a WB_D, pero cuando llegue a los bytes que está cambiando, escriba los valores nuevos en WB_D en lugar de los valores anteriores.
  6. Establezca el indicador LISTO en WB_R. Tenga en cuenta que esto significa que lo cambia a su estado no borrado. Dado que el estado borrado es 0xFF, esto significa que escribe 0x00.
  7. Borrar DEST (obteniendo la dirección del sector de WB_A).
  8. Copie el contenido de WB_D a DEST.
  9. Borrar WB_R.

Al encender, verifique el indicador LISTO y, si está configurado (cualquier otro que no sea 0xFF; es posible que solo se haya escrito o borrado parcialmente), salte directamente al paso 7.

Tenga en cuenta que con este algoritmo, cada uno de los sectores del búfer de escritura se escribe y borra al menos una vez por cada operación de escritura que realice. Esto podría convertirse en un problema si realiza muchas escrituras (más de 100 000) durante la vida útil del producto. Si ese es el caso, necesitará un algoritmo de nivelación de desgaste más sofisticado.

Mi técnica de escritura en búfer también debería funcionar, ¿verdad?
No sé, tendrías que describirlo con más detalle. Parece que podría estar confiando en el hecho de que su placa tiene suficiente almacenamiento de energía para completar ciertos tipos de ciclos de escritura.
He agregado una descripción de mi técnica de almacenamiento en búfer a la pregunta. No creo que el almacenamiento de energía tenga ningún efecto con lo que estoy haciendo, pero podría estar equivocado.
De acuerdo, dado que las escrituras de un solo byte son idempotentes (es decir, no importa si se realizan más de una vez), parece que su búfer de escritura puede funcionar. El único modo de falla que veo es si falla la alimentación durante la escritura de la dirección o los datos en el búfer de escritura o al configurar el indicador READY, esa escritura en particular nunca sucederá, pero al menos la dirección de destino no se borrará. Esta técnica también podría funcionar con flash externo, pero deberá tener en cuenta la mayor granularidad de borrado, ya que se basa en el búfer de escritura, el indicador READY y la ubicación de destino que se pueden borrar individualmente.
No estoy 100% claro acerca de la granularidad de borrado. ¿Estoy entendiendo correctamente que flash borra bloques grandes de forma inherente y reescribe todas las partes que no han cambiado? Si es así, mi idea del búfer definitivamente no funcionará...
No, con la mayoría de los dispositivos flash externos (hay excepciones), el borrado y la escritura deben realizarse explícitamente en dos pasos separados, y usted es responsable del almacenamiento temporal de los datos que no cambian en una operación de lectura, modificación y escritura. La escritura generalmente se puede hacer byte a byte, pero el borrado funciona en una "página" más grande o en un bloque de bytes a la vez. Ver mi edición de arriba.
Obviamente he estado usando la memoria equivocada. EEPROM es claramente una mejor opción para mi aplicación. Debería haber comenzado con esta pregunta: electronics.stackexchange.com/questions/65503/… ¡Agradezco la ayuda!
@StephenCollings: O una nvSRAM, que se almacena automáticamente utilizando la carga de un límite dedicado cuando el suministro principal cae.

La escritura en búfer no es suficiente. Debe tomar una hoja del sistema de archivos y la base de datos aquí: necesita una estructura de datos en flash que pueda recuperarse a un estado "bueno" cuando un bloque está dañado.

Una forma típica de hacer esto es haciendo ping-pong entre dos bloques. Haga que los últimos dos o cuatro bytes de los bloques sean el "número de serie" del bloque, y el resto de sus datos en el resto del bloque. Cuando escriba un nuevo bloque, incremente el número de serie del bloque anterior en uno, omitiendo el valor de borrado "0" (que puede ser 0xff, según el tipo de flash) y escriba el nuevo bloque con ese número de serie.

En el encendido, lea ambos bloques y vea cuál tiene el número de serie posterior (teniendo en cuenta el ajuste de 0xffff->0 e ignorando los valores de borrado omitidos). Use ese bloque. También es posible que desee agregar un CRC de sus datos para verificar que no se corrompieron en el medio (aunque si coloca la serie al final, eso "no debería" ser un problema).

Si tiene datos complejos, puede ampliarlos de la misma manera que una base de datos o un sistema de archivos actualizará un árbol en el disco, o incluso implementará un registro de escritura.

He usado ese enfoque en el pasado, pero desde entonces he encontrado dispositivos flash con bloques que se comportaron muy mal después de (presumiblemente) haber sido borrados parcialmente. Desde entonces, comencé a usar al menos tres bloques para que en cualquier momento siempre se pueda identificar el último bloque que se borró, usando solo la información de los otros dos bloques]. Dos bloques no son suficientes porque un bloque que se está borrando puede adquirir arbitrariamente el patrón de bits necesario para decir que el otro bloque es el último borrado. Usar tres bloques evita ese problema...
...ya que si dos bloques se "acusan" entre sí, entonces uno de ellos debe ser el último que se borre, y el bloque restante sabrá cuál fue.

Esta es un área donde necesita sentarse y planear cuidadosamente las cosas. Algunos detalles de la hoja de datos son:

  1. Borrado de sector 4k: 400 metro s peor de los casos
  2. Borrado de bloque de 32k: 800 metro s peor de los casos
  3. Borrado de bloque de 64k: 1000 metro s peor de los casos
  4. página escribir: 3 metro s peor de los casos
  5. escribir el primer byte: 40 m s peor de los casos
  6. siguiente byte escribir: 12 m s peor de los casos

Probablemente sea una buena idea, si tiene control sobre este detalle, organizar la energía local (a través de la carga almacenada de un condensador) que se mantendrá dentro de un "margen de caída" que determine mientras se realizan las escrituras críticas. Esto no tiene que ser un segundo completo, si usa sabiamente el tiempo de escritura del primer byte (no olvide incluir tiempos adicionales de comunicación/configuración con eso). Puede actualizar solo uno o dos bytes en una página especial que significa que se está iniciando un borrado de bloque o sector, por ejemplo. Esto le permite determinar, si experimenta un apagón o reinicio, dónde estuvo por última vez para poder finalizar el proceso. Es posible que también necesite más de una "página especial". ¡Pero en cualquier caso, debe considerar todos los casos a fondo!

Si se pierde energía mientras un chip flash está borrando el bloque, un software robusto debe asumir que el contenido del bloque puede cambiar arbitrariamente en cualquier momento a menos que o hasta que el bloque se vuelva a borrar y se complete un ciclo de borrado. Incluso si el bloque aún parece contener datos antiguos, no hay garantía de que continúe haciéndolo durante un período de tiempo prolongado. Incluso si el bloque parece estar borrado, no hay garantía de que los bits programados no "aparecen" espontáneamente. He visto algunos procesadores con flash interno que incluían la capacidad de verificar si los bits estaban "realmente" en blanco o estaban "completamente" programados, pero nunca había visto esa funcionalidad expuesta por un dispositivo flash externo.

Si se desea poder almacenar periódicamente datos en la memoria flash y asegurarse de que, en caso de corte de energía, todas las actualizaciones se realicen correctamente o no se realicen en absoluto, se deben tener al menos tres bloques de memoria flash y definir un protocolo tal que cada vez que un bloque se está borrando, uno puede determinar eso basándose únicamente en el contenido de los otros dos bloques. Hay una variedad de protocolos para implementar esto; Sugeriré uno simple aquí, suponiendo que la cantidad de información que se almacenará es un bloque completo menos una unidad programable de tamaño mínimo, y hay tres bloques disponibles, que llamaré X, Y y Z.

Cada bloque tendrá bits de "control" dentro de él que están reservados para rastrear el estado de validez/borrado; Llamaré a esos bits x, y y z. Durante la operación, el sistema mantendrá invariante que el bloque que contiene datos correctos tendrá su bit de control en blanco; el bloque "precedente" (X va precedido de Z) tendrá programado su bit de control. Los bits de control para el bloque restante (el que "sigue" al que tiene datos válidos) serán irrelevantes. Si todos los bits de control están en blanco, nunca se ha escrito nada correctamente; si todos los bits de control están programados, algo se ha corrompido gravemente.

Para escribir nuevos datos, borre el bloque que sigue al que contiene los datos correctos, luego almacene los nuevos datos en ese bloque. Finalmente, como último paso, programe el bit de control de lo que solía ser el bloque actual. Hasta que se programe ese bit de control, a nada le importará el contenido del bloque que se acaba de programar. Una vez que se programa ese bit, nada se preocupará por el contenido del bloque que sigue al nuevo bloque. Siempre que el sistema tenga suficiente energía disponible para garantizar que la programación de ese bit tenga éxito o falle limpiamente, se garantiza un funcionamiento confiable en todos los escenarios de pérdida de energía.

Suponga que x está programado, y está en blanco y z es cualquier cosa. Debido a que el bloque de datos válido debe tener su propia bandera en blanco y se debe programar la bandera del bloque anterior, X no puede ser un bloque válido (la bandera x está programada) y Z no puede ser un bloque válido (porque la bandera y está programada). En consecuencia, Y es el único bloque que puede contener datos válidos. El bloque X contiene la versión anterior de los datos y no se puede confiar en que Z contenga nada. Cuando sea necesario almacenar nuevos datos, el código debe comenzar por borrar Z (independientemente de que ya aparezca en blanco), y programar todos los datos que debe contener. Si se pierde energía en cualquier momento durante este proceso, el estado del sistema será el mismo que antes de que comenzara (según las banderas, se supone que el contenido de Z no tiene sentido, por lo que su contenido no afecta el estado del sistema en absoluto).

Solo después de que se completen todas las escrituras en Z y contenga datos válidos, se debe programar el indicador y. Una vez que se escribe esa bandera, Z será reconocible como el bloque que contiene datos válidos, ya que su propia bandera estará en blanco mientras se programa la bandera (y) de los bloques anteriores; el hecho de que y ahora esté programado significará que Y ya no es válido.

La próxima vez que sea necesario almacenar nuevos datos, se debe borrar el bloque X y almacenar los datos allí; la finalización debe indicarse mediante la marca de programación z. El tiempo después de eso, Y debe borrarse y tener datos almacenados allí, con finalización indicada por el indicador de programación x. Es vital que los intentos de programar los indicadores x, y y z se ejecuten hasta el final o no tengan ningún efecto, pero esas son las únicas operaciones que deben ser "atómicas garantizadas" a nivel de hardware. Todas las demás escrituras en la memoria se realizarán en un bloque cuyo contenido nunca se verá (*) a menos que se ejecuten hasta su finalización.

(*) Por lo general, el sistema no podrá evitar acceder al bloque no válido, pero el comportamiento del sistema no se verá afectado por el valor leído.

Por cierto, si uno no confía en la capacidad de garantizar que las escrituras de bandera se ejecuten hasta el final, existen varios enfoques con bits de bandera redundantes que podrían ayudar un poco, pero la confiabilidad ya no estará asegurada. Suponga, por ejemplo, que el sistema pierde energía mientras el bit y está parcialmente programado, por lo que a veces se leerá como programado pero a veces en blanco. Si en el primer encendido, y se lee en blanco, la próxima actualización borrará Z. Si durante ese borrado, el sistema pierde energía y en el siguiente encendido, y se lee como programado, el sistema asumirá que Z es el bloque válido. Si y hubiera leído según lo programado en ambas ocasiones, entonces Z habría sidoel bloque válido y el siguiente bloque borrado habrían sido X. Si se hubiera leído en blanco las dos veces, entonces Z se habría reconocido correctamente la segunda vez como el bloque no válido. Aunque uno podría tratar de protegerse contra estos peligros agregando bits de bandera redundantes, tales enfoques no ayudan mucho. Uno puede diseñar cosas para que sea "poco probable" que las banderas parcialmente programadas se comporten de manera problemática, pero eso es fundamentalmente diferente de la garantía de que si las escrituras de banderas funcionan atómicamente, nada que el chip pueda informar para cualquier otro dato parcialmente escrito. causaría ningún problema.