18F4550 - mémoire flash

Le microcontrolleur dispose d’une mémoire flash. C’est d’ailleurs dans cette mémoire qu’est stocké votre programme. Dans cet article, nous allons écrire et lire cette mémoire. Nous veillerons à ne pas écrire en plein sur la zone contenant le programme.
Je vous rassure, cette erreur, vous la ferez plusieurs fois. Notre exemple est assez petit et ne prendra pas plus d’un kilo-octet. On pourra donc travailler à l’adresse 1000h.

Commencez par lire le chapitre 6-Flash program memory de la datasheet. Ce qu’il faut savoir, c’est que l’on efface par blocks de 64 octets. Or, avant d’écrire, il faut effacer. Par commodité, les trois fonctions que je vous propose (lire, effacer, écrire) travailleront sur des blocks de 64 octets. Le plus simple est la lecture : il suffit de … lire. Par contre l’écriture est moins triviale. Je m’y suis cassé les dents. Pour écrire, il faut :

  1. lire la zone
  2. effacer la zone
  3. écrire sur la zone

En gros, écrire en mémoire flash, c’est mettre à jour les données en mémoire flash. Tout ça est bien intégré dans les trois fonctions proposées.

L’exemple que je vous propose est simple :

  1. on lit 64 octets à l’adresse 1000h
  2. on affiche un par un ces octets sur le port D (on constatera, au premier lancement du programme que tout est à FFh)
  3. on écrit 64 octets à l’adresse 1000h; les valeurs de ces octets vont de 1 à 64
  4. on lit 64 octets à l’adresse 1000h
  5. on affiche un par un ces octets sur le port D (on retrouve nos valeurs de 1 à 64)

Les sources

Nous avons besoin des fichiers suivant:

  • main.c
  • config.h
  • flash.c
  • flash.h

config.h

#ifndef CONFIG_H_INCLUDED
#define CONFIG_H_INCLUDED

#include <pic18fregs.h>

// Set the __CONFIG words:
__code __at (__CONFIG1L) _conf0    = _PLLDIV_DIVIDE_BY_5__20MHZ_INPUT__1L  & _CPUDIV__OSC1_OSC2_SRC___1__96MHZ_PLL_SRC___2__1L & _USBPLL_CLOCK_SRC_FROM_96MHZ_PLL_2_1L;  
__code __at (__CONFIG1H) _conf1    = _OSC_HS__HS_PLL__USB_HS_1H;  
__code __at (__CONFIG2L) _conf2    = _VREGEN_ON_2L;  
__code __at (__CONFIG2H) _conf3    = _WDT_DISABLED_CONTROLLED_2H;  
__code __at (__CONFIG3H) _conf4    = _PBADEN_PORTB_4_0__CONFIGURED_AS_DIGITAL_I_O_ON_RESET_3H;  
__code __at (__CONFIG4L) _conf5    = _ENHCPU_OFF_4L & _LVP_OFF_4L ;  
__code __at (__CONFIG5L) _conf6    = _CP_0_OFF_5L & _CP_1_OFF_5L & _CP_2_OFF_5L & _CP_3_OFF_5L;  
__code __at (__CONFIG5H) _conf7 = _CPD_OFF_5H & _CPB_OFF_5H;

#endif // CONFIG_H_INCLUDED

flash.h

#ifndef FLASH_H_INCLUDED
#define FLASH_H_INCLUDED

/**
 * Erase a 64-bytes row in the flash memory
 * FlashAddr is a multiple of 64
 *
 * @param unsigned int flashAddr address in flash memory
 */
void flash64Erase(unsigned int flashAddr);

/**
 * Read data from the flash memory (64-byte row)
 * FlashAddr is a multiple of 64
 * Buffer is 64 bytes wide
 *
 * @param unsigned int flashAddr address in flash memory
 * @param unsigned char* buffer  variable to get the data
 */
void flash64Read(unsigned int flashAddr, unsigned char* buffer);

/**
 * Write data to the flash memory (64-byte row)
 * FlashAddr is a multiple of 64
 * Buffer is 64 bytes wide
 *
 * @param unsigned int flashAddr address in flash memory
 * @param unsigned char* buffer  variable holding the data to write
 */
void flash64Write(unsigned int flashAddr, unsigned char* buffer);

#endif

flash.c

#include "flash.h"
#include <pic18fregs.h>

/**
 * Load the pointer to the address in Flash memory
 *
 * @param unsigned int flashAddr address in flash memory
 * @param unsigned char* buffer  variable to get the data
 */
void loadTBLPTR(unsigned int Addr)  
{
    TBLPTRU = 0;
    TBLPTRH = (unsigned char)(Addr >> 8);
    TBLPTRL = (unsigned char)(Addr & 0xC0);
}

/**
 * Erase a 64-bytes row in the flash memory
 *
 * @param unsigned int flashAddr address in flash memory
 */
