21 September 2012

Tutorial 19c: The 25xx080

Now that we have a handle on configuring the USI module to communicate using the SPI protocol, we can implement it in a real situation. Today we'll be using a Microchip Serial EEPROM (I'm using specifically the 25AA080C, though in principle this should work with any Microchip SPI EEPROM).  Looking at the datasheet for this chip, there are a few things to understand.

Instructions and the Status Register

This chip has 6, 8-bit instructions.  In this design, the Chip Select pin is actually used to mark the end of an instruction, and so is a necessary signal that should be enabled (set to 0) before and disabled (set to 1) after each of the following:

  • READ (0x03): Sending these 8 bits followed by the 16-bit address of the location in memory you want to read instructs the EEPROM to send the 8-bit value at the specified address on the next 8 clock cycles.  The whole instruction takes 32 clock cycles (8 for the command, 16 for the address, and 8 for the read-back).
  • WRITE (0x02): This command can be used in two different ways. First, after sending the command, a 16-bit address is sent followed by the 8-bit value to write. Second, rather than write single bytes at a time, the chip can write a group of bytes (called a page) at once. The 25AA080C has a page size of 16, so up to 16 bytes can be written in one instruction.  This mode uses the command bits, followed by the initial address (each byte is written in the subsequent address of the last), and then the values for each byte to write.  Note: writing does not occur until the chip select has been disabled (set).  In addition, the chip needs a small amount of time (about 5 ms, according to the datasheet) to perform the writing. Any subsequent write commands must be sent after the settling time to prevent interruption. We'll examine this point a little more later. This instruction takes 24 + 8n clock cycles, where n is the number of bytes being written (max of 16 on this chip).
  • WRDI (0x04): This simple, 8-bit only instruction disables writing to the EEPROM. Data can still be read, but write instructions will have no effect. This instruction takes only 8 clock cycles.
  • WREN (0x06): The opposite of WRDI, sending these 8 bits enables writing to the EEPROM. When a write cycle completes, the write-enable latch is reset, so this instruction must be issued before every write instruction. This instruction also only takes 8 clock cycles.
  • RDSR (0x05): The EEPROM also includes a Status Register that indicates the configuration of the chip. The 8-bit value can be read by issuing this command followed by 8 clock cycles to read the value back. This instruction takes 16 clock cycles.
  • WRSR (0x01): The Status Register can be written directly under certain circumstances. When those conditions are met, a write is done by issuing a WREN instruction, followed by this command and the 8 bits to write to the Status Register. This instruction takes 16 clock cycles, but like the WRITE instruction must be preceded by a WREN.
The Status Register uses 5 of the 8 bits in this EEPROM chip:
  • WIP (Bit 0): This bit is a flag that is set when the chip is in the middle of writing to the flash. Write instructions should not be issued until this is clear, which should take no more than 5 ms.
  • WEL (Bit 1): This bit flags when writing is enabled on the chip. A WREN command will set this bit, while a WRDI will clear it. Writing to the flash only occurs if this bit is set. Writing to the Status Register may be possible while set, depending on the chip's configuration. (See WPEN below.)
  • BP0 and BP1 (Bits 2-3): These bits enable write protection for individual blocks in the EEPROM. The 25AA080C is built with two blocks, corresponding to the upper half and lower half of the flash memory respectively. Setting both BP0 and BP1 will protect the entire chip from being over-written. These bits are set through WRSR instructions.
  • WPEN (Bit 7): This bit determines whether the Write Protect pin affects the writeability of the Status Register. If this bit is clear, the Status Register can be written to regardless of the state of WP (as long as WREN has been done, of course). If this bit is set, then the Status Register can be written to if WP is held high, but is protected if this bit is held low.
All of these instructions and register definitions can be encapsulated in a C header file. As an example, look at 25xx080c.h. This file was written with this single device specifically in mind, but can be expanded to generalize to any SPI EEPROM from Microchip. I will be using this header file in all of the example code below, in addition to the calibrations.h header file. If you run these code examples, be sure to include the two header files. If they are linked to, be sure to adjust the CCS project properties to search for the directory they reside in for both the compiler and the linker, and set the debugger to clear only the main memory so the DCO calibration doesn't get overwritten.  (If that happens, go back to the flash memory programming tutorial and re-calibrate your MSP430.)

MSP430 Issues

