• rehsd

Arduino Due, 6502, & SPI... oh my...

Updated: Oct 24, 2021

Previously, I shared how I was using an Arduino Nano to connect a 6502 to a PC to transfer image data from the 6502 to the PC. See 6502 Print Screen to PC (SPI+Nano+Serial). In this configuration, I used a Versatile Interface Adapter (VIA) to communicate to an Arduino Nano with Serial Peripheral Interface (SPI). From the Nano, I used USB serial to communicate with the PC.

Initial design using a Nano
Initial design using a Nano

I also shared an example where I used this design to send sound configuration data from the PC to the 6502. See Dynamic Sound Editor and 6502 Audio PCB Complete!.

Example use of the design: Sending audio config from PC to 6502
Example use of the design: Sending audio config from PC to 6502
Arduino Due
Arduino Due

While the setup above worked fine, a limiting factor was the speed of the USB serial between the Nano and the PC. I could safely run 112,500 bps, and in some cases, get up to 460,800 bps. I wanted to find a faster option for transferring data between the 6502 and the PC. When researching options, I came across the Arduino Due. The Due has two USB ports. The first is a programming port that connects to an ATMEL 16U2 which acts as a USB-to-Serial converter (likely, comparable performance to the Nano USB serial). It also has a Native port that is directly connected to the USB host pins of the 32-bit ARM SAM3X microcontroller on the Due.


PC to Due Communication (USB)

The Due's Native port is capable of communicating at 480 Mbps and can be used as a virtual serial port. In Arduino code, instead of using the "Serial" object, the "SerialUSB" object is used.


For cases where I want to transfer data from the PC to the 6502 (e.g., sending sound configuration information from the PC to the 6502), I start by sending data from the PC to the Due.


Winforms C# Code

Populate a buffer and send it.

byte[] buffer = new byte[60];
buffer[0] = (byte)'C';
buffer[1] = (byte)'B';	
..
alPort myPort;
myPort = new SerialPort(portName, CONNECTION_RATE);
myPort.Open();
myPort.Encoding = Encoding.UTF8;
myPort.Write(buffer,0,buffer.Length);

Arduino Due Code

On the Due, I check to see if the bytes are available, using SerialUSB. If so, I read the bytes and store them in a buffer on the Due.

void loop()
{
    if (SerialUSB.available() >= 60) 
        {
            char buffer[60];
            SerialUSB.readBytes(buffer, 60);
        }
}

When data is received on the Due from the PC (to be sent to the 6502), I raise an event on the 6502's VIA.

    digitalWrite(VIA3_CB2_PIN, LOW);
    delay(INTERRUPT_ENABLE_TIME);
    digitalWrite(VIA3_CB2_PIN, HIGH);

6502 VIA to Due Communication (SPI)

When the VIA event is raised, I handle the event by calling out to the Due from the 6502 with SPI, fetching the data byte by byte.


6502 Assembly Code
VIA3_CB2_handler:       ;Arduino with SPI connection to 6502
    jsr SPI_Ard_StartSession        ;start SPI
    lda #SPI_ARD_CMD_GETSOUNDINFO   ;SPI command to send to Due
    sta SPI_ARD_Next_Command
    jsr SPI_Ard_SendCommand

    ;expecting 56 bytes of data after initial status byte
    ;first byte - 0 (status)
    jsr SPI_Ard_ReceiveByte

    ;next byte - 1
    jsr SPI_Ard_ReceiveByte
    sta TonePeriodCourseLA

    ;2
    jsr SPI_Ard_ReceiveByte
    sta TonePeriodCourseLB

    ;3
    jsr SPI_Ard_ReceiveByte
    sta TonePeriodCourseLC

    ...
    
    ;56
    jsr SPI_Ard_ReceiveByte
    sta EnvelopeShapeCycleR2
    
    jsr SPI_Ard_EndSession
    bit PORT3B

    lda #$01
    sta audio_data_to_write    ;set flag to do something with new data
Arduino Due Code

As I quickly found out, the Nano SPI code I was using was not compatible with the Due, and I had to refactor my code a bit. Apparently, there are no SPI slave libraries for the Due. If you're aware of an SPI slave library for Due, please let me know! I found some sample code that was helpful. I setup the Due with an event to handle incoming SPI data. Key snippets are below.

#include <SPI.h>
#include <stdint.h>
#include <Wire.h>
#define SPI0_INTERRUPT_NUMBER (IRQn_Type)24
#define BUFFER_SIZE 1000
#define SS 10		//slave select on pin 10

void SPI0_Handler(void);

