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.
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!.
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.
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.
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.
Comments