Arduino Nano based program loader for 8bit CPU

August 12, 2022

As you might know from a previous post, I built Ben Eater’s 8bit TTL CPU on breadboards in the past. The CPU is fully programmable with 16 bytes of memory and 11 Op Codes that can be used to write programs, one instruction per byte. This programming has to be done manually using DIP switches every time the device is powered on. It is a bit tedious as you have to enter instructions in binary for each memory location by hand. I have been learning PCB design and would like to convert this project to a PCB build in the near future. One of the things I would like to add is a program loader that can automatically load programs into the memory on boot. Before implementing this on a PCB, I wanted to have a working prototype that connects with my Breadboard CPU.

Working build using an Arduino Nano, 2x 74HC595 Shift Register ICs, one I2C OLED module, one breadboard and some wires.
Pin Mapping

Pin Mapping

Arduino
D2 to Shift Register 1 Pin 14
D3 to Shift Register 2 Pin 14
D4 to Shift Register 1 Pin 12
D5 to Shift Register 2 Pin 12
D6 to Shift Register 1 Pin 11
D7 to Shift Register 2 Pin 11
A0 to Push Button input with 10K pulldown resistor
A1 to Output 0 on 74LS138 Decoder
A2 to CPU_RESET
A3 to MEM_RESET
A4 to OLED I2C SDA
A5 to OLED I2C SCL
A6 to the clock enable/disable button
A7 to the programming/run mode button

Shift Register 1
Pin 15 to Data DIP0 switch
Pin 1 to Data DIP1 switch
Pin 2 to Data DIP2 switch
Pin 3 to Data DIP3 switch
Pin 4 to Data DIP4 switch
Pin 5 to Data DIP5 switch
Pin 6 to Data DIP6 switch
Pin 7 to Data DIP7 switch

Shift Register 2
Pin 15 to Address DIP0 switch
Pin 1 to Address DIP1 switch
Pin 2 to Address DIP2 switch
Pin 3 to Address DIP3 switch

Note: Vin pin on the Arduino can optionally be connected to a +7-12 VDC input. Please ensure Vin and USB are not both connected at the same time.

You can find the most up-to-date code on my GitHub Repo. I use a 3rd party platform called PlatformIO for writing Arduino programs in VS Code. But you can simply copy the code from src/main.cpp and run that in the Arduino IDE.

The data to be programmed to the memory is stored in a byte array named code (in decimal format). 16 values are programmed in 16 memory locations, each value is converted to binary in their respective function call. The below code adds a number (saved in memory location 15 i.e. 1111) repeatedly to itself until the upper limit (255) is reached, it then repeatedly subtracts that value from itself until it reaches 0, and it keeps looping.

byte code[] = {224, 47, 116, 96, 63, 224, 128, 100, 0, 0, 0, 0, 0, 0, 0, 1};

Memory locationArray valueBinary valueOp Codes
00 (0000)2241110 0000OUT
01 (0001)470010 1111ADD 15
02 (0010)1160111 0100JC 4
03 (0011)960110 0000JMP 0
04 (0100)630011 1111SUB 15
05 (0101)2241110 0000OUT
06 (0110)1281000 0000JZ 0
07 (0111) 100 0110 0100JMP 4
08 (1000)   
09 (1001)   
10 (1010)   
11 (1011)   
12 (1100)   
13 (1101)   
14 (1110)   
15 (1111)1XXXX 0001 
#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>

Adafruit_SSD1306 display(-1);

//control pins
#define CPU_RESET A2
#define MEM_WRITE A3
#define SR1_INPUT 2
#define SR2_INPUT 3
#define SR1_CLOCK 4
#define SR2_CLOCK 5
#define SR1_LATCH 6
#define SR2_LATCH 7

//read pins
#define CONFIRM_BUTTON A0
#define COUNTER_0 A1
#define CLOCK_STOP A6
#define PROG_MODE A7

//constants
#define PULSE_DURATION 10

//variables
int confirmButton = 0;
bool programMode = false;

//reset all modules
void resetCPU(){
 
 //CPU reset attempt#1
 //Serial.println("\nCPU Reset Signal");
 display.clearDisplay();
 display.setCursor(0,0);
 display.println("CPU Reset Signal");
 //display.display();
 digitalWrite(CPU_RESET, HIGH);
 delay(PULSE_DURATION);
 digitalWrite(CPU_RESET, LOW);

 //Check if CPU reset worked properly and repeat if necessary.
 int resetCounter = 2;
 while(digitalRead(COUNTER_0)){
  //Serial.println("CPU Reset Attempt#"+resetCounter);
  display.println("CPU Reset Attempt#"+resetCounter);
  //display.display();
  resetCounter++;
  digitalWrite(CPU_RESET, HIGH);
  delay(PULSE_DURATION);
  digitalWrite(CPU_RESET, LOW);
 }
 //Serial.println("CPU Reset Successful");
 display.println("CPU Reset Successful");
 display.display();
 delay(900);
}

//shift 4 bits to SR2 and latch to memory address register.
void setMemoryAddress(byte address){
 
 digitalWrite(SR2_LATCH, LOW);
 shiftOut(SR2_INPUT, SR2_CLOCK, MSBFIRST, address);
 digitalWrite(SR2_LATCH, HIGH);

 String outputText = "";
 for (int i = 0; i < 4; i++){
  if(address & 1)
   outputText = "1" + outputText;
  else
   outputText = "0" + outputText;
  address = address >> 1;
 }
 //Serial.print(outputText+": ");

}

