RP2040 + MCP4728 + Mozzi is possible?

188 views
Skip to first unread message

Tarcisio León Drusin

unread,
Jan 28, 2023, 4:28:12 PM1/28/23
to Mozzi-users
Hi! 
I am trying to get synth output with a Raspberry Pico (RP2040) using Mozzi through a MCP4728 DAC.
I am using the <Wire.h> library to get voltage out of the MCP4728 DAC.

By using #define EXTERNAL_AUDIO_OUTPUT false, and just doing:
AudioOutput_t updateAudio(){
  return dac.analogWrite(MCP4728::DAC_CH::D,  aSin.next());
}
I get output, but it's very low pitch and if I offset the pitch for example trying a 2000Hz frequency, I get a real frequency of about 60Hz, but its very very low resolution, like 4 bits.

Tried using #define EXTERNAL_AUDIO_OUTPUT true, but when I try to use dac.analogWrite to get output, the Raspberry Pico just disconnects and I need to reset it using the BOOTSEC button.

I read that RP2040 is just recently supported, do you think it should be possible to use these two together???

Thanks!!! :)

--------------------
Here is the code that freezes the RP2040:

#include <MozziGuts.h>
#include <Oscil.h> // oscillator template
#include <tables/sin2048_int8.h> // sine table for oscillator
#include <Wire.h>
#include "MCP4728.h"

MCP4728 dac;
// use: Oscil <table_size, update_rate> oscilName (wavetable), look in .h file of table #included above
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);

// use #define for CONTROL_RATE, not a constant
#define CONTROL_RATE 256 // Hz, powers of 2 are most reliable


void setup(){
  startMozzi(CONTROL_RATE); // :)
  aSin.setFreq(440); // set the frequency

      Serial.begin(115200);  // initialize serial interface for print()
        Wire.setSDA(0);
    Wire.setSCL(1);
    Wire.begin();
    dac.attach(Wire, 14);
    dac.readRegisters();

    dac.selectVref(MCP4728::VREF::VDD, MCP4728::VREF::VDD, MCP4728::VREF::INTERNAL_2_8V, MCP4728::VREF::VDD);
    dac.selectPowerDown(MCP4728::PWR_DOWN::GND_100KOHM, MCP4728::PWR_DOWN::GND_100KOHM, MCP4728::PWR_DOWN::GND_500KOHM, MCP4728::PWR_DOWN::GND_500KOHM);
    dac.selectGain(MCP4728::GAIN::X1, MCP4728::GAIN::X1, MCP4728::GAIN::X2, MCP4728::GAIN::X2);
    dac.analogWrite(MCP4728::DAC_CH::A, 111);
    dac.analogWrite(MCP4728::DAC_CH::B, 222);
    dac.analogWrite(MCP4728::DAC_CH::C, 333);
    dac.analogWrite(MCP4728::DAC_CH::D, 3000);

    dac.enable(true);
}

void updateControl(){
}

void audioOutput(const AudioOutput f)
{
  // signal is passed as 16 bit, zero-centered, internally. This DAC expects 12 bits unsigned,
  // so shift back four bits, and add a bias of 2^(12-1)=2048
  uint16_t out = (f.l()) + 1024;
  dac.analogWrite(MCP4728::DAC_CH::D, out);   //-------->> THIS FREEZES THE RP2040 
}
AudioOutput_t updateAudio(){
  return MonoOutput::from8Bit(aSin.next()); // return an int signal centred around 0
}

void loop(){
  audioHook(); // required here
}


tomco...@live.fr

unread,
Jan 29, 2023, 4:37:49 PM1/29/23
to Mozzi-users
Hi,

Yes this port is very recent and I am not sure the external audio has been tested (should be implemented though).
I do not have the hardware to test right now but I have spotted a few things:


For #define EXTERNAL_AUDIO_OUTPUT true,  you talk about shifting the output but you do not actually do it, something like uint16_t out = (f.l()>>4) + 1024; should work better? Who knows what happens when you send too many bits to a 12 bits dac. Also, this DAC is a I2C dac. I2C protocol is generally too slow to generate clean audio, SPI or I2S should be used for that, that might hang up the whole processor, hence the reset.


Just as a side note:
Doing it with #define EXTERNAL_AUDIO_OUTPUT false, and updating the dac in updateAudio it definitively not a good idea has you are not guaranteed that updateAudio is called at the correct frequency (it is in average, but not for every sample as we are buffering its output). Note that, in that case you are sending 8 bits to the DAC (probably hence the low sound). Also, you are sending something centered on zero (which will create weird artefacts). Once again, this is not how it should be done ;).


Hope it helps,

Tarcisio León Drusin

