Skip to content

Commit 360475e

Browse files
committed
Implement audiobusio and enhance PIO for it
This adds I2SOut and PDMIn support via PIO. StateMachines can now: * read and read while writing * transfer in 1, 2 or 4 byte increments * init pins based on expected defaults automatically * be stopped and restarted * rxfifo can be cleared and rxstalls detected (good for tracking when the reading code isn't keeping up) Fixes #4162
1 parent e41137c commit 360475e

File tree

15 files changed

+1049
-193
lines changed

15 files changed

+1049
-193
lines changed

ports/raspberrypi/audio_dma.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@
3939

4040
#define AUDIO_DMA_CHANNEL_COUNT NUM_DMA_CHANNELS
4141

42+
void audio_dma_reset(void) {
43+
for (size_t channel = 0; channel < AUDIO_DMA_CHANNEL_COUNT; channel++) {
44+
if (MP_STATE_PORT(playing_audio)[channel] == NULL) {
45+
continue;
46+
}
47+
48+
audio_dma_stop(MP_STATE_PORT(playing_audio)[channel]);
49+
}
50+
}
51+
4252
void audio_dma_convert_signed(audio_dma_t* dma, uint8_t* buffer, uint32_t buffer_length,
4353
uint8_t** output_buffer, uint32_t* output_buffer_length) {
4454
if (dma->first_buffer_free) {
@@ -292,9 +302,16 @@ void audio_dma_stop(audio_dma_t* dma) {
292302
for (size_t i = 0; i < 2; i++) {
293303
size_t channel = dma->channel[i];
294304

305+
dma_channel_config c = dma_channel_get_default_config(dma->channel[i]);
306+
channel_config_set_enable(&c, false);
307+
dma_channel_set_config(channel, &c, false /* trigger */);
308+
295309
if (dma_channel_is_busy(channel)) {
296310
dma_channel_abort(channel);
297311
}
312+
dma_channel_set_read_addr(channel, NULL, false /* trigger */);
313+
dma_channel_set_write_addr(channel, NULL, false /* trigger */);
314+
dma_channel_set_trans_count(channel, 0, false /* trigger */);
298315
dma_channel_unclaim(channel);
299316
MP_STATE_PORT(playing_audio)[channel] = NULL;
300317
dma->channel[i] = NUM_DMA_CHANNELS;

ports/raspberrypi/bindings/rp2pio/StateMachine.c

Lines changed: 255 additions & 122 deletions
Large diffs are not rendered by default.

ports/raspberrypi/bindings/rp2pio/StateMachine.h

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,36 +36,38 @@
3636
extern const mp_obj_type_t rp2pio_statemachine_type;
3737

3838
// Construct an underlying SPI object.
39-
extern void common_hal_rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self,
39+
void common_hal_rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self,
4040
const uint16_t* program, size_t program_len,
4141
size_t frequency,
4242
const uint16_t* init, size_t init_len,
43-
const mcu_pin_obj_t * first_out_pin, uint8_t out_pin_count,
43+
const mcu_pin_obj_t * first_out_pin, uint8_t out_pin_count, uint32_t initial_out_pin_state, uint32_t initial_out_pin_direction,
4444
const mcu_pin_obj_t * first_in_pin, uint8_t in_pin_count,
45-
const mcu_pin_obj_t * first_set_pin, uint8_t set_pin_count,
46-
const mcu_pin_obj_t * first_sideset_pin, uint8_t sideset_pin_count,
45+
const mcu_pin_obj_t * first_set_pin, uint8_t set_pin_count, uint32_t initial_set_pin_state, uint32_t initial_set_pin_direction,
46+
const mcu_pin_obj_t * first_sideset_pin, uint8_t sideset_pin_count, uint32_t initial_sideset_pin_state, uint32_t initial_sideset_pin_direction,
4747
bool exclusive_pin_use,
4848
bool auto_pull, uint8_t pull_threshold, bool out_shift_right,
49+
bool wait_for_txstall,
4950
bool auto_push, uint8_t push_threshold, bool in_shift_right);
5051

51-
extern void common_hal_rp2pio_statemachine_deinit(rp2pio_statemachine_obj_t *self);
52-
extern bool common_hal_rp2pio_statemachine_deinited(rp2pio_statemachine_obj_t *self);
52+
void common_hal_rp2pio_statemachine_deinit(rp2pio_statemachine_obj_t *self);
53+
bool common_hal_rp2pio_statemachine_deinited(rp2pio_statemachine_obj_t *self);
5354

54-
// Writes out the given data.
55-
extern bool common_hal_rp2pio_statemachine_write(rp2pio_statemachine_obj_t *self, const uint8_t *data, size_t len);
56-
57-
// // Reads in len bytes while outputting zeroes.
58-
// extern bool common_hal_rp2pio_statemachine_read(rp2pio_statemachine_obj_t *self, uint8_t *data, size_t len, uint8_t write_value);
55+
void common_hal_rp2pio_statemachine_restart(rp2pio_statemachine_obj_t *self);
56+
void common_hal_rp2pio_statemachine_stop(rp2pio_statemachine_obj_t *self);
57+
void common_hal_rp2pio_statemachine_run(rp2pio_statemachine_obj_t *self, const uint16_t *instructions, size_t len);
5958

60-
// // Reads and write len bytes simultaneously.
61-
// extern bool common_hal_rp2pio_statemachine_transfer(rp2pio_statemachine_obj_t *self,
62-
// const uint8_t *data_out, size_t out_len,
63-
// uint8_t *data_in, size_t in_len);
59+
// Writes out the given data.
60+
bool common_hal_rp2pio_statemachine_write(rp2pio_statemachine_obj_t *self, const uint8_t *data, size_t len, uint8_t stride_in_bytes);
61+
bool common_hal_rp2pio_statemachine_readinto(rp2pio_statemachine_obj_t *self, uint8_t *data, size_t len, uint8_t stride_in_bytes);
62+
bool common_hal_rp2pio_statemachine_write_readinto(rp2pio_statemachine_obj_t *self,
63+
const uint8_t *data_out, size_t out_len, uint8_t out_stride_in_bytes,
64+
uint8_t *data_in, size_t in_len, uint8_t in_stride_in_bytes);
6465

65-
// Return actual SPI bus frequency.
66+
// Return actual state machine frequency.
6667
uint32_t common_hal_rp2pio_statemachine_get_frequency(rp2pio_statemachine_obj_t* self);
68+
void common_hal_rp2pio_statemachine_set_frequency(rp2pio_statemachine_obj_t* self, uint32_t frequency);
6769

68-
// This is used by the supervisor to claim SPI devices indefinitely.
69-
// extern void common_hal_rp2pio_statemachine_never_reset(rp2pio_statemachine_obj_t *self);
70+
bool common_hal_rp2pio_statemachine_get_rxstall(rp2pio_statemachine_obj_t* self);
71+
void common_hal_rp2pio_statemachine_clear_rxfifo(rp2pio_statemachine_obj_t *self);
7072

7173
#endif // MICROPY_INCLUDED_RASPBERRYPI_BINDINGS_RP2PIO_STATEMACHINE_H
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2021 Scott Shawcroft for Adafruit Industries
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#include <stdint.h>
28+
#include <string.h>
29+
30+
#include "mpconfigport.h"
31+
32+
#include "py/gc.h"
33+
#include "py/mperrno.h"
34+
#include "py/runtime.h"
35+
#include "common-hal/audiobusio/I2SOut.h"
36+
#include "shared-bindings/audiobusio/I2SOut.h"
37+
#include "shared-bindings/microcontroller/Pin.h"
38+
#include "shared-module/audiocore/__init__.h"
39+
#include "bindings/rp2pio/StateMachine.h"
40+
#include "supervisor/shared/translate.h"
41+
42+
const uint16_t i2s_program[] = {
43+
// ; Load the next set of samples
44+
// ; /--- LRCLK
45+
// ; |/-- BCLK
46+
// ; ||
47+
// pull noblock side 0b01 ; Loads OSR with the next FIFO value or X
48+
0x8880,
49+
// mov x osr side 0b01 ; Save the new value in case we need it again
50+
0xa827,
51+
// set y 14 side 0b01
52+
0xe84e,
53+
// bitloop1:
54+
// out pins 1 side 0b00 [2]
55+
0x6201,
56+
// jmp y-- bitloop1 side 0b01 [2]
57+
0x0a83,
58+
// out pins 1 side 0b10 [2]
59+
0x7201,
60+
// set y 14 side 0b11 [2]
61+
0xfa4e,
62+
// bitloop0:
63+
// out pins 1 side 0b10 [2]
64+
0x7201,
65+
// jmp y-- bitloop0 side 0b11 [2]
66+
0x1a87,
67+
// out pins 1 side 0b00 [2]
68+
0x6201
69+
};
70+
71+
const uint16_t i2s_program_left_justified[] = {
72+
// ; Load the next set of samples
73+
// ; /--- LRCLK
74+
// ; |/-- BCLK
75+
// ; ||
76+
// pull noblock side 0b11 ; Loads OSR with the next FIFO value or X
77+
0x9880,
78+
// mov x osr side 0b11 ; Save the new value in case we need it again
79+
0xb827,
80+
// set y 14 side 0b11
81+
0xf84e,
82+
// bitloop1:
83+
// out pins 1 side 0b00 [2]
84+
0x6201,
85+
// jmp y-- bitloop1 side 0b01 [2]
86+
0x0a83,
87+
// out pins 1 side 0b10 [2]
88+
0x6201,
89+
// set y 14 side 0b01 [2]
90+
0xea4e,
91+
// bitloop0:
92+
// out pins 1 side 0b10 [2]
93+
0x7201,
94+
// jmp y-- bitloop0 side 0b11 [2]
95+
0x1a87,
96+
// out pins 1 side 0b10 [2]
97+
0x7201
98+
};
99+
100+
void i2sout_reset(void) {
101+
}
102+
103+
// Caller validates that pins are free.
104+
void common_hal_audiobusio_i2sout_construct(audiobusio_i2sout_obj_t* self,
105+
const mcu_pin_obj_t* bit_clock, const mcu_pin_obj_t* word_select,
106+
const mcu_pin_obj_t* data, bool left_justified) {
107+
if (bit_clock->number != word_select->number - 1) {
108+
mp_raise_ValueError(translate("Bit clock and word select must be sequential pins"));
109+
}
110+
111+
const uint16_t* program = i2s_program;
112+
size_t program_len = sizeof(i2s_program) / sizeof(i2s_program[0]);
113+
if (left_justified) {
114+
program = i2s_program_left_justified;
115+
program_len = sizeof(i2s_program_left_justified) / sizeof(i2s_program_left_justified[0]);;
116+
}
117+
118+
// Use the state machine to manage pins.
119+
common_hal_rp2pio_statemachine_construct(&self->state_machine,
120+
program, program_len,
121+
44100 * 32 * 6, // Clock at 44.1 khz to warm the DAC up.
122+
NULL, 0,
123+
data, 1, 0, 0xffffffff, // out pin
124+
NULL, 0, // in pins
125+
NULL, 0, 0, 0x1f, // set pins
126+
bit_clock, 2, 0, 0x1f, // sideset pins
127+
true, // exclusive pin use
128+
false, 32, false, // shift out left to start with MSB
129+
false, // Wait for txstall
130+
false, 32, false); // in settings
131+
132+
self->playing = false;
133+
audio_dma_init(&self->dma);
134+
}
135+
136+
bool common_hal_audiobusio_i2sout_deinited(audiobusio_i2sout_obj_t* self) {
137+
return common_hal_rp2pio_statemachine_deinited(&self->state_machine);
138+
}
139+
140+
void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t* self) {
141+
if (common_hal_audiobusio_i2sout_deinited(self)) {
142+
return;
143+
}
144+
145+
common_hal_rp2pio_statemachine_deinit(&self->state_machine);
146+
}
147+
148+
void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self,
149+
mp_obj_t sample, bool loop) {
150+
if (common_hal_audiobusio_i2sout_get_playing(self)) {
151+
common_hal_audiobusio_i2sout_stop(self);
152+
}
153+
uint8_t bits_per_sample = audiosample_bits_per_sample(sample);
154+
// Make sure we transmit a minimum of 16 bits.
155+
// TODO: Maybe we need an intermediate object to upsample instead. This is
156+
// only needed for some I2S devices that expect at least 8.
157+
if (bits_per_sample < 16) {
158+
bits_per_sample = 16;
159+
}
160+
// We always output stereo so output twice as many bits.
161+
uint16_t bits_per_sample_output = bits_per_sample * 2;
162+
size_t clocks_per_bit = 6;
163+
uint32_t frequency = bits_per_sample_output * audiosample_sample_rate(sample);
164+
common_hal_rp2pio_statemachine_set_frequency(&self->state_machine, clocks_per_bit * frequency);
165+
common_hal_rp2pio_statemachine_restart(&self->state_machine);
166+
167+
uint8_t channel_count = audiosample_channel_count(sample);
168+
if (channel_count > 2) {
169+
mp_raise_ValueError(translate("Too many channels in sample."));
170+
}
171+
172+
audio_dma_result result = audio_dma_setup_playback(
173+
&self->dma,
174+
sample,
175+
loop,
176+
false, // single channel
177+
0, // audio channel
178+
true, // output signed
179+
bits_per_sample,
180+
(uint32_t) &self->state_machine.pio->txf[self->state_machine.state_machine], // output register
181+
self->state_machine.tx_dreq); // data request line
182+
183+
if (result == AUDIO_DMA_DMA_BUSY) {
184+
common_hal_audiobusio_i2sout_stop(self);
185+
mp_raise_RuntimeError(translate("No DMA channel found"));
186+
} else if (result == AUDIO_DMA_MEMORY_ERROR) {
187+
common_hal_audiobusio_i2sout_stop(self);
188+
mp_raise_RuntimeError(translate("Unable to allocate buffers for signed conversion"));
189+
}
190+
191+
// Turn on the state machine's clock.
192+
193+
self->playing = true;
194+
}
195+
196+
void common_hal_audiobusio_i2sout_pause(audiobusio_i2sout_obj_t* self) {
197+
audio_dma_pause(&self->dma);
198+
}
199+
200+
void common_hal_audiobusio_i2sout_resume(audiobusio_i2sout_obj_t* self) {
201+
// Maybe: Clear any overrun/underrun errors
202+
203+
audio_dma_resume(&self->dma);
204+
}
205+
206+
bool common_hal_audiobusio_i2sout_get_paused(audiobusio_i2sout_obj_t* self) {
207+
return audio_dma_get_paused(&self->dma);
208+
}
209+
210+
void common_hal_audiobusio_i2sout_stop(audiobusio_i2sout_obj_t* self) {
211+
audio_dma_stop(&self->dma);
212+
213+
common_hal_rp2pio_statemachine_stop(&self->state_machine);
214+
215+
self->playing = false;
216+
}
217+
218+
bool common_hal_audiobusio_i2sout_get_playing(audiobusio_i2sout_obj_t* self) {
219+
bool playing = audio_dma_get_playing(&self->dma);
220+
if (!playing && self->playing) {
221+
common_hal_audiobusio_i2sout_stop(self);
222+
}
223+
return playing;
224+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2021 Scott Shawcroft for Adafruit Industries
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#ifndef MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_I2SOUT_H
28+
#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_I2SOUT_H
29+
30+
#include "common-hal/microcontroller/Pin.h"
31+
#include "common-hal/rp2pio/StateMachine.h"
32+
33+
#include "audio_dma.h"
34+
#include "py/obj.h"
35+
36+
// We don't bit pack because we'll only have two at most. Its better to save code size instead.
37+
typedef struct {
38+
mp_obj_base_t base;
39+
bool left_justified;
40+
rp2pio_statemachine_obj_t state_machine;
41+
bool playing;
42+
audio_dma_t dma;
43+
} audiobusio_i2sout_obj_t;
44+
45+
void i2sout_reset(void);
46+
47+
#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_I2SOUT_H

0 commit comments

Comments
 (0)