Ce que nous allons faire - Prérequis
Nous allons voir dans ce cours :
Comment utiliser la mémoire flash interne du CH32V003
Les prérequis de ce cours sont :
Avoir suivi le cours CH32V003 : protocole SPI et mémoire flash.
Avoir suivi le cours CH32V003 : informations système.
La mémoire flash interne du CH32V003
Nous avons eu un aperçu de la carte mémoire du CH32V003 dans le cours sur les informations système et nous allons y revenir en nous focalisant sur l'espace dédié à la mémoire flash.
On y voit que la mémoire flash est composée de plusieurs zones :
Code FLASH est la zone accueillant votre code.
System FLASH contient le bootloader, c'est-à-dire le programme qui détecte au démarrage de micro-contrôleur s'il est en cours de programmation ou s'il doit exécuter votre code (zone Code FLASH). Le bootloader est flashé lors de la fabrication.
Vendor Bytes contient des données flashées lors de la fabrication comme la taille de la mémoire flash ou l'identifiant unique du micro-contrôleur.
Option Bytes contient certaines options de configuration modifiables par l'utilisateur ainsi que 2 octets disponibles pour l'application.
On remarque encore que les zones Code FLASH et System FLASH sont également décodées alternativement à l'adresse 0x00000000 car c'est là que le micro-contrôleur commence à exécuter son code.
La manière d'utiliser les zones Code FLASH et Option Bytes est décrite dans le manuel de référence du CH32V003, chapitre 16 "Flash Memory and User Option Bytes", et la zône Vendor Bytes est décrite chapitre 15 "Electronic Signature (ESIG)",
Dans la suite de ce cours, le terme "mémoire flash interne" désignera la zone Code FLASH, qui est celle qui nous intéresse ici. On a besoin de programmer la mémoire flash interne essentiellement dans 2 cas :
Pour mettre à jour votre firmware, souvent via une connexion WiFi. En anglais, on parle alors de OTA update (OTA = Over The Air). Le CH32V003 étant un micro-contrôleur très limité, il n'est pas concerné par ce point.
Pour stocker des données applicatives persistantes à la fin de la mémoire flash interne lorsque le firmware ne l'occupe pas en totalité. On évite ainsi le recours à une mémoire flash externe, ce qui simplifie la conception matérielle et réduit les coûts.
On gardera cependant à l'esprit que le nombre d'écritures que peut supporter une mémoire flash est limité. La plupart du temps, les fabricants garantissent au moins 100000 cycles d'écriture. Si on ne fait que des changements occasionnels de configuration, aucun souci, mais si l'application enregistre des données en continu, il faut vérifier si la durée de vie de la mémoire flash est compatible avec la durée de vie du produit fini.
Le code
Pour illustrer l'utilisation de la mémoire flash interne, nous allons utiliser le même programme que pour la mémoire flash SPI en changeant simplement l'implémentation des fonctions de lecture, écriture et effacement. Nous utiliserons pour cela les fonctions du SDK de WCH recommandées dans le manuel de référence, FLASH_ROM_ERASE() et FLASH_ROM_WRITE().
On remarquera au passage que les accès à la mémoire flash sont alignés sur des frontières de mot (4 octets) alors que la RAM supporte des accès sur 1, 2 ou 4 octets. On notera aussi que la taille de page de la mémoire flash interne est de 64 octets, ce qui fait que effacements et écritures sont faits par blocs de 64 octets alignés sur des frontières de 64 octets.
Créez un nouveau projet et modifiez system_ch32v00x.c comme vous en avez maintenant l'habitude, puis remplacez le contenu du fichier main.c par le code suivant :
#include <debug.h> // Pour memset() et memcpy(). #include <string.h> // ============================================================================= // Ci-dessous, on conserve les mêmes signatures des fonctions flash*() que // celles du cours sur SPI Flash, mais on modifie leur implémentation de // façon à utiliser la mémoire flash interne. // ============================================================================= // Voir manuel de référence du CH32V003, Chapter 15 Electronic Signature (ESIG). // La capacité de la mémoire flash est exprimée en kilo-octets sur 16 bits. #define R16_ESIG_FLACAP (*(uint16_t *) 0x1ffff7e0) // On se réserve 256 octets à la fin de la mémoire flash pour nos données. #define USER_FLASH_BYTES ((uint32_t) 256) #define USER_FLASH_START (FLASH_BASE + ((uint32_t) R16_ESIG_FLACAP) * 1024 - USER_FLASH_BYTES) /** * Efface un bloc d'octets dans la mémoire flash. * * @param count Nombre d'octets à effacer. Doit être un multiple de 64. * @param addr Adresse de destination dans la mémoire flash. Doit être un multiple de 64. * L'adresse est relative au début de la zone réservée (USER_FLASH_START). */ void flashErase(uint32_t count, uint32_t addr) { FLASH_ROM_ERASE(USER_FLASH_START + addr, count); } /** * Programme un bloc d'octets dans la mémoire flash. Il faut appeler * flashErase() avant d'utiliser flashProgram(). * * @param buffer Tableau d'octets à programmer. * @param count Nombre d'octets à programmer. Doit être un multiple de 64. * @param addr Adresse de destination dans la mémoire flash. Doit être un multiple de 64. * L'adresse est relative au début de la zone réservée (USER_FLASH_START). */ void flashProgram(const uint8_t *buffer, uint32_t count, uint32_t addr) { FLASH_ROM_WRITE(USER_FLASH_START + addr, (uint32_t *) buffer, count); } /** * Lit un bloc d'octets de la mémoire flash. * * @param buffer Tampon de destination. * @param count Nombre d'octets à lire. Doit être un multiple de 64. * @param addr Adresse source dans la mémoire flash. Doit être un multiple de 64. * L'adresse est relative au début de la zone réservée (USER_FLASH_START). */ void flashRead(uint8_t *buffer, uint32_t count, uint32_t addr) { memcpy(buffer, (const void *) (USER_FLASH_START + addr), count); } // ============================================================================= // En dessous de cette limite, le code est identique à celui de SPI Flash, // à l'initialisation du périphérique SPI près. // Ça illustre l'intérêt d'une bonne analyse et d'un bon découpage du code. // ============================================================================= /** * Utilitaire d'affichage hexadécimal. */ void dump(const uint8_t *buffer, uint32_t count) { for (uint32_t i = 0; i < count; i++) { if ((i % 16) == 0) { printf("\r\n%04x:", (int) i); } printf(" %02x", (int) buffer[i]); } printf("\r\n"); } int main() { // Nécessaire pour le calcul des délais. SystemCoreClockUpdate(); Delay_Init(); // Nécessaire pour pouvoir utiliser printf(). USART_Printf_Init(115200); while (1) { uint8_t buffer[256]; // On efface la mémoire flash. printf("Effacement de la mémoire flash.\r\n"); flashErase(sizeof(buffer), 0); // On relit le contenu de la mémoire flash. flashRead(buffer, sizeof(buffer), 0); // On affiche le résultat. printf("Contenu de la mémoire :"); dump(buffer, sizeof(buffer)); Delay_Ms(2000); // On initialise le buffer avec les données à programmer. uint8_t data = 0; for (uint32_t i = 0; i < sizeof(buffer); i++) { buffer[i] = data++; } // On programme la mémoire flash. printf("Programmation de la mémoire flash.\r\n"); flashProgram(buffer, sizeof(buffer), 0); // On initialise le buffer à 0 pour effacer les anciennes valeurs, // ce qui prouvera qu'on a reçu correctement les nouvelles. memset(buffer, 0, sizeof(buffer)); // On relit le contenu de la mémoire flash. flashRead(buffer, sizeof(buffer), 0); // On affiche le résultat. printf("Contenu de la mémoire :"); dump(buffer, sizeof(buffer)); Delay_Ms(2000); } }
Ce que nous avons appris
Vous savez maintenant :
Comment utiliser la mémoire flash interne du CH32V003.
Les bénéfices concrets d'une bonne analyse et conception.