|
| 1 | +// This file is part of the CircuitPython project: https://circuitpython.org |
| 2 | +// |
| 3 | +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple |
| 4 | +// |
| 5 | +// SPDX-License-Identifier: MIT |
| 6 | + |
| 7 | +#include <stdint.h> |
| 8 | +#include <string.h> |
| 9 | + |
| 10 | +#include "mpconfigport.h" |
| 11 | + |
| 12 | +#include "py/mperrno.h" |
| 13 | +#include "py/runtime.h" |
| 14 | +#include "common-hal/audiobusio/I2S.h" |
| 15 | +#include "shared-bindings/audiobusio/I2S.h" |
| 16 | +#include "shared-bindings/microcontroller/__init__.h" |
| 17 | +#include "shared-bindings/microcontroller/Pin.h" |
| 18 | +#include "shared-module/audiocore/__init__.h" |
| 19 | +#include "bindings/rp2pio/StateMachine.h" |
| 20 | + |
| 21 | +#include "audio_dma.h" |
| 22 | + |
| 23 | +#include "src/rp2_common/hardware_pio/include/hardware/pio_instructions.h" |
| 24 | + |
| 25 | +#define I2S_CODE(bits_per_sample, out, in, left_justified, swap) \ |
| 26 | + { \ |
| 27 | +/* /--- LRCLK */ \ |
| 28 | +/* |/-- BCLK */ \ |
| 29 | +/* || */ \ |
| 30 | +/* 00 */ pio_encode_set(pio_y, bits_per_sample - 2) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap) | pio_encode_delay(1), \ |
| 31 | + /* .wrap_target */ \ |
| 32 | +/* 01 */ (out ? pio_encode_pull(false, false) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap), \ |
| 33 | +/* 02 */ (out ? pio_encode_mov(pio_x, pio_osr) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap), \ |
| 34 | +/* 03 */ (out ? pio_encode_out(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b00) | pio_encode_delay(3), \ |
| 35 | +/* 04 */ (in ? pio_encode_in(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap), \ |
| 36 | +/* 05 */ pio_encode_jmp_y_dec(3) | pio_encode_sideset(2, 0b01 << swap) | pio_encode_delay(2), \ |
| 37 | +/* 06 */ (out ? pio_encode_out(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b00 | !left_justified << !swap) | pio_encode_delay(3), \ |
| 38 | +/* 07 */ (in ? pio_encode_in(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | !left_justified << !swap), \ |
| 39 | +/* 08 */ pio_encode_set(pio_y, bits_per_sample - 2) | pio_encode_sideset(2, 0b01 << swap | !left_justified << !swap) | pio_encode_delay(2), \ |
| 40 | +/* 09 */ (out ? pio_encode_out(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b10 >> swap) | pio_encode_delay(3), \ |
| 41 | +/* 10 */ (in ? pio_encode_in(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b11), \ |
| 42 | +/* 11 */ pio_encode_jmp_y_dec(9) | pio_encode_sideset(2, 0b11) | pio_encode_delay(2), \ |
| 43 | +/* 12 */ (out ? pio_encode_out(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b00 | left_justified << !swap) | pio_encode_delay(2), \ |
| 44 | +/* 13 */ pio_encode_set(pio_y, bits_per_sample - 2) | pio_encode_sideset(2, 0b00 | left_justified << !swap), \ |
| 45 | +/* 13 */ (in ? pio_encode_in(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap), \ |
| 46 | +/* 14 */ (in ? pio_encode_push(false, false) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap), \ |
| 47 | + /* .wrap */ \ |
| 48 | + } |
| 49 | + |
| 50 | +// Caller validates that pins are free. |
| 51 | +void common_hal_audiobusio_i2s_construct(audiobusio_i2s_obj_t *self, |
| 52 | + const mcu_pin_obj_t *bit_clock, const mcu_pin_obj_t *word_select, |
| 53 | + const mcu_pin_obj_t *data_out, const mcu_pin_obj_t *data_in, |
| 54 | + const mcu_pin_obj_t *main_clock, bool left_justified, |
| 55 | + uint32_t buffer_size, uint8_t channel_count, uint32_t sample_rate, |
| 56 | + uint8_t bits_per_sample, bool samples_signed) { |
| 57 | + if (main_clock != NULL) { |
| 58 | + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_main_clock); |
| 59 | + } |
| 60 | + |
| 61 | + const mcu_pin_obj_t *sideset_pin = NULL; |
| 62 | + bool swap = false; |
| 63 | + |
| 64 | + if (bit_clock->number == word_select->number - 1) { |
| 65 | + sideset_pin = bit_clock; |
| 66 | + } else if (bit_clock->number == word_select->number + 1) { |
| 67 | + sideset_pin = word_select; |
| 68 | + swap = true; |
| 69 | + } else { |
| 70 | + mp_raise_ValueError(MP_ERROR_TEXT("Bit clock and word select must be sequential GPIO pins")); |
| 71 | + } |
| 72 | + |
| 73 | + uint16_t program[] = I2S_CODE(bits_per_sample, data_out != NULL, data_in != NULL, left_justified, swap); |
| 74 | + |
| 75 | + // Use the state machine to manage pins. |
| 76 | + common_hal_rp2pio_statemachine_construct( |
| 77 | + &self->state_machine, |
| 78 | + program, MP_ARRAY_SIZE(program), |
| 79 | + sample_rate * bits_per_sample * 16, // Frequency based on sample rate and bit width |
| 80 | + NULL, 0, // init |
| 81 | + NULL, 0, // may_exec |
| 82 | + data_out, 1, 0, 0xffffffff, // out pin |
| 83 | + data_in, 1, // in pins |
| 84 | + 0, 0, // in pulls |
| 85 | + NULL, 1, 0, 0, // set pins |
| 86 | + sideset_pin, 2, false, 0, 0x1f, // sideset pins |
| 87 | + false, // No sideset enable |
| 88 | + NULL, PULL_NONE, // jump pin |
| 89 | + 0, // wait gpio pins |
| 90 | + true, // exclusive pin use |
| 91 | + false, 32, false, // out settings |
| 92 | + false, // Wait for txstall |
| 93 | + false, 32, false, // in settings |
| 94 | + false, // Not user-interruptible. |
| 95 | + 1, -1, // wrap settings |
| 96 | + PIO_ANY_OFFSET, |
| 97 | + PIO_FIFO_TYPE_DEFAULT, |
| 98 | + PIO_MOV_STATUS_DEFAULT, |
| 99 | + PIO_MOV_N_DEFAULT |
| 100 | + ); |
| 101 | + |
| 102 | + self->buffer_size = buffer_size; |
| 103 | + self->channel_count = channel_count; |
| 104 | + self->sample_rate = sample_rate; |
| 105 | + self->bits_per_sample = bits_per_sample; |
| 106 | + self->samples_signed = samples_signed; |
| 107 | + |
| 108 | + self->playing = false; |
| 109 | + audio_dma_init(&self->dma); |
| 110 | +} |
| 111 | + |
| 112 | +void i2s_configure_audio_dma(audiobusio_i2s_obj_t *self, mp_obj_t sample, bool loop, uint32_t sample_rate, uint8_t bits_per_sample) { |
| 113 | + |
| 114 | + if (self->dma.output_channel[0] != NUM_DMA_CHANNELS || self->dma.input_channel[0] != NUM_DMA_CHANNELS) { |
| 115 | + if (self->state_machine.out) { |
| 116 | + audio_dma_stop_output(&self->dma); |
| 117 | + } |
| 118 | + if (self->state_machine.in) { |
| 119 | + audio_dma_stop_input(&self->dma); |
| 120 | + } |
| 121 | + audio_dma_deinit(&self->dma); |
| 122 | + } |
| 123 | + |
| 124 | + common_hal_rp2pio_statemachine_set_frequency(&self->state_machine, sample_rate * bits_per_sample * 16); |
| 125 | + common_hal_rp2pio_statemachine_restart(&self->state_machine); |
| 126 | + |
| 127 | + // On the RP2040, output registers are always written with a 32-bit write. |
| 128 | + // If the write is 8 or 16 bits wide, the data will be replicated in upper bytes. |
| 129 | + // See section 2.1.4 Narrow IO Register Writes in the RP2040 datasheet. |
| 130 | + // This means that identical 16-bit audio data will be written in both halves of the incoming PIO |
| 131 | + // FIFO register. Thus we get mono-to-stereo conversion for the I2S output for free. |
| 132 | + audio_dma_result result = audio_dma_setup( |
| 133 | + &self->dma, |
| 134 | + sample, |
| 135 | + loop, |
| 136 | + false, // single channel |
| 137 | + 0, // audio channel |
| 138 | + true, // output signed |
| 139 | + bits_per_sample, |
| 140 | + (self->state_machine.out ? (uint32_t)&self->state_machine.pio->txf[self->state_machine.state_machine] : 0), // output register |
| 141 | + (self->state_machine.out ? self->state_machine.tx_dreq : 0), // output data request line |
| 142 | + (self->state_machine.in ? (uint32_t)&self->state_machine.pio->rxf[self->state_machine.state_machine] : 0), // input register |
| 143 | + (self->state_machine.in ? self->state_machine.rx_dreq : 0), // input data request line |
| 144 | + false); // swap channel |
| 145 | + |
| 146 | + if (result == AUDIO_DMA_DMA_BUSY) { |
| 147 | + common_hal_audiobusio_i2s_stop(self); |
| 148 | + mp_raise_RuntimeError(MP_ERROR_TEXT("No DMA channel found")); |
| 149 | + } else if (result == AUDIO_DMA_MEMORY_ERROR) { |
| 150 | + common_hal_audiobusio_i2s_stop(self); |
| 151 | + mp_raise_RuntimeError(MP_ERROR_TEXT("Unable to allocate buffers for signed conversion")); |
| 152 | + } |
| 153 | +} |
| 154 | + |
| 155 | +bool common_hal_audiobusio_i2s_deinited(audiobusio_i2s_obj_t *self) { |
| 156 | + return common_hal_rp2pio_statemachine_deinited(&self->state_machine); |
| 157 | +} |
| 158 | + |
| 159 | +void common_hal_audiobusio_i2s_deinit(audiobusio_i2s_obj_t *self) { |
| 160 | + if (common_hal_audiobusio_i2s_deinited(self)) { |
| 161 | + return; |
| 162 | + } |
| 163 | + |
| 164 | + common_hal_rp2pio_statemachine_deinit(&self->state_machine); |
| 165 | + |
| 166 | + audio_dma_deinit(&self->dma); |
| 167 | +} |
| 168 | + |
| 169 | +void common_hal_audiobusio_i2s_play(audiobusio_i2s_obj_t *self, |
| 170 | + mp_obj_t sample, bool loop) { |
| 171 | + if (!self->state_machine.out) { |
| 172 | + mp_raise_RuntimeError(MP_ERROR_TEXT("No data out")); |
| 173 | + } |
| 174 | + |
| 175 | + if (common_hal_audiobusio_i2s_get_playing(self)) { |
| 176 | + common_hal_audiobusio_i2s_stop(self); |
| 177 | + } |
| 178 | + |
| 179 | + uint8_t bits_per_sample = audiosample_bits_per_sample(sample); |
| 180 | + uint32_t sample_rate = audiosample_sample_rate(sample); |
| 181 | + uint8_t channel_count = audiosample_channel_count(sample); |
| 182 | + if (channel_count > 2) { |
| 183 | + mp_raise_ValueError(MP_ERROR_TEXT("Too many channels in sample.")); |
| 184 | + } |
| 185 | + |
| 186 | + if (self->state_machine.in) { |
| 187 | + if (bits_per_sample > self->bits_per_sample) { |
| 188 | + mp_raise_ValueError(MP_ERROR_TEXT("Bits per sample cannot be greater than input.")); |
| 189 | + } |
| 190 | + if (sample_rate != self->sample_rate) { |
| 191 | + mp_raise_ValueError(MP_ERROR_TEXT("Sample rate must match.")); |
| 192 | + } |
| 193 | + } |
| 194 | + |
| 195 | + i2s_configure_audio_dma(self, sample, loop, sample_rate, bits_per_sample); |
| 196 | + self->playing = true; |
| 197 | +} |
| 198 | + |
| 199 | +void common_hal_audiobusio_i2s_pause(audiobusio_i2s_obj_t *self) { |
| 200 | + if (!self->state_machine.out) { |
| 201 | + mp_raise_RuntimeError(MP_ERROR_TEXT("No data out")); |
| 202 | + } |
| 203 | + audio_dma_pause(&self->dma); |
| 204 | +} |
| 205 | + |
| 206 | +void common_hal_audiobusio_i2s_resume(audiobusio_i2s_obj_t *self) { |
| 207 | + if (!self->state_machine.out) { |
| 208 | + mp_raise_RuntimeError(MP_ERROR_TEXT("No data out")); |
| 209 | + } |
| 210 | + // Maybe: Clear any overrun/underrun errors |
| 211 | + audio_dma_resume(&self->dma); |
| 212 | +} |
| 213 | + |
| 214 | +bool common_hal_audiobusio_i2s_get_paused(audiobusio_i2s_obj_t *self) { |
| 215 | + if (!self->state_machine.out) { |
| 216 | + mp_raise_RuntimeError(MP_ERROR_TEXT("No data out")); |
| 217 | + } |
| 218 | + return audio_dma_get_paused(&self->dma); |
| 219 | +} |
| 220 | + |
| 221 | +void common_hal_audiobusio_i2s_stop(audiobusio_i2s_obj_t *self) { |
| 222 | + if (!self->state_machine.out) { |
| 223 | + mp_raise_RuntimeError(MP_ERROR_TEXT("No data out")); |
| 224 | + } |
| 225 | + |
| 226 | + audio_dma_stop(&self->dma); |
| 227 | + |
| 228 | + common_hal_rp2pio_statemachine_stop(&self->state_machine); |
| 229 | + |
| 230 | + self->playing = false; |
| 231 | +} |
| 232 | + |
| 233 | +bool common_hal_audiobusio_i2s_get_playing(audiobusio_i2s_obj_t *self) { |
| 234 | + if (!self->state_machine.out) { |
| 235 | + mp_raise_RuntimeError(MP_ERROR_TEXT("No data out")); |
| 236 | + } |
| 237 | + |
| 238 | + bool playing = audio_dma_get_playing(&self->dma); |
| 239 | + if (!playing && self->playing) { |
| 240 | + common_hal_audiobusio_i2s_stop(self); |
| 241 | + } |
| 242 | + return playing; |
| 243 | +} |
| 244 | + |
| 245 | +uint32_t common_hal_audiobusio_i2s_get_sample_rate(audiobusio_i2s_obj_t *self) { |
| 246 | + if (!self->state_machine.in) { |
| 247 | + mp_raise_RuntimeError(MP_ERROR_TEXT("No data in")); |
| 248 | + } |
| 249 | + return self->sample_rate; |
| 250 | +} |
| 251 | + |
| 252 | +uint8_t common_hal_audiobusio_i2s_get_channel_count(audiobusio_i2s_obj_t *self) { |
| 253 | + if (!self->state_machine.in) { |
| 254 | + mp_raise_RuntimeError(MP_ERROR_TEXT("No data in")); |
| 255 | + } |
| 256 | + return self->channel_count; |
| 257 | +} |
| 258 | + |
| 259 | +uint8_t common_hal_audiobusio_i2s_get_bits_per_sample(audiobusio_i2s_obj_t *self) { |
| 260 | + if (!self->state_machine.in) { |
| 261 | + mp_raise_RuntimeError(MP_ERROR_TEXT("No data in")); |
| 262 | + } |
| 263 | + return self->bits_per_sample; |
| 264 | +} |
| 265 | + |
| 266 | +void audiobusio_i2s_reset_buffer(audiobusio_i2s_obj_t *self, |
| 267 | + bool single_channel_output, |
| 268 | + uint8_t channel) { |
| 269 | + if (!self->state_machine.in) { |
| 270 | + mp_raise_RuntimeError(MP_ERROR_TEXT("No data in")); |
| 271 | + } |
| 272 | + |
| 273 | + if (single_channel_output) { |
| 274 | + mp_raise_NotImplementedError(MP_ERROR_TEXT("Single channel output not supported.")); |
| 275 | + } |
| 276 | + |
| 277 | + i2s_configure_audio_dma(self, self, true, self->sample_rate, self->bits_per_sample); |
| 278 | +} |
| 279 | + |
| 280 | +audioio_get_buffer_result_t audiobusio_i2s_get_buffer(audiobusio_i2s_obj_t *self, |
| 281 | + bool single_channel_output, |
| 282 | + uint8_t channel, |
| 283 | + uint8_t **buffer, |
| 284 | + uint32_t *buffer_length) { |
| 285 | + if (!self->state_machine.in) { |
| 286 | + mp_raise_RuntimeError(MP_ERROR_TEXT("No data in")); |
| 287 | + } |
| 288 | + |
| 289 | + if (single_channel_output) { |
| 290 | + mp_raise_NotImplementedError(MP_ERROR_TEXT("Single channel output not supported.")); |
| 291 | + } |
| 292 | + |
| 293 | + // Do other things while we wait for the buffer to fill. |
| 294 | + while (!audio_dma_has_buffer(&self->dma)) { |
| 295 | + // BUG: Issue with interrupt? |
| 296 | + if (self->state_machine.out) { |
| 297 | + common_hal_mcu_delay_us(1000000 / self->sample_rate); |
| 298 | + } else { |
| 299 | + RUN_BACKGROUND_TASKS; |
| 300 | + } |
| 301 | + } |
| 302 | + |
| 303 | + *buffer_length = self->buffer_size; |
| 304 | + *buffer = audio_dma_get_buffer(&self->dma); |
| 305 | + return GET_BUFFER_MORE_DATA; |
| 306 | +} |
| 307 | + |
| 308 | +void audiobusio_i2s_get_buffer_structure(audiobusio_i2s_obj_t *self, bool single_channel_output, |
| 309 | + bool *single_buffer, bool *samples_signed, |
| 310 | + uint32_t *max_buffer_length, uint8_t *spacing) { |
| 311 | + if (!self->state_machine.in) { |
| 312 | + mp_raise_RuntimeError(MP_ERROR_TEXT("No data in")); |
| 313 | + } |
| 314 | + |
| 315 | + *single_buffer = false; |
| 316 | + *samples_signed = self->samples_signed; |
| 317 | + *max_buffer_length = self->buffer_size; |
| 318 | + if (single_channel_output) { |
| 319 | + *spacing = self->channel_count; |
| 320 | + } else { |
| 321 | + *spacing = 1; |
| 322 | + } |
| 323 | +} |
0 commit comments