tierneytim / Pico-USB-audio

Sigma Delta Modulation for RP2040, ESP32, Teensy
GNU General Public License v3.0
143 stars 10 forks source link

Working with 2 cores on the Pico Pi #12

Open Dresch123 opened 2 years ago

Dresch123 commented 2 years ago

Hi Tim, Branching over from the Instructables page. I don't think there is anything wrong with the way you implemented the PDM software on two cores... I think it is a problem with the way I am assuming things work with the Pico Pi when running two cores. I have been working on things like this: https://www.instructables.com/Enclosure-for-Your-TTGO-T-Display-Project/ but would like to add sound. This project was based on an ESP32 board but I am currently doing something similar with the Pico Pi but reading video files off a micro SD card. The video works by itself and the audio (WAV files in flash) works by itself. My thought was to interleave the video and audio files and feed an audio buffer in one core and the video buffer in the other core. Currently I seem to be running into memory collision problems with both cores running simultaneously and the cores not being able to simultaneously read from their respective buffers. I would be happy to share what I am doing but things are a bit of a mess right now. I will try to have an update in a couple of days. I am interested in how you might do software volume control, and I was using a version of your tones code to run out of just the 2nd core. Thanks, Bill

tierneytim commented 2 years ago

thanks, looking forward to seeing where this goes.

With volume control, I think there are two software options.

  1. divide the data by some integer factor here.
  2. multiply the comparison voltage by some integer factor.

The first option is easy but requires division which is not a single cycle instruction on the M0+ and resolution is lost. I would prefer 2 because no software division is required and, in theory, no resolution is lost. This should reduce the voltage by the factor 65336/32767*scale factor. Essentially the bigger that constant value the lower the output voltage is. Currently, the default is 65536 which is twice as big as the max value of a 16-bit integer and therfore the pk-pk voltage should be 1.65V. I'll write a method for this at the weekend but if you want a quick fix make that value massive and the volume should drop massively.

If you wanted to be fancy you could add a button to your mini-tv that would cause an interrupt to change this value and therefore allow human control of the volume.

Dresch123 commented 2 years ago

