This post continues my discussion of register programming of the ATmega328P microcontroller. Specifically, the ATmega's Serial Peripheral Interface, commonly just called SPI. It also continues the discussion of my large LED display from my last blog post.
I have a five row by ninety column LED display. What would be the best way to deal with the content I wish to display? I could hard code the message in a variable. That's fine if I don't ever want to change the message. I also run into the 2 kbytes SRAM memory limitation of the ATmega328P. I have several different messages to display and would like to add, delete, and edit messages. I also display dynamic messages like the ever changing weather. In fact, if you look at my last post you see I display the current weather and weather forecasts from Weather Underground. I get weather updates every 10 minutes. I don't need to update any more often, and there is a limit to the number of accesses I can make without incurring a charge. Therefore, I need to continuously display the same information until I get an update. The best way to handle all of these issues is to store all messages in another memory device - a nonvolatile memory, of course.
My nonvolatile memory is a 25AA512 serial EEPROM. This eight pin IC contains 64 kilobytes of memory and has builtin SPI. This is perfect, both the serial EEPROM and the ATmega328P have SPI. The Arduino IDE comes with an SPI library that looks pretty comprehensive. But, I just feel more comfortable programming the applicable ATmega328P registers. I have more confidence that I can successfully get the two devices talking to each other. Besides, the Arduino library has limitations.
You can see the serial EEPROM sitting to the right if the ATmega328P on my interface board. Look towards the bottom of my last post.
The SPI, or Serial Peripheral Interface is really quite simple. It's not good for long distances but can be reasonably fast. It always involves one master device and one or more slave devices. The master is in complete control. The slaves do not start a conversation, which is just fine when the slave is a serial EEPROM. The master talks to only one slave at a time.
There are four signal lines:
- SCK, for Serial clock
- MISO, for Master In, Slave Out
- MOSI, for Master out, Slave in
- SS, for Slave Select
The master and all slaves connect to SCK, MISO, and MOSI. However, each slave must receive its own SS connection from the master.
The master contains a clock generator, a shift register, and control circuitry. Each slave has its own shift register. The slave, if it sees its SS line active, uses the clock from the master (SCK) to shift out the data in its shift register onto MISO. At the same time, it shifts in data from the master, on MOSI, into the same shift register. If it does not find SS active it keep its MISO in a high impedance state, allowing other slaves access to MISO.
When the master wishes to write to, or read from, a slave, it brings that particular slave's slave select (SS) line to a logic low. The master will send eight clock pulses to its shift register to shift out the byte of data that was previously stored in its shift register. Each bit appears on MOSI. As each bit is clocked out on MOSI, a bit is simultaneously clocked in from the active slave on MISO.
On the slave side, SS low, allows the SCK clock from the master to be passed to the slave's shift register and takes the shift register output out of a tristate (or open drain). Each clock pulse, clocks out the contents of the slave shift register onto MISO, while clocking data in from the master on MOSI. When the data transfer is complete, the master brings SS high again.
It is possible for multiple bytes to be transferred while SS is low. The clock runs for eight pulses, the shift register is loaded with a new byte and the clock runs for eight cycles again. This is repeated until the entire message is sent or received. The transfer is completed by bring SS high.
There is flexibility built into the SPI. The designer of a device can choose how it's implemented. As stated before, data will be sampled (clocked in), and outputted (clocked out), simultaneously on both MOSI and MISO. The two operations will be done on opposite edges of SCK. The designer can choose to output on the rising edge and sample on the falling edge, or output on the falling edge and sample on the rising edge. The master and slaves must use the same selection.
In SPI parlance, this is controlled by two parameters (not part of the specification, but most manufactures comply with these definitions), CPOL, and CPHA. As follows:
- CPOL: Polarity of the clock when in an idle state. CPOL = 0, if SCK is low at idle. CPOL = 1, if SCK is high at idle
- CPHA: Cock Phase. CPHA = 0, if data is sampled on the rising edge of the clock and output on the falling edge of the clock. CPHA = 1 if data is sampled on the falling edge of the clock and output on the rising edge of the clock.
The combination of the clock polarity at idle and the edge to sample and clock out comprises four modes (most manufacturers comply with this):
- MODE 0: CPOL = 0, CPHA = 0
- MODE 1: CPOL = 0, CPHA = 1
- MODE 2: CPOL = 1, CPHA = 0
- MODE 3: CPOL = 1, CPHA = 1
A diagram is in order:
Notice that as soon as SS goes low, a bit, either the MSB or the LSB of the master's shift register, and the selected slave's shift register, will appear on MOSI, and MISO respectively. With CPHA = 0, that bit will be the first bit of the requested data. With CPHA = 1, the first bit of requested data does not appear until the first clock transition.
Another Option: Notice in the diagram the bit names like "BIT 0 or 7". Another option the user has is to determine how the bits are shifted out - either LSB (BIT 0) first or MSB (BIT 7) first.
Another option available to the user may be the selection of the clock frequency.
SPI Implementation Using the ATmega328P and the Serial EEPROM
Serial EEPROM Requirements
The 25AA512's SPI bus has definite requirements. You could say that the ATmega328P master is a slave to the slave's requirements. Therefore, the place to start is with the 25AA512's data sheet, as seen here.
The first thing that jumps out at you is that the data sheet does not use the expected pin nomenclature. While they use SCK, there is no MOSI, MISO, or SS. How then do you know that the thing has an SPI? It tell you so. On the first page under "Description" is says, "The memory is accessed via a simple Serial Peripheral Interface (SPI) compatible serial bus.". That's pretty definitive. Their pin definition is as follows: SCK is SCK, MOSI is SI (slave in), MISO is SO (slave out), and SS is CS (chip select).
You don't see the terms CPOL, CPHA, or MODE 0, MODE 1, MODE 2, or MODE 3. This means you have to figure it out for yourself. Luckily, it's obvious. On page 8, Figure 2-1, you see that the clock, SCK, is low when idle, therefore CPOL is 0. The data bits on SI and SO line up with the falling edge of SCK, therefore CPHA is also 0. This equates to Mode 0. The figure also tells you that the most significant bit is available first on SO, and it expects the most significant bit first from the master on SI.
To see the maximum clock speed, you need to look at the AC characteristics and the timing diagrams. The maximum clock frequency is 20 MHz with a VCC of 5V.
There is a lot more information in the data sheet you need to know. For every operation (read, write, erase), there is an instruction that must first be sent to the device. See Table 2-1. Every operation you can do is detailed in the data sheet. You also learn that you can read and write multiple bytes before bringing SS high. However, when writing data you can not cross over to another page when writing multiple bytes.
Programming the ATmega328P
Here is a fragment of code taken from several sketches and distilled down to only reference communicating with the serial EEPROM by the SPI. Here is a link to all the Arduino sketches I wrote for the display.
The sketch only includes some #defines and functions. Looking at the function, initializeSPI(), there is a great deal of information in the comments. You can see the ATmega pin assignments on port B used for SPI. To use the built-in SPI of the ATmega328P, SCK, MISO, and MOSI must use the pins as stipulated in the comments. SS does not necessarily have to be on PB2 if the ATmega is the master. If the ATmega is the slave, then SS must be on PB2. In our case, the ATmega is the master.
There are #defines at the top. The ones under "Serial EEPROM Instructions" come directly from the 25AA512 data sheet, table 2-1 on page 7. SLAVE_SELECT definition relates to the pin on port B, pin PB2 which is the SS pin. .
There are three registers within the ATmega328P related to SPI. See section 23.5 of the ATmega328P data sheet for details of each register:
- SPCR: SPI Control Register
- SPSR: SPI Status Register
- SPDR: SPI Data Register
The comments, "Setting the Control Register, SPCR" shows the choices I made for my project. Notice bits 0, and 1 set SCK frequency to 4MHz.
SPDR is the register that holds the data. Basically, SPDR acts as the shift register. SPSR is the status register. We are only interested in one bit of SPSR, the SPIF bit. This is the SPI interrupt flag. When the transfer of a byte is complete (eight clocks), this bit is set. If enabled (not so in my project), an interrupt will also occur.
The operation of transferring a byte over SPI is contained in my function "SPIoperation()". This function is used for both sending data to the serial EEPROM and reading from the serial EEPROM, Loading a byte of data into SPDR starts the data transfer process. Even if our intention is to read data, we must send send a byte of data. Next, we simply poll SPSR looking for SPIF to be set. When that occurs, we can simply read SPDR to see what was returned from the serial EEPROM. Pretty simple.
The last function "readFromSerialEEPROM()" shows the read process. We send it an address (16 bits) of the location in the serial EEPROM containing the first byte of data we wish to read. We also send it the number of bytes we want to read. We set SS low to start things off. We then write three bytes to the serial EEPROM ignoring what is returned. The first byte is the "READ" instruction. The next two bytes are the upper and lower bytes of the address. After that we simply send something the serial EEPROM will ignore (0xFF) and read a byte from the serial EEPROM. Every time we send 0xFF, the serial EEPROM will increment its address counter and send back a new byte. After reading the number of bytes requested, we bring SS high.
Writing to the serial EEPROM is similar but a little more involved. Note the function "writeToSerialEEPROM(). We send this what we want to store in the serial EEPROM as a String object (my choice here. You can also send it an array of characters). We also send it the number of bytes to store and the starting address in the serial EEPROM. Firs,t we must convert the String to an array of characters.
The memory of the 25AA512 is organized into 128 byte pages. You can write into consecutive addresses, like you can read from consecutive addresses, as is done in my read function. However, unlike a read, you can not cross a page boundary when you write. Taking an example, if you wish to write 129 bytes starting at byte 0, by taking advantage of the consecutive write feature, the 129th byte will not go into address 128, it will go into address 0, overwriting what you put there first.
The write process also starts by bringing SS low. We follow that by write four bytes to the serial EEPROM, WREN (write enable), WRITE, and the upper and lower bytes of the starting address. Next, we can write consecutive bytes, but we have to monitor the address we are going to write to. If that address is a multiple of 128 we are at the page boundary. In that case we must bring SS high, bring SS low, send WREN, WRITE, and the new address, before writing more bytes of data. When you have written the last byte, SS can be brought high again.