Tinkering TI MSP430F5529

Internal Flash Memory and Cyclic Redundancy Check (CRC) Module

There are many microcontrollers in today’s market that don’t come with embedded EEPROM memory. Most ARM microcontrollers, for example, don’t have EEPROM memories. EEPROM is typically used for the storage of non-volatile data like configurations, calibrations, etc that are usually not changed frequently and need to be retained even after reset/power failure. EEPROM memories are slow and have lower endurance. Due to these limitations, it is better to use segments of flash memory that will not be used to hold application code. Flash memories have comparatively higher life cycles, high reliability and are faster. The only problem with them is writing. We can’t write to individual locations. We have to write/erase a whole segment of multiple byte areas at a time. No matter which memory class we are dealing with, frequent writing/erasing of memory locations also leads to wear and permanent damage. These issues can be avoided by a number of software-based means like wear-leveling. Data reading has no issues as data can be read individually or as a whole. MSP430F5529 has four flash memory areas called information memory (A to D). Information memory is where we can store non-volatile data and these locations have no conflict with application/main memory locations.

In MSP430F5529 micro, there is a Cyclic Redundancy Check (CRC) hardware. This hardware has several uses. Most commonly it is used to ensure or check data integrity against data corruption. In communications and data storage, CRC is highly needed since there is no other good and simple mean to cross-check data integrity efficiently and quickly.

CRC is like an agreement/encryption that two/more parties agree upon. As an example, consider two people, A and B sharing a common safe. A leaves a message in the safe for B. B has to open the safe with the same kind of key that A used to lock the safe or else B won’t get the message. Here the keys form the agreement that both of these persons agreed upon and the safe is the method to encapsulate the message from unwanted people.    

Modern communication and data storage use CRC just like the example detailed above. CRC is an algorithm that tells dictates this encryption. Data is sent/stored along with a CRC code. When the sent/stored data is received/read along with the CRC content of the data, CRC is recalculated and verified against the sent/stored CRC. If both sent/stored CRC and received/read CRC match then it is safe to assume that no data corruption has occurred.   

Shown below is the CRC module block diagram of MSP430F5529. The CRC module produces a signature for a given sequence of data values. The signature is generated through a feedback path from data bits 0, 4, 11, and 15. The CRC signature is based on the polynomial given in the CRC-CCITT-BR polynomial.

Code Example

#include "driverlib.h"
#include "delay.h"
#include "lcd.h"
#include "lcd_print.h"

#define INFOA_START     0x1980
#define INFOA_END       0x19FF

#define INFOB_START     0x1900
#define INFOB_END       0x197F

#define INFOC_START     0x1880
#define INFOC_END       0x18FF

#define INFOD_START     0x1800
#define INFOD_END       0x187F

void clock_init(void);
void GPIO_init(void);
char Flash_Read_Char(unsigned int address);
unsigned int Flash_Read_Word(unsigned int address);