unread,
Jan 30, 2023, 4:51:00 PM1/30/23
to Mozzi-users
Thanks Tom for taking the time to answer! :)
I discovered I can do Wire.setClock(1400000L) to get the I2C protocol to the maximum speed that the MCP4728 supports, 1.4Mhz.
After doing that, with  #define EXTERNAL_AUDIO_OUTPUT false, I was able to generate a clean looking wave!! I guess the value was updated very slowly, that's why it looked like sample and hold before. For some reason the osc.setFreq() does not generate the correct frequency, but I guess it could be offset in software. Tried doing an ascending pitch and it does sounds and looks fine at any frecuency (of course before alliasing comes to play)

" or #define EXTERNAL_AUDIO_OUTPUT true,  you talk about shifting the output but you do not actually do it, something like uint16_t out = (f.l()>>4) + 1024; should work better?"
It still hangs, even if I just use out=0 to test.

Well I think it works fine for what I was going to use it, a dubsiren!

--------------------------------------HERE IS THE CODE THAT WORKS! (with incorrect pitch anyways)--------------------------
------also use #define EXTERNAL_AUDIO_OUTPUT false ------------------------


#include <MozziGuts.h>
#include <Oscil.h> // oscillator template
#include <tables/saw8192_int8.h> // sine table for oscillator

#include <Wire.h>
#include "MCP4728.h"

MCP4728 dac;
// use: Oscil <table_size, update_rate> oscilName (wavetable), look in .h file of table #included above
Oscil <SAW8192_NUM_CELLS, AUDIO_RATE> aSin(SAW8192_DATA);


// use #define for CONTROL_RATE, not a constant
#define CONTROL_RATE 256 // Hz, powers of 2 are most reliable


void setup(){
  startMozzi(CONTROL_RATE); // :)
  //aSin.setFreq(2000); // set the frequency


      Serial.begin(115200);  // initialize serial interface for print()
        Wire.setSDA(0);
    Wire.setSCL(1);
    Wire.begin();
    Wire.setClock(1400000L); //maximo

    dac.attach(Wire, 14);
    dac.readRegisters();
   
   
    dac.selectVref(MCP4728::VREF::VDD, MCP4728::VREF::VDD, MCP4728::VREF::INTERNAL_2_8V, MCP4728::VREF::VDD);
    dac.selectPowerDown(MCP4728::PWR_DOWN::GND_100KOHM, MCP4728::PWR_DOWN::GND_100KOHM, MCP4728::PWR_DOWN::GND_500KOHM, MCP4728::PWR_DOWN::GND_500KOHM);
    dac.selectGain(MCP4728::GAIN::X1, MCP4728::GAIN::X1, MCP4728::GAIN::X2, MCP4728::GAIN::X2);
    dac.analogWrite(MCP4728::DAC_CH::A, 111);
    dac.analogWrite(MCP4728::DAC_CH::B, 222);
    dac.analogWrite(MCP4728::DAC_CH::C, 333);
    dac.analogWrite(MCP4728::DAC_CH::D, 3000);

    dac.enable(true);

    dac.readRegisters();
    //printStatus();
    pinMode(28,OUTPUT);
}


void updateControl(){
  static int counter = 0;
  counter=counter +1;

  Serial.println(counter);
  aSin.setFreq(1000+counter);

  dac.analogWrite(MCP4728::DAC_CH::A, random(0,4092));
  dac.analogWrite(MCP4728::DAC_CH::C, counter);
 
  if (counter > 4092)  counter = 0;

}


AudioOutput_t updateAudio(){
  int out= aSin.next()+1024;
  return dac.analogWrite(MCP4728::DAC_CH::B, out);
  //return dac.analogWrite(2046, out, 0, 0);
  //return MonoOutput::from8Bit(aSin.next()); // return an int signal centred around 0

}


void loop(){
  audioHook(); // required here
}

tomco...@live.fr

unread,
Jan 31, 2023, 1:03:07 PM1/31/23
to Mozzi-users
No problem! Happy that you find a fix even though that is not the one I was expecting in the first place.

Nitpick, note that, if I'm correct you are still outputting a 8bits value in a 12bits DAC, maybe a shift is needed in int out= aSin.next()+1024;?

The fact that the EXTERNAL_AUDIO_OUTPUT is not working on RP2040 is interesting. As you noticed this port is fairly recent. Will try to reproduce and fix!

Best,

tomco...@live.fr

unread,
Feb 2, 2023, 7:24:01 AM2/2/23
to Mozzi-users
Hi again,
After checking here it seems that the EXTERNAL_AUDIO_OUTPUT works here, at least with a fast DAC (I2S). My guess is that your DAC is too slow to follow the needed rate. As said before, I2C is not really designed for audio applications ;)
Reply all
Reply to author
Forward
0 new messages