As you get more involved with MSP430 design, you'll want to become aware of problems in the hardware itself. Every device on TI's website has a link to a "Device Errata", that details known problems with the hardware. The MSP430G2231 Device Errata lists a known problem labeled as "USI5", which applies to using the SPI protocol. For some reason, this device will send an extra clock cycle the first time it is used; so instead of sending 8 clock cycles after writing 8 to USICNT, it will send 9. There are two ways to get around this: the errata suggests writing one less to USICNT the first time, so writing 7 instead of 8. I think that makes for messy code, since the first transmission has to be treated differently, so instead I clear the bug in initializing USI. Before enabling the SDO and SDI pins on the MSP430, but after starting the USI, I send a command to transmit a single bit. Two pulses are actually sent, due to the bug, but since P1.6 and P1.7 haven't been enabled in USI yet, it has no effect on any devices attached to the MSP430. Once the two pulses have cleared, the pins are enabled, and the MSP430 is free to use the module as expected. The code I used to do this looks like this:

void USI_init(void) {
        // Enable SCLK, Master mode, enable output and reset USI
    USICTL1 = USICKPH + USIIE; // Mode 0 requires CKPH=1, enable interrupt
    USICKCTL = USIDIV_3 + USISSEL_2;    // SMCLK div 8 -> 921.6 kHz USI clock
 // One write command should take ~50 us at this rate,
 // while 1 bit via UART @ 9600 should take ~100 us.

    USICTL0 &= ~USISWRST;        // Clear USI for use

    USICNT = 1;  // clear 1st transmit due to errata USI5
    __delay_cycles(50);          // finish clearing (minimum (n+1)*16 cycles)
    USICTL0 |= USIPE7 + USIPE6;  // Enable SDI/SDO pins.
} // USI_init

Another technique you will see used in these examples was used in the UART tutorials: a custom flag is used to indicate when SPI transmission is occuring. This flag is polled to hold the program until the transmission has completed, ensuring that the USISR is not changed until all bits have been transmitted.  This flag is cleared in the interrupt routine for the USI.  The chip select is done manually in each example below.  See the Reader Exercise at the end of the tutorial for an idea on a better way to handle the chip select.

Example Code

Writing, Method 1

First up, eepromwrite_spiG2231.c demonstrates writing to the EEPROM device. The code uses P1.4 as the chip select signal, and that pin should be connected to pin 1 of the EEPROM. Be sure to connect P1.5/SCLK to SCK (pin 6), P1.6/SDO to SI (pin 5), and P1.7/SDI to SO (pin 2). Recall that the SCLK, SDO, and SDI functions are enabled in the MSP430 in the USI registers, not in P1SEL. It might not hurt to remove the jumper to the LED on P1.6 on the LaunchPad.  In addition, though the code may work without it, it's a good idea to tie the SDI-SO line (pin 2) to Vcc. The MSP430 does not automatically set this line when idling, as one would expect, so a simple resistor (say ~10k) will do the trick.  Finally, be sure to tie HOLD (pin 7) to Vcc as well.

RealTerm lets you send data from a text file, and
add the necessary 5 ms delay between characters.
Once running, this code will write the first 1024 bytes received by UART (at 9600 baud) to the EEPROM. Once that has completed, it lights the LED on P1.0 and goes into a low power mode, signaling that the write process is done. Now, if you think about it, there's a problem with this code. At 9600 baud, each byte takes just over 1 ms to transmit. While that's more than enough time to send the write command to the EEPROM at the USI clock rate being used, it's not enough time for the write to settle; there should be roughly 5 ms between write commands. If you are transmitting each character by hand, you'll probably be ok. If you're like me and really don't want to press keys 1024 times just to test this code, you'll want to write a 1024 character text file, and transmit that directly. For that to work, you need to have some delay between characters in the transmission. I used the Windows program RealTerm, which in addition to having a feature to transmit from a file has a feature to introduce a manual delay between characters. With that configuration, I was able to transmit the file mspsci.txt and have it write successfully to the EEPROM.


To get this file to display nicely, you need to enable the
newLine mode in RealTerm. Displaying an extra line or
two also helps.
Second, eepromread_spiG2231.c will read back the contents of the EEPROM and send them via UART (at 9600 baud) for display. If you run this code before any writing, it will display whatever the contents of your chip are prior to doing either of the write programs. If your chip is new, it will likely be very boring, consisting of the value 0xFF in every address.  If you run either of the write methods, running this program should result in a display much like what is shown here.