//shift 8 bits to SR1 and latch to memory data.
void writeToRAM(byte data){
 
 digitalWrite(SR1_LATCH, LOW);
 shiftOut(SR1_INPUT, SR1_CLOCK, MSBFIRST, data);
 digitalWrite(SR1_LATCH, HIGH);
 
 String outputText = "";
 for (int i = 0; i < 8; i++){
  if(data & 1)
   outputText = "1" + outputText; 
  else
   outputText = "0" + outputText;
  data = data >> 1;
 }
 //Serial.println(outputText);
 pinMode(MEM_WRITE, OUTPUT);
 digitalWrite(MEM_WRITE, LOW);
 delay(PULSE_DURATION);
 pinMode(MEM_WRITE, INPUT);
}

void setup() {

 //initialize with the I2C addr 0x3C
 display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  

 //clear the buffer.
 display.clearDisplay();

 display.setTextSize(1);
 display.setTextColor(WHITE);

 //initalize serial connection
 Serial.begin(115200);
 Serial.println();

 //initialize pins
 digitalWrite(CPU_RESET, LOW);
 digitalWrite(SR1_INPUT, LOW);
 digitalWrite(SR2_INPUT, LOW);
 digitalWrite(SR1_CLOCK, LOW);
 digitalWrite(SR2_CLOCK, LOW);
 digitalWrite(SR1_LATCH, LOW);
 digitalWrite(SR2_LATCH, LOW);
 pinMode(CPU_RESET, OUTPUT);
 pinMode(SR1_INPUT, OUTPUT);
 pinMode(SR2_INPUT, OUTPUT);
 pinMode(SR1_CLOCK, OUTPUT);
 pinMode(SR2_CLOCK, OUTPUT);
 pinMode(SR1_LATCH, OUTPUT);
 pinMode(SR2_LATCH, OUTPUT);
 
 pinMode(CONFIRM_BUTTON, INPUT);
 pinMode(MEM_WRITE, INPUT);
 pinMode(COUNTER_0, INPUT);
 pinMode(CLOCK_STOP, INPUT);
 pinMode(PROG_MODE, INPUT);

 //notify user to disable the clock and switch to programming mode.
 //program execution will not continue unless these conditions are satisfied.
 programMode = (analogRead(CLOCK_STOP) < 210 && analogRead(PROG_MODE) < 210) ? true : false;
 if(!programMode){
  //Serial.println("Please switch Clock to manual and Memory to programming mode.");
  display.setCursor(0,0);
  display.println("Please switch Clock\nto manual and Memory to programming mode.");
  display.display();
 }
 while(!programMode){
  programMode = (analogRead(CLOCK_STOP) < 210 && analogRead(PROG_MODE) < 210) ? true : false;
 }

 //wait for user to ensure all DIP switches are in the ON (HIGH) position. These switches
 //are inverted in Ben's design, hence moving them to the UP position disconnects them.
 
 //Serial.println("Ensure ALL DIP switches are in the ON (HIGH) position.\nPress button to continue.");
 display.clearDisplay();
 display.setCursor(0,0);
 display.println("Ensure ALL DIP\nswitches are in the\nON (HIGH) position.\n\nPress button to\ncontinue.");
 display.display();

 //loop until the user presses the button.
 confirmButton = digitalRead(CONFIRM_BUTTON);
 while(confirmButton != 1){
  confirmButton = digitalRead(CONFIRM_BUTTON);
 }

 resetCPU();
 display.clearDisplay();
 display.setCursor(0,0);

 //disable memory address dip switch input.
 //Serial.println("\nProgramming the memory");
 display.println("Programming the\nmemory");
 byte code[] = {224, 47, 116, 96, 63, 224, 128, 100, 0, 0, 0, 0, 0, 0, 0, 1};
 for (int address = 0; address < 16; address++){
  setMemoryAddress(address);
  writeToRAM(code[address]);
  display.print(".");
  display.display();
  delay(10);
 }

 //disable shift register latches, and reset all modules again.
 digitalWrite(SR1_LATCH, LOW);
 digitalWrite(SR2_LATCH, LOW);
 resetCPU();
 //Serial.println("\nPlease switch to run mode and enable the clock...");

 programMode = false;
}

void loop() {
   programMode = (analogRead(CLOCK_STOP) > 210 && analogRead(PROG_MODE) > 210) ? true : false;
 if(programMode){
  display.clearDisplay();
  display.setCursor(40,30);
  display.println("RUN MODE");
  display.display();
 }
 else{
  display.clearDisplay();
  display.setCursor(0,0);
  display.println("Please switch to run\nmode and enable the\nclock...");
  display.display();
 }
 delay(100);
} 
Posted in UncategorizedTags:
Related Posts

I learned Schematic and PCB design using Altium Designer at BCIT. My plan was to do some more practice over the Summer. To my surprise, a few weeks after the end of the course our accounts were disabled even though I thought we had…

This is my second Ben Eater project. In this video series, Ben talks about how VGA signals work and walks us through building a circuit that provides the correct timing of sync signals so that a monitor recognizes the signal and displays an image…

A bench power supply is an essential tool for all electronics hobbyists. Building one is the right of passage for every electrical engineer. My BCIT program included a 4-week course ELEX 1102: Electronics Fabrication Tools and Techniques, in which we built a very basic…

Write a comment