Tim, Because my application is really pretty simple, I implemented your first suggestion as a shift instead of a divide: void pdmAudio::FILEwrite(int16_t amp) {

  int16_t *lessRawBuffer = (int16_t *)myFileBuffer;
  for (int i = 0; i < 48; i++) {
    pdmAudio::write((*lessRawBuffer++)>>amp);
  }

} This is my modification of your USBWrite routine to play a mono WAV file stored in flash memory (or read off an SD card). In my Arduino sketch, every time I get thru playing the WAV file, I increase the shift value. I tested the shift value from 0 to 8, or full volume to divided by 256, so a logarithmic adjustment. The WAV is still just barely audible at shift 8 but this seems like a fine range for what I want to do. Here is my main loop of the sketch: int16_t amp=0; void loop() { // write whatever is in the USB buffer to the PDM -DAC for (i = 0; i < ((sizeof(boxachoc)-48)/2); i+=48){ icnt = 0; for (j = i; j < (i+48);j++) {FileBuff[icnt++] = boxachoc[j];} pdm.FILEwrite(amp); } amp++; if (amp == 9) amp = 0; } Thanks for pointing this out... it works well even with just 8 steps. As I recall human perception of sound amplitude is sort of logarithmic anyway... The WAV file I tested with was the Forrest Gump "Box of Chocolates" speech, Tom Hanks was completely understandable at the lowest level.

tierneytim commented 2 years ago

Glad the shift works for you. I know this is what is done in the raspberry pi examples but I was a little worried about "implementation defined" behaviour for shifting signed values.

With regards to the first problem of simultaneous access that seems a bit odd if audio is in flash and video is on sd. I would have thought they shouldn't share any memory and therefore shouldn't interact.

Would be cool to have a look when things are less messy

Dresch123 commented 2 years ago

Tim, Good point about shifting signed values, I did not even think about that! It worked first time with no distortion I could detect, so OK. The problem with flash vs SD card (I think) is that everything runs out of flash, code and data. So when one core is accessing flash to pull in the WAV file, the other core is prevented from running its instructions. If this is a wrong assumption, let me know. I also don't understand how to define a RAM buffer for each core that each core can read/write without the cores colliding . If you have any references or suggestions there I would love to see/hear them. I guess the question is: are there two separate data paths to RAM that can access it simultaneously. I will try to zip up my running-a-WAV-from-flash variant and post it here tonight after work if that would be useful. If you are interested, I can write up how I converted the WAV and made a loadable .h file from it. Bill

Dresch123 commented 2 years ago

Tim, I use mono WVA files rather than stereo simply because I save half the memory and processing. I first run whatever WAV file chosen thru Audacity to make it a mono, 48kHz sampled WAV file. Audacity https://www.audacityteam.org/

After that I create a .h file for use with my Arduino sketch. I used this program for that. WAVToCode https://colinjs.com/software.htm#t_WAVToCode https://colinjs.com/wavtocode/wavtocode.htm

I modify the top of the .h to looks like this: const int16_t boxachoc[] = { 0, 0, 0, 0, 1, -2, 2, -2, / 0-7 / 1, 0, 1, -3, 3, -2, 1, 0, / 8-15 / -1, 1, 0, 0, 0, 0, -1, 2, / 16-23 / ... Note declaring the array as a const seemed to get me the best results. I include the .h file with the sketch to make it easier to compile. Arduino

I am attaching a zip that has my very simplistic mods to your code, to allow the playing of the WAV file from Pico Pi memory. pdmWAV_FLASH.zip The mods are just in pdmAudio.cpp and pdmAudio.h src.zip

Let me know if you have any questions. I think you can just compile and run to hear Forrest Gump getting quieter and quieter...

Dresch123 commented 2 years ago

In Audacity, when you export the file you are given the option to save as signed 16 bit. I do now have audio and video running simultaneously but the audio have lots of noise. I need to determine if that is collision or electrical noise.

tierneytim commented 2 years ago

wonderful that it's running, what was the solution? what does the noise sound like?

I have just been looking at the code you sent me. some things I noticed.

  1. non const variables are in RAM so can avoid collision but need to be less than 250KB
  2. const varaibles appear to be in flash and can be as large as flash but run the risk of issues
  3. the code can be made a bit simpler by removing buffers.
    
    #include "pdmAudio.h"
    #include "boxachoc.h"

// PDM object pdmAudio pdm; int boxSize;

void setup() { // set pin 14 to be the output pdm.begin(14); boxSize = sizeof(boxachoc)/sizeof(boxachoc[0]); }

int16_t amp=0; void loop() { for (int i = 0; i < boxSize; i++){ pdm.write(boxachoc[i]>>amp); } amp++; if (amp == 9) amp = 0; }

Dresch123 commented 2 years ago

Hi Tim, The way I got it running was to take your main processing loop and dedicate the file processing-to-PDM in there. So the video and audio are running completely independently on the two cores. The most likely source of the noise is pulling the audio data out of flash by core 2 while core 1 is accessing flash. I just did that because it was convenient. The video buffer takes up a lot of Pico's RAM memory, much more than half. The noise sounds like a bad-reception radio station. When I run the same music, without the video playing, it sounds fine.

My next attempt will be loading a WAV file onto the same SD card and having a handshake between the two cores on which core gets to read from the video or audio file. My thought is to stagger the audio file reads to match the time it takes to display an image frame, so both the video and audio buffers will be used up at the same timing rate. I am hoping the SDFat Arduino library will allow me to have two open files at that same so I don't have to keep opening and closing them.

Hopefully I will have that running in the next day or two. Bill

tierneytim commented 2 years ago

awesome, in case you haven't found these functions you can lock one core while the other core accesses flash, might be useful.

Dresch123 commented 2 years ago

I will have to give that a try!

Dresch123 commented 2 years ago

I implemented the playing of a WAV file, no video just audio, from an SD card and get similar noise from that, as in my previous test. Previously I ran video from the SD card and played the music (with noise) from the flash memory. I rebuilt the circuit with much better ground structure and still got the same issue. If I just run the program that just plays music from the flash memory, without any SD card or video (no SPI activity) it sounds fine. So next attempts will be to see if moving the PDM pin to one that does not have SPI capability could help or I have another idea with deeper buffers... will let you know if I make any progress.

tierneytim commented 2 years ago

cool, I think in general the hardware guide(section 3.4.1) for the rp2040 recommends a logic buffer supplied by a separate regulator for the pulses. I thought this would be overkill but maybe not.

Do you know if your SD reader is competing with the PDM for the PIO?

Dresch123 commented 2 years ago

Tim, OK, that is interesting! If all else fails I will try a digital buffer. The SD card / SPI circuit pins should not (at least on a program level) be interfering with PIN 14. I tried an external power supply on the PAM8403 amplifier and that did not help the noise, so I think the Pico's power supply is OK for powering both the SD and the amplifier. If you have a minute, could you explain this: void pdmAudio::begin(uint pin) { delay(1000); // less noisy power supply _gpio_init(23); gpio_set_dir(23, GPIO_OUT); gpio_put(23, 1); I don't have anything connected to PIN 23... should I? Bill

tierneytim commented 2 years ago

Yes, this is hidden in the rp2040 datasheet somewhere. If pin 23 is high the power is controlled by pwm which is low noise but high power. If it is low(default) the power is controlled some other way which is low power but high noise(or at least noise in audible frequency band).

Dresch123 commented 2 years ago

Thanks, found it: "GPIO23 controls the RT6150 PS (Power Save) pin. When PS is low (the default on Pico) the regulator is in Pulse Frequency Modulation mode, which, at light loads, saves considerable power by only turning on the switching MOSFETs occasionally to keep the output capacitor topped up. Setting PS high forces the regulator into Pulse Width Modulation (PWM) mode. PWM mode forces the SMPS to switch continuously, which reduces the output ripple considerably at light loads (which can be good for some use cases) but at the expense of much worse efficiency. Note that under heavy load the switcher will be in PWM mode irrespective of the PS pin state."