The Problem: I have been working on a project that listens to audio (using the PDM library) and sends IR signals (using IRremote). The issue is well known: The PDM library uses high-frequency interrupts to capture audio. The IRremote library needs precise timing to generate the 38kHz carrier wave. When you try to run both, they fight for resources. The audio crashes, or the IR signal comes out garbled because the processor is too busy handling microphone data.
The "Standard" Advice: Most threads suggest you cannot do both, or you need to dive deep into nRF52840 registers to set up a PPI (Programmable Peripheral Interconnect) channel, which is very complex.
The Workaround (That Works!): We found a stable solution using the Mbed OS features native to the Nano 33 BLE. Instead of asking the CPU to bit-bang the 38kHz carrier (which fails under load), we configure a Hardware PWM pin to run at 38kHz constantly in the background. We then "modulate" the signal simply by changing the Duty Cycle from 0.0 (Off) to 0.3 (On).
This effectively offloads the heavy lifting to the hardware PWM generator. We also explicitly pause the PDM interrupts during the brief transmission window.
The Code Snippet:
C++
#include <mbed.h>
#include <PDM.h>
// 1. Define the PWM Object (mbed style)
mbed::PwmOut ir_pwm(digitalPinToPinName(5)); // Send Pin is D5
void setup() {
// 2. Set the Carrier Frequency (38kHz) once in setup
ir_pwm.period(1.0 / 38000.0);
ir_pwm.write(0.0); // Start with it OFF
}
// 3. The "Blast" Function
// Takes an array of raw ticks (Microseconds) captured from a remote
void blast(const unsigned int* rawData, int len) {
// CRITICAL STEP: Stop the Mic.
// This prevents PDM interrupts from ruining the IR timing.
PDM.end();
// Loop through the raw timing data
for (int i = 0; i < len; i++) {
unsigned int duration = rawData[i];
if (i % 2 == 0) {
// MARK (Send 38kHz Carrier)
// We don't use digitalWrite. We just open the PWM gate.
ir_pwm.write(0.3); // 30% Duty Cycle is standard for IR
} else {
// SPACE (Silence)
ir_pwm.write(0.0); // 0% Duty Cycle
}
// Hold state for the duration of the pulse
delayMicroseconds(duration);
}
// Ensure off at the end
ir_pwm.write(0.0);
// CRITICAL STEP: Restart the Mic
PDM.begin(1, 16000);
}
Why this works:
-
Hardware Offloading: The 38kHz wave is generated by the PWM peripheral, not the CPU code. The CPU only has to wake up every few hundred microseconds to toggle the duty cycle.
-
Resource gating: By calling
PDM.end()right before blasting, we silence the noisy interrupts for the ~50ms it takes to send a command, ensuring perfect IR timing.
Hope this helps anyone else trying to build voice/audio reactive remotes!