Skip to content

Instantly share code, notes, and snippets.

@jfrey-xx
Last active November 15, 2022 12:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jfrey-xx/37c645b0fa87d86375f8a356104951d9 to your computer and use it in GitHub Desktop.
Save jfrey-xx/37c645b0fa87d86375f8a356104951d9 to your computer and use it in GitHub Desktop.
MCLK for I2S arduino-pico using PIO -- see https://github.com/earlephilhower/arduino-pico/issues/647
#include <Arduino.h>
#include "MCLK.h"
#include "mclk.pio.h"
MCLK::MCLK() {
_running = false;
_pin = 29;
_freq = 48000;
}
MCLK::~MCLK() {
}
bool MCLK::setPin(pin_size_t pin) {
if (_running) {
return false;
}
_pin = pin;
return true;
}
bool MCLK::setFrequency(unsigned int newFreq, unsigned int newMultiplier) {
_freq = newFreq;
_multiplier = newMultiplier;
if (_running) {
float bitClk = _freq * _multiplier * 2.0 /* edges per clock */;
pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk);
}
return true;
}
bool MCLK::begin() {
_running = true;
int off = 0;
_mclk = new PIOProgram(&mclk_program);
_mclk->prepare(&_pio, &_sm, &off);
mclk_program_init(_pio, _sm, off, _pin);
setFrequency(_freq, _multiplier);
pio_sm_set_enabled(_pio, _sm, true);
return true;
}
void MCLK::end() {
_running = false;
delete _mclk;
_mclk = nullptr;
}
#pragma once
class MCLK {
public:
MCLK();
virtual ~MCLK();
bool setPin(pin_size_t pin);
// set MCLK clock, based on audio sampling rate (e.g. 48000hz) and multiplier (typically 256, check specs)
bool setFrequency(unsigned int newFreq, unsigned int newMultiplier);
bool begin(unsigned int sampleRate, unsigned int multiplier ) {
setFrequency(sampleRate, multiplier);
return begin();
}
bool begin();
void end();
private:
pin_size_t _pin;
unsigned int _freq = 48000;
unsigned int _multiplier = 256;
bool _running;
PIOProgram *_mclk;
PIO _pio;
int _sm;
};
.program mclk
; Generate mclk clock
.wrap_target
set pins, 1 ; Turn on
set pins, 0 ; Turn off, most complicated ever
.wrap
% c-sdk {
// Helper function (for use in C program) to initialize this PIO program
void mclk_program_init(PIO pio, uint sm, uint offset, uint pin) {
// Sets up state machine and wrap target. This function is automatically
// generated in mclk.pio.h.
pio_sm_config c = mclk_program_get_default_config(offset);
// Allow PIO to control GPIO pin (as output)
pio_gpio_init(pio, pin);
// Connect pin to SET pin (control with 'set' instruction)
sm_config_set_set_pins(&c, pin, 1);
// Set the pin direction to output (in PIO)
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// Load configuration and jump to start of the program
pio_sm_init(pio, sm, offset, &c);
}
%}
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ---- //
// mclk //
// ---- //
#define mclk_wrap_target 0
#define mclk_wrap 1
static const uint16_t mclk_program_instructions[] = {
// .wrap_target
0xe001, // 0: set pins, 1
0xe000, // 1: set pins, 0
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program mclk_program = {
.instructions = mclk_program_instructions,
.length = 2,
.origin = -1,
};
static inline pio_sm_config mclk_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + mclk_wrap_target, offset + mclk_wrap);
return c;
}
// Helper function (for use in C program) to initialize this PIO program
void mclk_program_init(PIO pio, uint sm, uint offset, uint pin) {
// Sets up state machine and wrap target. This function is automatically
// generated in mclk.pio.h.
pio_sm_config c = mclk_program_get_default_config(offset);
// Allow PIO to control GPIO pin (as output)
pio_gpio_init(pio, pin);
// Connect pin to SET pin (control with 'set' instruction)
sm_config_set_set_pins(&c, pin, 1);
// Set the pin direction to output (in PIO)
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// Load configuration and jump to start of the program
pio_sm_init(pio, sm, offset, &c);
}
#endif
// minimal example to setup MCLK clock with PIO, used that in combination with I2S library
#include "MCLK.h"
// changing clock
#include <pico/stdlib.h>
MCLK mclk;
#define pMCLK 18 // pin 24
// chose arbitrary samplerate so we can have a cpu clock which is a divider of the MCLK
const int sampleRate = 40000;
// typical for many DAC
const int mclkMultiplier = 256;
void setup() {
// set CPU speed for a good ratio with 256*fs -- flag to bock if requested speed cannot be achieved
set_sys_clock_khz(102400, true);
Serial.begin(115200);
mclk.setPin(pMCLK);
if (!mclk.begin(sampleRate, mclkMultiplier)) {
while (1) {
Serial.println("Failed to initialize MCLK!");
delay(1000);
}
}
}
void loop() {
// do your things
delay(1000);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment