2018-07-04 17:42:04, In: DIY, Electronics
Arduino Uno is a cheap microcontroller platform which can be used in lots of applications. If we prototype our solution using Arduino board, it is possible to dump the Arduino board and use single microcontroller instead which is even cheaper and looks more professional even on an universal solder-in PCB. However, ATMega328 chip used in Uno has a significant drawback - its memory which is only 2kB. If we want to make a small control device it's not a problem, but for some specific applications 2kB may be too small.
For example, since few weeks I have an idea to develop a BASIC computer using AVR chips. Implementation of Tiny BASIC uses some memory and has some code clutter which can be replaced with something more usable by the cost of e.g. moving storage code to external devices. Unfortunately Tiny BASIC leaves a very small memory for user's program and its variables.
So what are options to add RAM into Arduino?
- Use a larger Arduino module. This solution is present in Internet forums all time. Although using e.g. Mega gives you 8kB of fast RAM, it also locks you in Mega module - you can't just pop the chip out of it and use it elsewhere.
- Use a SPI Static RAM - this is a good option if you want to minimize pin usage and maximize speed. Microchip provides a 8kB 23K640 SPI RAM, or even 32kB 23K256. Unfortunately, getting these chips is a real problem - I have not found any shop in my part of Europe which has them!
- Use a conventional static RAM - These RAMs come in variety of sizes, from 1 to 32kB or more, and are easily accessible - can be obtained in electronic shops or salvaged from dead 3/486 mainboards (they are used as cache chips there). Usually these mainboards are dead because the battery has leaked and in many cases recovering them is impossible, so they go well as parts source - in higher-end configurations you will get 4 32kB chips used in 128kB cache configuration. In low-end configuration you'll get 8 8kB chips (64kB cache) or similar. 32kB chip is labeled as "62256" or "61256" - generally 256 kilobits, exact type depends on manufacturer.
Static RAM is fast and does not require refreshing, but is addressed using 15 address lines (for 32kB chip) and 8 data lines. Having a 20-pin Arduino Uno this will just not fit - 20 I/O pins of Arduino, but 23 pins for chip (and /WE pin is 24th). We need to expand pins and do it fast.
The most common way to expand output pins in Arduino is to use a shift register - for example a 74HC595 chip. These chips take data in a serial way, clocked in using 2 pins. Third pin allows to "apply" or latch the introduced data into outputs. Each chip has 8 outputs and one for cascading, so they can be cascaded to give 16, 24, 32 outputs etc. More registers, the longer programming time as it's needed to "blow" the bits in one by one.
So, to drive 15 address inputs of SRAM chip, we need 15 pins - 2 registers with its 16 pins are sufficient with 64kB update ability.
However, shift registers driven by Arduino's GPIO are slow. We need to make some built-in fast adapters drive the clock and feed the data to pins. How to make it faster? Use SPI! SPI's MOSI and Clock pins are exactly like Clock and Data for shift registers. SPI interface is faster and it's built-in. So instead of banging IO pins, we just pull the latch pin low, transfer our address with SPI and pull latch high again to make data visible on outputs. So, we've used SPI CLK (Uno's pin 13), MOSI (11), SS (10) and we reserved MISO (12) as unused input with /WE (Write Enable pin - pull low to write data) connection - 4 pins.
This Write Enable needs to be "banged" to low and high during write. To do it using our input, we can turn SPI off for a while. In SPI mode it's permanently input, so if it's Hi-Z then, most SRAM will take it as high and if it's pulled up, we're OK too. If not - we can use pull-up resistor but I found it not needed.
How about 8 data pins? To make read and write possible it needs to be bidirectional, so we have to use Arduino's GPIO pins (serial expanders are too slow). The best situation would be if there would be a complete 8-bit port on Uno, as reading and writing data to port is just using a specific area of memory. This unfortunately is impossible - Port D has serial port, port B - SPI and port C is incomplete. I used first 6 bits from port C (analog pins) and PB0 and PB1 for two last bits (Arduino's 8 and 9). This way I can still manipulate Port C for first 6 bits and port B for the rest.
In the picture below there is a schematic of the solution. The RAM chip is permanently enabled, as well as registers. About buses, the thing here is that as long as you're doing the addressing the same way it does NOT matter how you connect address pins - if you swap A0 and A1, you're still addressing some place and if you do it for reading and writing, it won't matter. Similar thing is for 8 data bits - nothing bad will happen of you store them in an order different than SRAM manufacturer designated.
The circuit looks complex, but can be built on a simple prototype board. The picture below shows a working prototype.
The code consists of 3 main functions: To initialize the set-up, to read and write byte from memory. SPI shifting is implemented in a helper function. To make this code faster I had to use a few problematic solutions, here are they:
- No digitalRead nor DigitalWrite, as their pin lookup is slow. Instead peeking PIN registers and poking PORT registers.
- No pinMode as its pin lookup is slow - instead, direct writes to DDR registers.
- Instead of writing bits to Port B to write data, assuming SPI pins as permanently granted and throwing a complete byte of data into it caring only about LATCH kept high (although it saves a few cycles, this is an example of a not-a-good-idea solution if we talk about interoperability, this is the essence of the "spaghetti code", but gives a few cycles of doing something useful).
- So the thing is permanently bound to its pins which cannot be easily changed.
The code with some sample testing codes can be downloaded from here. The user-interfaceable parts are write_byte(address, data) and read_byte(address).
So let's test the solution, how fast it is and is it useful at all. After code optimization, a complete write into 32kB of memory took about 312ms and reading a whole took 236ms. Write speed: about 102.5kB/s, read speed: 135.5kB/s It means that the solution is really slow even comparing to these SPI SRAM chips.
So to sum up, the problems are:
- Large number of pins used (12)
- SPI occupied all time.
- Low speed.
- Relatively small capacity and poor expandability (only to 64kB).
While good things are:
- It works,
- The chip is better available than SPI RAM.