void flash64Erase(unsigned int flashAddr)  
{
    /* Loading flash address */
    loadTBLPTR(flashAddr);

    EECON1 &= 0xBF;  /* CFGS=0 */
    EECON1 |= 0x94;  /* WREN=1 FREE=1 EEPGD=1 */
    INTCON &= 0x7F;  /* Disable interrupts */
    EECON2 = 0x55;   /* Erasing ... */
    EECON2 = 0xAA;   /* ... sequence */
    EECON1 |= 0x02;  /* Start erase (CPU stall) */
    INTCON |= 0x80;  /* Enable interrupts */
    //EECON1 &= 0xFD;  /* Disable memory write*/
}

/**
 * Read data from the flash memory (64-byte row)
 * FlashAddr is a multiple of 64
 * Buffer is 64 bytes wide
 *
 * @param unsigned int flashAddr address in flash memory
 * @param unsigned char* buffer  variable to get the data
 */
void flash64Read(unsigned int flashAddr, unsigned char* buffer)  
{
    /* counter to scan flash memory */
    char counter;
    unsigned char byteFromFlash;

    /* Loading flash address */
    loadTBLPTR(flashAddr);

    /* Reading data */
    for(counter=0;counter<64;counter ++) {
        __asm
            TBLRD*+
        __endasm;
        byteFromFlash = TABLAT;
        if (buffer) buffer[counter] = byteFromFlash;
    }
  }

/**
 * Write data to the flash memory (64-byte row)
 * FlashAddr is a multiple of 64
 * Buffer is 64 bytes wide
 *
 * @param unsigned int flashAddr address in flash memory
 * @param unsigned char* buffer  variable holding the data to write
 */
void flash64Write(unsigned int flashAddr, unsigned char* buffer)  
{
 /* Writeing 64 bytes in flash memory */
    char counter;
    char blockCount;

    /* Read Data to trash */
    flash64Read(flashAddr, 0);

    /* Erase flash memory row */
    flash64Erase(flashAddr);

    __asm
        TBLRD*- /* Dummy read decrement*/
    __endasm;

    for(blockCount=0;blockCount<2; blockCount++) {
        for(counter=0;counter<32;counter++) {
            TABLAT = buffer[blockCount*32+counter];
            __asm
                TBLWT+*
            __endasm;

        }
        /* Writing to flash */
        EECON1 &= 0xBF;  /* CFGS=0 */
        EECON1 |= 0x84;  /* WREN=1 EEPGD=1 */
        INTCON &= 0x7F;  /* Disable interrupts */
        EECON2 = 0x55;   /* Writing ... */
        EECON2 = 0xAA;   /* ... sequence */
        EECON1 |= 0x02;  /* Start Writing (CPU stall) */
    }

    INTCON |= 0x80;      /* Enable interrupts */
    EECON1 &= 0xFD;      /* Disable memory write*/
}

Vous remarquerez que dans la fonction d’écriture, on effectue bien une lecture et un effacement. Vous remarquerez aussi que lors du chargement de l’adresse d’écriture, de lecture ou de chargement, on y applique un masque (FCh sur l’octet de poids faible). C’est pour être sûr de cibler un adresse qui est un multiple de 64 (étant donné que l’on travaille sur des rangées de 64 octets).

main.c

#include <pic18fregs.h>
#include <delay.h>
#include "config.h"
#include "flash.h"

void ledOff()  
{
    // set ports B and D as output and switch them off
    TRISD=0;
    TRISB=0;
    LATD = 0x00;
    LATB = 0x00;
}

void main()  
{
    //int i;
    int j;

    /* buffer containing the data to write to memory */
    char buffer[64] = {
      1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
     17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
     33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
     49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64
     };

    /* buffer used to read memory */
     char buffer2[64] = {
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
     };

    /* Prepare leds */
    ledOff();

    /* Check that flash memory at 1000h is really empty
     * Port D should remain ON (on first start only)
     * Port B will blink 32 times during the 64 bytes check
     */
    flash64Read(0x1000, buffer2);
    for(j=0; j<64; j++) {
        delay1mtcy(1);
        LATD=buffer2[j];
        LATB = ~LATB;
    }

    /* Write data to flash memory
     * Port B will blink 32 times during the 64 bytes check
     */
    flash64Write(0x1000, buffer);

    /* Check that flash memory at 1000h has data
     * Port D should count from 1 to 64
     * Port B will blink 32 times during the 64 bytes check
     */
    flash64Read(0x1000, buffer2);
    for(j=0; j<64; j++) {
        delay1mtcy(1);
        LATD=buffer2[j];
        LATB = ~LATB;
    }

    while(1);
}

Déroulement du programme

Juste après avoir transféré le programme dans votre microcontrolleur, placez-le sur votre mini-lab. Prévoyez que les LED du port B et D devront s’allumer. Mettez votre microcontrolleur sous tension. Au début le port D reste allumé alors que le port B clignote 32 fois. la zone mémoire lue est vide. Pendant les 32 clignotements suivant du port B, vous verrez le port D compter. les données ont été écrite en mémoire et lue sur le port D. La mémoire flash étant persistante, si vous faites un reset de votre microcontrolleur, le port D comptera dès le début : la zone mémoire n’est plus vide !

Téléchargement

Les sources, c’est ici : flash.tar.gz