Arduino SRAM Extension ====================== Issue: Arduinos are notoriously short on RAM. That's not a problem for some basic sensing and actuating, but as soon as logging and/or statistical analysis are required, an Uno's 1k or a Mega's 8 is soon too little. Fix: add some static memory. Static RAM (SRAM) ICs contain, in addition to power and ground, three types of pins, address, data and control. Address and control are input-only, data in- and output. ByteWide chips always have 8 data pins, n address pins to address 2 to the nth degree individual memory locations, then WR (write), OE (output enable, aka read), and CS (chip select), all of them active low. WR and OE are not supposed to be 0 (zero, as in zero volts) at the same time, as the data pins can be configured to be only one direction, input or output, at any time. CS (chip select) is used to select one out of many SRAM ICs on the same bus, by having a binary decode pull one CS low depending on a processor's higher address bits. Processors typically provide WR and OE in some form. CS comes courtesy of some external decoder logic. Timing is everything: SRAM has to provide stored, or accept data to be stored within a certain time, often referred to as the response time. A processor sends an address, and, for a write operation, data, then asserts WR, or, for a read, asserts OE. SRAM is supposed to store data received, or provide data within a certain number of CPU clock cycles. The time an SRAM IC needs to safely, reliable perform this activity is called the access time, typically measured in nanoseconds (nsec, ns); it needs to be short/low enough to reliable always act within the processor's timing restrictions. An Arduino Mega runs at 16MHz, so one clock cycle is 1/16,000,000 == 0.000,000,0625 seconds long, that's 62.5 nanoseconds. Somebody count some zeroes, please, and check this math. If memory circuitry needs more time to respond to a processors requests, wait cycles need to be inserted. A wait cycle is a configurable delay, causing the processor to wait longer for the memory to respond. An Arduino Mega 2560 can be configured to use a subset of its IO to talk directly to an SRAM, as outlined in https://www.rugged-circuits.com/s/ATmega2560-fv8d.pdf, starting at page 28. A Mega 2560 comes with 8kb RAM by default, located at address 0 (zero) up to 0x2200. Adding a 64K SRAM backs the remainder of the address space up to 0xffff. The addition is split into a lower and higher sector, each one individually configurable. Adding native external memory to a 2560 causes three ports to no longer be available for generic IO: - Port A is used for both data and the lower 8 bits of a memory address, - Port C carries the higher 8 bits of a memory address, - Port G provides ALE on bit 2, RD on bit 1, WR on bit 0. QuadRAM's extension provides 8 banks of memory, selectable by bits 6, 7 and 8 of port L (pin 44, 43 and 42), with port D's bit 7 (pin 38) acting as chip select. To enable external RAM, set SRE to one (ATmega2560+.pdf pg37 ff), select sector limit according to pg 38 as well as wait states. Given a Mega's timing, an older 120ns SRAM might just work with 2 wait states. An old 200ns EPROM won't. To not sacrifice more IO ports than necessary, a Mega provides the lower 8 bits of the external memory's address on the data bus, provides ALE (address latch enable) to tell an external latch to hold on to the data bits as part of the address. Going forward, "1" (one) is synomym to "high", and "0" (zero) to "low". Memory access takes place in this manner: 1. ALE is set high, telling the address latch to accept data. As long as ALE is high, the latch's output bits follow the latch's input bits. 2. Both OE (output enable) and WR (write) are high, causing the SRAM to stay passive 3. Address bits 8-15 change, address bits 0-7 become available as data bits 0-7, 4. ALE goes low, telling the address latch to no longer accept data. The address latch's output bits now contain whatever was present on the data bus at the time of the high-to-low transition. 5. For a read operation, OE goes active/low, WR stays high. SRAM places data on the bus, processor reads data after the cycles configured in SRW*. 6. For a write operation, WR goes active/low, OE stays high. SRAM accepts data from the bus after the amount of cycles configured in SRW* 7. Both OE and WR go back to high. Additional output bits can be used as additional address bits, allowing to switch between an arbitrary amount of banks. Some pointer magic (C is your friend) is required to (ab)use additional memory. One could either allocate memory by typecasting unsigned integer values to pointers directly (don't get confused), or tell reconfigure libc's dynamic memory management's variables to include expansion-provided memory, by setting these variables: Both __malloc_heap_start and __brkval point to the beginning of the memory region ot be used, __malloc_heap_end to the end, in this case the highest expansion-backed address. The "break" (__brkval) is the current end of the heap. In systems with an MMU, accessing memory between the break (i.e. the end of the heap) and the lowest address of the stack results in a segmentation violation, core dump, process termination, not so on an Arduino. When switching banks, _malloc_heap_start, __malloc_heap_end and __brkval need to be modified to properly reflect heap configuration in the new bank. Application code als needs to keep track which pointers are valid for each bank.