03 November 2011

Tutorial 16e: Programming the Flash Memory

When we call the calibration values CALDCO_1MHZ and CALBC1_1MHZ in the code for our MSP430, those values refer to the address in SegmentA where the values are stored. They're not located in a place that's easy to find, however; they're stored in the linker command file, specific to each device. (If you're curious, take a look at the file in ${base dir}/Texas Instruments/ccsv4/msp430/include/msp430g2231.cmd, where your base directory is wherever you've installed CCS (likely in C:\Program Files for Windows). Be careful not to change anything in this file, though; this file is essential for proper programming of the G2231 device, as each is to their respective devices.) We could make a copy of this file and use it in our linker when we program the MSP430, but that requires a number of steps that are difficult to remember. All we really need is a way to tell our program to look at the specific address where we've saved the calibration values for our UART. All we really need is a pointer.

We've not dealt with pointers up to this tutorial, but they're not really a difficult concept. Let's say we have code that declares the following two items:
   int var;
   int *ptr;
The first is what we're used to seeing; we declare a variable of type int (16 bits in MSP430), which will store a signed integer value. The second is also a declaration of type int, but rather than a variable, it is a pointer. The stored number in ptr doesn't refer to the value of a variable, but rather the address where a variable of type int is stored. (The actual size of the pointer in an MSP430 is 16 bits, so char *ptr; would also be a 16 bit value, even though it points to memory where an 8 bit value is being stored.)

In the C language, we have a way to refer to the address of a specific variable by using the & character. So from this point, if we assign ptr = &var; then ptr now points to the address where we've stored the int value var. Similarly, we can reference the value stored in a pointer with the * character. int var2 = *ptr; would store the value at address ptr in the new variable var2. If we want to assign a specific address to a pointer, rather than reference the address of a variable, we can use a literal of the form (int *)0x1234.

In the MSP430, an int variable takes 16 bits, while a char variable take 8. When we want to reference a specific word, we'll use a pointer of type int. If we want to reference a specific byte, such as the calibration values for the DCO, we'll use a pointer of type char. To use the DCO values we'll program in SegmentB, we can use the following:
    char *CALBC1_UART = (char *) 0x10BE;
    char *CALDCO_UART = (char *) 0x10BF;
or, if we'd rather not use extra memory, define in the header:
    #define  CALBC1_UART    *(char *) 0x10BE
    #define  CALDCO_UART    *(char *) 0x10BF

The Flash Memory Controller
Ok, now we've got a handle on how to reference portions of the flash memory. Now it's time to learn how to actually write to it. The MSP430 has a peripheral designed specifically to handle managing the flash memory called the Flash Memory Controller. Since it would be dangerous to manipulate the flash memory willy-nilly, it's set up like the Watchdog Timer, so that a key is needed to change any of its four control registers. The key value is 0xA5, conveniently provided in the header files as the value FWKEY. Here are the portions we'll need to know about:


  • FWKEY (bits 15-8):  The key is read as the value 0x96. Each register has this.
  • BLKWRT (bit 7): control bit for block write mode. We will be writing word by word, so we'll save this function for a future tutorial.
  • WRT (bit 6): write mode enable. Before we can write to any flash segment, this bit must be enabled.
  • MERAS & ERASE (bits 2 and 1): Control the mode of erasure. You can erase a single segment, the entire main memory, or the main memory and information segments both. (Depending on whether SegA is unlocked.)
  • FSSELx (bits 7-6): These bits select which clock source to use for the programming.
  • FNx (bits 5-0): These bits allow you to divide the source clock by any factor up to 64. (Divide by FNx + 1)
  • FAIL (bit 7): We don't want to see this bit set! For our purposes, it could mean the clock source has failed for some reason. If this happens, it needs to be reset by software before anything else can be done.
  • LOCKA (bit 6): I won't go into how this works; this bit controls whether SegA can be erased/written. If you find you absolutely must change something in this segment, you can read in the Family User's Guide to learn how to use this bit.
  • LOCK (bit 4): When this bit is set, the flash memory is completely locked, and cannot be erased or written.
  • WAIT (bit 3): Indicates when the flash memory is being erased or written.
  • This register is not available on all devices; it controls the 'marginal read mode'. We won't be using it here.
To do a successful programming of the Information Segment, we first set the clock for the controller. The actual frequency isn't terribly important, as long as it falls between 257 and 476 kHz. The default DCO setting of the MSP430 is about 1.1 MHz, and so a division of 3 (FNx = 2) works well, giving us roughly 370 kHz. We'll calibrate the DCO to 7.3728 MHz, so a division of 20 (FNx = 19) will give us about the same.

If we have anything we want to save in the segment, then the entire contents would need to be saved to a buffer. Likely, your SegB is blank, so we'll just proceed forward. Next, we set the controller to erase mode. We don't want to erase the main memory segments, so we only need the ERASE bit. We then clear the LOCK bit to allow changing the flash memory. When the controller is configured this way, we initiate the erase cycle by writing to any address within the segment. There's nothing magic about what value we write, but it is absolutely crucial that you have your address pointer pointing to somewhere in the segment you actually want to erase!

Once it has erased, we reconfigure the controller to write mode (not block write), and then proceed to write the values we need, either as bytes or words. If you preserved the prior contents, a better method is to change the contents to the new values and use block write mode. For this tutorial, we'll keep it simple.

Finally, when everything is written, we clean up, clearing the write mode bit and locking the flash memory.

The code I wrote for writing the UART DCO calibration to Segment B using the TLV format is found in uartcalG2231.c. Remember, if you run this, it requires the watch crystal. If there is anything already stored in SegB, it will be lost. If you'd rather store it in Segment C (0x1040 to 0x107F) or Segment D (0x1000 to 0x103F), change the address in the assignment instructions accordingly. If you choose to put in in Segment A (0x10C0 to 0x10FF), look in the Family User's Guide for instructions on unlocking the Segment. I do not recommend this, however, since we're calibrating a non-standard DCO frequency. Wherever you choose to store it, you'll need to remember the address. If you use my method here, that address is 0x10BE for the CALBC1 value and 0x10BF for the CALDCO value. Next time we'll write code that pulls these calibration values from memory and configures the DCO and TimerA to start setting up our software UART.

This tool will be very useful to you; keep in mind that though the Value Line devices only have one DCO calibration and don't appear to have any calibrations for the ADC, there is space for them! This process is not too difficult, you can upgrade your devices to have better calibrations, essential for accurate scientific work. For more information on the ADC calibration values that are often included in MSP430 devices, see the TLV chapter of the Family User's Guide.


James M. Dinsmore said...

Thanks a million for making this technology and line of chips more accessible (TI presents a steep learning curve and a high entry level barrier). I greatly appreciate your sharing and mentoring.

David said...

Nice work on all your posts. 16d & 16e were particularly timely & helpful for me.


Greg_G said...

Hello David,

The MSP430 and C were a mystery to me when I started your blog. I've grown to enjoy both by working through and building the examples. Thanks!

Nagasaki45 said...

Hello David,

I'm trying to write simple value (my birth year) to SegmentB but can't solve a problem.

The problem is that after I re-enter to debug mode SegmentB is erased.
I've already checked: "Erase main memory only" (it's the default).

A wired solution I've found to the problem is to write "__delay_cycles(10000)" at the beginning of the code just after stopping WDT.

Here is my code:

I will appreciate if you or any body else would have something to say about the weird problem.

Thanks again, you are doing great job!!!