/* MIT License Copyright (c) 2024-6 Chris Johnson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* ComputerCard - by Chris Johnson version 0.3.0 - 12 May 2026 ComputerCard is a header-only C++ library, providing a class that manages the hardware aspects of the Music Thing Modular Workshop System Computer. It aims to present a very simple C++ interface for card programmers to use the jacks, knobs, switch and LEDs, for programs running at a fixed 48kHz audio sample rate. See examples/ directory */ #ifndef COMPUTERCARD_H #define COMPUTERCARD_H #include "hardware/gpio.h" #include "hardware/pwm.h" #define PULSE_1_RAW_OUT 8 #define PULSE_2_RAW_OUT 9 #define CV_OUT_1 23 #define CV_OUT_2 22 // USB host status pin #define USB_HOST_STATUS 20 class ComputerCard { constexpr static int numLeds = 6; constexpr static uint8_t leds[numLeds] = { 10, 11, 12, 13, 14, 15 }; public: /// Knob index, used by KnobVal enum Knob {Main, X, Y}; /// Switch position, used by SwitchVal enum Switch {Down, Middle, Up}; /// Input jack socket, used by Connected and Disconnected enum Input {Audio1, Audio2, CV1, CV2, Pulse1, Pulse2}; /// Hardware version enum HardwareVersion_t {Proto1=0x2a, Proto2_Rev1=0x30, Rev1_1=0x0C, Unknown=0xFF}; /// USB Power state enum USBPowerState_t {DFP, UFP, Unsupported}; ComputerCard(); /** \brief Start audio processing. The Run method starts audio processing, calling ProcessSample using an interrupt. Run is a blocking function (it never returns) */ void Run() { ComputerCard::thisptr = this; AudioWorker(); } /// Use before Run() to enable Connected/Disconnected detection void EnableNormalisationProbe() {useNormProbe = true;} static ComputerCard *ThisPtr() {return thisptr;} protected: class NotchFilter { public: NotchFilter() { mix1 = mix2 = mixf1 = mixf2 = 0; } int32_t operator()(int32_t val) { int32_t mixf = (ooa0 * (val + mix2) - a2oa0 * mixf2) >> 14; mix2 = mix1; mix1 = val; mixf2 = mixf1; mixf1 = mixf; return mixf; } private: // 12kHz notch filter, to remove interference from mux lines int32_t mix1, mix2, mixf1, mixf2; static constexpr int32_t ooa0 = 16302, a2oa0 = 16221; // Q = 100, very narrow notch }; NotchFilter notchLeft, notchRight; /// Callback, called once per sample at 48kHz virtual void ProcessSample() = 0; /// Read knob position (returns 0-4095) int32_t __not_in_flash_func(KnobVal)(Knob ind) {return knobs[ind];} /// Read switch position Switch __not_in_flash_func(SwitchVal)() {return switchVal;} /// Read switch position bool __not_in_flash_func(SwitchChanged)() {return switchVal != lastSwitchVal;} /// Set Audio output (values -2048 to 2047) void __not_in_flash_func(AudioOut)(int i, int16_t val) { dacOut[i] = val; } /// Set Audio 1 output (values -2048 to 2047) void __not_in_flash_func(AudioOut1)(int16_t val) { dacOut[0] = val; } /// Set Audio 2 output (values -2048 to 2047) void __not_in_flash_func(AudioOut2)(int16_t val) { dacOut[1] = val; } /// Set CV output (values -2048 to 2047) void __not_in_flash_func(CVOut)(int i, int16_t val) { if (val<-2048) val = -2048; if (val > 2047) val = 2047; cvValue[i] = (2047-val)*125; } /// Set CV 1 output (values -2048 to 2047) void __not_in_flash_func(CVOut1)(int16_t val) { if (val<-2048) val = -2048; if (val > 2047) val = 2047; cvValue[0] = (2047-val)*125; } /// Set CV 2 output (values -2048 to 2047) void __not_in_flash_func(CVOut2)(int16_t val) { if (val<-2048) val = -2048; if (val > 2047) val = 2047; cvValue[1] = (2047-val)*125; } /// Set CV output (values -262144 to 262143) void __not_in_flash_func(CVOutPrecise)(int i, int32_t val) { if (val<-262144) val = -262144; if (val > 262143) val = 262143; cvValue[i] = ((262143-val)*125)>>7; } /// Set CV 1 output (values -262144 to 262143) void __not_in_flash_func(CVOut1Precise)(int32_t val) { if (val<-262144) val = -262144; if (val > 262143) val = 262143; cvValue[0] = ((262143-val)*125)>>7; } /// Set CV 2 output (values -262144 to 262143) void __not_in_flash_func(CVOut2Precise)(int32_t val) { if (val<-262144) val = -262144; if (val > 262143) val = 262143; cvValue[1] = ((262143-val)*125)>>7; } /// Set CV 1 output from calibrated MIDI note number (values 0 to 127) void __not_in_flash_func(CVOutMIDINote)(int i, uint8_t noteNum) { cvValue[i] = MIDIToDAC(noteNum, i); } /// Set CV 1 output from calibrated MIDI note number (values 0 to 127) void __not_in_flash_func(CVOut1MIDINote)(uint8_t noteNum) { cvValue[0] = MIDIToDAC(noteNum, 0); } /// Set CV 2 output from calibrated MIDI note number (values 0 to 127) void __not_in_flash_func(CVOut2MIDINote)(uint8_t noteNum) { cvValue[1] = MIDIToDAC(noteNum, 1); } /// Set CV 1 output from calibrated MIDI note number (values 0 to 127) bool __not_in_flash_func(CVOutMillivolts)(int i, int32_t millivolts) { bool limited = false; cvValue[i] = MillivoltsToDAC(millivolts, i, limited); return limited; } /// Set CV 1 output from calibrated MIDI note number (values 0 to 127) bool __not_in_flash_func(CVOut1Millivolts)(int32_t millivolts) { bool limited = false; cvValue[0] = MillivoltsToDAC(millivolts, 0, limited); return limited; } /// Set CV 2 output from calibrated MIDI note number (values 0 to 127) bool __not_in_flash_func(CVOut2Millivolts)(int32_t millivolts) { bool limited = false; cvValue[1] = MillivoltsToDAC(millivolts, 1, limited); return limited; } /// Set Pulse output (true = on) void __not_in_flash_func(PulseOut)(int i, bool val) { gpio_put(PULSE_1_RAW_OUT + i, !val); } /// Set Pulse 1 output (true = on) void __not_in_flash_func(PulseOut1)(bool val) { gpio_put(PULSE_1_RAW_OUT, !val); } /// Set Pulse 2 output (true = on) void __not_in_flash_func(PulseOut2)(bool val) { gpio_put(PULSE_2_RAW_OUT, !val); } /// Return audio in (-2048 to 2047) int16_t __not_in_flash_func(AudioIn)(int i){return i?adcInR:adcInL;} /// Return audio in 1 (-2048 to 2047) int16_t __not_in_flash_func(AudioIn1)(){return adcInL;} /// Return audio in 1 (-2048 to 2047) int16_t __not_in_flash_func(AudioIn2)(){return adcInR;} /// Return CV in (-2048 to 2047) int16_t __not_in_flash_func(CVIn)(int i){return cv[i];} /// Return CV in 1 (-2048 to 2047) int16_t __not_in_flash_func(CVIn1)(){return cv[0];} /// Return CV in 2 (-2048 to 2047) int16_t __not_in_flash_func(CVIn2)(){return cv[1];} /// Read pulse in bool __not_in_flash_func(PulseIn)(int i){return pulse[i];} /// Return true for one sample on pulse rising edge bool __not_in_flash_func(PulseInRisingEdge)(int i){return pulse[i] && !last_pulse[i];} /// Return true for one sample on pulse falling edge bool __not_in_flash_func(PulseInFallingEdge)(int i){return !pulse[i] && last_pulse[i];} /// Read pulse in 1 bool __not_in_flash_func(PulseIn1)(){return pulse[0];} /// Return true for one sample on pulse 1 rising edge bool __not_in_flash_func(PulseIn1RisingEdge)(){return pulse[0] && !last_pulse[0];} /// Return true for one sample on pulse 1 falling edge bool __not_in_flash_func(PulseIn1FallingEdge)(){return !pulse[0] && last_pulse[0];} /// Read pulse in 2 bool __not_in_flash_func(PulseIn2)(){return pulse[1];} /// Return true for one sample on pulse 2 falling edge bool __not_in_flash_func(PulseIn2FallingEdge)(){return !pulse[1] && last_pulse[1];} /// Return true for one sample on pulse 2 rising edge bool __not_in_flash_func(PulseIn2RisingEdge)(){return pulse[1] && !last_pulse[1];} /// Return true if jack connected to input bool __not_in_flash_func(Connected)(Input i){return connected[i];} /// Return true if no jack connected to input bool __not_in_flash_func(Disconnected)(Input i){return !connected[i];} /// Set LED brightness, values 0-4095 // Led numbers are: // 0 1 // 2 3 // 4 5 void __not_in_flash_func(LedBrightness)(uint32_t index, uint16_t value) { pwm_set_gpio_level(leds[index], (value*value)>>8); } /// Turn LED on/off void __not_in_flash_func(LedOn)(uint32_t index, bool value = true) { pwm_set_gpio_level(leds[index], value?65535:0); } /// Turn LED off void __not_in_flash_func(LedOff)(uint32_t index) { pwm_set_gpio_level(leds[index], 0); } // Return power state of USB port USBPowerState_t USBPowerState() { if (HardwareVersion() != Rev1_1) return Unsupported; else if (gpio_get(USB_HOST_STATUS)) return UFP; else return DFP; } /// Return hardware version HardwareVersion_t HardwareVersion() const { return hw; } /// Return ID number unique to flash card uint64_t UniqueCardID() const { return uniqueID; } /// Return true iff CV outputs are calibrated. /// Returns false if using default calibration values. bool CVOutsCalibrated() const { return cvOutsCalibrated; } void Abort(); uint16_t CRCencode(const uint8_t *data, int length); private: typedef struct { float m, b; int32_t mi, bi; } CalCoeffs; typedef struct { int32_t dacSetting; int8_t voltage; } CalPoint; static constexpr int calMaxChannels = 2; static constexpr int calMaxPoints = 10; static volatile uint32_t cvValue[2]; uint8_t numCalibrationPoints[calMaxChannels]; CalPoint calibrationTable[calMaxChannels][calMaxPoints]; CalCoeffs calCoeffs[calMaxChannels]; uint64_t uniqueID; uint8_t ReadByteFromEEPROM(unsigned int eeAddress, bool &failed); int ReadIntFromEEPROM(unsigned int eeAddress, bool &failed); void CalcCalCoeffs(int channel); int ReadEEPROM(); uint32_t MIDIToDAC(int midiNote, int channel); uint32_t MillivoltsToDAC(int millivolts, int channel, bool &limited); HardwareVersion_t hw; HardwareVersion_t ProbeHardwareVersion(); int16_t dacOut[2]; volatile int32_t knobs[4] = { 0, 0, 0, 0 }; // 0-4095 volatile bool pulse[2] = { 0, 0 }; volatile bool last_pulse[2] = { 0, 0 }; volatile int32_t cv[2] = { 0, 0 }; // -2047 - 2048 volatile int16_t adcInL = 0x800, adcInR = 0x800; volatile uint8_t mxPos = 0; // external multiplexer value volatile int32_t plug_state[6] = {0,0,0,0,0,0}; volatile bool connected[6] = {0,0,0,0,0,0}; bool useNormProbe; Switch switchVal, lastSwitchVal; volatile uint8_t runADCMode; bool cvOutsCalibrated; // Buffers that DMA reads into / out of uint16_t ADC_Buffer[2][8]; uint16_t SPI_Buffer[2][2]; uint8_t adc_dma, spi_dma; // DMA ids uint8_t dmaPhase = 0; // Convert signed int16 value into data string for DAC output uint16_t __not_in_flash_func(dacval)(int16_t value, uint16_t dacChannel) { if (value<-2048) value = -2048; if (value > 2047) value = 2047; return (dacChannel | 0x3000) | (((uint16_t)((value & 0x0FFF) + 0x800)) & 0x0FFF); } uint32_t next_norm_probe(); void CorrectADCDNL(uint16_t &value) const; void BufferFull(); void AudioWorker(); static void AudioCallback() { thisptr->BufferFull(); } static ComputerCard *thisptr; // 19-bit CV outputs static void OnCVPWMWrap() { static int32_t error1 = 0, error2 = 0; pwm_clear_irq(pwm_gpio_to_slice_num(CV_OUT_1)); // clear the interrupt flag uint32_t truncated_cv1_val = (cvValue[0]-error1) & 0xFFFFFF00; error1 += truncated_cv1_val - cvValue[0]; pwm_set_gpio_level(CV_OUT_1, (truncated_cv1_val>>8)); uint32_t truncated_cv2_val = (cvValue[1]-error2) & 0xFFFFFF00; error2 += truncated_cv2_val - cvValue[1]; pwm_set_gpio_level(CV_OUT_2, (truncated_cv2_val>>8)); } }; #ifndef COMPUTERCARD_NOIMPL #include "hardware/adc.h" #include "hardware/clocks.h" #include "hardware/dma.h" #include "hardware/flash.h" #include "hardware/i2c.h" #include "hardware/irq.h" #include "hardware/spi.h" // Input normalisation probe pin #define NORMALISATION_PROBE 4 // Mux pins #define MX_A 24 #define MX_B 25 // ADC input pins #define AUDIO_L_IN_1 27 #define AUDIO_R_IN_1 26 #define MUX_IO_1 28 #define MUX_IO_2 29 #define DAC_CHANNEL_A 0x0000 #define DAC_CHANNEL_B 0x8000 #define DAC_CS 21 #define DAC_SCK 18 #define DAC_TX 19 #define EEPROM_SDA 16 #define EEPROM_SCL 17 #define PULSE_1_INPUT 2 #define PULSE_2_INPUT 3 #define DEBUG_1 0 #define DEBUG_2 1 #define SPI_PORT spi0 #define SPI_DREQ DREQ_SPI0_TX #define BOARD_ID_0 7 #define BOARD_ID_1 6 #define BOARD_ID_2 5 // The ADC (/DMA) run mode, used to stop DMA in a known state before writing to flash #define RUN_ADC_MODE_RUNNING 0 #define RUN_ADC_MODE_REQUEST_ADC_STOP 1 #define RUN_ADC_MODE_ADC_STOPPED 2 #define RUN_ADC_MODE_REQUEST_ADC_RESTART 3 #define EEPROM_ADDR_ID 0 #define EEPROM_ADDR_VERSION 2 #define EEPROM_ADDR_CRC_L 87 #define EEPROM_ADDR_CRC_H 86 #define EEPROM_VAL_ID 2001 #define EEPROM_NUM_BYTES 88 #define EEPROM_PAGE_ADDRESS 0x50 // Initialise CV output delta-sigma target to half-way (near 0V) volatile uint32_t ComputerCard::cvValue[2] = {262144,262144}; ComputerCard *ComputerCard::thisptr; // Return pseudo-random bit for normalisation probe uint32_t __not_in_flash_func(ComputerCard::next_norm_probe)() { static uint32_t lcg_seed = 1; lcg_seed = 1664525 * lcg_seed + 1013904223; return lcg_seed >> 31; } // Main audio core function void __not_in_flash_func(ComputerCard::AudioWorker)() { adc_select_input(0); adc_set_round_robin(0b0001111U); // enabled, with DMA request when FIFO contains data, no erro flag, no byte shift adc_fifo_setup(true, true, 1, false, false); // ADC clock runs at 48MHz // 48MHz ÷ (124+1) = 384kHz ADC sample rate // = 8×48kHz audio sample rate adc_set_clkdiv(124); // claim and setup DMAs for reading to ADC, and writing to SPI DAC adc_dma = dma_claim_unused_channel(true); spi_dma = dma_claim_unused_channel(true); dma_channel_config adc_dmacfg, spi_dmacfg; adc_dmacfg = dma_channel_get_default_config(adc_dma); spi_dmacfg = dma_channel_get_default_config(spi_dma); // Reading from ADC into memory buffer, so increment on write, but no increment on read channel_config_set_transfer_data_size(&adc_dmacfg, DMA_SIZE_16); channel_config_set_read_increment(&adc_dmacfg, false); channel_config_set_write_increment(&adc_dmacfg, true); // Synchronise ADC DMA the ADC samples channel_config_set_dreq(&adc_dmacfg, DREQ_ADC); // Setup DMA for 8 ADC samples dma_channel_configure(adc_dma, &adc_dmacfg, ADC_Buffer[dmaPhase], &adc_hw->fifo, 8, true); // Turn on IRQ for ADC DMA dma_channel_set_irq0_enabled(adc_dma, true); // Call buffer_full ISR when ADC DMA finished irq_set_enabled(DMA_IRQ_0, true); irq_set_exclusive_handler(DMA_IRQ_0, ComputerCard::AudioCallback); // Turn on IRQ for CV output PWM uint slice_num = pwm_gpio_to_slice_num(CV_OUT_1); pwm_clear_irq(slice_num); pwm_set_irq_enabled(slice_num, true); irq_set_exclusive_handler(PWM_IRQ_WRAP, ComputerCard::OnCVPWMWrap); irq_set_priority(PWM_IRQ_WRAP, 255); irq_set_enabled(PWM_IRQ_WRAP, true); // Set up DMA for SPI spi_dmacfg = dma_channel_get_default_config(spi_dma); channel_config_set_transfer_data_size(&spi_dmacfg, DMA_SIZE_16); // SPI DMA timed to SPI TX channel_config_set_dreq(&spi_dmacfg, SPI_DREQ); // Set up DMA to transmit 2 samples to SPI dma_channel_configure(spi_dma, &spi_dmacfg, &spi_get_hw(SPI_PORT)->dr, NULL, 2, false); adc_run(true); while (1) { // If ready to restart if (runADCMode == RUN_ADC_MODE_REQUEST_ADC_RESTART) { runADCMode = RUN_ADC_MODE_RUNNING; dma_hw->ints0 = 1u << adc_dma; // reset adc interrupt flag dma_channel_set_write_addr(adc_dma, ADC_Buffer[dmaPhase], true); // start writing into new buffer dma_channel_set_read_addr(spi_dma, SPI_Buffer[dmaPhase], true); // start reading from new buffer adc_set_round_robin(0); adc_select_input(0); adc_set_round_robin(0b0001111U); adc_run(true); } else if (runADCMode == RUN_ADC_MODE_ADC_STOPPED) { // We can't remove the PWM IRQ from within the ADC IRQ callback, so we do it here instead. irq_set_enabled(PWM_IRQ_WRAP, false); pwm_clear_irq(pwm_gpio_to_slice_num(CV_OUT_1)); // reset CV PWM interrupt flag irq_remove_handler(PWM_IRQ_WRAP, ComputerCard::OnCVPWMWrap); break; } } } void ComputerCard::Abort() { runADCMode = RUN_ADC_MODE_REQUEST_ADC_STOP; } void __not_in_flash_func(ComputerCard::CorrectADCDNL)(uint16_t &value) const { uint16_t adc512 = value + 512; value += ((value & 0x3FF) == 0x1FF) << 2; value += (adc512 >> 10) << 3; value = uint32_t(value * 520349) >> 19; // Multiply by factor that maps 0-4095 input into 0-4095 output } // Per-audio-sample ISR, called when two sets of ADC samples have been collected from all four inputs void __not_in_flash_func(ComputerCard::BufferFull)() { static int startupCounter = 8; // Decreases by 1 each sample, can do startup things when nonzero. static int mux_state = 0; static int norm_probe_count = 0; // Internal variables for IIR filters on knobs/cv static volatile int32_t knobssm[4] = { 0, 0, 0, 0 }; static volatile int32_t cvsm[2] = { 0, 0 }; __attribute__((unused)) static int np = 0, np1 = 0, np2 = 0; adc_select_input(0); // Advance external mux to next state int next_mux_state = (mux_state + 1) & 0x3; gpio_put(MX_A, next_mux_state & 1); gpio_put(MX_B, next_mux_state & 2); // Set up new writes into next buffer uint8_t cpuPhase = dmaPhase; dmaPhase = 1 - dmaPhase; dma_hw->ints0 = 1u << adc_dma; // reset adc interrupt flag dma_channel_set_write_addr(adc_dma, ADC_Buffer[dmaPhase], true); // start writing into new buffer dma_channel_set_read_addr(spi_dma, SPI_Buffer[dmaPhase], true); // start reading from new buffer //////////////////////////////////////// // Collect various inputs and put them in variables for the DSP // Set CV inputs, with ~240Hz LPF on CV input int cvi = mux_state % 2; // Compensation of ADC DNL errors. CorrectADCDNL(ADC_Buffer[cpuPhase][7]); // CV inputs CorrectADCDNL(ADC_Buffer[cpuPhase][0]); // Audio inputs CorrectADCDNL(ADC_Buffer[cpuPhase][4]); CorrectADCDNL(ADC_Buffer[cpuPhase][1]); CorrectADCDNL(ADC_Buffer[cpuPhase][5]); cvsm[cvi] = (15 * (cvsm[cvi]) + 16 * ADC_Buffer[cpuPhase][7]) >> 4; cv[cvi] = 2048 - (cvsm[cvi] >> 4); // Set audio inputs, by averaging the two samples collected. // Invert to counteract inverting op-amp input configuration adcInR = -(((ADC_Buffer[cpuPhase][0] + ADC_Buffer[cpuPhase][4]) - 0x1000) >> 1); adcInL = -(((ADC_Buffer[cpuPhase][1] + ADC_Buffer[cpuPhase][5]) - 0x1000) >> 1); // 12kHz notch filters adcInR = notchRight(adcInR); adcInL = notchLeft(adcInL); // Set pulse inputs last_pulse[0] = pulse[0]; last_pulse[1] = pulse[1]; pulse[0] = !gpio_get(PULSE_1_INPUT); pulse[1] = !gpio_get(PULSE_2_INPUT); // Set knobs, with ~60Hz LPF int knob = mux_state; knobssm[knob] = (127 * (knobssm[knob]) + 16 * ADC_Buffer[cpuPhase][6]) >> 7; knobs[knob] = knobssm[knob] >> 4; // Set switch value switchVal = static_cast((knobs[3]>1000) + (knobs[3]>3000)); if (startupCounter) { // Don't detect switch changes in first few cycles lastSwitchVal = switchVal; // Should initialise knob and CV smoothing filters here too } //////////////////////////// // Normalisation probe if (useNormProbe) { // Set normalisation probe output value // and update np to the expected history string if (norm_probe_count == 0) { int32_t normprobe = next_norm_probe(); gpio_put(NORMALISATION_PROBE, normprobe); np = (np<<1)+(normprobe&0x1); } // CV sampled at 24kHz comes in over two successive samples if (norm_probe_count == 14 || norm_probe_count == 15) { plug_state[2+cvi] = (plug_state[2+cvi]<<1)+(ADC_Buffer[cpuPhase][7]<1800); } // Audio and pulse measured every sample at 48kHz if (norm_probe_count == 15) { plug_state[Input::Audio1] = (plug_state[Input::Audio1]<<1)+(ADC_Buffer[cpuPhase][5]<1800); plug_state[Input::Audio2] = (plug_state[Input::Audio2]<<1)+(ADC_Buffer[cpuPhase][4]<1800); plug_state[Input::Pulse1] = (plug_state[Input::Pulse1]<<1)+(pulse[0]); plug_state[Input::Pulse2] = (plug_state[Input::Pulse2]<<1)+(pulse[1]); for (int i=0; i<6; i++) { connected[i] = (np != plug_state[i]); } } // Force disconnected values to zero, rather than the normalisation probe garbage if (Disconnected(Input::Audio1)) adcInL = 0; if (Disconnected(Input::Audio2)) adcInR = 0; if (Disconnected(Input::CV1)) cv[0] = 0; if (Disconnected(Input::CV2)) cv[1] = 0; if (Disconnected(Input::Pulse1)) pulse[0] = 0; if (Disconnected(Input::Pulse2)) pulse[1] = 0; } //////////////////////////////////////// // Run the DSP ProcessSample(); //////////////////////////////////////// // Collect DSP outputs and put them in the DAC SPI buffer // CV/Pulse outputs are done immediately in ProcessSample // Invert dacout to counteract inverting output configuration SPI_Buffer[cpuPhase][0] = dacval(-dacOut[0], DAC_CHANNEL_A); SPI_Buffer[cpuPhase][1] = dacval(-dacOut[1], DAC_CHANNEL_B); mux_state = next_mux_state; // If Abort called, stop ADC and DMA if (runADCMode == RUN_ADC_MODE_REQUEST_ADC_STOP) { adc_run(false); adc_set_round_robin(0); adc_select_input(0); dma_hw->ints0 = 1u << adc_dma; // reset adc interrupt flag dma_channel_cleanup(adc_dma); dma_channel_cleanup(spi_dma); irq_set_enabled(DMA_IRQ_0, false); irq_remove_handler(DMA_IRQ_0, ComputerCard::AudioCallback); runADCMode = RUN_ADC_MODE_ADC_STOPPED; } norm_probe_count = (norm_probe_count + 1) & 0xF; lastSwitchVal = switchVal; if (startupCounter) startupCounter--; } ComputerCard::HardwareVersion_t ComputerCard::ProbeHardwareVersion() { // Enable pull-downs, and measure gpio_set_pulls(BOARD_ID_0, false, true); gpio_set_pulls(BOARD_ID_1, false, true); gpio_set_pulls(BOARD_ID_2, false, true); sleep_us(1); // Pull-down state in bits 0, 2, 4 uint8_t pd = gpio_get(BOARD_ID_0) | (gpio_get(BOARD_ID_1) << 2) | (gpio_get(BOARD_ID_2) << 4); // Enable pull-ups, and measure gpio_set_pulls(BOARD_ID_0, true, false); gpio_set_pulls(BOARD_ID_1, true, false); gpio_set_pulls(BOARD_ID_2, true, false); sleep_us(1); // Pull-up state in bits 1, 3, 5 uint8_t pu = (gpio_get(BOARD_ID_0) << 1) | (gpio_get(BOARD_ID_1) << 3) | (gpio_get(BOARD_ID_2) << 5); // Combine to give 6-bit ID uint8_t id = pd | pu; // Set pull-downs gpio_set_pulls(BOARD_ID_0, false, true); gpio_set_pulls(BOARD_ID_1, false, true); gpio_set_pulls(BOARD_ID_2, false, true); switch (id) { case Proto1: case Proto2_Rev1: case Rev1_1: return static_cast(id); default: return Unknown; } } ComputerCard::ComputerCard() { runADCMode = RUN_ADC_MODE_RUNNING; adc_run(false); adc_select_input(0); useNormProbe = false; for (int i=0; i<6; i++) { connected[i] = false; } //////////////////////////////////////// // Initialise LEDs (PWM, set up in pairs due pinout and PWM hardware) for (int i = 0; i < numLeds; i+=2) { gpio_set_function(leds[i], GPIO_FUNC_PWM); gpio_set_function(leds[i]+1, GPIO_FUNC_PWM); // now create PWM config struct pwm_config config = pwm_get_default_config(); pwm_config_set_wrap(&config, 65535); // 16-bit PWM // now set this PWM config to apply to the two outputs pwm_init(pwm_gpio_to_slice_num(leds[i]), &config, true); pwm_init(pwm_gpio_to_slice_num(leds[i]+1), &config, true); // set initial level pwm_set_gpio_level(leds[i], 0); pwm_set_gpio_level(leds[i]+1, 0); } //////////////////////////////////////// // Initialise knobs / audio in / CV in (ADC + Mux) adc_init(); // Initialize the ADC // Set ADC pins adc_gpio_init(AUDIO_L_IN_1); adc_gpio_init(AUDIO_R_IN_1); adc_gpio_init(MUX_IO_1); adc_gpio_init(MUX_IO_2); // Initialize Mux Control pins gpio_init(MX_A); gpio_init(MX_B); gpio_set_dir(MX_A, GPIO_OUT); gpio_set_dir(MX_B, GPIO_OUT); //////////////////////////////////////// gpio_init(PULSE_1_RAW_OUT); gpio_set_dir(PULSE_1_RAW_OUT, GPIO_OUT); gpio_put(PULSE_1_RAW_OUT, true); // set raw value high (output low) gpio_init(PULSE_2_RAW_OUT); gpio_set_dir(PULSE_2_RAW_OUT, GPIO_OUT); gpio_put(PULSE_2_RAW_OUT, true); // set raw value high (output low) //////////////////////////////////////// // Initialise pulse inputs gpio_init(PULSE_1_INPUT); gpio_set_dir(PULSE_1_INPUT, GPIO_IN); gpio_pull_up(PULSE_1_INPUT); // NB Needs pullup to activate transistor on inputs gpio_init(PULSE_2_INPUT); gpio_set_dir(PULSE_2_INPUT, GPIO_IN); gpio_pull_up(PULSE_2_INPUT); // NB: Needs pullup to activate transistor on inputs //////////////////////////////////////// // Initialise audio outputs (SPI for external DAC) spi_init(SPI_PORT, 15625000); spi_set_format(SPI_PORT, 16, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); gpio_set_function(DAC_SCK, GPIO_FUNC_SPI); gpio_set_function(DAC_TX, GPIO_FUNC_SPI); gpio_set_function(DAC_CS, GPIO_FUNC_SPI); //////////////////////////////////////// // Initialise CV outputs // We set up the PWM here, and add the IRQ for sigma-delta later one Run() is called // First, tell the CV pins that the PWM is in charge of the value. gpio_set_function(CV_OUT_1, GPIO_FUNC_PWM); gpio_set_function(CV_OUT_2, GPIO_FUNC_PWM); // now create PWM config struct { pwm_config config = pwm_get_default_config(); pwm_config_set_wrap(&config, 1999); // less than 11-bit PWM // now set this PWM config to apply to the two outputs // NB: CV_A and CV_B share the same PWM slice, which means that they share a PWM config // They have separate 'gpio_level's (output compare unit) though, so they can have different PWM on-times pwm_init(pwm_gpio_to_slice_num(CV_OUT_1), &config, true); // Slice 1, channel A pwm_init(pwm_gpio_to_slice_num(CV_OUT_2), &config, true); // slice 1 channel B (redundant to set up again) } // set initial level to half way (0V) pwm_set_gpio_level(CV_OUT_1, 1000); pwm_set_gpio_level(CV_OUT_2, 1000); //////////////////////////////////////// // Miscellaneous pins // Initialise board version ID pins gpio_init(BOARD_ID_0); gpio_init(BOARD_ID_1); gpio_init(BOARD_ID_2); gpio_set_dir(BOARD_ID_0, GPIO_IN); gpio_set_dir(BOARD_ID_1, GPIO_IN); gpio_set_dir(BOARD_ID_2, GPIO_IN); // Initialise USB host status pin gpio_init(USB_HOST_STATUS); gpio_disable_pulls(USB_HOST_STATUS); // Initialise normalisation probe pin gpio_init(NORMALISATION_PROBE); gpio_set_dir(NORMALISATION_PROBE, GPIO_OUT); gpio_put(NORMALISATION_PROBE, false); // Initialise EEPROM (I2C) i2c_init(i2c0, 100 * 1000); gpio_set_function(EEPROM_SDA, GPIO_FUNC_I2C); gpio_set_function(EEPROM_SCL, GPIO_FUNC_I2C); // If not using UART pins for UART, instead use as debug lines #ifndef ENABLE_UART_DEBUGGING // Debug pins gpio_init(DEBUG_1); gpio_set_dir(DEBUG_1, GPIO_OUT); gpio_init(DEBUG_2); gpio_set_dir(DEBUG_2, GPIO_OUT); #endif // Read hardware version hw = ProbeHardwareVersion(); // Read EEPROM calibration values cvOutsCalibrated = (ReadEEPROM() == 0); // Read unique card ID flash_get_unique_id((uint8_t *) &uniqueID); // Do some mixing up of the bits using full-cycle 64-bit LCG // Should help ensure most bytes change even if many bits of // the original flash unique ID are the same between flash chips. for (int i=0; i<20; i++) { uniqueID = uniqueID * 6364136223846793005ULL + 1442695040888963407ULL; } } // Read a byte from EEPROM uint8_t ComputerCard::ReadByteFromEEPROM(unsigned int eeAddress, bool &failed) { uint8_t deviceAddress = EEPROM_PAGE_ADDRESS | ((eeAddress >> 8) & 0x0F); uint8_t data = 0xFF; uint8_t addr_low_byte = eeAddress & 0xFF; if (i2c_write_timeout_us(i2c0, deviceAddress, &addr_low_byte, 1, false, 10000) <= 0) { failed = true; return 0; } if (i2c_read_timeout_us(i2c0, deviceAddress, &data, 1, false, 10000) <= 0) { failed = true; return 0; } return data; } // Read a 16-bit integer from EEPROM int ComputerCard::ReadIntFromEEPROM(unsigned int eeAddress, bool &failed) { uint8_t highByte = ReadByteFromEEPROM(eeAddress, failed); uint8_t lowByte = ReadByteFromEEPROM(eeAddress + 1, failed); return (highByte << 8) | lowByte; } uint16_t ComputerCard::CRCencode(const uint8_t *data, int length) { uint16_t crc = 0xFFFF; // Initial CRC value for (int i = 0; i < length; i++) { crc ^= ((uint16_t)data[i]) << 8; // Bring in the next byte for (uint8_t bit = 0; bit < 8; bit++) { if (crc & 0x8000) { crc = (crc << 1) ^ 0x1021; // CRC-CCITT polynomial } else { crc = crc << 1; } } } return crc; } int ComputerCard::ReadEEPROM() { // Set up default values in the calibration table, // to be used if we can't read valid calibration from EEPROM for (unsigned channel = 0; channel < calMaxChannels; channel++) { numCalibrationPoints[channel] = 3; calibrationTable[channel][0].voltage = -20; // -2V calibrationTable[channel][0].dacSetting = 347700; calibrationTable[channel][1].voltage = 0; // 0V calibrationTable[channel][1].dacSetting = 261200; calibrationTable[channel][2].voltage = 20; // +2V calibrationTable[channel][2].dacSetting = 174400; CalcCalCoeffs(channel); // calculate the coefficients } // Read magic number // Failure here could occur if I2C failed, or if incorrect/no magic number stored in EEPROM bool i2cFailed = false; if (ReadIntFromEEPROM(EEPROM_ADDR_ID, i2cFailed) != EEPROM_VAL_ID) { return 1; } // Read the EEPROM into RAM uint8_t buf[EEPROM_NUM_BYTES]; for (int i = 0; i < EEPROM_NUM_BYTES; i++) { buf[i] = ReadByteFromEEPROM(i, i2cFailed); } // Check CRC and fail if incorrect uint16_t calculatedCRC = CRCencode(buf, 86); uint16_t foundCRC = ((uint16_t)buf[EEPROM_ADDR_CRC_H] << 8) | buf[EEPROM_ADDR_CRC_L]; if (calculatedCRC != foundCRC) { return 1; } // CRC passed, so now read the calibration information for (uint8_t channel = 0; channel < calMaxChannels; channel++) { int channelOffset = 4 + (41 * channel); // channel 0 = 4, channel 1 = 45 numCalibrationPoints[channel] = buf[channelOffset++]; for (uint8_t point = 0; point < numCalibrationPoints[channel]; point++) { // Unpack Pack targetVoltage (int8_t) from buf int8_t targetVoltage = (int8_t)buf[channelOffset++]; // Unpack dacSetting (uint32_t) from buf (4 bytes) uint32_t dacSetting = 0; dacSetting |= ((uint32_t)buf[channelOffset++]) << 24; // MSB dacSetting |= ((uint32_t)buf[channelOffset++]) << 16; dacSetting |= ((uint32_t)buf[channelOffset++]) << 8; dacSetting |= ((uint32_t)buf[channelOffset++]); // LSB // Write settings into calibration table calibrationTable[channel][point].voltage = targetVoltage; calibrationTable[channel][point].dacSetting = dacSetting; } // Now calculate the calibration coeffs that are actually used // by the calibrated CVOut functions CalcCalCoeffs(channel); } return 0; } void ComputerCard::CalcCalCoeffs(int channel) { float sumV = 0.0; float sumDAC = 0.0; float sumV2 = 0.0; float sumVDAC = 0.0; int N = numCalibrationPoints[channel]; for (int i = 0; i < N; i++) { float v = calibrationTable[channel][i].voltage * 0.1f; float dac = calibrationTable[channel][i].dacSetting; sumV += v; sumDAC += dac; sumV2 += v * v; sumVDAC += v * dac; } float denominator = N * sumV2 - sumV * sumV; if (denominator != 0) { calCoeffs[channel].m = (N * sumVDAC - sumV * sumDAC) / denominator; } else { calCoeffs[channel].m = 0.0; } calCoeffs[channel].b = (sumDAC - calCoeffs[channel].m * sumV) / N; calCoeffs[channel].mi = int32_t(calCoeffs[channel].m * 1.333333333333333f + 0.5f); calCoeffs[channel].bi = int32_t(calCoeffs[channel].b + 0.5f); } uint32_t ComputerCard::MIDIToDAC(int midiNote, int channel) { int32_t dacValue = ((calCoeffs[channel].mi * (midiNote - 60)) >> 4) + calCoeffs[channel].bi; if (dacValue > 524287) dacValue = 524287; if (dacValue < 0) dacValue = 0; return (dacValue*125)>>7; } /// Converts voltage in millivolts to corresponding 19-bit sigma-delta PWM DAC value /// Returns true if requested voltage is outside of full range of DAC values /// millivolts should be in range -6000 to 6000. /// Accuracy is dependent, of course, on the calibration coefficients uint32_t ComputerCard::MillivoltsToDAC(int millivolts, int channel, bool &limited) { limited = false; int32_t dacValue = ((((calCoeffs[channel].mi * millivolts) >> 9) * 1573) >> 12) + calCoeffs[channel].bi; if (dacValue > 524287) { dacValue = 524287; limited = true; } if (dacValue < 0) { dacValue = 0; limited = true; } return (dacValue*125)>>7; } #endif #endif