void slaveBegin(uint8_t _pin) 
{
    // Setup the SPI Interrupt registers.
    NVIC_ClearPendingIRQ(SPI0_INTERRUPT_NUMBER);
    NVIC_EnableIRQ(SPI0_INTERRUPT_NUMBER);

    // Initialize the SPI device with Arduino default values
    SPI.begin(_pin);

    REG_SPI0_CR = SPI_CR_SWRST;     // reset SPI
    SPI.setBitOrder(MSBFIRST);

    // Setup interrupt
    REG_SPI0_IDR = SPI_IDR_TDRE | SPI_IDR_MODF | SPI_IDR_OVRES | 
    SPI_IDR_NSSR | SPI_IDR_TXEMPTY | SPI_IDR_UNDES;
    REG_SPI0_IER = SPI_IER_RDRF;

    // Setup the SPI registers.
    REG_SPI0_CR = SPI_CR_SPIEN;     // enable SPI
    REG_SPI0_MR = SPI_MR_MODFDIS;     // slave and no modefault
    REG_SPI0_CSR = SPI_MODE0;  // DLYBCT=0, DLYBS=0, SCBR=0, 8 bit xfer
}

void SPI0_Handler(void)
{
    byte b = 0;
    // Receive byte
    b = REG_SPI0_RDR;    
    HandleSPIbyte(b);
}

void setup()
{
    slaveBegin(SS);        // Setup the SPI as Slave
}

void HandleSPIbyte(byte recvByte)
{
     switch (currentCommand) {
        case CMD_GETSOUNDINFO:          //4
            if (bytesSent < 56)
            {
                sendByte = audio_data[bytesSent];
                bytesSent++;
                REG_SPI0_TDR = sendByte;
            }
      }
 }

As can be seen in the 6502 Audio PCB Complete! video, I am using the Due successfully for PC to 6502 communication. I am really liking communications performance of the Due.


Word of Caution: Most Arduinos (or at least the Mega and Nano that I usually use) work with 5 volt signals. The Due uses 3.3 volt signals. You will have communication issues between the VIA and Due if directly connecting VIA 5 volt outputs to Due 3.3 volt inputs. In my current setup, I am using a couple of simple voltage dividers to drop the 5v down to 3.3v for SCK and MOSI. For MISO, I am taking the 3.3v from the Due directly into the VIA, and it's handling it fine.

5v to 3.3v
5v to 3.3v with voltage dividers

While voltage dividers work, I don't think they are ideal. I will replace the voltage dividers with logic level converters, like these or these.


Another note... I have other devices (those three Nanos in the image above) connected to the same SCK, MOSI, and MISO (they have unique SS/CS/OE pins). The Nanos were pulling down signal levels enough to cause problems. Loading the following code on the three empty Nanos took care of the problem. It seems that the MISO pin being set to output, when the Nano isn't enabled through SS/CS/OE, causes issues. As I spin up some of these Nanos for new projects, I'll need to work through it. My guess is that if I properly initialize the SPI library on the Nanos, the proper pin states will be managed by the library.

#include<SPI.h>
#include <Wire.h>

void setup() {
    //pinMode(MISO, OUTPUT);
    pinMode(SS, INPUT_PULLUP);
    pinMode(MOSI, INPUT);
    pinMode(SCK, INPUT);
    //digitalWrite(MISO, LOW);
}

void loop() {

}

OLED Displays for Debugging

In the earlier image (or in the 6502 Audio PCB Complete! video), you'll notice that I have a pair of small 128x64 OLED displays connected to the Due. These displays have been absolutely great for debugging communications between the 6502 and PC. Currently, I use the right screen to display data received into the Due from the PC. I use the left screen to display data fetched from the Due by the 6502. Since there are so many places for me to mess up the data transfer (e.g., loading the byte array on the source, transferring the byte array to the Due, caching the byte array on the Due, sending the byte array over USB or SPI to the destination, or receiving and processing the byte array on the destination), being able to easily see that data (without sending it out to serial) has saved me a great deal of time.


More Info

For more details on the SPI expansion card for my 6502, check out 6502 SPI Expansion Card. I have shared the EasyEDA files for the SPI card schematic and PCB on my GitHub VGA-6502. My latest 6502 assembly code is available here. The Arduino DUE code is available here. The Dynamic Sound Editor (Winforms) code for the is available here.


Next Steps

I need to install a level shifter / converter for the 5v to 3.3v differences. I would like to see if I can further speed up communication by tracking down bottlenecks. I would also like to improve the robustness of all the code; up to this point, my focus has been basic functionality.


Postscript

I tried out this level converter. It seems like it can't move fast enough (see image below). I will try this high-speed converter in the coming week.

Level converter that doesn't appear to be able to move fast enough.
Level converter that doesn't appear to be able to move fast enough.

PPS

I put in this level converter, and it seems to be working well. It uses the TXS0108E. I am running CS/OEB, SCK, and MOSI through the level converter. MISO worked fine directly and didn't like the level converter, so I left it connected directly between the Due and SPI card. The converter requires a 5v source, 3.3v source, GND, and OEB. I tied into the reset circuit's OEB by connecting to pin 40 of the 6502. While I was at it, I connected the sound card PSGs into the system OEB; I had forgotten to do this earlier. You can see on the top PSG on the sound card, where I soldered OEB to pin 23. I need to do some more wiring cleanup, but good enough for now.

TXS0108E level converter that works well.
TXS0108E level converter that works well.
Updated setup as of 24 October 2021.
Updated setup as of 24 October 2021.

146 views0 comments