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.
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.
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.
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.
Esta es un área donde necesita sentarse y planear cuidadosamente las cosas. Algunos detalles de la hoja de datos son:
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.
usuario36129
pjc50
Esteban Collings
Super gato