void main(void)
{
    unsigned char s = 0x00;
    unsigned char temp = 0x00;
    unsigned char value[2] = {0x00, 0x00};
    unsigned char set_values[4] = {33, 64, 99, 16};
    unsigned int status = 0x0000;

    WDT_A_hold(WDT_A_BASE);

    clock_init();
    GPIO_init();

    LCD_init();
    LCD_clear_home();

    CRC_setSeed(CRC_BASE,
                0xABCD);

    CRC_set8BitData(CRC_BASE,
                    Flash_Read_Char(INFOB_START));

    temp = CRC_getResult(CRC_BASE);

    LCD_goto(0, 0);
    LCD_putstr("Checking CRC");

    LCD_goto(0, 1);
    LCD_putstr("STO");

    LCD_goto(8, 1);
    LCD_putstr("CRC");

    print_C(3, 1, Flash_Read_Char(INFOB_START + 1));
    print_C(11, 1, temp);

    delay_ms(1600);

    LCD_clear_home();

    LCD_goto(0, 0);
    LCD_putstr("INFO A Seg.");

    if(temp != Flash_Read_Char(INFOB_START + 1))
    {
        do
        {
            FlashCtl_eraseSegment((unsigned char *)INFOB_START);

            status = FlashCtl_performEraseCheck((unsigned char *)INFOB_START,
                                                128);
        }while (status == STATUS_FAIL);
    }

    if((GPIO_getInputPinValue(GPIO_PORT_P2,
                              GPIO_PIN1) == false))
    {
        GPIO_setOutputHighOnPin(GPIO_PORT_P4,
                                GPIO_PIN7);

        FlashCtl_unlockInfoA();

        do
        {
            FlashCtl_eraseSegment((unsigned char *)INFOA_START);

            status = FlashCtl_performEraseCheck((unsigned char *)INFOA_START,
                                                128);
        }while (status == STATUS_FAIL);

        FlashCtl_write8(set_values,
                        (unsigned char *)INFOA_START,
                        4);

        FlashCtl_lockInfoA();

        LCD_goto(0, 1);
        LCD_putstr("Set: 33 64 99 16");

        delay_ms(3000);

        GPIO_setOutputLowOnPin(GPIO_PORT_P4,
                               GPIO_PIN7);
    }

    else
    {
        LCD_goto(0, 1);
        LCD_putstr("Read");

        for(s = 0; s < 4; s++)
        {
            temp = Flash_Read_Char(INFOA_START + s);
            print_C(5, 1, s);
            print_C(13, 1, temp);
            delay_ms(2000);
        }
    }

    LCD_clear_home();

    LCD_goto(0, 0);
    LCD_putstr("INFO B Seg.");

    temp = Flash_Read_Char(INFOB_START);
    LCD_goto(0, 1);
    LCD_putstr("WR: ---");
    LCD_goto(9, 1);
    LCD_putstr("RD:");
    print_C(12, 1, temp);
    delay_ms(2000);

    temp = 0;

    while(1)
    {
        if(GPIO_getInputPinValue(GPIO_PORT_P1,
                                 GPIO_PIN1) == false)
        {
            while(GPIO_getInputPinValue(GPIO_PORT_P1,
                                        GPIO_PIN1) == false);

            print_C(3, 1, value[0]);

            do
            {
                FlashCtl_eraseSegment((unsigned char *)INFOB_START);

                status = FlashCtl_performEraseCheck((unsigned char *)INFOB_START,
                                                    128);
            }while (status == STATUS_FAIL);

            CRC_setSeed(CRC_BASE,
                        0xABCD);

            CRC_set8BitData(CRC_BASE,
                            value[0]);

            value[1] = CRC_getResult(CRC_BASE);

            FlashCtl_write8(value,
                            (unsigned char *)INFOB_START,
                            2);

            GPIO_setOutputHighOnPin(GPIO_PORT_P4,
                                    GPIO_PIN7);

            delay_ms(1000);

            GPIO_setOutputLowOnPin(GPIO_PORT_P4,
                                   GPIO_PIN7);

        }

        delay_ms(20);

        print_C(3, 1, value[0]);
        value[0] = temp++;

        delay_ms(200);
    };
}

void clock_init(void)
{
    PMM_setVCore(PMM_CORE_LEVEL_3);

    GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P5,
                                               (GPIO_PIN4 | GPIO_PIN2));

    GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P5,
                                                (GPIO_PIN5 | GPIO_PIN3));

    UCS_setExternalClockSource(XT1_FREQ,
                               XT2_FREQ);

    UCS_turnOnXT2(UCS_XT2_DRIVE_4MHZ_8MHZ);

    UCS_turnOnLFXT1(UCS_XT1_DRIVE_3,
                    UCS_XCAP_3);

    UCS_initClockSignal(UCS_FLLREF,
                        UCS_XT2CLK_SELECT,
                        UCS_CLOCK_DIVIDER_4);

    UCS_initFLLSettle(MCLK_KHZ,
                      MCLK_FLLREF_RATIO);

    UCS_initClockSignal(UCS_SMCLK,
                        UCS_XT2CLK_SELECT,
                        UCS_CLOCK_DIVIDER_2);

    UCS_initClockSignal(UCS_ACLK,
                        UCS_XT1CLK_SELECT,
                        UCS_CLOCK_DIVIDER_1);
}

