I'm trying to track down a weird problem, and it's possible the problem is a constipated USB serial buffer. The objective: communicate between an RPi Pico2 and a patch running inplugdata. The Pico2 is mounted on a PCB called an Oblique Palette with 8 i2c DACs and 8 multiplexed channels running to one ADC, plus of course their support electronics. You can see it running here. Expected Behavior: - The plugdata patch running on a computer opens the serial port.
- The µC (the Palette) reports its configuration as a confirmation of the connection.
- The patch receives the configuration, makes it visible.
- The patch sends over a packet of 8 delimited values, one for each channel.
- The Palette receives the values, sets the DACs to mirror the values from the patch, then reads all the ADC channels and sends them to the patch.
- When the patch receives the values from the Palette, the patch sends its output values again.
- Repeat steps 4-7 until your creative objectives are realized.
What happens: MacOS: Works okay, but at a sample rate of 40Hz, which is pretty damn slow considering the speed of communication I should be getting. I don't see why I should be getting slower than kilohertz sample rates. Note: I had incorrect pullup values earlier, and the result before that was that it would communicate OK at ~40Hz, then, after a random number of seconds, the Palette would start waiting exactly 4 seconds (minus about 100 µsecs) before continuing. Linux: Works great for a random number of seconds at ~350Hz, then stops altogether, neither reading nor writing, and the LED status light, which should be switching each time it completes a task, is completely solid, indicating that the µC is not running at all. If I try to close the serial port from the computer side, plugdata hangs, as it is waiting to hear back from the completely frozen serial port. Unplugging the USB immediately restores the patch, and plugging the board back in brings it back to life as normal. This has been driving me bonkers for almost two months now, and I've solved what seems to be half the problem (needing to switch to surprisingly small i2c pullup resistors), but now something (and I suspect the serial port) is stopping me. Man, I'd love some help. Code follows // Oblique Palette// Communication of values in ASCII between the Easel and the Palette.// The Palette (this firmware) speaks with the Easel abstraction patch in plugdata, running on a separate GP computer.// It announces its presence with its name, version number, and hardware configuration.// The Easel responds with output values whenever it gets DAC values from the Palette.// The Palette responds back ADC values, (later, followed by the measured time between communications, to the Easel)// The Palette updates all DACs// ( The Palette adjusts its sample rate according to the round trip time)// ( The Easel adjusts it sample rate according to the round trip time)// #include <Adafruit_TinyUSB.h>// High efficiency USB serial communication#include <Arduino.h> // Implicit in the Arduino IDE#include <Mux.h> // Multiplexer for analog inputs#include <Servo.h> // For servo-range PWM controls#include <Wire.h> // i2c communication for DACs#include <Adafruit_MCP4725.h> // i2c DACs#include <pico/stdlib.h> // To access Pico-specific libraries#include <hardware/adc.h> // Direct ADC read access for high speed reads#include <hardware/gpio.h> // Direct GPIO access#include "Adafruit_TinyUSB.h" // Better USB communicationusing namespace admux; // for ADC multiplexer/**********************************Hardware configuration of the Palette***********************************/const char modelName[] = "Palette"; // Model nameconst char firmwareVersion[] = "v0.5.6"; // Version number of this firmwareconst int DACBitDepth = 12; // Output/DAC resolutionconst int numOutputChannels = 8; // Number of output channels on the Paletteconst int ADCBitDepth = 12; // Input/ADC resolutionconst int numInputChannels = 8; // Number of input channels on the Palettechar specsheet[32]; // Buffer to carry the specsheet string to the Easelconst int numSamplesPerChannel = 1; // How many samples to pack for transmission (for increasing sample rate later)const int maxDACValue = (pow(2, DACBitDepth) - 1); // The maximum DAC valueconst int maxADCValue = (pow(2, ADCBitDepth) - 1); // The maximum ADC valueconst int numValuesFromEaselPerPacket = numOutputChannels * numSamplesPerChannel;const int numValuesToEaselPerPacket = numInputChannels * numSamplesPerChannel;const byte numi2cBusses = 2; // Using i2c busses 0 and 1/*********************************Analytics*********************************/long lastTime; // For holding the time to measure the amount of time processing takesconst int communicationTimeout = 1000; // How long to wait to hear backlong communicationTimeoutStart; // If communication goes down, start a timerbool ledState = 0;/*********************************ADCs and DACs*********************************/#define ADCPin 28 // The µC ADC pinMux ADCMux(Pin(ADCPin, INPUT, PinType::Analog), Pinset(21, 20, 17)); // For addressing ADC channels with a CD4051 multiplexerAdafruit_MCP4725 DAC[numOutputChannels]; // All DACsconst byte DACAddress[numOutputChannels] = { 0x60, 0x61, 0x62, 0x63, 0x60, 0x61, 0x62, 0x63 }; // Their respective (duplicate) i2c addressesconst byte DACi2cBus[numOutputChannels] = { 0, 0, 0, 0, 1, 1, 1, 1 }; // Which bus each DAC is on respectivelybyte servoPin[numOutputChannels] = { 2, 3, 4, 5, 6, 7, 8, 9 }; // Servo pins mirror the output of their associated DAC channelServo servoChannel[numOutputChannels]; // All output servos/********************************************************************Serial buffer and the values to parse between Easel and Palette & back********************************************************************/int DACSamplesFromEasel[numValuesFromEaselPerPacket] = { 511, 1023, 1535, 2047, 2559, 3071, 3583, 4095 }; // The buffer of values received from the Easelint ADCSamplesToEasel[numValuesToEaselPerPacket]; // The values to send to the Easel/****************************************************************************************************************************************Main code****************************************************************************************************************************************/void setup() { analogReadResolution(ADCBitDepth); // Use the full bit depth of the ADCs pinMode(LED_BUILTIN, OUTPUT); pinMode(ADCPin, INPUT); testBlink(); // Switch whenever something happens // TinyUSBDevice.begin(115200); // Baud setting is ignored, as it's handled by USB peripherals & Easel/OS testBlink(); // Switch whenever something happens Wire.setClock(400000); Wire.setSDA(0); // Data line for DACs 0-3 Wire.setSCL(1); // Serial clock line for DACs 0-3 testBlink(); // Switch whenever something happens Wire1.setClock(1000000); Wire1.setSDA(10); // Data line for DACs 4-7 Wire1.setSCL(11); // Serial clock line for DACs 4-7 testBlink(); // Switch whenever something happens // begin all DACs on both i2c busses for (byte thisDAC = 0; thisDAC < numOutputChannels; thisDAC++) { if (DACi2cBus[thisDAC] == 0) { DAC[thisDAC].begin(DACAddress[thisDAC]); // begin each DAC on i2c bus 0 } else { // DAC[thisDAC].begin(DACAddress[thisDAC], &Wire1); // begin each DAC on i2c bus 1 } } testBlink(); for (byte servoToSetup = 0; servoToSetup < numOutputChannels; servoToSetup++) { // Make a servo object for each DAC channel servoChannel[servoToSetup].attach(servoPin[servoToSetup]); // Attach each servo to its pin } testBlink(); // Switch whenever something happens while (!findEasel()) // Look for the Easel on boot until the Palette gets back real values ;}/**************************************** VVV LOOP VVV ****************************************/void loop() { lastTime = micros() - lastTime; // Time for the whole loop. Use for diagnostics if you gotta. // Receive Serial values into array receiveDACs(); testBlink(); // Read ADC values into array readAllADCs(); // Send ADC values sendADCToEasel(); // Update DAC values updateAllDACs(); // Update all servo DAC values updateAllServos();}/**************************************** ΛΛΛ LOOP ΛΛΛ ****************************************//******************************************************************************************Functions******************************************************************************************//*************************** DAC/output functions ***************************/// Receive all DAC values from the Easelvoid receiveDACs() { for (byte thisDACChannel = 0; thisDACChannel < numOutputChannels; thisDACChannel++) { DACSamplesFromEasel[thisDACChannel] = Serial.parseInt(); }}// Scroll through all DACs to update them with data from the Easelvoid updateAllDACs() { for (byte thisi2cDACChannel = 0; thisi2cDACChannel < numOutputChannels; thisi2cDACChannel++) { // Write to each DAC DAC[thisi2cDACChannel].setVoltage(DACSamplesFromEasel[thisi2cDACChannel], false, 400000); }}// Map analog values to servo outputsvoid updateAllServos() { for (byte thisServoToUpdate = 0; thisServoToUpdate < numOutputChannels; thisServoToUpdate++) { servoChannel[thisServoToUpdate].writeMicroseconds(map(DACSamplesFromEasel[thisServoToUpdate], 0, maxDACValue, 2000, 1000)); // Map the DAC value to the servos within their range }}/*************************** ADC/input functions ***************************/void readAllADCs() { for (byte thisADCChannel = 0; thisADCChannel < numInputChannels; thisADCChannel++) { ADCSamplesToEasel[thisADCChannel] = ADCMux.read(thisADCChannel); // use regular ol' analogRead() max sample rate: ~ 177Hz // ADCSamplesToEasel[thisADCChannel] = (thisADCChannel * 512) - 1; // Test signal bypassing analogRead() // if (!adc_fifo_is_empty()) { // Look at the ADC register, and if it's got anything in it, read it // ADCSamplesToEasel[thisADCChannel] = adc_fifo_get(); // // } }}void sendADCToEasel() { for (byte thisADCChannel = 0; thisADCChannel < numInputChannels; thisADCChannel++) { Serial.print(ADCSamplesToEasel[thisADCChannel]); Serial.write(32); // Space to delineate values Serial.flush(); } Serial.write(13); // Carriage return to end the transmission}// Insert this function before sending to the Easel to just repeat back input channels so we can make sure the Easel is sending/Palette is receiving.void testRepeater() { for (int thisChannelToRepeat = 1; thisChannelToRepeat < numInputChannels; thisChannelToRepeat++) { ADCSamplesToEasel[thisChannelToRepeat] = DACSamplesFromEasel[thisChannelToRepeat]; } ADCSamplesToEasel[0] = lastTime / 100;}// Call out for the Easel and blink the onboard LED if it's not found yetbool findEasel() { if (Serial.available()) { //If the Palette is receiving data from the Easel, start talking! testBlink(); communicationTimeoutStart = micros(); return (1); } else { // When the Palette hasn't heard from the Easel yet, ping out with identifying information sprintf(specsheet, "%s %s %u %u %u %u", modelName, firmwareVersion, numOutputChannels, DACBitDepth, numInputChannels, ADCBitDepth); Serial.println(specsheet); // Error code/waiting blinky for not hearing back from the Easel is ._ repeating digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(200); return (0); }}void testBlink() { digitalWrite(LED_BUILTIN, !ledState); // Switch whenever something happens ledState = !ledState;}
|