-
Notifications
You must be signed in to change notification settings - Fork 234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add an example of using PWMs to generate an audio output #356
base: main
Are you sure you want to change the base?
Conversation
f3fd1f3
to
cda2415
Compare
cda2415
to
d54217c
Compare
I tried this with a tiny speaker I had lying around here, and it seems to work fine. |
I wonder if it would be possible to avoid the binary .raw file. While it's not huge, it would be the largest file in the repo, and it's not obvious what it contains. |
Yes it would be very easy to synthesize the sound but I suppose @DrChat wants to demonstrate the ease of including sound files into the program. At least half the sample is empty, presumably to improve the impression when repeated (though that could be done just with a delay). If the blank parts are removed it would be 31.6k The other thing is that it might be better using a WAV format file which would obviously be a sound sample and would have the advantage that it could be played by clicking on it in most file browsers. I can't find an option in Audactiy to save WAV as signed 8-bit so to do that the instructions would have to be /// If you want to create your own, use Audacity to create a recording with a
/// sample rate of 32,000 Hz and then export it as WAV file coding Unsigned 8-bit PCM
const AUDIO: &[u8] = include_bytes!("pico_pwm_audio.wav"); and later for i in &AUDIO[44..] {
...
let i = ((*i as u16) << 3).wrapping_add(2048) & 0xFFF; |
Oh, huh that is clever - I didn't know you could just skip the wav header and directly play audio samples. As for the file size, I suppose technically it would be best to check it in as a git LFS file as that's the best practice for binary files. And yeah, I figure it'll allow more creativity from users if the sample included an easy-to-replace audio file rather than a synthesized sound. |
//! Drives GPIO0 with PWM to generate an audio signal for use with a speaker. | ||
//! | ||
//! Note that you will need to supply your own speaker. When hooked up to GPIO0, | ||
//! you should hear an audible chime. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure, but I assume it's not a good idea to connect some random speaker directly to the GPIO pin. It may work for some speakers with proper specs, but for most, some kind of amplifier or at least a resistor should be inserted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I'm sure it's not good, especially something that might have inductance. I found a 2N2222 transistor with resistors already soldered from another project (tiny motor from arduino I think) and that makes it a bit louder (a bad thing after only a few seconds!) and safer. But it does add some external hardware. Maybe just mention that a transistor and resistors would be a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, that may be a good point! I hope I'm not damaging my Pi by wiring a speaker straight up to the GPIO pin haha.
What would be a better wording? My knowledge about software engineering is certainly better than my knowledge of electrical engineering.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend skipping the transistor and resistor recommendations and jump straight to recommending a mono audio amplifier. You can pick one up for a couple of dollars on ebay, it will sound a lot better, have adjustable volume, and folks are far less likely to destroy their microcontroller that way.
Note that you still need to condition the signal in this case - audio amps aren't expecting this much voltage either.
// Throttle until the PWM channel delivers us an interrupt saying it's done | ||
// with this cycle (the internal counter wrapped). The interrupt handler will | ||
// clear the interrupt and we'll send out the next sample. | ||
cortex_m::asm::wfi(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only works if the PWM interrupt is the only one enabled.
Even for an example, I'd prefer a way which can be composed with other functionality.
Also, see the documentation of WFI: "WFI is intended for power saving only. When writing software assume that WFI might behave as a NOP operation."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, projects playing sounds are likely to have other hardware inputs such as ADC, buttons etc. so not ideal if the example stops working when other functionality is introduced.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah good point. You guys think something like wfe
and sev
would be better instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The approach used in @WeirdConstructor's code https://github.com/WeirdConstructor/RustRP2040Code/blob/master/pwm_dac_saw_sampling/src/example_1000hz_saw.rs which I think is the same as the pwm_blinky example seems much simpler. However there might be disadvantages I'm unaware of.
EDIT The following appears to run OK but may lack some useful functionality of the interrupt controlled version.
#![no_std]
#![no_main]
use cortex_m::prelude::*;
use embedded_hal::digital::v2::OutputPin;
use embedded_time::duration::Extensions;
use panic_halt as _;
use rp_pico::entry;
const AUDIO: &[u8] = include_bytes!("pico_pwm_audio.wav");
#[entry]
fn main() -> ! {
let mut pac = rp_pico::hal::pac::Peripherals::take().unwrap();
let _core = rp_pico::hal::pac::CorePeripherals::take().unwrap();
let mut watchdog = rp_pico::hal::Watchdog::new(pac.WATCHDOG);
let _clocks = rp_pico::hal::clocks::init_clocks_and_plls(
rp_pico::XOSC_CRYSTAL_FREQ,
pac.XOSC,
pac.CLOCKS,
pac.PLL_SYS,
pac.PLL_USB,
&mut pac.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
let timer = rp_pico::hal::Timer::new(pac.TIMER, &mut pac.RESETS);
let mut count_down = timer.count_down();
let mut count_down_sample = timer.count_down();
let sio = rp_pico::hal::Sio::new(pac.SIO);
let pins = rp_pico::Pins::new(
pac.IO_BANK0,
pac.PADS_BANK0,
sio.gpio_bank0,
&mut pac.RESETS,
);
let mut led_pin = pins.led.into_push_pull_output();
let pwm_slices = rp_pico::hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS);
let mut pwm = pwm_slices.pwm0;
pwm.default_config();
pwm.set_top(256); // as unsigned 8-bit WAV
pwm.set_div_int(1);
pwm.enable();
let channel = &mut pwm.channel_a;
channel.output_to(pins.gpio0);
loop {
count_down_sample.start(2000.milliseconds());
for i in &AUDIO[0x2C..] {
count_down.start((31250_u32).nanoseconds());
channel.set_duty(*i as u16);
let _ = nb::block!(count_down.wait());
}
led_pin.set_high().unwrap();
let _ = nb::block!(count_down_sample.wait());
led_pin.set_low().unwrap();
}
}
As my previous comments may sound somewhat negative: I really like the idea of an example outputting sounds! Just blinking LEDs gets boring. Bonus points for simple hardware (ie. not needing external i2s hardware or similar). |
Not too long ago I built this, it's two simple RC filters in a row and some code to generate a saw tooth: That code can be found here, there is also a sine wave example: https://github.com/WeirdConstructor/RustRP2040Code/tree/master/pwm_dac_saw_sampling/src Also: I put my headphones directly to that output and got a clearly audible sound at the right pitch. Of course, adding an op amp stage would be better to amplify the signal. But for experimentation this is alright. |
@WeirdConstructor that looks a tidy example. I see on your repo you have switched from the It reminded me of something I did ages ago with at atmega32 https://www.youtube.com/watch?v=HrDFpDNN2cw I'm not sure where the code is for that now but I think I avoided floats by using lookup tables, based on this sketch by Adrian Freed |
A reminder for who-ever merges this: be sure to squash to avoid both audio files ending up in the repo history. |
Section 3.1.4 of Hardware Design with RP2040 outlines a good circuit for getting line-out audio with PWM. It also has a C project that uses the same system-frequency-matched PWM style that this example does, but they also switch the power supply from PFM to PWM mode to reduce switching noise (at the cost of increased power consumption). |
Oh, huh that is clever - I didn't know you could just skip the wav header
and directly play audio samples.
Let me play around with that, maybe I could even pull in a no_std wav
parser and give it some flexibility.
As for the file size, I suppose technically it would be best to check it in
as a git LFS file as that's the best practice for binary files.
And yeah, I figure it'll allow more creativity from users if the sample
included an easy-to-replace audio file rather than a synthesized sound.
…On Thu, Jun 16, 2022, 9:56 AM paddywwoof ***@***.***> wrote:
Yes it would be very easy to synthesize the sound but I suppose @DrChat
<https://github.com/DrChat> wants to demonstrate the ease of including
sound files into the program. At least half the sample is empty, presumably
to improve the impression when repeated (though that could be done just
with a delay). If the blank parts are removed it would be 31.6k
The other thing is that it might be better using a WAV format file which
would obviously be a sound sample and would have the advantage that it
could be played by clicking on it in most file browsers. I can't find an
option in Audactiy to save WAV as signed 8-bit so to do that the
instructions would have to be
/// If you want to create your own, use Audacity to create a recording with a/// sample rate of 32,000 Hz and then export it as WAV file coding Unsigned 8-bit PCMconst AUDIO: &[u8] = include_bytes!("pico_pwm_audio.wav");
and later
for i in &AUDIO[44..] {
...
let i = ((*i as u16) << 3).wrapping_add(2048) & 0xFFF;
—
Reply to this email directly, view it on GitHub
<#356 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AASKQQMHC3OL6BJKI4PAKXTVPM6AJANCNFSM5YSDMYKQ>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
This PR merely adds a basic example of using the PWM controller to output an audio signal at 32kHz on GPIO0.
One of the more unique parts of this example is configuring the system clock to a nonstandard rate that is a multiple of the audio sample rate, so that the PWM clock can be divided by an integral number to deliver the audio samples.