void GPIO_init(void)
{
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P1,
                                         GPIO_PIN1);

    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P2,
                                         GPIO_PIN1);

    GPIO_setAsOutputPin(GPIO_PORT_P1,
                        GPIO_PIN0);

    GPIO_setDriveStrength(GPIO_PORT_P1,
                          GPIO_PIN0,
                          GPIO_FULL_OUTPUT_DRIVE_STRENGTH);

    GPIO_setAsOutputPin(GPIO_PORT_P4,
                        GPIO_PIN7);

    GPIO_setDriveStrength(GPIO_PORT_P4,
                          GPIO_PIN7,
                          GPIO_FULL_OUTPUT_DRIVE_STRENGTH);
}

char Flash_Read_Char(unsigned int address)
{
    char value = 0x00;
    char *FlashPtr = (char *)address;

    value = *FlashPtr;

    return value;
}

unsigned int Flash_Read_Word(unsigned int address)
{
    unsigned int value = 0x0000;
    unsigned int *FlashPtr = (unsigned int *)address;

    value = *FlashPtr;

    return value;
}

Hardware Setup

Explanation

The idea here is to use internal flash memory for storing non-volatile user data and CRC module for verification of data integrity.

MSP430F5529’s flash memory is partitioned into main, information, and BSL memory sections. The main flash memory is used for storing application code, BSL memory is used for storing bootloader and the information memory is used for storing non-volatile user data. Check the memory map shown below. Here two out of three types of flash memories are shown only. Different sizes of data can be read/written in flash memory but when erasing only segment-sized erase is possible. The segment size of main and BSL memories is 512 bytes and the segment size of information memories is 128 bytes. Since information memory segments are smaller than other flash memories, it is best suited for user data storage for obvious memory segment size advantage. Lesser size means faster operation, lesser wear and efficient memory management. As mentioned before, frequent writes/erases wear memories and so it is a good practice not do either frequently. Complex software-based wear-levelling techniques and using RAM-backed buffers can be applied to reduce memory wears.

Segment A is a very important segment as it can be used to stores important/critical internal calibration data, configurations and other important set points. This is why, it is protected and locked separately. It is wise to leave it and use the other three segments of information memory space to store user data that frequently change.

Now let’s see how the code works here.

Firstly, let us start by defining the location boundaries of the information memory segments.

#define INFOA_START     0x1980
#define INFOA_END       0x19FF

#define INFOB_START     0x1900
#define INFOB_END       0x197F

#define INFOC_START     0x1880
#define INFOC_END       0x18FF

#define INFOD_START     0x1800
#define INFOD_END       0x187F

CRC check is done in the beginning of the code although at first run CRC check is invalid as no user data is initially stored. However, this comes to use when we reset or power up after saving our data.

For CRC module to work, we need to give a seed, i.e. a number in form of a key. Here it is arbitrarily set as 0XABCD. CRC module uses this seed and given data to compute CRC result. After this we visually check if the stored CRC matches with the calculated CRC. The check is done in code too. The CRC values are stored in two locations of the B segment of information memory.

CRC_setSeed(CRC_BASE, 0xABCD);

CRC_set8BitData(CRC_BASE, Flash_Read_Char(INFOB_START));

temp = CRC_getResult(CRC_BASE);

LCD_goto(0, 0);
LCD_putstr("Checking CRC");

LCD_goto(0, 1);
LCD_putstr("STO");

LCD_goto(8, 1);
LCD_putstr("CRC");

print_C(3, 1, Flash_Read_Char(INFOB_START + 1));
print_C(11, 1, temp);


delay_ms(1600);

LCD_clear_home();

LCD_goto(0, 0);
LCD_putstr("INFO A Seg.");

if(temp != Flash_Read_Char(INFOB_START + 1))
{
    do
    {
        FlashCtl_eraseSegment((unsigned char *)INFOB_START);

        status = FlashCtl_performEraseCheck((unsigned char *)INFOB_START,
                                             128);
     }while(status == STATUS_FAIL);
}

If the contents of both locations of Info B memory are different then it is likely that the memories are corrupt/empty and so an erase cycle is necessary to wipe out inconsistent data. The entire Info B segment is erased. If it is otherwise then the erase is not performed.

Now let us put some simulated fixed data as if these data are set points of some process or hardware. To set these data in Info A memory we have to push P2.1 button. First, the segment is unlocked and then an erase is performed to get rid of old data. After the erase cycle, data (array named “set_values”) is stored and the flash segment is locked. The locking ensures that no other operation is possible with this memory unless unlocked.   

If the P2.1 button is not pressed then stored data is simply read.

Note no CRC check is performed for this segment.

if((GPIO_getInputPinValue(GPIO_PORT_P2, GPIO_PIN1) == false))
{
    GPIO_setOutputHighOnPin(GPIO_PORT_P4, GPIO_PIN7);

    FlashCtl_unlockInfoA();

    do
    {
       FlashCtl_eraseSegment((unsigned char *)INFOA_START);

       status = FlashCtl_performEraseCheck((unsigned char *)INFOA_START, 128);
     }while (status == STATUS_FAIL);

     FlashCtl_write8(set_values, (unsigned char *)INFOA_START, 4);

     FlashCtl_lockInfoA();

     LCD_goto(0, 1);
     LCD_putstr("Set: 33 64 99 16");

     delay_ms(2600);

     GPIO_setOutputLowOnPin(GPIO_PORT_P4, GPIO_PIN7);
    }

else
{
    LCD_goto(0, 1);
    LCD_putstr("Read");

    for(s = 0; s < 4; s++)
    {
        temp = Flash_Read_Char(INFOA_START + s);
        print_C(5, 1, s);
        print_C(13, 1, temp);
        delay_ms(2000);
    }
}

All of the tasks mentioned so far are done before the main loop. In the main loop a value named “temp” is incremented. When P1.1 button is pressed, the current value of temp is stored in Info B segment. Since we cannot rewrite or overwrite a flash memory, we must perform an erase operation before storing new data. When a new data is stored, a CRC is also performed with 0xABCD as seed as in the beginning of the code. The new data and CRC are saved in two different locations. These two values will be used for checking data integrity when the MSP430 micro resets or powers up next. This is what we saw at the beginning of the code.

Note that there is no Info B locking unlocking feature.

if(GPIO_getInputPinValue(GPIO_PORT_P1, GPIO_PIN1) == false)
{
   while(GPIO_getInputPinValue(GPIO_PORT_P1, GPIO_PIN1) == false);

   print_C(3, 1, value[0]);

   do
   {
       FlashCtl_eraseSegment((unsigned char *)INFOB_START);

       status = FlashCtl_performEraseCheck((unsigned char *)INFOB_START, 128);
    }while (status == STATUS_FAIL);

    CRC_setSeed(CRC_BASE, 0xABCD);
    
    CRC_set8BitData(CRC_BASE, value[0]);

    value[1] = CRC_getResult(CRC_BASE);

    FlashCtl_write8(value, (unsigned char *)INFOB_START, 2);

    GPIO_setOutputHighOnPin(GPIO_PORT_P4, GPIO_PIN7);

    delay_ms(1000);

    GPIO_setOutputLowOnPin(GPIO_PORT_P4, GPIO_PIN7);
}

delay_ms(20);

print_C(3, 1, value[0]);
value[0] = temp++;

delay_ms(200);

Demo

Pages: 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

Related Posts

14 comments

Leave a Reply

Your email address will not be published. Required fields are marked *