A Tribute to the AY-3-8910

Desk of contents
UPDATE: this submit was initially a lot shorter and revealed as “Half 1” on 30 Jan 2024, however I’ve since (10 Feb 2024) added extra content material to match the present state of the undertaking as a complete. Additional progress on this undertaking shall be made as new posts nonetheless.
What’s the AY-3-8910?
I am fairly a fan of the outdated AY-3-8910 synthesizer chip. I’ve used one earlier than within the psc, however questioned what a totally AY-only synth is likely to be like.
I solely had 2 of those chips till lately, one for the psc
and one other … spare? I did not need to disassemble the psc
to make the following undertaking so checked out buying some extra. I discovered a list on AliExpress for lots of 10 for simply ÂŁ7. I wasn’t certain if this was a legit itemizing, however determined the affordable worth was price a bet. Some time later, a bundle arrived from China and inside was 10 chips accurately labelled as AY-3-8910
.
This was a pleasant begin, however I nonetheless wasn’t satisfied that these have been actual chips as they appear very clear and new and I used to be certain these chips have not been manufactured for a protracted whereas. These chips additionally all look considerably totally different from one another. The print markings high quality varies and the mouldings and markings on the undersides differ as properly.
So, I picked the primary chip off the stack, rigged up a clone of the psc
code with one other ESP32-S3, attached the management strains and waited to see if the chip really behaved like an AY. It seems not less than two of them are completely actual however I have never examined all of them but. So, we’re good to go along with planning a completely AY synthesizer with not less than 2 chips.
By comparability here is the brand new chips subsequent to considered one of my outdated classic ones, marked GI for Normal Devices:
Limitations of the AY
The AY chips produce solely sq. waves. This can be a good begin for chip-tune vibes however I feel this synth may do with a bit extra flavour. For my analogue synth I’ve constructed some Voltage Managed Filters and like how they’ll modify the AY sound, so I feel we may make some extra VCFs and possibly have one per AY output.
Every AY has 3 oscillators and a noise generator; but it surely has solely 3 audio outputs. Every oscillator is allotted to its personal output, however the noise generator may be blended into any or all the outputs. So, we do not have as a lot management over the noise generator as we do the oscillators, however I feel we will work with that. I feel we should always combine all the audio outputs collectively into 1 or 2 outputs for the synth anyway.
The multi-chip chip-tune synth
Three oscillators is good, however as a result of I’ve a great deal of these chips now, it begs the query of precisely what number of may be made to work collectively. Every AY chip requires the next enter indicators:
- 1x clock
- 8x information strains
- 1x bus course line
- 1x bus management line
It seems that for a number of chips underneath the management of the identical microcontroller, the clock, information strains, and bus course line may be shared to all the chips. Solely the bus management line is exclusive per chip and acts like a conventional chip choose line. So, the whole variety of microcontroller outputs we’d like for N
AY chips is 10 + N
.
With the concept of a filter per audio output, we may also want management voltages for every filter. We due to this fact want 3N
management analogue voltages. Most microcontrollers haven’t got very many usable DAC outputs, and often they’re fairly low decision, so we are going to in all probability want some devoted DAC units connected to the microcontroller. I’ve settled on utilizing AD5328 units. These use a 3-wire SPI interface however present 8 analogue voltages every with 12-bit decision. Every extra DAC requires just one extra chip choose sign from the microcontroller.
With the ESP32-S3 microcontroller performing as a USB system, it has 24 different usable output pins.
So, lets see what number of microcontroller pins we’d like for some N
of AY chips:
N | AY management pins |
Audio outputs/ VCFs |
DACs | DAC management pins |
TOTAL pins |
---|---|---|---|---|---|
1 | 11 | 3 | 1 | 3 | 14 |
2 | 12 | 6 | 1 | 3 | 15 |
3 | 13 | 9 | 2 | 4 | 17 |
4 | 14 | 12 | 2 | 4 | 18 |
5 | 15 | 15 | 2 | 4 | 19 |
6 | 16 | 18 | 3 | 5 | 21 |
7 | 17 | 21 | 3 | 5 | 22 |
8 | 18 | 24 | 3 | 5 | 23 |
OK, so we may hook up 8 AY chips for a complete of 24 oscillators and eight noise mills and 24 VCFs. That is a tempting thought, however that is maybe too many and leaves no scope for added I/O on the microcontroller.
I like the concept of getting a bit of display screen for configuration and standing show, and we additionally will want some contact or button inputs to regulate the person interface. I’ll use the identical ST7735s sort display screen as I used for the psc
and this shares the SPI bus with the DACs, however wants 3 extra management strains. For person interface inputs, I feel we’d like a minimal of three contact or button inputs. That is a complete of 6 extra pins for person interface. If we subtract that from the 24 out there, that leaves 18 remaining for the audio circuits.
So, we’re ready to make use of 4 AYs … 4 AYs … Fourays … FOURAYS!
And with that, we could have used each usable pin on the ESP32-S3 and located a reputation for the undertaking!
Microcontroller hookup
And with that lets begin allocating pins on the ESP32-S3:
This consists of:
- 3x contact inputs
- 8x AY information bus strains
- 1x AY clock
- 1x AY bus course line
- 4x AY bus management / chip choose strains
- 2x SPI bus strains (clock & information)
- 2x DAC chip choose strains
- 3x display screen management strains
- 2x USB information strains
There are technically 8 extra pins uncovered, however for numerous causes we can not use these. 4 affect the ESP startup (“strapping”) and these are finest prevented. Three interface with a inbuilt peripheral, the PSRAM. The final one interfaces with an on-board RGB LED, which we do not want.
It must be famous as properly that these pin selections should not arbitrary, this picture comes from the longer term relative to this introductory submit, having already examined and established which pins swimsuit which features. For instance the contact inputs are solely out there on the primary 14 GPIOs.
AY hookup
The AY chips have been initially designed for use with 11 management strains and 1 clock sign. Nevertheless, it has been established that fewer than that’s strictly vital. I’ve used one diminished pin scheme within the psc
design, however I’ve discovered a special one to make use of right here, which saves one other pin. I bought this data from dogemicrosystems.
Every AY chip hookup due to this fact appears like this:
One factor I have never talked about but is that every AY additionally has two 8-bit GPIO ports integrated. I have never made use of those within the design to this point, but it surely might be thought-about as a substitute option to management the filters if we run into limitations with the microcontroller or DACs.
By setting these GPIO as outputs, we may implement an R-2R resistor ladder DAC. If we would like excessive precision we must sacrifice the variety of VCFs, maybe have one per AY chip as an alternative of 1 per audio output, or we may divide the GPIOs into a number of DACs with decrease decision. If we need to keep one VCF per audio output, we might solely have 5 bits out there per VCF management voltage, lowering the decision fairly considerably (65536 ranges to simply 32 ranges).
Truly, driving the VCF management voltages from the AY itself would have the small benefit of making certain that filter modifications occur a lot nearer in time because the word modifications on the audio outputs, for the reason that microcontroller could be setting each of those over the identical bus in fast succession. I do not assume the timing alignment shall be pretty much as good utilizing DAC units, nonetheless I anticipate in observe the timing variations won’t be audible in any respect, as a result of we will drive both bus at MHz frequencies.
If I do not use these GPIOs for anything, I would simply hook up some LEDs as blinkenlights.
One ultimate factor to notice is that though the AY is specified as requiring a 5 volt provide, I’ve been in a position to energy it efficiently from the ESP32-S3 3.3 volt provide and it nonetheless outputs tones and on the right frequencies. So, that ought to simplify the ability necessities for the synth as properly.
VCF filter design
The ultimate circuit sort to contemplate on this synth is the voltage managed filter. I’ve merely lifted another person’s design for this, specifically the kassutronics transistor ladder filter. This design could be very near the YuSynth Minimoog VCF which I made earlier than for my analogue synth. I just like the sound of that filter, however the kassutronics model has the benefit of not requiring CA3046 chips that are now not out there.
Setting Design Targets
I feel all the above makes a smart plan for a synthesizer. We can:
- use the ESP32-S3 as a USB MIDI system
- map MIDI channels/notes to AY oscillators
- map MIDI CC controllers to filter cut-off frequencies
We are able to combine the audio voices collectively and configure the synth to have both 1 output for mono or 2 outputs for stereo. We are able to determine afterward the MIDI configuration and audio mixing technique although.
ESP32-S3 Firmware
With a view to have full management over the ESP32-S3 as a USB system, I’m going to make use of ESP-IDF framework.
The firmware shall be structured into a number of items that are principally loosely coupled:
board
: only a bunch of constants which outline the peripheral pinouts.usb
: initialises the USB interface, system descriptors.midi
: initialises a MIDI interface which consists of bridging the ESP-IDF USB MIDI low stage API to a MIDI packet interpreter. Exposes a set ofsign
objects which may be noticed for various MIDI message varieties.config
: describes tips on how to map MIDI messages to system peripherals, and an API to alter these mappings.contact
: watches the contact enter pins and exposes a contactsign
when contact occasions happen.ay
: AY-3-8910 system driver.dac
: DAC system driver.tft
: LCD display screen graphics rendering features.
Indicators
I will loosely couple most of those items utilizing the sign
sample. A Sign
is an object which may be noticed and which may be referred to as upon to emit occasions.
#embrace <indicators.h>
Sign<> demoSignal;
void onSignal_1() {}
void onSignal_2() {}
int most important() {
demoSignal.emit(); // No observers, nothing occurs
demoSignal.join(onSignal_1); // Registered one callback for the sign
demoSignal.emit(); // onSignal_1 known as
demoSignal.join(onSignal_2); // Registered one other callback for the sign
demoSignal.emit(); // onSignal_1 and onSignal_2 are referred to as
return 0;
}
Models
Lets have a look at among the items in a bit extra element. However first, what do I imply by “unit”? Virtually talking every unit to this point is only a pair of .h
/.cpp
information (actually a C++ translation unit). Inside every unit I encapsulate the interface right into a namespace. Most items expose a setup()
operate and maybe some indicators.
example-unit.h
namespace fourays::instance
{
Sign<> &exampleSignalGetter();
void setup();
}
example-unit.cpp
#embrace "example-unit.h"
namespace fourays::instance
{
Sign<> s_example;
Sign<> &exampleSignalGetter()
{
return s_example;
}
void setup()
{
// do some setup for instance
}
}
Notice that within the descriptions of the next items I’m in no way exhibiting all the code required, but it surely’s price exhibiting among the essential components which give the items their core performance. A hyperlink to the precise code repo for fourays firmware shall be revealed as open supply sooner or later, as soon as I’ve bought all these items working properly collectively (extra on this later).
Unit: usb
This can be a minor unit which initialises the tinyusb
stack with my very own system descriptor info and to initialise the USB configuration as a MIDI system.
#embrace <tinyusb.h>
#embrace "fourays-usb.h"
namespace fourays::usb
{
/** TinyUSB descriptors **/
// Interface counter
enum interface_count
{
#if CFG_TUD_MIDI
ITF_NUM_MIDI = 0,
ITF_NUM_MIDI_STREAMING,
#endif
ITF_COUNT
};
// USB Endpoint numbers
enum usb_endpoints
{
// Out there USB Endpoints: 5 IN/OUT EPs and 1 IN EP
EP_EMPTY = 0,
#if CFG_TUD_MIDI
EPNUM_MIDI,
#endif
};
#outline TUSB_DESCRIPTOR_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_MIDI * TUD_MIDI_DESC_LEN)
/**
* @temporary String descriptor
*/
static const char *s_str_desc[5] = {
// array of pointer to string descriptors
(char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
"lon.dev", // 1: Producer
"fourays", // 2: Product
"123456", // 3: Serials, ought to use chip ID
"fourays", // 4: MIDI
};
/**
* @temporary Configuration descriptor
*
* This can be a easy configuration descriptor that defines 1 configuration and a MIDI interface
*/
static const uint8_t s_midi_cfg_desc[] = EPNUM_MIDI), 64),
;
void setup()
{
tinyusb_config_t const tusb_cfg = {
.device_descriptor = NULL, // If device_descriptor is NULL, tinyusb_driver_install() will use Kconfig
.string_descriptor = s_str_desc,
.string_descriptor_count = sizeof(s_str_desc) / sizeof(s_str_desc[0]),
.external_phy = false,
.configuration_descriptor = s_midi_cfg_desc,
.self_powered = false,
.vbus_monitor_io = -1,
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
}
}
It looks as if it’s doable to outline a number of distinct MIDI interfaces within the USB configuration ought to we have to, nonetheless for now I feel we solely want one and I additionally can not discover any option to know which interface acquired which information after we need to learn it afterward.
Unit: midi
On this unit I’ve wrapped the Arduino MIDI Library. This library is definitely not Arduino framework particular, however is templated to permit integrating with parts of different frameworks. Specifically, we have to present an implementation for one thing referred to as Transport
and one thing referred to as a Platform
.
template<class Transport, class _Settings = DefaultSettings, class _Platform = DefaultPlatform>
class MidiInterface
{
// --- snip ---
The Transport
implements the interface to learn and write information to the bodily interface, that’s to say the tinyusb
USB MIDI interface:
#embrace <tinyusb.h>
class TUSBTransport
{
public:
void start() {}
unsigned out there()
{
return tud_midi_available();
}
byte learn()
{
uint8_t ch;
return tud_midi_stream_read(&ch, 1) ? (int)ch : (-1);
}
bool beginTransmission(int val)
{
// nothing to do right here
return false;
}
void write(int val)
{
// nothing to do right here
}
void endTransmission()
{
// nothing to do right here
}
public:
bool thruActivated = false;
};
Platform
for some purpose simply wants a way to get the present timestamp:
#embrace <esp_timer.h>
class ESPIDFPlatform
{
public:
static unsigned lengthy now()
{
return esp_timer_get_time() / 1000;
}
};
As soon as we have these outlined, we will create our MIDI interface and use it as per the library documentation. I am additionally utilizing a FreeRTOS activity to handle studying the MIDI information arriving and map it to some indicators which this unit exposes.
namespace fourays::midi
{
static TUSBTransport transport;
static ::midi::MidiInterface<TUSBTransport, ::midi::DefaultSettings, ESPIDFPlatform> midi(transport);
void midi_task_read(void *arg)
{
for (;;)
{
vTaskDelay(1);
whereas (midi.learn())
{
change (midi.getType())
{
case ::midi::MidiType::Cease:
s_allNotesOff.emit();
break;
case ::midi::MidiType::SystemReset:
s_allNotesOff.emit();
break;
case ::midi::MidiType::NoteOn:
s_noteOn.emit(midi.getChannel(), midi.getData1(), midi.getData2());
break;
case ::midi::MidiType::NoteOff:
s_noteOff.emit(midi.getChannel(), midi.getData1(), midi.getData2());
break;
case ::midi::MidiType::ControlChange:
s_controlChange.emit(midi.getChannel(), midi.getData1(), midi.getData2());
break;
default:
break;
}
}
}
}
void setup()
{
midi.start(MIDI_CHANNEL_OMNI);
midi.turnThruOff();
xTaskCreate(midi_task_read, "midi_task_read", 8 * 1024, NULL, 5, NULL);
}
}
Unit: contact
I discovered a great reference implementation of utilizing contact inputs within the ESP-IDF documentation right here:
I’ve used this virtually verbatim, apart from redefining my very own buttons array and what occurs contained in the learn activity the place I emit a sign when an enter is activated:
#outline TOUCH_BUTTON_NUM 3
const touch_pad_t button[TOUCH_BUTTON_NUM] = {
TOUCH_LEFT,
TOUCH_RIGHT,
TOUCH_SELECT,
};
// --- snip ---
utilizing TouchSignal = Sign<touch_pad_t>;
TouchSignal s_touch;
void touchsensor_read_task(void *pvParameter)
{
// --- snip ---
if (evt.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE)
{
s_touch.emit((touch_pad_t)evt.pad_num);
}
// --- snip ---
}
Unit: ay
The AY unit exposes a easy interface for writing register values to an AY system:
// Libs
#embrace <AY38910.h>
namespace fourays::ay
{
void setup();
void writeRegisters(const uint8_t cs, const AY38910::DataFrame frm);
}
The setup
operate initialises the output pins:
- clock: utilizing the LEDC PWM clock generator.
- 8-bit bus: utilizing Dedicated GPIO Bundle. The benefit of doing that is that we will then write an 8-bit unsigned int to the bundle and the {hardware} will map every bit to the output pins within the right order in a single operate name. I feel that is fairly neat.
- BDIR and chip choose pins: as common GPIO outputs.
The writeRegisters
operate iterates over the DataFrame
and writes every worth to the corresponding AY register. The implementation of the dogemicrosystems addressing scheme appears like this:
enum class BC2Control
{
INACTIVE = 0b00,
WRITE = 0b11,
LATCH = 0b10
};
void setControl(uint8_t bc2, BC2Control mode)
{
// BC2 have to be modified earlier than BDIR
const auto bc2val = (uint8_t)mode & 0b01 ? 1 : 0;
gpio_set_level((gpio_num_t)bc2, bc2val);
const auto bdir = (uint8_t)mode & 0b10 ? 1 : 0;
gpio_set_level((gpio_num_t)AY_BDIR, bdir);
}
void setData(unsigned char information)
{
dedic_gpio_bundle_write(portD, 0xFF, information);
}
void writeRegister(uint8_t cs, uint8_t reg, uint8_t worth)
{
// Ref: https://dogemicrosystems.ca/wiki/Dual_AY-3-8910_MIDI_module#Code
uint8_t bc2 = 0;
change (cs)
{
case 0:
bc2 = AY_1_CS;
break;
case 1:
bc2 = AY_2_CS;
break;
case 2:
bc2 = AY_3_CS;
break;
case 3:
bc2 = AY_4_CS;
break;
}
// Inactive (BDIR BC2 BC1 0 0 0)
setControl(bc2, BC2Control::INACTIVE); // BDIR, BC2
// Set register handle
setData(reg);
// BAR (Latch) (BDIR BC2 BC1 1 0 0)
setControl(bc2, BC2Control::LATCH); // BDIR
// Inactive (BDIR BC2 BC1 0 0 0)
setControl(bc2, BC2Control::INACTIVE); // BDIR
// Set register contents
setData(worth);
// Write (BDIR BC2 BC1 1 1 0)
setControl(bc2, BC2Control::WRITE); // BC2, BDIR
// Inactive (BDIR BC2 BC1 0 0 0)
setControl(bc2, BC2Control::INACTIVE); // BC2, BDIR
}
What shouldn’t be proven right here is the AY38910
mapper class which exposes a MIDI like interface however internally simply updates the register states. We are going to use this later to obtain occasions from the MIDI unit and replace the state to ship out to the AY chips.
class AY38910
{
public:
bool allOff();
bool noteOn(const uint8_t channel, const uint8_t word, const uint8_t velocity);
bool noteOff(const uint8_t channel);
bool noiseOn(const uint8_t freq, const uint8_t output_mask);
// --- snip ---
personal:
DataFrame m_frame;
}
Unit: dac
I couldn’t discover any present libraries for interfacing with the AD5382 DACs. It seems although that that is fairly easy, we mix the DAC channel quantity with the information and write it out over the SPI bus. Keep in mind that we now have 2 DAC units (by way of cs
) every with 8 channels (by way of ch
), for a complete of 16 outputs, of which we’d like solely 12.
namespace fourays::dac
{
void writeDac(const int cs, const uint16_t information)
{
buf[0] = information >> 8;
buf[1] = information & 0xFF;
spi->mode(2);
gpio_set_level((gpio_num_t)cs, 0); // /SYNC
spi->beginTransaction();
spi->writeBytes(buf, 2, false, true);
spi->endTransaction();
gpio_set_level((gpio_num_t)cs, 1); // /SYNC
}
void writeDacOutput(const int cs, const uint8_t ch, const uint16_t val)
(val & 0xFFF);
writeDac(cs, information);
}
This works as a result of within the setup
we configure the DACs for “steady replace utilizing SYNC” mode
namespace fourays::dac
{
void setup(psc::io::SPI_sptr bus)
{
// Handle the management pins manually
for (auto pin : cs_gpios)
0x00;
writeDac(pin, ldacmode);
}
}
Unit: tft
This unit initialises the SPI bus, and supplies a operate to show the MIDI mapping configuration on the display screen. I’ve completed this to this point utilizing the LovyanGFX library. I actually like this library for a number of causes:
- The API could be very simple and mimics the TFT_eSPI library I’ve used earlier than within the
psc
. I due to this fact was in a position to begin the configuration rendering implementation principally by copying what I might already written forpsc
. - It appears to configure the TFT LCD for good color distinction and readability. I’ve seen different libraries utilizing totally different display screen initialisation instructions which finally ends up making the graphics look fuzzy and/or washed out.
- Its SPI interface is quick. Actually quick. I am undecided what it does to attain this (and we’ll learn later extra about this), however different libraries do not appear to have the ability to do full display screen updates wherever close to as quick as this library.
The lengthy listing of graphics API requires rendering the configuration is not significantly enlightening to learn, so I am going to skip exhibiting any code for this unit.
most important
To sew all the above collectively, we have to create an occasion of every unit, name their setup
features after which begin connecting indicators:
utilizing namespace fourays;
namespace state
{
static config::Config cfg;
static dac::DACState dac;
}
extern "C" void app_main(void)
{
usb::setup();
midi::setup();
ay::setup();
dac::setup(io::SPI());
tft::setup(io::SPI());
midi::allNotesOff().join(handleAllNotesOff);
midi::noteOn().join(handleNoteOn);
midi::noteOff().join(handleNoteOff);
midi::controlChange().join(handleControlChange);
auto controller = state::cfg.controller();
contact::contact().join(
[](const touch_pad_t &pad)
{
change (pad)
{
case TOUCH_LEFT:
controller->subsequent({.sort = State::ControlEventType::LEFT});
break;
case TOUCH_RIGHT:
controller->subsequent({.sort = State::ControlEventType::RIGHT});
break;
case TOUCH_SELECT:
controller->subsequent({.sort = State::ControlEventType::SELECT});
break;
default:
break;
}
});
contact::setup();
state::cfg.modified().join(
[]()
{
tft::displayConfig(state::cfg);
});
controller->navigate().join(
[]()
{
tft::displayConfig(state::cfg);
});
config::setup();
state::cfg.load("default");
}
The assorted deal with*
features not proven right here use the cfg
to seek out out the place to route the information and should ultimately find yourself calling a dac::replace
or ay::writeRegisters
.
I additionally have not described what the config controller
is. That’s worthy of its personal article, and I’ll comply with up with extra particulars on that.
Unit Compatibility Points
Because it stands proper now, every unit by itself works completely properly. However there’s a main downside I’m nonetheless making an attempt to resolve. This revolves round the truth that the DAC and TFT share an SPI bus.
I’ve established the next all works:
- Utilizing DAC by itself works completely
- Utilizing the show by itself works completely
- Utilizing the DAC after which the show, each work
- Utilizing the show after which the DAC – the show is up to date, however the DAC stops responding to any additional instructions
I’ve tracked this right down to the truth that no matter LovyanGFX is doing to make the SPI writes to the show so quick could be very particular to writing to a single TFT system on the bus. The library expects no different units shall be utilizing the bus, which type of makes it not-a-bus. When the following DAC write happens, the bus has been reconfigured right into a state or mode which the DACs don’t perceive and due to this fact they now not replace.
I’ve raised this as a problem with LovyanGFX (#491) however I’m nonetheless not satisfied the writer understands the problem and I’ve little hope that it will be resolved any time quickly.
There’s in all probability 3 ways I can go from right here:
- Discover one other graphics library which does not fiddle with the SPI bus a lot. I’d think about this a cop-out although, as I am not eager on shedding the show refresh efficiency nor the acquainted graphics API.
- See if I can assign the DAC and TFT to totally different SPI busses, however I’m technically out of obtainable pins to make use of, and I am going to want two extra. I might want to do some additional assessments to see if I can use any of the “unavailable” pins. For instance, utilizing a “strapping” pin must be advantageous so long as I initialise and use it properly after the ESP has began up. This glosses over the problems with the graphics library on the expense of extra {hardware} complexity.
- Connect a logic analyser to the bus and discover out what’s being despatched to the DACs earlier than and after the show has been used. There’s an opportunity with this technique I can discover out what has modified and hint down the code which has made that change, and presumably then reverse that when the DAC is used. This due to this fact would maybe profit not solely myself however everybody else wanting to make use of this graphics library.
1. Various Graphics Libraries
I’ve completed a good quantity of trying to find libraries to make use of already, previous to integrating LovyanGFX. There’s not quite a bit to select from. Most libraries which flip up in outcomes assume you might be utilizing the Arduino framework, which I’m not as a result of it lacks first rate help for utilizing the ESP32-S3 as a USB system. Different libraries or examples I’ve discovered are for comparable shows utilizing interfaces which aren’t SPI.
- LVGL – appears very flashy however ESP32 help could be very restricted. The documentation is definitely very sparse and lot of hyperlinks on the web site do not work or result in 404s. After I did ultimately get it to “work”, the efficiency was horrible and the display screen colors incorrect. I gave up making an attempt to repair it.
- nopnop2002/esp-idf-ili9340 – discovering out that the ST7735s is usually appropriate with ILI9340 was information to me. This really works however I needed to make a number of minor fixes. Efficiency shouldn’t be nice although and the initialisation instructions make the show look kinda fuzzy. I could not get the initialisation instructions from different libraries to work on this one.
- Official espressif ili9341 driver – I have never tried this one but, I did not know initially that this might be appropriate with my ST7735s system.
2. Cut up the SPI busses
I am undecided why I did not consider this earlier than I began writing this text, maybe I used to be too hung up on which ESP32-S3 pins ought to by no means be touched. Nevertheless, lower than one hour hacking proves this to really work. Thankfully as properly the ESP32-S3 has one other out there SPI host driver. So, we’ll use SPI2_HOST
on one set of pins for the TFT and SPI3_HOST
with one other set of pins for the DACs. For the time being I’m nonetheless utilizing the LovyanGFX SPI driver even for the DACs, as I assume I’ll get the identical benefit of SPI velocity/effectivity that it implements, despite the fact that I do not know tips on how to measure that effectivity and check that speculation for the time being.
diff --git a/esp32-s3-idf/src/psc-dac.cpp b/esp32-s3-idf/src/psc-dac.cpp
index b7d13dc..de92022 100644
--- a/esp32-s3-idf/src/psc-dac.cpp
+++ b/esp32-s3-idf/src/psc-dac.cpp
@@ -44,6 +44,10 @@ namespace psc::dac
writeDac(idx, information);
}
+ psc::io::SPI_sptr bus = std::make_shared<psc::io::PSCSPI>(
+ SPI3_HOST, SPI_DAC_SCLK, SPI_DAC_MOSI, SPI_DAC_MISO, -1,
+ 2, SPI_MASTER_FREQ_8M);
+
void setup()
{
gpio_config_t io_cs_conf = {
@@ -70,7 +74,7 @@ namespace psc::dac
.spics_io_num = DAC_1_CS,
.queue_size = 1,
};
- ret = spi_bus_add_device(SPI2_HOST, &devcfg0, &dacs[0]);
+ ret = spi_bus_add_device(bus->config().spi_host, &devcfg0, &dacs[0]);
ESP_ERROR_CHECK(ret);
spi_device_interface_config_t devcfg1 = {
@@ -79,7 +83,7 @@ namespace psc::dac
.spics_io_num = DAC_2_CS,
.queue_size = 1,
};
- ret = spi_bus_add_device(SPI2_HOST, &devcfg1, &dacs[1]);
+ ret = spi_bus_add_device(bus->config().spi_host, &devcfg1, &dacs[1]);
ESP_ERROR_CHECK(ret);
// -----
@@ -108,11 +112,13 @@ namespace psc::dac
void beginTransaction(spi_device_handle_t dev)
SPI_DOUTDIN;
if (spi_mode == 1
diff --git a/esp32-s3-idf/src/psc-tft.cpp b/esp32-s3-idf/src/psc-tft.cpp
index 2539604..9fb0790 100644
--- a/esp32-s3-idf/src/psc-tft.cpp
+++ b/esp32-s3-idf/src/psc-tft.cpp
@@ -4,6 +4,8 @@
#embrace <memory>
+#embrace <driver/spi_master.h>
+
namespace psc::tft
{
std::shared_ptr<LGFX> show;
@@ -18,9 +20,12 @@ namespace psc::tft
// Cols offset +4px for single char column centred
constexpr uint8_t COLSC[] = {5, 22, 39, 56, 73, 90, 107, 124, 141, 158};
- void setup(psc::io::SPI_sptr bus)
+ psc::io::SPI_sptr bus = std::make_shared<psc::io::PSCSPI>(
+ SPI2_HOST, SPI_TFT_SCLK, SPI_TFT_MOSI, SPI_TFT_MISO, TFT_DC,
+ 0, SPI_MASTER_FREQ_40M);
+
+ void setup()
{
show = std::make_shared<LGFX>(bus);
display->init();
diff --git a/esp32-s3-idf/src/psc-tft.h b/esp32-s3-idf/src/psc-tft.h
index 37cda32..7da7a3c 100644
--- a/esp32-s3-idf/src/psc-tft.h
+++ b/esp32-s3-idf/src/psc-tft.h
@@ -69,7 +69,7 @@ namespace psc::tft
}
};
- void setup(psc::io::SPI_sptr bus);
+ void setup();
void displayConfig(psc::config::Config &cfg);
} // namespace psc::tft
3. Debug the SPI bus
Nicely it seems in the meanwhile I do not want to do that. I am going to proceed to construct this undertaking out utilizing twin SPI busses for now.
PCB Design
Up up to now I’ve been prototyping this technique completely on breadboard. This check rig consists of two AY chips, one DAC and the TFT show. The DAC is attached to LEDs to examine that the outputs are working.
I’ve additionally in parallel been laying out the schematic and PCBs in KiCAD.
Prime-level system hookup
AY chip hookup
AY audio circuits
VCF filter
AY audio outputs mixer
For the PCB structure, I’ll place one AY and three VCF filters on one board, and due to this fact want 4 situations of those boards. I’ve routed all of the AY and VCF ins and outs to bus connectors, however every board has breakout pads between the bus connector and the units so that every AY and VCF may be related to a special a part of the bus.

AY/VCF board bus connectors. The smaller connector is the shared AY management bus, the bigger connector is the AY choose and audio I/O bus.
AY/VCF board structure and KiCAD 3D render
On every board I should manually join every of the blue pads to the proper pins on the bus connectors. Nevertheless, on this approach it implies that I can manufacture 4 equivalent boards.
The bus connectors are additionally current in the identical location on a controller board, the place each connection is routed to the suitable ESP32-S3 pin or audio circuit.
Controller board structure. The 4 units on this board are the ESP32-S3, 2x AD5328 DACs, one op-amp audio mixer. The massive connectors on the high are the board busses. The small pin headers on the underside row are USB, TFT, Contact inputs respectively from left to proper.
Meeting and {Hardware} Design
General dimensions
By inserting all the bus connectors in the identical location on each board, it means that all the boards may be assembled collectively in a vertical stack, quite than facet by facet. At the moment although I’ve not discovered any of the bodily meeting of this factor. Nevertheless, my intestine feeling is that each a 5 board stack or a 5 board flat structure will result in this being fairly a big system total. A vertical stack will find yourself being a chunky dice of boards. The flat structure will take over 475 cm2 – equal to a sq. with 22 cm sides.
I/O and controls
The system ideally shall be completely USB bus powered, nonetheless I feel I nonetheless want extra energy for the audio mixer, as these units usually run on +/- 12 volts, which is required to get sufficient headroom for the op-amps to do their factor. I am undecided but tips on how to get that voltage, I most probably will want an exterior DC energy provide and embrace some regulators as properly. There’s sufficient spare area on the controller board to incorporate the ability enter and regulation.
The synth could have three contact enter controls for adjusting the configuration. These will probably both be hidden underneath the case and indicated by painted or drawn markings on high, or I may expose some small metallic pads or buttons which might in all probability make them each extra seen and extra dependable to make use of.
I may additionally present some guide potentiometer controls for the filters. The filters’ cut-off frequency is designed to be MIDI managed, however these may be blended with guide controls as properly. I’ve additionally allowed within the PCB design for the filter resonances to be both fastened by putting in fastened resistors, or to be manually adjustable from potentiometers. A full set of guide filter controls then would include 24 potentiometers, which can be too many. I’m not conscious of any option to make the resonance MIDI controllable, even the filters I’ve constructed earlier than for my analogue synth didn’t have CV management over resonance.
I’ll do some 3D render mocks for the meeting and see what appears good and in addition serves the sensible goal of the synth.
For those who really feel like serving to ?
I’m very happy to attempt to full this undertaking with my very own curiosity and willpower, however I’d additionally like to have some assistance on this – even when its simply to drop a word about an thought you’ve gotten or some remark which can be useful to steer the undertaking in direction of completion the place there are nonetheless unanswered questions. Or in the event you’ve noticed bugs or points in my firmware code by all means ship a remark or PR.
The repository for Fourays together with all of the firmware, library code and KiCAD information is right here:
Your entire repository is GPL 3 licensed, so you might be additionally free to make use of something from this undertaking in your personal so long as you comply with the phrases of the license.