If you're using my text file and RealTerm, note that I've enabled newLine mode; the character being transmitted by default only initiates a line feed, without a carriage return. This mode adds the carriage return, so that it displays properly. If you lose a line of the display, increase the number of rows as well.

Writing, Method 2

Having to wait 5 ms between characters is a pain; we've effectively reduced our transmission rate to a crawling 200 baud. Fortunately, being able to write up to a single page at once saves us, since even after writing 16 bytes the settling time is still only 5 ms! At 9600 baud, each character takes just over 1 ms to transmit, so as long as we write more than 5 characters at a time, the amount of time it takes to transmit and store those characters is longer than the settling time needed between write commands. The disadvantage, of course, is that we need to employ a buffer to hold all of those characters in memory in the MSP430 while we wait. The MSP430 devices should have sufficient RAM to do this, however, so it's not much of a sacrifice. (But it is something of which we need to be aware in more complicated programs that write to EEPROM!) 

In eepromwrite2_spiG2231.c, I implement the same technique as before, but by writing a full page of 16 bytes at once. Received values are stored in the buffer[] array, and then transmitted to the EEPROM to be written. The amount of time it takes to send the complete write command is less than the time it takes to receive the next character, so the code returns to the for loop waiting for the next 16 characters before the next character is received. If we operate SPI at a slower rate such that this condition isn't met, we can run into a race condition. 

SPI Signal Details

Let's take a look at some of the details of the signals being passed back and forth in these programs.
These two images show signals from the first program, eepromwrite_spiG2231.c. The SPI lines are labeled from the perspective of the EEPROM, the UART from the MSP430. Notice in the first panel that the SPI lines only operate every 5 ms, between received characters.  There's a lot of wasted time in this method. The second panel shows the important signal lines in detail. Note that there are two instructions being used, since CS shows two pulses. The SCLK is configured correctly for the chosen mode, idling low and reading on the rising edge of the clock. In a write command, the EEPROM is sending nothing back, so SDO is high throughout the instructions. The EEPROM sees the instruction 0x06 (WREN) followed by 0x02 (WRITE). Then the 16-bit address 0x06 is received followed by the value 0x2A to write to that address. (0x2A corresponds to the asterisk symbol.)

For the program eepromread_spiG2231.c, the UART signals now follow the SPI instructions (as makes sense; the UART is transmitting the values read). There is much less dead time in this program, since no settling is required between reads as there was between writes. Zooming in on the bottom panel, we see a single instruction. 0x03 (READ) is followed by an address, 0x08 in this case, then 8 dummy bits. At the same time, the SDO line idles until the last 8 bits, where it reports the value 0x2A as the value stored at address 0x08 in the EEPROM. After a short delay (due to steps taken in the MSP430), the next UART transmission is begun.

One thing to note in this chart; the SDI line is not idling at high all the time. This is due to the same reason we wanted to put a resistor on the SDO line to tie it to Vcc; likely it's a good idea to do this to both lines. By leaving it off, I was able to demonstrate that the process still works fine; it's only convention that requires us to have a specified idle state.

Finally, we have three panels to illustrate the eepromwrite2_spiG2231.c program. The first and third are analogous to those shown previously, giving a view of the overall signals being sent and a zoomed in picture of the SPI signals themselves. Notice there is almost no dead time in the UART signals received. The middle panel shows that even though we now have to send 16 bytes of data to write, the total length of the write instruction on SPI is less than the time to receive a single byte via UART. If this were not the case, and a UART transmission were to complete first, it could mess up the transmission by changing the values in the buffer array. Fortunately, SPI can operate very quickly compared to this transmission rate, even faster than is implemented in this code.

This post completes the tutorial on SPI (for now, anyway!), and next time we'll switch gears and learn to use USI with the I2C protocol.

Reader Exercise: There's a lot of commands that are retyped in each of these examples... wouldn't it be nice if instead of manually writing each instruction you could call a function that handles it for you? Write a C function that handles write instructions and read instructions, passing the address of interest, and the value to write (in the write instruction case) as parameters. A general write function for multiple bytes may be tricky to do; can you write one that handles both single-byte and multiple-byte cases?

1 comment:

renasuprema said...

i'm reading your approach on msp430 instrumentation fundamentals,
Love the work,
helping a lot!!
Thank you for your dedication Dr. D. Olson.