These removable non-volatile memory cartridges should work with the new Fruit Jam board and many other Adafruit microcontroller boards with I2C. The 3D printed shells hold a perma-proto PCB with a 4-pin magnetic connector and an I2C FRAM breakout board. To avoid I2C bus glitches, the base includes TCA4307 hot-swap I2C buffers and a PCA9546 4-channel I2C multiplexer. The multiplexer makes it possible to use two FRAM chips with the same I2C address at the same time.
The CircuitPython code for this project is just a simple demo of writing to and reading from FRAM using the byte array interface provided by the adafruit-circuitpython-fram library. To use these memory cartridges for game save files, you would need to come up with a way to encode whatever data you want to store into a byte array of 32KB or less.
Transparency note & shameless plug: Adafruit provided most of the parts I used for this guide (Thanks Adafruit!). Adafruit makes good stuff, please support them by buying from the Adafruit shop.
This guide is written for a build with two cartridge slots. But, if you only want one cartridge slot, you can omit the PCA9546 mux and adjust the order quantities for other items (see table below).
| Item | 2-slot Qty. | 1-slot Qty. |
|---|---|---|
| I2C FRAM breakout | 2 | 1 |
| Perma-proto PCB 3-pack | 2 | 1 |
| 4-pin Magnetic Connector | 2 | 1 |
| Qwiic / Stemma QT breakout | 2 | 1 |
| TCA4307 I2C buffer breakout | 2 | 1 |
| Short STEMMA QT / Qwiic cable | 4 | 1 |
| Long STEMMA QT / Qwiic cable | 1 | 1 |
| PCA9546 I2C mux breakout | 1 | 0 |
For the base, you will need:
| Item | Qty. |
|---|---|
| 10x10 swirly grid | 1 |
| 10x5 swirly grid | 1 |
| TT motor mount | 2 |
| M2.5 nylon screw kit | 1 |
Thread Rolling Screws
The 3D print for this project is designed for #2-28 thread rolling screws (pan head, 5/16 inch long). People often use fine thread metric machine screws for 3D prints, but I wanted something that was intended for use in plastic. These thread rolling screws use a coarse thread pitch with a profile that's designed to self-tap into plastic without creating chips. These work great with 2mm holes.
Microcontroller board
This should work with many Adafruit Dev boards as long as they have a STEMMA QT / Qwiic I2C port and mounting holes (to avoid shorts on the swirly grid). I tested the code on a rev B Fruit Jam prototype and an ESP32-S3 Feather.
Tools & Materials
You will need:
- Soldering iron and solder
- Diagonal flush cutters
- Needle nose pliers
- Wire strippers
- Small Phillips head screwdriver
- Kapton tape (or good quality electrical tape)
- Hookup wire such as Adafruit #3174
- 3D printer (e.g. P1S, X1C, etc) and PLA filament
- PCB holder vise with channels long enough to hold two boards in the same plane
The PCB vise is important for making sure that the perma-proto PCBs and magnetic connectors are aligned properly. In particular, if the magnetic connectors get soldered at a weird angle, the memory card assembly may not fit into the 3D printed shell, or the cartridge may sit at an angle when it is connected to the base.
Soldering
1. Line up two PCBs in the vise, leaving a gap for the magnetic connector. Make the vise jaws tight enough to avoid dropping the boards but loose enough that you can slide one board to adjust the size of the gap.
2. Carefully seat the pins of the magnetic connectors into the holes of the perma-proto boards. Make sure the 4 pins are centered on the short side of the perma-proto boards. Gently slide the boards in the vise to get the right gap so the magnetic connector pins go in as far as possible. Once the connector is seated, make sure the vise jaws are parallel, then tighten the jaws.
CAUTION: This is one of the most important steps in the whole assembly procedure. Please be sure the vise jaws are parallel and the magnetic connector is fully seated against both boards before soldering. The cartridge shell's opening allows 1 mm of clearance for variations in the magnetic connector's alignment. You can be a little off, but not way off. (check pictures below for examples of what good alignment looks like)
3. Solder the magnetic connector pins from the top side of the boards. Then, on the board that has the magnetic connector with pins (as opposed to the one with pads), place a Qwiic / STEMMA QT breakout board on header pins next to the magnetic connector. To keep the board level, you can take a piece of header pin, cut off the leads on the top side, and place it into the board underneath one end of the breakout.
4. Solder the header pins on the top side of the I2C breakout board. Pull out the piece of header that was propping up the other side of the board and turn it on its side (so you can remove it later). Insert the I2C breakout with its header pins aligned to the magnetic connector pins, then tape it to the perma-proto PCB.
5. Turn the perma-proto board assembly over in the vise to prepare for bending and soldering the header pins.
6. Use needle nose pliers (or the tip of a flat-head screwdriver, the side of your diagonal flush cutter jaws, or whatever) to bend the header pins toward the magnetic connector pins. Solder the header pins and bridge them to the corresponding magnetic connector pins. Once the soldering is done, use diagonal flush cutters to trim the pins.
CAUTION: Be careful to avoid unintentional solder bridges.
7. Flip the perma-proto PCB assembly over in the vise (remove tape and the sideways piece of header). Now, on the perma-proto board whose magnetic connector has the pads (as opposed to the pogo pins), prepare hookup wires as shown in the picture below. Remove about 1/4" of insulation from each end with your wire strippers, then form the leads with needle nose pliers. Your goal is to connect the VCC, GND, SCL, and SDA pins on the FRAM breakout to the matching pins on the magnetic connector. Once the wires are ready, secure them with tape.
Note: the FRAM board has a WP pin between GND and SCL. Be sure to skip that row as you arrange the hookup wire.
8. Flip the PCB assembly over in the vise so you can see the back. Bend the ends of the hookup wire next to the magnetic connector so that they touch the corresponding magnetic connector pins. Trim the excess hookup wire (see picture below).
9. For the hookup wire ends next to the magnetic connector, solder the wires to their corresponding magnetic connector pins. For the hookup wires in the breadboard area, just solder each wire into place in its breadboard row. Trim all the protruding wire ends with your diagonal flush cutters.
CAUTION: Inspect carefully for unintended solder bridges or incomplete connections and clean them up if needed. For example, in the picture below, I still need to clean up the top two breadboard rows (not enough solder)
10. Flip the perma-proto assembly over in the vise so you can see the top (remove tape). Prepare a row of header pins for the FRAM breakout board, lining up the VCC, GND, SCL, and SDA pins with the corresponding wires. Put a piece of header turned on its side on the opposite side of the board to provide mechanical support during soldering.
11. Put the FRAM breakout board on the header pins and tape it in place. Solder the header pins onto the FRAM breakout board.
12. Flip the perma-proto assembly over in the vise so you can see the back. Solder the header pins. Trim the excess pin length with diagonal flush cutters. After you finish soldering and trimming pins, Take the perma-proto assembly out of the vise. Remove tape and the sideways piece of header.
Use the download buttons below to get files for 3D printing the cart shell.
If you have a Bambu Lab Printer, you can try the .3mf build plate (PLA, standard 0.2mm layer height preset modified for cubic infill and slower maximum acceleration).
If you want to set up your own print job, get both the front and back cart shell STL files.
If you want to edit the model in Blender, get the .blend file.
Once the print is done, your cart shell should look like this:
Final Assembly
Screw Shell Together
Put the FRAM card assembly in one half of the cart shell. If you start with the back half of the shell, the top of the perma-proto PCB should face outward, like this:
Or, if you start with the top half of the shell (the one with the arrow), the bottom of the perma-proto board should face outward, like this:
Put the other half of the cart shell on and prepare four #2-28 x 5/16 inch thread rolling screws.
Double check that the shell halves and FRAM card assembly are aligned properly, then screw the shell together. The result should look like this:
Swirly Grid Base
- Assemble the two swirly grids with the L-bracket TT motor mounts and M2.5 nylon screws with nuts.
- Mount the perma-proto boards, TCA4307 I2C buffer breakouts, and PCA9546 I2C mux breakout to the swirly grids with M2.5 nylon standoffs and screws.
- Mount your choice of microcontroller dev board to the swirly grid (dev board not shown in picture)
- Connect everything up with STEMMA QT / Qwiic jumper cables as shown in the pictures below. I used 50 mm cables from the breakout to the TCA4307 and from the TCA4307 to the PCA9545. I used a 100mm cable from the PCA9545 to the microcontroller board. NOTE: Use PCA9545 I2C mux port 0 for the left cart slot and port 1 for the right cart slot.
To slot a memory cart, line it up with the arrow on the bottom front, then move it near one of the magnetic connectors on the base. The magnets should pull the connector into place.
To remove a memory cart, tilt it sideways slightly, then pull.
CAUTION: The I2C bus GND and VCC lines are on adjacent pogo pins of the magnetic connectors. Be careful not to accidentally short them together.
CircuitPython Code
This CircuitPython code should work on most Adafruit microcontroller boards as long as they have a STEMMA QT I2C port. I tested this on an ESP32S3 Feather running CircuitPython 9.2.7 and a Fruit Jam rev B prototype board running the 10.0.0-alpha.3 build.
It isn't always this way across major version releases, but it currently happens to be the case that libraries from the 9.x bundle run fine on 10.x. Since 10.0.0 hasn't been released yet, I'm only including the 9.x libraries in my bundle file.
Download & Install Project Bundle
To copy the project bundle files to your CIRCUITPY drive:
-
Download the project bundle .zip file using the Download Project Bundle button below.
-
Expand the zip file by opening it, or use
unzipin a Terminal. The zip archive should expand to a folder. When you open the folder, it should contain aREADME.txtfile and aCircuitPython 9.xfolder. -
Open the CircuitPython 9.x folder and copy all of its contents to your CIRCUITPY drive.
To learn more about copying libraries to your CIRCUITPY drive, check out the CircuitPython Libraries section of the Welcome to CircuitPython! learn guide.
CIRCUITPY/code.py
This is what the code looks like. It's built to automatically start up in 1 slot mode or 2 slot mode depending on whether you connect a PCA9546A I2C multiplexer to the I2C bus.
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: Copyright 2025 Sam Blenny
#
# This presents a menu on the serial port for reading, writing, or erasing the
# initial bytes of an I2C FRAM chip. This is meant to be used with two TCA4307
# hot-swap I2C buffers on channels 0 and 1 of a PCA9546A multiplexer. The
# buffers each go to a magnetic pogo pin connector. The code anticipates that
# the fram chip might appear or disappear at any time.
#
# Related Docs:
# - https://learn.adafruit.com/adafruit-pca9546-4-channel-stemma-qt-multiplexer
# - https://learn.adafruit.com/adafruit-tca4307
# - https://learn.adafruit.com/adafruit-i2c-fram-breakout
# - https://docs.circuitpython.org/projects/tca9548a/en/latest/
#
from board import STEMMA_I2C
from micropython import const
from adafruit_fram import FRAM_I2C
from adafruit_tca9548a import PCA9546A
LIMIT = const(64)
I2C_ADDR_FRAM = const(0x50)
I2C_ADDR_MUX = const(0x70)
MENU_2_SLOTS = f"""Menu:
1 Scan for FRAM carts and print their header bytes
2 Write message to slot 0 FRAM (max {LIMIT} bytes)
3 Write message to slot 1 FRAM (max {LIMIT} bytes)
4 Erase first {LIMIT} bytes (set to 0) of slot 0 FRAM
5 Erase first {LIMIT} bytes (set to 0) of slot 1 FRAM
choice [1]: """
MENU_1_SLOT = f"""Menu:
1 Print FRAM cart header bytes
2 Write message to FRAM (max {LIMIT} bytes)
3 Erase first {LIMIT} bytes of FRAM
choice [1]: """
NO_CART_ERROR = "Unable to access FRAM"
def hexdump(data):
row_len = 16
for row_start in range(0, len(data), row_len):
hex_buf = []
ascii_buf = []
for b in data[row_start:row_start + row_len]:
hex_buf.append('%02x' % b)
ascii_buf.append(chr(b) if 32 <= b <= 127 else '.')
if len(hex_buf) < row_len:
hex_buf.append(' ' * row_len - len(hex_buf))
print(' ', ' '.join(hex_buf), ' ', ''.join(ascii_buf))
def read(fram, start=0, end=LIMIT):
# Read + hexdump a range of bytes from FRAM chip on selected mux channel.
# This may raise exceptions if FRAM is not present or range not valid.
hexdump(fram[start:end])
def write(fram):
# Prompt for message and write it to an FRAM cart on selected mux channel
# This may raise exceptions if FRAM is not present.
msg = input(f"Message to write? (max {LIMIT} chars): ")
clipped_msg = msg.encode()[:LIMIT]
fram[0:len(clipped_msg)] = clipped_msg
print("ok")
def erase(fram, start=0, end=LIMIT):
# Erase a range of bytes of FRAM chip on selected mux channel
for i in range(LIMIT):
fram[i] = 0
print("ok")
def mux_probe(mux_channel, addr):
# Return true if device with address addr exists on specified mux channel
present = False
if mux_channel.try_lock():
present = mux_channel.probe(addr)
mux_channel.unlock()
return present
def probe_without_mux(i2c, addr):
# Return true if device with address addr exists on i2c bus
present = False
i2c.try_lock()
try:
i2c.writeto(addr, b'')
present = True
except OSError:
pass
finally:
i2c.unlock()
return present
def scan_slot(mux, channel):
# Check for FRAM cart on selected channel, hexdump header bytes if found
present = mux_probe(mux[channel], I2C_ADDR_FRAM)
if not present:
print(f"Slot {channel}: Empty")
else:
print(f"Slot {channel} fram_bytes[0:{LIMIT}]:")
fram = FRAM_I2C(mux[channel])
read(fram, 0, LIMIT)
def two_slot_main_loop(mux):
while True:
# Always start by scanning for carts and printing their header bytes
print()
scan_slot(mux, 0)
print()
scan_slot(mux, 1)
print()
# Show menu prompt and parse the response
n = input(MENU_2_SLOTS)
try:
if n == "1":
# Skip to next loop iteration to print the header bytes
continue
if n == "2":
# Write to slot 0
fram = FRAM_I2C(mux[0])
write(fram)
if n == "3":
# Write to slot 1
fram = FRAM_I2C(mux[1])
write(fram)
elif n == "4":
# Erase slot 0
fram = FRAM_I2C(mux[0])
erase(fram)
elif n == "5":
# Erase slot 1
fram = FRAM_I2C(mux[1])
erase(fram)
except OSError:
print(NO_CART_ERROR)
except ValueError:
print(NO_CART_ERROR)
def one_slot_main_loop(i2c):
while True:
# Start by checking for cart and trying to print its header bytes
print()
if not probe_without_mux(i2c, I2C_ADDR_FRAM):
print("Slot: Empty")
else:
try:
fram = FRAM_I2C(i2c)
print(f"fram_bytes[0:{LIMIT}]:")
read(fram, 0, LIMIT)
except ValueError as e:
raise e
print()
# Show menu prompt and parse the response
n = input(MENU_1_SLOT)
try:
if n == "1":
# Skip to next loop iteration to print the header bytes
continue
if n == "2":
# Write to FRAM
if probe_without_mux(i2c, I2C_ADDR_FRAM):
fram = FRAM_I2C(i2c)
write(fram)
else:
print(NO_CART_ERROR)
elif n == "3":
# Erase slot FRAM
if probe_without_mux(i2c, I2C_ADDR_FRAM):
fram = FRAM_I2C(i2c)
erase(fram)
else:
print(NO_CART_ERROR)
except OSError:
print(NO_CART_ERROR)
except ValueError:
print(NO_CART_ERROR)
def main():
i2c = STEMMA_I2C()
use_one_slot = False
try:
mux = PCA9546A(i2c)
two_slot_main_loop(mux)
except OSError as e:
print("PCA9546A MUX NOT FOUND. STARTING IN ONE SLOT MODE.")
print()
use_one_slot = True
if use_one_slot:
one_slot_main_loop(i2c)
main()
Serial Console Logs
These are some examples of running the code with the hardware configured for either 2 slot mode (with a mux) or 1 slot mode (no mux and only one TCA4307).
Two Slots with Mux
Here's a serial console log where I:
1. Start with both slots are empty
2. Slot gray FRAM cart into slot 0
3. Slot green FRAM cart into slot 1
4. Erase both carts
5. Set a new message for both carts
6. Remove both carts
7. Slot the carts back on the opposite slots
The lines with Menu: ... choice [1]: are prompts to input a number to make a selection from the menu of actions.
Slot 0: Empty Slot 1: Empty Menu: 1 Scan for FRAM carts and print their header bytes 2 Write message to slot 0 FRAM (max 64 bytes) 3 Write message to slot 1 FRAM (max 64 bytes) 4 Erase first 64 bytes (set to 0) of slot 0 FRAM 5 Erase first 64 bytes (set to 0) of slot 1 FRAM choice [1]: 1 Slot 0 fram_bytes[0:64]: 54 68 69 73 20 69 73 20 74 68 65 20 67 72 61 79 This is the gray 20 63 61 72 74 00 00 00 00 00 00 00 00 00 00 00 cart........... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Slot 1: Empty Menu: 1 Scan for FRAM carts and print their header bytes 2 Write message to slot 0 FRAM (max 64 bytes) 3 Write message to slot 1 FRAM (max 64 bytes) 4 Erase first 64 bytes (set to 0) of slot 0 FRAM 5 Erase first 64 bytes (set to 0) of slot 1 FRAM choice [1]: 1 Slot 0 fram_bytes[0:64]: 54 68 69 73 20 69 73 20 74 68 65 20 67 72 61 79 This is the gray 20 63 61 72 74 00 00 00 00 00 00 00 00 00 00 00 cart........... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Slot 1 fram_bytes[0:64]: 54 68 69 73 20 69 73 20 74 68 65 20 67 72 65 65 This is the gree 6e 20 63 61 72 74 00 00 00 00 00 00 00 00 00 00 n cart.......... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Menu: 1 Scan for FRAM carts and print their header bytes 2 Write message to slot 0 FRAM (max 64 bytes) 3 Write message to slot 1 FRAM (max 64 bytes) 4 Erase first 64 bytes (set to 0) of slot 0 FRAM 5 Erase first 64 bytes (set to 0) of slot 1 FRAM choice [1]: 4 ok Slot 0 fram_bytes[0:64]: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Slot 1 fram_bytes[0:64]: 54 68 69 73 20 69 73 20 74 68 65 20 67 72 65 65 This is the gree 6e 20 63 61 72 74 00 00 00 00 00 00 00 00 00 00 n cart.......... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Menu: 1 Scan for FRAM carts and print their header bytes 2 Write message to slot 0 FRAM (max 64 bytes) 3 Write message to slot 1 FRAM (max 64 bytes) 4 Erase first 64 bytes (set to 0) of slot 0 FRAM 5 Erase first 64 bytes (set to 0) of slot 1 FRAM choice [1]: 5 ok Slot 0 fram_bytes[0:64]: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Slot 1 fram_bytes[0:64]: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Menu: 1 Scan for FRAM carts and print their header bytes 2 Write message to slot 0 FRAM (max 64 bytes) 3 Write message to slot 1 FRAM (max 64 bytes) 4 Erase first 64 bytes (set to 0) of slot 0 FRAM 5 Erase first 64 bytes (set to 0) of slot 1 FRAM choice [1]: 2 Message to write? (max 64 chars): GRAY CART ok Slot 0 fram_bytes[0:64]: 47 52 41 59 20 43 41 52 54 00 00 00 00 00 00 00 GRAY CART....... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Slot 1 fram_bytes[0:64]: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Menu: 1 Scan for FRAM carts and print their header bytes 2 Write message to slot 0 FRAM (max 64 bytes) 3 Write message to slot 1 FRAM (max 64 bytes) 4 Erase first 64 bytes (set to 0) of slot 0 FRAM 5 Erase first 64 bytes (set to 0) of slot 1 FRAM choice [1]: 3 Message to write? (max 64 chars): GREEN CART ok Slot 0 fram_bytes[0:64]: 47 52 41 59 20 43 41 52 54 00 00 00 00 00 00 00 GRAY CART....... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Slot 1 fram_bytes[0:64]: 47 52 45 45 4e 20 43 41 52 54 00 00 00 00 00 00 GREEN CART...... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Menu: 1 Scan for FRAM carts and print their header bytes 2 Write message to slot 0 FRAM (max 64 bytes) 3 Write message to slot 1 FRAM (max 64 bytes) 4 Erase first 64 bytes (set to 0) of slot 0 FRAM 5 Erase first 64 bytes (set to 0) of slot 1 FRAM choice [1]: 1 Slot 0: Empty Slot 1: Empty Menu: 1 Scan for FRAM carts and print their header bytes 2 Write message to slot 0 FRAM (max 64 bytes) 3 Write message to slot 1 FRAM (max 64 bytes) 4 Erase first 64 bytes (set to 0) of slot 0 FRAM 5 Erase first 64 bytes (set to 0) of slot 1 FRAM choice [1]: 1 Slot 0 fram_bytes[0:64]: 47 52 45 45 4e 20 43 41 52 54 00 00 00 00 00 00 GREEN CART...... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Slot 1 fram_bytes[0:64]: 47 52 41 59 20 43 41 52 54 00 00 00 00 00 00 00 GRAY CART....... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Menu: 1 Scan for FRAM carts and print their header bytes 2 Write message to slot 0 FRAM (max 64 bytes) 3 Write message to slot 1 FRAM (max 64 bytes) 4 Erase first 64 bytes (set to 0) of slot 0 FRAM 5 Erase first 64 bytes (set to 0) of slot 1 FRAM choice [1]:
One Slot No Mux
Here's a serial console log where I:
1. Start with the slot empty
2. Slot gray FRAM cart
3. Erase gray cart then change its message
3. Remove gray cart
4. Slot green cart then change its message
The lines with Menu: ... choice [1]: are prompts to input a number to make a selection from the menu of actions.
PCA9546A MUX NOT FOUND. STARTING IN ONE SLOT MODE. Slot: Empty Menu: 1 Print FRAM cart header bytes 2 Write message to FRAM (max 64 bytes) 3 Erase first 64 bytes of FRAM choice [1]: 1 fram_bytes[0:64]: 47 52 41 59 20 43 41 52 54 00 00 00 00 00 00 00 GRAY CART....... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Menu: 1 Print FRAM cart header bytes 2 Write message to FRAM (max 64 bytes) 3 Erase first 64 bytes of FRAM choice [1]: 3 ok fram_bytes[0:64]: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Menu: 1 Print FRAM cart header bytes 2 Write message to FRAM (max 64 bytes) 3 Erase first 64 bytes of FRAM choice [1]: 2 Message to write? (max 64 chars): This is the gray cart ok fram_bytes[0:64]: 54 68 69 73 20 69 73 20 74 68 65 20 67 72 61 79 This is the gray 20 63 61 72 74 00 00 00 00 00 00 00 00 00 00 00 cart........... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Menu: 1 Print FRAM cart header bytes 2 Write message to FRAM (max 64 bytes) 3 Erase first 64 bytes of FRAM choice [1]: 1 Slot: Empty Menu: 1 Print FRAM cart header bytes 2 Write message to FRAM (max 64 bytes) 3 Erase first 64 bytes of FRAM choice [1]: 1 fram_bytes[0:64]: 47 52 45 45 4e 20 43 41 52 54 00 00 00 00 00 00 GREEN CART...... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Menu: 1 Print FRAM cart header bytes 2 Write message to FRAM (max 64 bytes) 3 Erase first 64 bytes of FRAM choice [1]: 3 ok fram_bytes[0:64]: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Menu: 1 Print FRAM cart header bytes 2 Write message to FRAM (max 64 bytes) 3 Erase first 64 bytes of FRAM choice [1]: 2 Message to write? (max 64 chars): This is the green cart ok fram_bytes[0:64]: 54 68 69 73 20 69 73 20 74 68 65 20 67 72 65 65 This is the gree 6e 20 63 61 72 74 00 00 00 00 00 00 00 00 00 00 n cart.......... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Menu: 1 Print FRAM cart header bytes 2 Write message to FRAM (max 64 bytes) 3 Erase first 64 bytes of FRAM choice [1]:
Notes on 3D Modeling in Blender
After trying several different 3d modeling tools, I've come to really like working in Blender. In particular, by using geometry node modifiers, it's possible to get precise control over complex geometry using dimensions computed from other objects. For example, here's a screenshot showing some of the geometry nodes involved in making the cavity around the perma-proto PCB (space for the FRAM breakout):
Here are a few more Blender screenshots showing orthographic wireframe views of my model:
This page (I2C FRAM Memory Carts) was last updated on May 03, 2025.