element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      • Japan
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Vietnam
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Pi-Fest
  • Challenges & Projects
  • Design Challenges
  • Pi-Fest
  • More
  • Cancel
Pi-Fest
Blog Playing audio WAV files from a Pico emulated USB Mass Storage Device
  • Blog
  • Forum
  • Documents
  • Polls
  • Files
  • Leaderboard
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: BigG
  • Date Created: 4 Jun 2022 9:02 PM Date Created
  • Views 6823 views
  • Likes 10 likes
  • Comments 5 comments
  • raspberry pi pico
  • WAV audio
  • noise
Related
Recommended

Playing audio WAV files from a Pico emulated USB Mass Storage Device

BigG
BigG
4 Jun 2022

Introduction

I'm no musician or songwriter, so I'll avoid the blurb about where I got my inspiration from. This Pico project just morphed out of my last project working with a 4-digit 7-segment display and the HC-SR04 ultrasonic sensor... I had a notion to develop something where the HC-SR04 sensor would trigger some audio.

This blog is about that journey of discovery. So I hope some of you find this blog useful.

Using Arduino IDE

As with my segment display project, I'm using the Arduino IDE as my development platform and, as I'm sure you all well know, the Arduino programming language includes a built-in tone function.

There is even a couple of tone examples included in the default example menu (inside the 02. Digital folder). These all work fine from any digital pin when using the main core (there's an issue with the tone function if trying to use in core 1).

However, the issue using the tone library is that I would have to create my own tone files to create a tune and as I'm tone deaf (excuse the pun) this wouldn't work for my project.

The alternative, which has been demonstrated to work countless times with Arduino UNO's, is to use an audio WAV file where these audio WAV files are stored on an SD card and an SD card reader is attached to the Arduino and using SPI the data is read and played back through an onboard DAC and an op-amp.

And the good news is that you do not even have to build one from scratch. One of my very first Arduino shields was the Adafruit Wave Shield, which is still available for purchase.

https://learn.adafruit.com/adafruit-wave-shield-audio-shield-for-arduino

But taking this route would be just too easy and besides, using a shield fails to take advantage of some of the unique features found on the Pico.

I wanted to try something different.

I then found this blog on Hackster, which described a technique of using PWM to generate the audio signals. This isn't new of course, or unique to Pico, as I could just as easily have done the same with an Arduino UNO. The difference here is that the Pico processor clocks at a much higher frequency than the UNO (133MHz vs. 16MHz) and so the quality of the audio is that much better.

Unfortunately, the code provided in the blog is utilising the Pico SDK library and was not created using the Arduino programming language.

Ah, but is this really a problem.

Well, thankfully it's not.

In most cases you can simply copy and paste the c code directly into the Arduino IDE and it will compile this code (once you've made a few changes, e.g. changing int main to void setup and adding in the loop function) and it will also upload the new sketch onto the Pico for you.

So this is what I tried.

However, there was one snag. The example code uses a command set_sys_clock_khz to change the clock speed of the Pico and this is one of the few commands which is not available via the Arduino IDE.

Thankfully I found a solution to this problem, which was offered as a response to a similar issue raised within the Arduino mbed-core GitHub repository.

All I needed to do was add this code snippet into my project and this then allows overclocking:

/***********************************************************************************
 * Inline functions to enable overclocking
 * 
************************************************************************************/ 
void set_sys_clock_pll(uint32_t vco_freq, uint post_div1, uint post_div2) {
  if (!running_on_fpga()) {
    clock_configure(clk_sys,
                    CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
                    CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
                    48 * MHZ,
                    48 * MHZ);

    pll_init(pll_sys, 1, vco_freq, post_div1, post_div2);
    uint32_t freq = vco_freq / (post_div1 * post_div2);

    // Configure clocks
    // CLK_REF = XOSC (12MHz) / 1 = 12MHz
    clock_configure(clk_ref,
                    CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC,
                    0,  // No aux mux
                    12 * MHZ,
                    12 * MHZ);

    // CLK SYS = PLL SYS (125MHz) / 1 = 125MHz
    clock_configure(clk_sys,
                    CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
                    CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,
                    freq, freq);

    clock_configure(clk_peri,
                    0,  // Only AUX mux on ADC
                    CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
                    48 * MHZ,
                    48 * MHZ);
  }
}
bool check_sys_clock_khz(uint32_t freq_khz, uint *vco_out, uint *postdiv1_out, uint *postdiv_out) {
  uint crystal_freq_khz = clock_get_hz(clk_ref) / 1000;
  for (uint fbdiv = 320; fbdiv >= 16; fbdiv--) {
    uint vco = fbdiv * crystal_freq_khz;
    if (vco < 400000 || vco > 1600000) continue;
    for (uint postdiv1 = 7; postdiv1 >= 1; postdiv1--) {
      for (uint postdiv2 = postdiv1; postdiv2 >= 1; postdiv2--) {
        uint out = vco / (postdiv1 * postdiv2);
        if (out == freq_khz && !(vco % (postdiv1 * postdiv2))) {
          *vco_out = vco * 1000;
          *postdiv1_out = postdiv1;
          *postdiv_out = postdiv2;
          return true;
        }
      }
    }
  }
  return false;
}
static inline bool set_sys_clock_khz(uint32_t freq_khz, bool required) {
  uint vco, postdiv1, postdiv2;
  if (check_sys_clock_khz(freq_khz, &vco, &postdiv1, &postdiv2)) {
    set_sys_clock_pll(vco, postdiv1, postdiv2);
    return true;
  } else if (required) {
    panic("System clock of %u kHz cannot be exactly achieved", freq_khz);
  }
  return false;
}

/********************************************************************/

The Arduino code is pretty much as is:

/***********************************************************************************
 * MIT License

Copyright (c) 2021 Robin Grosset

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.

 **********************************************************************************/

 
#include <stdio.h>
#include "stdlib.h"   // stdlib 
#include "hardware/irq.h"  // interrupts
#include "hardware/pwm.h"  // pwm 
#include "hardware/sync.h" // wait for interrupt 

#include "hardware/pll.h"
#include "hardware/clocks.h"

 
// Audio PIN is to match some of the design guide shields. 
#define AUDIO_PIN 18  // you can change this to whatever you like

/* 
 * This include brings in static arrays which contain audio samples. 
 * if you want to know how to make these please see the python code
 * for converting audio samples into static arrays. 
 */
#include "sample.h"
int wav_position = 0;

/***********************************************************************************
 * Inline functions to enable overclocking
 * 
************************************************************************************/ 
void set_sys_clock_pll(uint32_t vco_freq, uint post_div1, uint post_div2) {
  if (!running_on_fpga()) {
    clock_configure(clk_sys,
                    CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
                    CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
                    48 * MHZ,
                    48 * MHZ);

    pll_init(pll_sys, 1, vco_freq, post_div1, post_div2);
    uint32_t freq = vco_freq / (post_div1 * post_div2);

    // Configure clocks
    // CLK_REF = XOSC (12MHz) / 1 = 12MHz
    clock_configure(clk_ref,
                    CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC,
                    0,  // No aux mux
                    12 * MHZ,
                    12 * MHZ);

    // CLK SYS = PLL SYS (125MHz) / 1 = 125MHz
    clock_configure(clk_sys,
                    CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
                    CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,
                    freq, freq);

    clock_configure(clk_peri,
                    0,  // Only AUX mux on ADC
                    CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
                    48 * MHZ,
                    48 * MHZ);
  }
}
bool check_sys_clock_khz(uint32_t freq_khz, uint *vco_out, uint *postdiv1_out, uint *postdiv_out) {
  uint crystal_freq_khz = clock_get_hz(clk_ref) / 1000;
  for (uint fbdiv = 320; fbdiv >= 16; fbdiv--) {
    uint vco = fbdiv * crystal_freq_khz;
    if (vco < 400000 || vco > 1600000) continue;
    for (uint postdiv1 = 7; postdiv1 >= 1; postdiv1--) {
      for (uint postdiv2 = postdiv1; postdiv2 >= 1; postdiv2--) {
        uint out = vco / (postdiv1 * postdiv2);
        if (out == freq_khz && !(vco % (postdiv1 * postdiv2))) {
          *vco_out = vco * 1000;
          *postdiv1_out = postdiv1;
          *postdiv_out = postdiv2;
          return true;
        }
      }
    }
  }
  return false;
}
static inline bool set_sys_clock_khz(uint32_t freq_khz, bool required) {
  uint vco, postdiv1, postdiv2;
  if (check_sys_clock_khz(freq_khz, &vco, &postdiv1, &postdiv2)) {
    set_sys_clock_pll(vco, postdiv1, postdiv2);
    return true;
  } else if (required) {
    panic("System clock of %u kHz cannot be exactly achieved", freq_khz);
  }
  return false;
}

/********************************************************************/


/*
 * PWM Interrupt Handler which outputs PWM level and advances the 
 * current sample. 
 * 
 * We repeat the same value for 8 cycles this means sample rate etc
 * adjust by factor of 8   (this is what bitshifting <<3 is doing)
 * 
 */
void pwm_interrupt_handler() {
    pwm_clear_irq(pwm_gpio_to_slice_num(AUDIO_PIN));    
    if (wav_position < (WAV_DATA_LENGTH<<3) - 1) { 
        // set pwm level 
        // allow the pwm value to repeat for 8 cycles this is >>3 
        pwm_set_gpio_level(AUDIO_PIN, WAV_DATA[wav_position>>3]);  
        wav_position++;
    } else {
        // reset to start
        wav_position = 0;
    }
}

void setup()
{
    /* Overclocking for fun but then also so the system clock is a 
     * multiple of typical audio sampling rates.
     */
    //stdio_init_all();     -- this is not needed in Arduino (it will return a 'was not declared in this scope' error)
    set_sys_clock_khz(176000, true); 
    gpio_set_function(AUDIO_PIN, GPIO_FUNC_PWM);

    int audio_pin_slice = pwm_gpio_to_slice_num(AUDIO_PIN);

    // Setup PWM interrupt to fire when PWM cycle is complete
    pwm_clear_irq(audio_pin_slice);
    pwm_set_irq_enabled(audio_pin_slice, true);
    // set the handle function above
    irq_set_exclusive_handler(PWM_IRQ_WRAP, pwm_interrupt_handler); 
    irq_set_enabled(PWM_IRQ_WRAP, true);

    // Setup PWM for audio output
    pwm_config config = pwm_get_default_config();
    /* Base clock 176,000,000 Hz divide by wrap 250 then the clock divider further divides
     * to set the interrupt rate. 
     * 
     * 11 KHz is fine for speech. Phone lines generally sample at 8 KHz
     * 
     * 
     * So clkdiv should be as follows for given sample rate
     *  8.0f for 11 KHz
     *  4.0f for 22 KHz
     *  2.0f for 44 KHz etc
     */
    pwm_config_set_clkdiv(&config, 8.0f); 
    pwm_config_set_wrap(&config, 250); 
    pwm_init(audio_pin_slice, &config, true);

    pwm_set_gpio_level(AUDIO_PIN, 0);

}

void loop()
{
       __wfi(); // Wait for Interrupt
  
}

I then wired up my Pico according the example, but the audio output was not as good as I hoped for.

So, I used a fair bit of trial and error and added in a few bits more to create my own filtered digital output, which also included an old TS922 op-amp (only because I had one). This is what I came up with and it seems to work for me.

{gallery}Pico PWM Digital Audio Output Circuit

image

image

And here is the audio output from that example (video captured using mobile phone):

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

And where is the speaker? Well, its actually attached underneath the Ikea cardboard box lid, which I'm using as my sound box. This speaker, which I had purchased a good few years ago, is a transducer speaker which vibrates the surface to produce sound. It's pretty neat, in my humble opinion and the sound output isn't bad either.

image

Now that I was able to play audio from an embedded file, I wanted to explore options that did not involve an SD card.

Using the MbedOS USBMSD API

If you open up the Examples menu in Arduino IDE, you should see a section called "Examples for Raspberry Pi Pico" if you have the Raspberry Pi Pico board selected within the Boards Manager tool. As shown below, two examples are provided under the category "USB Mass Storage".

image

The one that caught my eye was titled "AccessFlashAsUSBDisk".

This was exactly what I wanted.

Unfortunately, this example returned a "fatal error: QSPIFBlockDevice.h: No such file or directory" when using the Pico board.

As I could not find anything online, I raised the issue on GitHub and the response received explained the reason: "on RP2040 based boards the QSPI flash is also the 'system' flash, so it's not recommended to access it bypassing the internal flash APIs". A solution was also provided suggesting that the FlashIAPBlockDevice driver could safely be used instead, which proved to be the case. I was able to quickly create a working example, which was as follows:

#include "PluggableUSBMSD.h"
#include "FlashIAPBlockDevice.h"

static FlashIAPBlockDevice bd(XIP_BASE + 0x100000, 0x100000);
USBMSD MassStorage(&bd);

static FILE *f = nullptr;
// Note that file is written to the actual root directory as created when USBMSD is mounted
// File is not stored within a folder called root.
// File is only found when you remove usb and replace again
const char *fname = "/root/myfile.txt";


void USBMSD::begin()
{
  int err = getFileSystem().mount(&bd);
  if (err) {
   Serial.println(" filesystem mount failed\ntry to reformat device...");
    err = getFileSystem().reformat(&bd);
  }
  if (err) {
    Serial.println("Error: Unable to format/mount the device.");
    while(1);
  }
}

mbed::FATFileSystem &USBMSD::getFileSystem()
{
  static mbed::FATFileSystem fs("root");
  return fs;
}

void writeContents() {
    f = fopen(fname, "w+");
    if (f != nullptr) {
      fprintf(f, "Hello World\n");
      fflush(f);      
      fclose(f);
      Serial.println("Written to File");
    }
    else {
      Serial.println("File not found");
    }  
}

void readContents() {
    f = fopen(fname, "r");
    if (f != nullptr) {
      fclose(f);
      Serial.println("File found");
    }
    else {
      Serial.println("File not found");
    }  
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  MassStorage.begin();
  // If you do not want to wait for Serial Monitor to load then use a long delay here.
  // Found a delay helps if you want to capture the initial serial output.
  //while(!Serial) {;;}
  delay(1000);
  Serial.println("MassStorage mounted");
  writeContents();
  //readContents();

}

void loop() {
  // put your main code here, to run repeatedly:
  MassStorage.process();
}

Parsing Audio WAV Files

All that was left to do was create some code that would be able to read and parse an Audio WAV File.

Once again there's plenty of information online to help. For example, I found this link which provided a good introduction to the WAV header format: http://soundfile.sapp.org/doc/WaveFormat/

I also found this stackoverflow post, which provided me with the actual WAV audio parsing code.

I then found one code gotcha, when trying to use WAV audio generated from Audacity.

Embedded within Audacity WAV audio files are LIST chunks found within the RIFF header. Once again the Internet was my helper and I found this useful reference to guide me: https://www.recordingblogs.com/wiki/list-chunk-of-a-wave-file

Once that was sorted, it was basically a task of putting it all together. And this is my result:

/***********************************************************************************
 * MIT License
 * 
 * Application Copyright (c) C Gerrish (BigG/Gerrikoio) - relates to this code
 * 
 * Copyright (c) 2021 Robin Grosset - relates to PWM audio
 * https://github.com/rgrosset/pico-pwm-audio
 * 
 * Source on how to read header info from WAV audio file:
 * https://stackoverflow.com/questions/13660777/c-reading-the-data-part-of-a-wav-file
 * 
 * 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.

 **********************************************************************************/

#include <stdio.h>
#include "stdlib.h"   // stdlib 
#include "hardware/irq.h"  // interrupts
#include "hardware/pwm.h"  // pwm 
#include "hardware/sync.h" // wait for interrupt 

#include "hardware/pll.h"
#include "hardware/clocks.h"

#include "PluggableUSBMSD.h"
#include "FlashIAPBlockDevice.h"

// Note that file is written to the actual root directory as created when USBMSD is mounted
// File is not stored within a folder called root.
// File is only found when you remove usb and replace again
const char *fname = "/root/sound1.wav";

static FlashIAPBlockDevice bd(XIP_BASE + 0x100000, 0x100000);
USBMSD MassStorage(&bd);

static FILE *f = nullptr;

// Audio PIN is to match some of the design guide shields. 
#define AUDIO_PIN 18  // you can change this to whatever you like

uint32_t wav_position = 0;

uint32_t ChunkSize = 0;                 // this is the actual sound data size
uint8_t* WAV_DATA;

typedef struct  WAV_HEADER
{
    /* RIFF Chunk Descriptor */
    uint8_t         RIFF[4];        // RIFF Header Magic header
    uint32_t        ChunkSize;      // RIFF Chunk Size
    uint8_t         WAVE[4];        // WAVE Header
    /* "fmt" sub-chunk */
    uint8_t         fmt[4];         // FMT header
    uint32_t        Subchunk1Size;  // Size of the fmt chunk
    uint16_t        AudioFormat;    // Audio format 1=PCM,6=mulaw,7=alaw,     257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM
    uint16_t        NumOfChan;      // Number of channels 1=Mono 2=Sterio
    uint32_t        SamplesPerSec;  // Sampling Frequency in Hz
    uint32_t        bytesPerSec;    // bytes per second
    uint16_t        blockAlign;     // 2=16-bit mono, 4=16-bit stereo
    uint16_t        bitsPerSample;  // Number of bits per sample
    /* "data" sub-chunk */
    uint8_t         Subchunk2ID[4]; // "data"  string
    uint32_t        Subchunk2Size;  // Sampled data length
} wav_hdr;


/***********************************************************************************
 * Inline functions to enable overclocking
 * 
************************************************************************************/ 
void set_sys_clock_pll(uint32_t vco_freq, uint post_div1, uint post_div2) {
  if (!running_on_fpga()) {
    clock_configure(clk_sys,
                    CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
                    CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
                    48 * MHZ,
                    48 * MHZ);

    pll_init(pll_sys, 1, vco_freq, post_div1, post_div2);
    uint32_t freq = vco_freq / (post_div1 * post_div2);

    // Configure clocks
    // CLK_REF = XOSC (12MHz) / 1 = 12MHz
    clock_configure(clk_ref,
                    CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC,
                    0,  // No aux mux
                    12 * MHZ,
                    12 * MHZ);

    // CLK SYS = PLL SYS (125MHz) / 1 = 125MHz
    clock_configure(clk_sys,
                    CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
                    CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,
                    freq, freq);

    clock_configure(clk_peri,
                    0,  // Only AUX mux on ADC
                    CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
                    48 * MHZ,
                    48 * MHZ);
  }
}
bool check_sys_clock_khz(uint32_t freq_khz, uint *vco_out, uint *postdiv1_out, uint *postdiv_out) {
  uint crystal_freq_khz = clock_get_hz(clk_ref) / 1000;
  for (uint fbdiv = 320; fbdiv >= 16; fbdiv--) {
    uint vco = fbdiv * crystal_freq_khz;
    if (vco < 400000 || vco > 1600000) continue;
    for (uint postdiv1 = 7; postdiv1 >= 1; postdiv1--) {
      for (uint postdiv2 = postdiv1; postdiv2 >= 1; postdiv2--) {
        uint out = vco / (postdiv1 * postdiv2);
        if (out == freq_khz && !(vco % (postdiv1 * postdiv2))) {
          *vco_out = vco * 1000;
          *postdiv1_out = postdiv1;
          *postdiv_out = postdiv2;
          return true;
        }
      }
    }
  }
  return false;
}
static inline bool set_sys_clock_khz(uint32_t freq_khz, bool required) {
  uint vco, postdiv1, postdiv2;
  if (check_sys_clock_khz(freq_khz, &vco, &postdiv1, &postdiv2)) {
    set_sys_clock_pll(vco, postdiv1, postdiv2);
    return true;
  } else if (required) {
    panic("System clock of %u kHz cannot be exactly achieved", freq_khz);
  }
  return false;
}

/********************************************************************/

void USBMSD::begin()
{
  int err = getFileSystem().mount(&bd);
  if (err) {
   //String FN = getFileSystem().getName();
   Serial.println(" filesystem mount failed. Try to reformat device...");
    err = getFileSystem().reformat(&bd);
  }
  if (err) {
    Serial.println("Error: Unable to format/mount the device.");
    while(1);
  }
}

mbed::FATFileSystem &USBMSD::getFileSystem()
{
  static mbed::FATFileSystem fs("root");
  return fs;
}

/*
 * PWM Interrupt Handler which outputs PWM level and advances the 
 * current sample. 
 * 
 * We repeat the same value for 8 cycles this means sample rate etc
 * adjust by factor of 8   (this is what bitshifting <<3 is doing)
 * 
 */
 
void pwm_interrupt_handler() {
    pwm_clear_irq(pwm_gpio_to_slice_num(AUDIO_PIN));    
    if (wav_position < (ChunkSize<<3) - 1) { 
        // set pwm level 
        // allow the pwm value to repeat for 8 cycles this is >>3 
        pwm_set_gpio_level(AUDIO_PIN, WAV_DATA[wav_position>>3]);  
        wav_position++;
    } else {
        // reset to start
        wav_position = 0;
    }
}

void readContents() {
    wav_hdr wavHeader;
    int headerSize = sizeof(wav_hdr);
    
    f = fopen(fname, "r");
    if (f != nullptr) {
      size_t bytesRead = fread(&wavHeader, 1, headerSize, f);
      Serial.print("Header Read "); Serial.print(bytesRead); Serial.println(" bytes.");
      if (bytesRead > 0) {
        //Read the data
        uint16_t bytesPerSample = wavHeader.bitsPerSample / 8;      //Number     of bytes per sample
        uint64_t numSamples = wavHeader.ChunkSize / bytesPerSample; //How many samples are in the wav file?
        //static const uint16_t BUFFER_SIZE = 4096;
        
        Serial.println();
        Serial.print("RIFF header                :"); Serial.print((char)wavHeader.RIFF[0]); Serial.print((char)wavHeader.RIFF[1]); Serial.print((char)wavHeader.RIFF[2]); Serial.println((char)wavHeader.RIFF[3]);
        Serial.print("Chunk Size                 :"); Serial.print(wavHeader.ChunkSize); Serial.print(" TOTAL: "); Serial.print(wavHeader.ChunkSize+8);  Serial.print(" DATA: "); Serial.println(wavHeader.ChunkSize-(36 + wavHeader.Subchunk2Size)-8);
        Serial.print("WAVE header                :"); Serial.print((char)wavHeader.WAVE[0]); Serial.print((char)wavHeader.WAVE[1]); Serial.print((char)wavHeader.WAVE[2]); Serial.println((char)wavHeader.WAVE[3]);
        Serial.print("Subchunk1 ID (fmt)         :"); Serial.print((char)wavHeader.fmt[0]); Serial.print((char)wavHeader.fmt[1]); Serial.print((char)wavHeader.fmt[2]); Serial.println((char)wavHeader.fmt[3]);
        Serial.print("Subchunk1 size             :"); Serial.print(wavHeader.Subchunk1Size); Serial.println(wavHeader.Subchunk1Size==16?" PCM":"");

        // Display the sampling Rate from the header
        Serial.print("Audio Format               :"); Serial.println(wavHeader.AudioFormat==1?"PCM":(wavHeader.AudioFormat==6?"mulaw":(wavHeader.AudioFormat==7?"alaw":(wavHeader.AudioFormat==257?"IBM Mu-Law":(wavHeader.AudioFormat==258?"IBM A-Law":"ADPCM")))));
        Serial.print("Number of channels         :"); Serial.println(wavHeader.NumOfChan==1?"Mono":(wavHeader.NumOfChan==2?"Mono":"Other"));
        Serial.print("Sampling Rate              :"); Serial.println(wavHeader.SamplesPerSec);
        Serial.print("Number of bytes per second :"); Serial.println(wavHeader.bytesPerSec);
        Serial.print("Block align                :"); Serial.print(wavHeader.blockAlign); Serial.print(" validate: "); Serial.println(bytesPerSample * wavHeader.NumOfChan);
        Serial.print("Number of bits per sample  :"); Serial.println(wavHeader.bitsPerSample);
        Serial.print("Data length                :"); Serial.println(wavHeader.Subchunk2Size);
        // Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM

        Serial.print("Data string (Subchunk2 ID) :"); Serial.print((char)wavHeader.Subchunk2ID[0]); Serial.print((char)wavHeader.Subchunk2ID[1]); Serial.print((char)wavHeader.Subchunk2ID[2]); Serial.println((char)wavHeader.Subchunk2ID[3]);
        Serial.print("Subchunk2 size             :"); Serial.print(wavHeader.Subchunk2Size); Serial.print(" validate: "); Serial.println(numSamples * wavHeader.NumOfChan * bytesPerSample);

        if (memcmp((char*)wavHeader.Subchunk2ID,"LIST",4) == 0) {
          Serial.println("List chunk (of a RIFF file):");
          
          uint8_t ListType[wavHeader.Subchunk2Size];        // RIFF Header Magic header
          
          bytesRead = fread(&ListType, 1, wavHeader.Subchunk2Size, f);
          
          if ( bytesRead > 0 ) {
            Serial.print(" --- List type ID :"); Serial.print((char)ListType[0]); Serial.print((char)ListType[1]); Serial.print((char)ListType[2]); Serial.println((char)ListType[3]);
            if (memcmp((char*)ListType,"INFO",4) == 0) {
              // INFO tag
              Serial.print(" --- --- INFO1 type ID :"); Serial.print((char)ListType[4]); Serial.print((char)ListType[5]); Serial.print((char)ListType[6]); Serial.println((char)ListType[7]);
              uint8_t sizeTxt = (ListType[11] << 24) | (ListType[10] << 16) | (ListType[9] << 8) | ListType[8];
              Serial.print(" --- --- SizeD :"); Serial.print(sizeTxt); Serial.print(" Validate: "); Serial.println(wavHeader.Subchunk2Size - sizeTxt);
            }
            for (uint8_t x = 12; x < wavHeader.Subchunk2Size; x++) {
              if (ListType[x] >= 32 && ListType[x] <= 126) {
                Serial.print((char)ListType[x]);
              }
              else if (ListType[x] > 0) {
                Serial.print("0x"); Serial.print(ListType[x],HEX);
              }
              Serial.print(" ");
            }
            Serial.println("");

            // Checking for data
            bytesRead = fread(&ListType, 1, 4, f);
            if (bytesRead > 0) {
              Serial.print("Data string (Subchunk3 ID) :"); 
              for (uint8_t x = 0; x < 4; x++) {
                if (ListType[x] >= 32 && ListType[x] <= 126) {
                  Serial.print((char)ListType[x]);
                }
                else if (ListType[x] > 0) {
                  Serial.print("0x"); Serial.print(ListType[x],HEX);
                }               
              }
              Serial.println("");
            }

            bytesRead = fread(&ChunkSize, 1, 4, f);
            if (bytesRead > 0) {
              Serial.print("Subchunk3 size             :");
              Serial.print(ChunkSize); Serial.print(" validate: "); Serial.println(8+numSamples * wavHeader.NumOfChan * bytesPerSample);
            }
          }
        }
        else {
          ChunkSize = wavHeader.Subchunk2Size;
        }

        WAV_DATA = new uint8_t[ChunkSize];

        //int8_t* buffer = new int8_t[BUFFER_SIZE];
        bytesRead = fread(WAV_DATA, sizeof WAV_DATA[0], ChunkSize / (sizeof WAV_DATA[0]), f);
        if (bytesRead)
        {
          Serial.println("Sound File Data Read ");
          
          int fileSize = 0;
          fseek(f, 0, SEEK_END);
          fileSize = ftell(f);
          fseek(f, 0, SEEK_SET);
          Serial.print("File size is: "); Serial.print(fileSize); Serial.println(" bytes.");
          
          fclose(f);
      
          gpio_set_function(AUDIO_PIN, GPIO_FUNC_PWM);
      
          int audio_pin_slice = pwm_gpio_to_slice_num(AUDIO_PIN);
      
          // Setup PWM interrupt to fire when PWM cycle is complete
          pwm_clear_irq(audio_pin_slice);
          pwm_set_irq_enabled(audio_pin_slice, true);
          // set the handle function above
          irq_set_exclusive_handler(PWM_IRQ_WRAP, pwm_interrupt_handler); 
          irq_set_enabled(PWM_IRQ_WRAP, true);
      
          // Setup PWM for audio output
          pwm_config config = pwm_get_default_config();
          /* Base clock 176,000,000 Hz divide by wrap 250 then the clock divider further divides
           * to set the interrupt rate. 
           * 
           * 11 KHz is fine for speech. Phone lines generally sample at 8 KHz
           * 
           * 
           * So clkdiv should be as follows for given sample rate
           *  8.0f for 11 KHz
           *  4.0f for 22 KHz
           *  2.0f for 44 KHz etc
           */
          pwm_config_set_clkdiv(&config, 8.0f); 
          pwm_config_set_wrap(&config, 250); 
          pwm_init(audio_pin_slice, &config, true);
      
          pwm_set_gpio_level(AUDIO_PIN, 0);
              
        }
        //delete [] buffer;
        //buffer = nullptr;
      }
      
    }
    else {
      Serial.println("File not found");
    }  
}

void setup() {

  set_sys_clock_khz(176000, true); 
  
  // put your setup code here, to run once:
  Serial.begin(115200);
  MassStorage.begin();
  // If you do not want to wait for Serial Monitor to load then use a long delay here.
  // Found a delay helps if you want to capture the initial serial output.
  while(!Serial) {;;}
  //delay(1000);
  Serial.println("\r\nMassStorage mounted");
  Serial.println("");
  Serial.flush();
  //writeContents();
  readContents();
  
}

void loop() {
  // put your main code here, to run repeatedly:
  __wfi(); // Wait for Interrupt
}

You will notice in the code a "while (!Serial) {;;}". This is basically to pause playing the audio until the serial monitor is opened. This is not required and is simply there for demo purposes.

Another important point to make about the code is that I am buffering the whole file into RAM. This limits the size of the audio file to about 10 seconds for 8 bit sampling rate at 11kHz. This was simply a short cut to get me started. Please note that there are no size checks in the code, so it's best to amend if larger audio files want to be tested.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

  • Sign in to reply
Parents
  • BigG
    BigG over 3 years ago

    I decided to follow through with my idea of using the range finder to trigger audio.

    This application uses both cores. Core 0 handles the audio side and Core 1 handles the range finder.

    The code is not great as it crashes Core 0 quite a bit. I suspect it's related to the PWM interrupts but I haven't spent any time debugging. You are more than welcome to work out where the problems are. Here is the code:

    /***********************************************************************************
     * This example reads the distance measured by an HC-SR04 Ultrasonic distance sensor, which
     * triggers audio, which is played using PWM.
     * 
     * MIT License
     * 
     * Application Copyright (c) C Gerrish (BigG) - relates to this example
     * 
     * Copyright (c) 2021 Robin Grosset - relates to PWM audio
     * https://github.com/rgrosset/pico-pwm-audio
     * 
     * Copyright (c) 2020-2021 Alan Yorinks All rights reserved (NanoConnectHcSr04 library).
     * 
     * Source on how to read header info from WAV audio file:
     * https://stackoverflow.com/questions/13660777/c-reading-the-data-part-of-a-wav-file
     * 
     * 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.
    
     **********************************************************************************/
    /*
     The NanoConnectHcSr04 library is free software; you can redistribute it and/or
     modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
     Version 3 as published by the Free Software Foundation; either
     or (at your option) any later version.
     This library is distributed in the hope that it will be useful,f
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     General Public License for more details.
     
     */
    
    #include <stdio.h>
    #include "stdlib.h"   // stdlib 
    #include "pico/multicore.h"
    #include "hardware/irq.h"  // interrupts
    #include "hardware/pwm.h"  // pwm 
    #include "hardware/sync.h" // wait for interrupt 
    
    #include "hardware/pll.h"
    #include "hardware/clocks.h"
    
    #include "PluggableUSBMSD.h"
    #include "FlashIAPBlockDevice.h"
    
    
    #include <NanoConnectHcSr04.h>
    
    // LED pin numbers:
    static const int LEDREDPIN =    12;// the number of the LED pin
    static const int LEDGREENPIN =  13;// the number of the LED pin
    
    // Audio pin is to match some of the design guide shields. 
    static const int  AUDIO_PIN =   18;  // you can change this to whatever you like
    
    static const float TRIG_DIST = 70.0;
    
    // Note that files are written to the actual root directory as created when USBMSD is mounted
    // File is not stored within a folder called root.
    // File is only found when you remove usb and replace again
    const char *fname1 = "/root/sound1.wav";
    const char *fname2 = "/root/sound2.wav";
    
    static FlashIAPBlockDevice bd(XIP_BASE + 0x100000, 0x100000);
    USBMSD MassStorage(&bd);
    
    static FILE *f = nullptr;
    
    static uint32_t wav_position = 0;
    
    static uint32_t ChunkSize = 0;                 // this is the actual sound data size
    static uint8_t* WAV_DATA;
    
    typedef struct  WAV_HEADER
    {
        /* RIFF Chunk Descriptor */
        uint8_t         RIFF[4];        // RIFF Header Magic header
        uint32_t        ChunkSize;      // RIFF Chunk Size
        uint8_t         WAVE[4];        // WAVE Header
        /* "fmt" sub-chunk */
        uint8_t         fmt[4];         // FMT header
        uint32_t        Subchunk1Size;  // Size of the fmt chunk
        uint16_t        AudioFormat;    // Audio format 1=PCM,6=mulaw,7=alaw,     257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM
        uint16_t        NumOfChan;      // Number of channels 1=Mono 2=Sterio
        uint32_t        SamplesPerSec;  // Sampling Frequency in Hz
        uint32_t        bytesPerSec;    // bytes per second
        uint16_t        blockAlign;     // 2=16-bit mono, 4=16-bit stereo
        uint16_t        bitsPerSample;  // Number of bits per sample
        /* "data" sub-chunk */
        uint8_t         Subchunk2ID[4]; // "data"  string
        uint32_t        Subchunk2Size;  // Sampled data length
    } wav_hdr;
    
    
    // D15 == trigger, D14 == echo
    // Using pio0 and sm 0
    NanoConnectHcSr04 sonar(15,14, pio0, 0); //Instantiate HR-SR04 controller object
    
    // distance value returned
    static float c1_value = 0.0;
    static float c1_prev = 0.0;
    static uint8_t range_mode = 0;
    
    static unsigned long LED_timer = 0L;
    static int deciSeconds = 0;
    
    static bool redTrigger = false;
    static bool greenTrigger = false;
    
    static bool TriggerAudio = false;
    static bool TriggerThankyou = false;
    
    
    /***********************************************************************************
     * Inline functions to enable overclocking
     * 
    ************************************************************************************/ 
    void set_sys_clock_pll(uint32_t vco_freq, uint post_div1, uint post_div2) {
      if (!running_on_fpga()) {
        clock_configure(clk_sys,
                        CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
                        CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
                        48 * MHZ,
                        48 * MHZ);
    
        pll_init(pll_sys, 1, vco_freq, post_div1, post_div2);
        uint32_t freq = vco_freq / (post_div1 * post_div2);
    
        // Configure clocks
        // CLK_REF = XOSC (12MHz) / 1 = 12MHz
        clock_configure(clk_ref,
                        CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC,
                        0,  // No aux mux
                        12 * MHZ,
                        12 * MHZ);
    
        // CLK SYS = PLL SYS (125MHz) / 1 = 125MHz
        clock_configure(clk_sys,
                        CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
                        CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,
                        freq, freq);
    
        clock_configure(clk_peri,
                        0,  // Only AUX mux on ADC
                        CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
                        48 * MHZ,
                        48 * MHZ);
      }
    }
    bool check_sys_clock_khz(uint32_t freq_khz, uint *vco_out, uint *postdiv1_out, uint *postdiv_out) {
      uint crystal_freq_khz = clock_get_hz(clk_ref) / 1000;
      for (uint fbdiv = 320; fbdiv >= 16; fbdiv--) {
        uint vco = fbdiv * crystal_freq_khz;
        if (vco < 400000 || vco > 1600000) continue;
        for (uint postdiv1 = 7; postdiv1 >= 1; postdiv1--) {
          for (uint postdiv2 = postdiv1; postdiv2 >= 1; postdiv2--) {
            uint out = vco / (postdiv1 * postdiv2);
            if (out == freq_khz && !(vco % (postdiv1 * postdiv2))) {
              *vco_out = vco * 1000;
              *postdiv1_out = postdiv1;
              *postdiv_out = postdiv2;
              return true;
            }
          }
        }
      }
      return false;
    }
    static inline bool set_sys_clock_khz(uint32_t freq_khz, bool required) {
      uint vco, postdiv1, postdiv2;
      if (check_sys_clock_khz(freq_khz, &vco, &postdiv1, &postdiv2)) {
        set_sys_clock_pll(vco, postdiv1, postdiv2);
        return true;
      } else if (required) {
        panic("System clock of %u kHz cannot be exactly achieved", freq_khz);
      }
      return false;
    }
    
    /********************************************************************/
    
    void USBMSD::begin()
    {
      int err = getFileSystem().mount(&bd);
      if (err) {
       //String FN = getFileSystem().getName();
       Serial.println(" filesystem mount failed. Try to reformat device...");
        err = getFileSystem().reformat(&bd);
      }
      if (err) {
        Serial.println("Error: Unable to format/mount the device.");
        while(1);
      }
    }
    
    mbed::FATFileSystem &USBMSD::getFileSystem()
    {
      static mbed::FATFileSystem fs("root");
      return fs;
    }
    
    /*
     * PWM Interrupt Handler which outputs PWM level and advances the 
     * current sample. 
     * 
     * We repeat the same value for 8 cycles this means sample rate etc
     * adjust by factor of 8   (this is what bitshifting <<3 is doing)
     * 
     */
     
    void pwm_interrupt_handler() {
        pwm_clear_irq(pwm_gpio_to_slice_num(AUDIO_PIN));
        if (TriggerAudio) {
          if (wav_position < (ChunkSize<<3) - 1) { 
              // set pwm level 
              // allow the pwm value to repeat for 8 cycles this is >>3 
              pwm_set_gpio_level(AUDIO_PIN, WAV_DATA[wav_position>>3]);  
              wav_position++;
          } else {
              // reset to start
              wav_position = 0;
              TriggerAudio = false;
          }
        }
    }
    
      
    void core1_entry() {
    
        
      while (1) {
        // Function pointer is passed to us via the FIFO
        // We have one incoming int32_t as a parameter, and will provide an
        // int32_t return value by simply pushing it back on the FIFO
        // which also indicates the result is ready.
    
        // put your main code here, to run repeatedly:
        c1_value = sonar.readSonar();
        
        // Sanity check on values received
        if (c1_value > 0.0 && c1_value < 300.0) {
    
          if (c1_prev > 0.0 && c1_value < TRIG_DIST && c1_prev < TRIG_DIST) {
            if (!redTrigger && !LED_timer) {
              redTrigger = true;
              digitalWrite(LEDGREENPIN, LOW);
              digitalWrite(LEDREDPIN, HIGH);
              if (greenTrigger) greenTrigger = false;
              // Send results back to core 0
              if (multicore_fifo_wready()) multicore_fifo_push_blocking(1);
            }
          }
          else if (c1_prev > 0.0 && c1_value > (TRIG_DIST+20.0) && c1_prev > (TRIG_DIST+20.0)) {
            if (redTrigger) {
              redTrigger = false;
              digitalWrite(LEDREDPIN, LOW);
              digitalWrite(LEDGREENPIN, HIGH);
              greenTrigger = true;
              LED_timer = millis();
              if (multicore_fifo_wready()) multicore_fifo_push_blocking(2);
            }
            else {
              if (greenTrigger) {
                if ((millis() - LED_timer) > 2000) {
                  greenTrigger = false;
                  digitalWrite(LEDGREENPIN, LOW);
                  LED_timer = 0;
                  if (multicore_fifo_wready()) multicore_fifo_push_blocking(3);
                }
              }
            }
          }
          c1_prev = c1_value;
        }
        else if (c1_value > 300.0) {
          if (LED_timer && (millis() - LED_timer) > 2000) {
            LED_timer = 0;
            if (multicore_fifo_wready()) multicore_fifo_push_blocking(0);
            greenTrigger = false;
            redTrigger = false;
            digitalWrite(LEDREDPIN, LOW);
            digitalWrite(LEDGREENPIN, LOW);
          }
        }
      } 
    }
    
    
    void setup() {
     
      set_sys_clock_khz(176000, true);
    
      // set the digital pin as output:
      pinMode(LEDREDPIN, OUTPUT);
      pinMode(LEDGREENPIN, OUTPUT);
    
      Serial.begin(115200);
      delay(500);
      //while (!Serial) {;;}
    
      //timer = millis();
    
      multicore_launch_core1(core1_entry);
      
      MassStorage.begin();
    
      Serial.println("\r\nPico PIO Multicore SonicRanger AudioWAV Playback demo\r\n");
      
      // Check that the two audio files exist
      f = fopen(fname1, "r");
      if (f != nullptr) {
        fclose(f);
        f = fopen(fname2, "r");
        if (f != nullptr) {
          fclose(f);
          gpio_set_function(AUDIO_PIN, GPIO_FUNC_PWM);
          int audio_pin_slice = pwm_gpio_to_slice_num(AUDIO_PIN);
      
          // Setup PWM interrupt to fire when PWM cycle is complete
          pwm_clear_irq(audio_pin_slice);
          pwm_set_irq_enabled(audio_pin_slice, true);
          // set the handle function above
          irq_set_exclusive_handler(PWM_IRQ_WRAP, pwm_interrupt_handler); 
          irq_set_enabled(PWM_IRQ_WRAP, true);
      
          // Setup PWM for audio output
          pwm_config config = pwm_get_default_config();
          pwm_config_set_clkdiv(&config, 8.0f); 
          pwm_config_set_wrap(&config, 250); 
          pwm_init(audio_pin_slice, &config, true);
      
          pwm_set_gpio_level(AUDIO_PIN, 0);
        }
        else {
          Serial.println("Sound2 file not found");
        }
      }
      else {
        Serial.println("Sound1 file not found");
      }
    }
    
    void loop() {
    
      if (multicore_fifo_rvalid()) {
        range_mode = multicore_fifo_pop_blocking();
        Serial.println(range_mode);
      }
      if (!TriggerAudio && range_mode == 1) {
        if (readContents(fname1)) {
          TriggerThankyou = true;
          TriggerAudio = true;
        }
      }
      else if (!TriggerAudio && (range_mode == 2 || TriggerThankyou)) {
        if (readContents(fname2)) {
          TriggerThankyou = false; 
          TriggerAudio = true;
        }
      }
    
      
    
        
    }
    
    bool readContents(const char *fname) {
        wav_hdr wavHeader;
        int headerSize = sizeof(wav_hdr);
        
        f = fopen(fname, "r");
        if (f != nullptr) {
          size_t bytesRead = fread(&wavHeader, 1, headerSize, f);
          Serial.print("Header Read "); Serial.print(bytesRead); Serial.println(" bytes.");
          if (bytesRead > 0) {
            //Read the data
            uint16_t bytesPerSample = wavHeader.bitsPerSample / 8;      //Number     of bytes per sample
            uint64_t numSamples = wavHeader.ChunkSize / bytesPerSample; //How many samples are in the wav file?
            //static const uint16_t BUFFER_SIZE = 4096;
            
            /**************************************************************************************
            Serial.println();
            Serial.print("RIFF header                :"); Serial.print((char)wavHeader.RIFF[0]); Serial.print((char)wavHeader.RIFF[1]); Serial.print((char)wavHeader.RIFF[2]); Serial.println((char)wavHeader.RIFF[3]);
            Serial.print("Chunk Size                 :"); Serial.print(wavHeader.ChunkSize); Serial.print(" TOTAL: "); Serial.print(wavHeader.ChunkSize+8);  Serial.print(" DATA: "); Serial.println(wavHeader.ChunkSize-(36 + wavHeader.Subchunk2Size)-8);
            Serial.print("WAVE header                :"); Serial.print((char)wavHeader.WAVE[0]); Serial.print((char)wavHeader.WAVE[1]); Serial.print((char)wavHeader.WAVE[2]); Serial.println((char)wavHeader.WAVE[3]);
            Serial.print("Subchunk1 ID (fmt)         :"); Serial.print((char)wavHeader.fmt[0]); Serial.print((char)wavHeader.fmt[1]); Serial.print((char)wavHeader.fmt[2]); Serial.println((char)wavHeader.fmt[3]);
            Serial.print("Subchunk1 size             :"); Serial.print(wavHeader.Subchunk1Size); Serial.println(wavHeader.Subchunk1Size==16?" PCM":"");
    
            // Display the sampling Rate from the header
            Serial.print("Audio Format               :"); Serial.println(wavHeader.AudioFormat==1?"PCM":(wavHeader.AudioFormat==6?"mulaw":(wavHeader.AudioFormat==7?"alaw":(wavHeader.AudioFormat==257?"IBM Mu-Law":(wavHeader.AudioFormat==258?"IBM A-Law":"ADPCM")))));
            Serial.print("Number of channels         :"); Serial.println(wavHeader.NumOfChan==1?"Mono":(wavHeader.NumOfChan==2?"Mono":"Other"));
            Serial.print("Sampling Rate              :"); Serial.println(wavHeader.SamplesPerSec);
            Serial.print("Number of bytes per second :"); Serial.println(wavHeader.bytesPerSec);
            Serial.print("Block align                :"); Serial.print(wavHeader.blockAlign); Serial.print(" validate: "); Serial.println(bytesPerSample * wavHeader.NumOfChan);
            Serial.print("Number of bits per sample  :"); Serial.println(wavHeader.bitsPerSample);
            Serial.print("Data length                :"); Serial.println(wavHeader.Subchunk2Size);
            // Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM
    
            Serial.print("Data string (Subchunk2 ID) :"); Serial.print((char)wavHeader.Subchunk2ID[0]); Serial.print((char)wavHeader.Subchunk2ID[1]); Serial.print((char)wavHeader.Subchunk2ID[2]); Serial.println((char)wavHeader.Subchunk2ID[3]);
            Serial.print("Subchunk2 size             :"); Serial.print(wavHeader.Subchunk2Size); Serial.print(" validate: "); Serial.println(numSamples * wavHeader.NumOfChan * bytesPerSample);
            *************************************************************************************/
            
            if (memcmp((char*)wavHeader.Subchunk2ID,"LIST",4) == 0) {
              Serial.println("List chunk (of a RIFF file):");
              
              uint8_t ListType[wavHeader.Subchunk2Size];        // RIFF Header Magic header
              
              bytesRead = fread(&ListType, 1, wavHeader.Subchunk2Size, f);
              
              if ( bytesRead > 0 ) {
                Serial.print(" --- List type ID :"); Serial.print((char)ListType[0]); Serial.print((char)ListType[1]); Serial.print((char)ListType[2]); Serial.println((char)ListType[3]);
                if (memcmp((char*)ListType,"INFO",4) == 0) {
                  // INFO tag
                  Serial.print(" --- --- INFO1 type ID :"); Serial.print((char)ListType[4]); Serial.print((char)ListType[5]); Serial.print((char)ListType[6]); Serial.println((char)ListType[7]);
                  uint8_t sizeTxt = (ListType[11] << 24) | (ListType[10] << 16) | (ListType[9] << 8) | ListType[8];
                  Serial.print(" --- --- SizeD :"); Serial.print(sizeTxt); Serial.print(" Validate: "); Serial.println(wavHeader.Subchunk2Size - sizeTxt);
                }
                for (uint8_t x = 12; x < wavHeader.Subchunk2Size; x++) {
                  if (ListType[x] >= 32 && ListType[x] <= 126) {
                    Serial.print((char)ListType[x]);
                  }
                  else if (ListType[x] > 0) {
                    Serial.print("0x"); Serial.print(ListType[x],HEX);
                  }
                  Serial.print(" ");
                }
                Serial.println("");
    
                // Checking for data
                bytesRead = fread(&ListType, 1, 4, f);
                if (bytesRead > 0) {
                  Serial.print("Data string (Subchunk3 ID) :"); 
                  for (uint8_t x = 0; x < 4; x++) {
                    if (ListType[x] >= 32 && ListType[x] <= 126) {
                      Serial.print((char)ListType[x]);
                    }
                    else if (ListType[x] > 0) {
                      Serial.print("0x"); Serial.print(ListType[x],HEX);
                    }               
                  }
                  Serial.println("");
                }
    
                bytesRead = fread(&ChunkSize, 1, 4, f);
                if (bytesRead > 0) {
                  Serial.print("Subchunk3 size             :");
                  Serial.print(ChunkSize); Serial.print(" validate: "); Serial.println(8+numSamples * wavHeader.NumOfChan * bytesPerSample);
                }
              }
            }
            else {
              ChunkSize = wavHeader.Subchunk2Size;
            }
    
            WAV_DATA = new uint8_t[ChunkSize];
    
            //int8_t* buffer = new int8_t[BUFFER_SIZE];
            bytesRead = fread(WAV_DATA, sizeof WAV_DATA[0], ChunkSize / (sizeof WAV_DATA[0]), f);
            if (bytesRead)
            {
              Serial.println("Sound File Data Read ");
              
              int fileSize = 0;
              fseek(f, 0, SEEK_END);
              fileSize = ftell(f);
              fseek(f, 0, SEEK_SET);
              Serial.print("File size is: "); Serial.print(fileSize); Serial.println(" bytes.");
              
              fclose(f);
                        
            }
            //delete [] buffer;
            //buffer = nullptr;
          }
          
        }
        else {
          Serial.println("File not found");
          return false;
        }
        
        return true;
    }

    I am using two audio files this time.

    image

    The audio files I am using originate from freesound.org.

    Sound 1: https://freesound.org/people/AmeAngelofSin/sounds/165596/

    Sound 1 is licensed under creativecommons.org/.../

    Sound 2: https://freesound.org/people/DjHamSammich/sounds/624079/

    Sound 2 is licensed under creativecommons.org/.../

    Here is a video demo. Basically if you move too close to the ultrasonic sensor it triggers sound1 and as you retreat it triggers sound2.

    Apologies for the sound quality as it appears that the Ultrasonic Range Finder echos were being detected by my mobile phone's microphone.

    Video processing failed.
    You don't have permission to edit metadata of this video.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • BigG
    BigG over 3 years ago in reply to BigG

    There seems to be a problem uploading the video, so here's a link to the video I uploaded onto YouTube:

    www.youtube.com/watch

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
Comment
  • BigG
    BigG over 3 years ago in reply to BigG

    There seems to be a problem uploading the video, so here's a link to the video I uploaded onto YouTube:

    www.youtube.com/watch

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
Children
No Data
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube