Demiurge 1 came about the desire to create a digital audio processing system, suitable for experimentation of various audio algorithms and build a Eurorack synthesizer from scratch.
This is a project that participates in the "Hack It! RISC-V Design Challenge with RT-Thread, WCH Electronics and RISC-V International" and was accepted as one of the contestants.
CH32V307 is a microcontroller with a RISC-V core, and has peripherals that somewhat matches the STM32F407, but not register compatible.
RT-Thread has CH32V307 support and we are using RT-Thread Nano for start-up initialization and interrupt context switching.
HardwareOf course KiCAD is used and running on Linux desktop (KDE), as we are strong supporters of open source software.
Eurorack operates with analog voltages and we decided to give Demiurge 1 four analog input jacks, that can be used either for audio or for control voltages (CVs) or triggers. Simply put, a -10V to +10V A/D range.
Two analog outputs with the same -10V to +10V range. The CH32V307 has fast 12-bit A/D converters with multiplexer and two fast D/A converters.
We also threw in one bi-directional I/O (0V - 5V) for trigger/gate signalling.
Often, a CV input is accompanied with a potentiometer that is used to adjust/tune the CV input, or used as the primary input (musician directly). Interrestingly, we first tried to connect the potentiometer directly to the A/D converter input but the internal capacitor in the A/D converter forms a low-pass filter with the potentiometer, and with the resistance value we had on the potentiometer, it filtered too much. Therefor, the op-amp buffer.
And grouping together with the input jacks and potentiometers, we decided to put one RGD LED and one push button for each. And there is a nice RGD LED driver called AW9523 that directly drives RGB LEDs (current limiting) on 16 pins. we needed 4x3 which gave us 4 pins on AW9523 available for the push button inputs.
Eurorack uses either a 10-pin or a 16-pin 100mil header. We decided to use the 16-pin one. It is quite a wasteful connector, since it only carries +12V, -12V and GND in the 10 pin one, and additionally +5V and two non-standard, seldom used signals.
One of the most annoying things with Eurorack modules is their sensitivity to blow-up when power is connected the wrong direction. We came up with a solution, where turning the power connector the wrong way, a Red LED lits up, instead of the Green one. We didn't try to reverse the polarity so direction doesn't matter, but that might also be possible.
USB is sometimes used for MIDI-signals, so that would be cool to have on the front panel. Nowadays, USB-C is the only sane choice. It should also be possible to use the USB-C for programming (hasn't been tested yet).
We also thought it would be a cool idea to attach an extra 16MB flash on-board and a SDcard connector for additional storage, potentially into the gigabytes.
And the MCU is clocked at 16MHz and derives a 144MHz clock speed from that, a pull-up of NRST (technically not required) and a pull-down of BOOT0 (required). The two jumpers JP2 and JP3 are for debugging/troubleshooting purposes.
And finally (left out by-pass capacitors and LDO voltage regulator) we have a programming header for flashing and debugging.
When it comes to physical dimensions and layout, Eurorack Modular Synthesizers are bound by a specification, and we end up with a maximum board size of ~110mm tall, and the width we can choose in HP units, 0.2 inch each, but synthesizer real-estate is precious, so more narrow is preferred. We went for a 8 HP, i.e. 1.6 inches wide, then slimmed it to 1.58 inches for tolerances.
Layout was fairly straight forward, but attention to digital noise in analog circuitry was needed. Analog ground is not mixed with digital ground, and no analog ground loops, instead flowing to a single point.
Two layer board to save cost, and the result looks like this.
KiCAD has 3D view as well.
All components were selected from JLCPCB's set for Assembly, and after sending the order to them, it was time to dig into the software, and testing using the development kit that was supplied by the contest organizers.
SoftwareFirst of all, C needs to be used and not C++. C++ has too many pointer dereferencing and potentially vtable lookups to be suitable. Need maximum performance.
There are several aspects of the software that needs special consideration,
- Hardware interfacing
- Audio processing
- Timing
Timing is critical, and it is required that the user, i.e. the person who writes the functionality running on the Demiurge 1, can select the sampling frequency (it is like a frame rate in a video game). CDs are 44.1ksamples/sec (ksps) and DVD uses 48ksps, but if the raw computing power available can't handle the processing needed, for instance in filters, the user may want to run at a lower rate, say 12ksps, which often is fast enough for music.
Jitter is also a really bad thing to have. Our ears can pick that up very easily. The sample rate must be spot on, and if there are conditionals in the code during the processing of "current sample", then if we write the sample on completion, there will be jitter. So we buffer by one sample and write the outputs at the beginning of each "loop".
Speaking of "loop". After some experimentation, a high priority timer will run the audio processing loop at the sample and the IRQ handler will first write the output, then "tick()" the audio processing pipeline.
We want it to be simple to build audio functionality, and came up with a "block pull"-system. There are self-containing "blocks" with very specific functionality, which are wired together on initialization, and after that form a network of processing nodes. Each input of a block can be wired/configured to an output of another block. Each block also caches the output(s), so if it is wired to several inputs it only computes its output once. The process is starting at the outputs, which requests the "input" value from upstream block's output. This "read upstream" is what we consider to be "pull", rather than the outputs pushing the values to each of the wired inputs. This results in a simpler/faster data model to flow the values through the system, as well as ensuring the values are in correct order.
Also, the CH32V307 has single precision float-point capability. And that suits us well.
Let's look at an example. This is dual-VCO, Voltage Controlled Oscillator, one of the basic synth modules. It has 2 oscillators connected to audio_outport, and a pair of CV inputs and potentiometers will control the frequency and amplitude of each of those oscillators.
This only uses control_pair, audio_port and oscillator blocks. There are many more either completed, or in the works, or planned. It is expected that this library will grow over time.
This is completely void of hardware specific code, and that is intentional. In the main.c, we make the calls to set up the hardware and initialize the various subsystems. This code will be practically the same in most applications.
Everything that involves the specific hardware, in terms of MCU, USB, Flash, A/D & D/A converters, and so on are separated from the generic audio processing and application examples, to allow for additional hardware solutions to use the same software platform. This could include more higher DMIPS microcontrollers, such as STM32H family, or even running the software in desktop computer settings, such as emulation.
Each hardware "board" has its own directory, boards/demi1-ch32/
, which is selected in the build process. And in that directory there is one "driver" file for each hardware peripheral used; 11 C files in total at the time of writing, and one C file (ch32v307.c) that ties it all together.
The hardware SPI is specified in components/demiurge/inc/demiurge-spi.h
and there is expected to be an entry in the boards/demiurge-hardware.h
that includes the actual boards/demi1-ch32/hardware.h
file. This is still work in progress, likely to change quite a bit as more and more of the hardware is falling in place, and possibly also if/when other hardware options becomes available.
Just as we were to order the next round of prototypes and get this article out, we noticed that the AW9523 RGB driver chip is marked "discontinued" at LCSC, and we need to replace it. Luckily there is a reasonable replacement in AW20036.
It operates with a switching multiplexer and PWMs.
That means that the LEDs need to be rerouted quite a lot, as their anode is now fed from the chip rather than 3.3V.
And we go the AW20036 to work. Later we weill do tuning of currents and reasonable "quick values" for easier programming, but for now this is good enough.
And while looking at the hardware again, we decided to make the design STM32F405/STM32F407 compatible, by introducing zero ohm resistors on 2 pins, which in case of STM32F405 needs to replaced with capacitors (VCAP_1 & VCAP_2), and in the same process we noticed that the higher performance STM32F730 could be made to work with a few tweaks on port usage and an additional VCAP capacitor that will end up on an unused GPIO pin for the other two microcontrollers. There is also an STM32F446
Back to the regular programming...
MounRiver IDE -> cmakeAlthough the MounRiver IDE is the only "supported" development environment for Linux, that isn't the professional choice. Personally I prefer CLion and other will swear by VS Code and yet others love their emacs setup. Project should have a command-line build system as the primary one, and the IDEs have to adjust to that.
So, let's modify the project to be primarily based around CMake.
We notice that the MounRiver comes with a MODIFIED GCC. What? Well, it is related to the non-standard
__attribute__((interrupt("WCH-Interrupt-fast")))
.
Valid values are actually "user", "supervisor" and "machine".
After further investigation, the WCH specific attribute only save away a much smaller set of registers (20 if I count correctly) rather than 37 registers when using the standard compiler "machine"
. And browsing through the assembler of all the code that is executing in the interrupt service routine, I concluded that WCH approach depends on RT-Thread's rt_interrupt_enter() which saves the full context, where the "user", "supervisor" and "machine" tries to save the context by itself...Or?
Let's change to the official GNU C Compiler for RISC-V, which can be downloaded from the xPack 3rd Party Development Tools GitHub repository.
And we managed to get that working. So, now after installing that toolchain and checking out the Demiurge repository;
git clone https://github.com/AwesomeAudioApparatus/demiurge
cd demiurge/software
cmake .
make
Much more flexible, but the flashing and debugging part of that is not solved yet.
ExamplesWe have created 3 examples so far;
- Dual VCO (mentioned above)
- Dual VCA (Voltage Controlled Amplifier)
- Envelope (Attack, Decay, Sustain, Release function)
And with these three it is possible to create a rudimentary synthesizer. More examples to follow shortly. Follow the Demiurge GitHub project for updates.
UPDATE: The first test with these 3 samples was successful;
I connected up a computer to play a MIDI track, over USB to my Arturia Keystep which can convert MIDI to pitch and gate. Here is that lovely tune;
USB portWe have multiple intentions for the USB port.
- Programming/debugging port
- Serial communications
- MIDI interface
We have chosen USB C because it is mechanically better and more oriented to the future. However the connector is incredibly difficult to solder (see video below).
Programming turned out to be easy, simply put the MCU in bootloader mode (BOOT0 high + reset) and WCH-Link software will be able to flash the memory. See "Programming the Target" below.
We have no immediate need for serial communications, so no effort has been put into this.
Programming the TargetThe MounRiver IDE was recommended by the RISC-V Contest, and also the RT-Thread Studio. The RT-Thread Studio only works on Windows, so that is a "No Go" straight off the bat.
Installation of MounRiver on Linux went without much problem. And testing a Blinky on the Ch32V307 Dev Kit was also very straight forward. An interesting and good deviation from most dev kits is that the LEDs and push buttons are not wired to particular GPIO pins, but to headers on the side. Up to us to jump the suitable GPIO to the LED pin. Sweet!
The Dev Kit has a programmer attached to it, and it is possible to use it to program an external target. Same principle of removing the jumpers and wire the target to the programmer instead.
We managed to get that working fairly straight forward.
But it should be possible to program the target via USB port, and the decision to have a USB C port was "interesting". The connector is very difficult to solder, and several didn't work, and one had a spectacular problem.
But once we had a working USB connector, by reset with a high BOOT0, the built-in bootloader is started and waiting for instructions. With the tool MarsTechHAN/ch552tool on Github, it is possible to program via the USB. And that is very fast indeed, to the point I suspected that it didn't work at first.
NOTE: On Linux, the PIP installation doesn't work, as the python program expects the.wcfg files to be in the ~/.local/bin
directory, and they are not there. Clone the Git repository and simply create a shell script that calls the python in situ is the way to go.
And USB programming is really fast. Nice!
ConclusionWe would not have been able to complete this contest if CH32V307 was not as hardware compatible as it is to the STM32F405. We think that with both a price and an availability advantage when we set out on this, it has been worthwhile to experience this microcontroller.
The CH32V307 documentation is "Ok", as most information is there and it is available in English. But there are quite a handful of small errors (obvious copy/paste errors), lacking table of content and could explain the functionality better. The documentation alone is not enough, and one needs to frequently resort to the examples in GitHub to get the details correct, then adjust from there.
This project continues developing and although important milestones have been reached, we are not done. If you want to follow along or get involved, please join our Facebook group.
Comments