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
This commit is contained in:
Scott Shawcroft 2021-02-23 15:50:00 -08:00
parent e41137c745
commit 360475e266
No known key found for this signature in database
GPG Key ID: 0DFD512649C052DA
15 changed files with 1038 additions and 182 deletions

View File

@ -39,6 +39,16 @@
#define AUDIO_DMA_CHANNEL_COUNT NUM_DMA_CHANNELS
void audio_dma_reset(void) {
for (size_t channel = 0; channel < AUDIO_DMA_CHANNEL_COUNT; channel++) {
if (MP_STATE_PORT(playing_audio)[channel] == NULL) {
continue;
}
audio_dma_stop(MP_STATE_PORT(playing_audio)[channel]);
}
}
void audio_dma_convert_signed(audio_dma_t* dma, uint8_t* buffer, uint32_t buffer_length,
uint8_t** output_buffer, uint32_t* output_buffer_length) {
if (dma->first_buffer_free) {
@ -292,9 +302,16 @@ void audio_dma_stop(audio_dma_t* dma) {
for (size_t i = 0; i < 2; i++) {
size_t channel = dma->channel[i];
dma_channel_config c = dma_channel_get_default_config(dma->channel[i]);
channel_config_set_enable(&c, false);
dma_channel_set_config(channel, &c, false /* trigger */);
if (dma_channel_is_busy(channel)) {
dma_channel_abort(channel);
}
dma_channel_set_read_addr(channel, NULL, false /* trigger */);
dma_channel_set_write_addr(channel, NULL, false /* trigger */);
dma_channel_set_trans_count(channel, 0, false /* trigger */);
dma_channel_unclaim(channel);
MP_STATE_PORT(playing_audio)[channel] = NULL;
dma->channel[i] = NUM_DMA_CHANNELS;

View File

@ -36,6 +36,7 @@
#include "lib/utils/buffer_helper.h"
#include "lib/utils/context_manager_helpers.h"
#include "lib/utils/interrupt_char.h"
#include "py/binary.h"
#include "py/mperrno.h"
#include "py/objproperty.h"
#include "py/runtime.h"
@ -64,16 +65,23 @@
//| init: Optional[ReadableBuffer] = None,
//| first_out_pin: Optional[microcontroller.Pin] = None,
//| out_pin_count: int = 1,
//| initial_out_pin_state: int = 0,
//| initial_out_pin_direction: int = 0xffffffff,
//| first_in_pin: Optional[microcontroller.Pin] = None,
//| in_pin_count: int = 1,
//| first_set_pin: Optional[microcontroller.Pin] = None,
//| set_pin_count: int = 1,
//| initial_set_pin_state: int = 0,
//| initial_set_pin_direction: int = 0x1f,
//| first_sideset_pin: Optional[microcontroller.Pin] = None,
//| sideset_pin_count: int = 1,
//| initial_sideset_pin_state: int = 0,
//| initial_sideset_pin_direction: int = 0x1f,
//| exclusive_pin_use: bool = True,
//| auto_pull: bool = False,
//| pull_threshold : int = 32,
//| out_shift_right : bool = True,
//| wait_for_txstall: bool = True,
//| auto_push: bool = False,
//| push_threshold : int = 32,
//| in_shift_right : bool = True) -> None:
@ -87,12 +95,18 @@
//| is started so instructions may be intermingled
//| :param ~microcontroller.Pin first_out_pin: the first pin to use with the OUT instruction
//| :param int out_pin_count: the count of consecutive pins to use with OUT starting at first_out_pin
//| :param int initial_out_pin_state: the initial output value for out pins starting at first_out_pin
//| :param int initial_out_pin_direction: the initial output direction for out pins starting at first_out_pin
//| :param ~microcontroller.Pin first_in_pin: the first pin to use with the IN instruction
//| :param int in_pin_count: the count of consecutive pins to use with IN starting at first_in_pin
//| :param ~microcontroller.Pin first_set_pin: the first pin to use with the SET instruction
//| :param int set_pin_count: the count of consecutive pins to use with SET starting at first_set_pin
//| :param int initial_set_pin_state: the initial output value for set pins starting at first_set_pin
//| :param int initial_set_pin_direction: the initial output direction for set pins starting at first_set_pin
//| :param ~microcontroller.Pin first_sideset_pin: the first pin to use with a side set
//| :param int sideset_pin_count: the count of consecutive pins to use with a side set starting at first_sideset_pin
//| :param int initial_sideset_pin_state: the initial output value for sideset pins starting at first_sideset_pin
//| :param int initial_sideset_pin_direction: the initial output direction for sideset pins starting at first_sideset_pin
//| :param bool exclusive_pin_use: When True, do not share any pins with other state machines. Pins are never shared with other peripherals
//| :param bool auto_pull: When True, automatically load data from the tx FIFO into the
//| output shift register (OSR) when an OUT instruction shifts more than pull_threshold bits
@ -100,6 +114,10 @@
//| :param bool out_shift_right: When True, data is shifted out the right side (LSB) of the
//| OSR. It is shifted out the left (MSB) otherwise. NOTE! This impacts data alignment
//| when the number of bytes is not a power of two (1, 2 or 4 bytes).
//| :param bool wait_for_txstall: When True, writing data out will block until the TX FIFO and OSR are empty
//| and an instruction is stalled waiting for more data. When False, data writes won't
//| wait for the OSR to empty (only the TX FIFO) so make sure you give enough time before
//| deiniting or stopping the state machine.
//| :param bool auto_push: When True, automatically save data from input shift register
//| (ISR) into the rx FIFO when an IN instruction shifts more than push_threshold bits
//| :param int push_threshold: Number of bits to shift before saving the ISR value to the RX FIFO
@ -113,29 +131,42 @@ STATIC mp_obj_t rp2pio_statemachine_make_new(const mp_obj_type_t *type, size_t n
rp2pio_statemachine_obj_t *self = m_new_obj(rp2pio_statemachine_obj_t);
self->base.type = &rp2pio_statemachine_type;
enum { ARG_program, ARG_frequency, ARG_init,
ARG_first_out_pin, ARG_out_pin_count,
ARG_first_out_pin, ARG_out_pin_count, ARG_initial_out_pin_state, ARG_initial_out_pin_direction,
ARG_first_in_pin, ARG_in_pin_count,
ARG_first_set_pin, ARG_set_pin_count,
ARG_first_sideset_pin, ARG_sideset_pin_count,
ARG_first_set_pin, ARG_set_pin_count, ARG_initial_set_pin_state, ARG_initial_set_pin_direction,
ARG_first_sideset_pin, ARG_sideset_pin_count, ARG_initial_sideset_pin_state, ARG_initial_sideset_pin_direction,
ARG_exclusive_pin_use,
ARG_auto_pull, ARG_pull_threshold, ARG_out_shift_right,
ARG_wait_for_txstall,
ARG_auto_push, ARG_push_threshold, ARG_in_shift_right};
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_program, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_frequency, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_init, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_first_out_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_out_pin_count, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} },
{ MP_QSTR_initial_out_pin_state, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_initial_out_pin_direction, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} },
{ MP_QSTR_first_in_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_in_pin_count, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} },
{ MP_QSTR_first_set_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_set_pin_count, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} },
{ MP_QSTR_initial_set_pin_state, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_initial_set_pin_direction, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0x1f} },
{ MP_QSTR_first_sideset_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_sideset_pin_count, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} },
{ MP_QSTR_initial_sideset_pin_state, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_initial_sideset_pin_direction, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0x1f} },
{ MP_QSTR_exclusive_pin_use, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} },
{ MP_QSTR_auto_pull, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
{ MP_QSTR_pull_threshold, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} },
{ MP_QSTR_out_shift_right, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} },
{ MP_QSTR_wait_for_txstall, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} },
{ MP_QSTR_auto_push, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
{ MP_QSTR_push_threshold, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} },
{ MP_QSTR_in_shift_right, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} },
@ -201,12 +232,13 @@ STATIC mp_obj_t rp2pio_statemachine_make_new(const mp_obj_type_t *type, size_t n
bufinfo.buf, bufinfo.len / 2,
args[ARG_frequency].u_int,
init_bufinfo.buf, init_bufinfo.len / 2,
first_out_pin, args[ARG_out_pin_count].u_int,
first_out_pin, args[ARG_out_pin_count].u_int, args[ARG_initial_out_pin_state].u_int, args[ARG_initial_out_pin_direction].u_int,
first_in_pin, args[ARG_in_pin_count].u_int,
first_set_pin, args[ARG_set_pin_count].u_int,
first_sideset_pin, args[ARG_sideset_pin_count].u_int,
first_set_pin, args[ARG_set_pin_count].u_int, args[ARG_initial_set_pin_state].u_int, args[ARG_initial_set_pin_direction].u_int,
first_sideset_pin, args[ARG_sideset_pin_count].u_int, args[ARG_initial_sideset_pin_state].u_int, args[ARG_initial_sideset_pin_direction].u_int,
args[ARG_exclusive_pin_use].u_bool,
args[ARG_auto_pull].u_bool, pull_threshold, args[ARG_out_shift_right].u_bool,
args[ARG_wait_for_txstall].u_bool,
args[ARG_auto_push].u_bool, push_threshold, args[ARG_in_shift_right].u_bool);
return MP_OBJ_FROM_PTR(self);
}
@ -247,11 +279,53 @@ STATIC void check_for_deinit(rp2pio_statemachine_obj_t *self) {
}
}
// // | def restart(self, *other_state_machines) -> None:
// // | """Restarts this state machine and any others given. They must share
// // | an underlying PIO. An exception will be raised otherwise."""
// // | ...
// // |
//| def restart(self) -> None:
//| """Resets this state machine, runs any init and enables the clock."""
// TODO: "and any others given. They must share an underlying PIO. An exception will be raised otherwise.""
//| ...
//|
STATIC mp_obj_t rp2pio_statemachine_restart(mp_obj_t self_obj) {
rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(self_obj);
check_for_deinit(self);
common_hal_rp2pio_statemachine_restart(self);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(rp2pio_statemachine_restart_obj, rp2pio_statemachine_restart);
//| def run(self, instructions: ReadableBuffer) -> None:
//| """Runs all given instructions. They will likely be interleaved with
//| in-memory instructions. Make sure this doesn't wait for input!
//|
//| This can be used to output internal state to the RX FIFO and then
//| read with `readinto`."""
//| ...
//|
STATIC mp_obj_t rp2pio_statemachine_run(mp_obj_t self_obj, mp_obj_t instruction_obj) {
rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(self_obj);
check_for_deinit(self);
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(instruction_obj, &bufinfo, MP_BUFFER_READ);
common_hal_rp2pio_statemachine_run(self, bufinfo.buf, bufinfo.len);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(rp2pio_statemachine_run_obj, rp2pio_statemachine_run);
//| def stop(self) -> None:
//| """Stops the state machine clock. Use `restart` to enable it."""
//| ...
//|
STATIC mp_obj_t rp2pio_statemachine_stop(mp_obj_t self_obj) {
rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(self_obj);
check_for_deinit(self);
common_hal_rp2pio_statemachine_stop(self);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(rp2pio_statemachine_stop_obj, rp2pio_statemachine_stop);
//| def write(self, buffer: ReadableBuffer, *, start: int = 0, end: Optional[int] = None) -> None:
//| """Write the data contained in ``buffer`` to the state machine. If the buffer is empty, nothing happens.
@ -261,7 +335,6 @@ STATIC void check_for_deinit(rp2pio_statemachine_obj_t *self) {
//| :param int end: End of the slice; this index is not included. Defaults to ``len(buffer)``"""
//| ...
//|
STATIC mp_obj_t rp2pio_statemachine_write(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_buffer, ARG_start, ARG_end };
static const mp_arg_t allowed_args[] = {
@ -279,12 +352,17 @@ STATIC mp_obj_t rp2pio_statemachine_write(size_t n_args, const mp_obj_t *pos_arg
int32_t start = args[ARG_start].u_int;
size_t length = bufinfo.len;
normalize_buffer_bounds(&start, args[ARG_end].u_int, &length);
if (length == 0) {
return mp_const_none;
}
bool ok = common_hal_rp2pio_statemachine_write(self, ((uint8_t*)bufinfo.buf) + start, length);
uint8_t* original_pointer = bufinfo.buf;
int stride_in_bytes = mp_binary_get_size('@', bufinfo.typecode, NULL);
if (stride_in_bytes > 4) {
mp_raise_ValueError(translate("Buffer elements must be 4 bytes long or less"));
}
bool ok = common_hal_rp2pio_statemachine_write(self, ((uint8_t*)bufinfo.buf) + start, length, stride_in_bytes);
if (mp_hal_is_interrupted()) {
return mp_const_none;
}
@ -296,110 +374,132 @@ STATIC mp_obj_t rp2pio_statemachine_write(size_t n_args, const mp_obj_t *pos_arg
MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_write_obj, 2, rp2pio_statemachine_write);
// // | def readinto(self, buffer: WriteableBuffer, *, start: int = 0, end: Optional[int] = None) -> None:
// // | """Read into ``buffer``. If the number of bytes to read is 0, nothing happens.
// // |
// // | :param ~_typing.WriteableBuffer buffer: Read data into this buffer
// // | :param int start: Start of the slice of ``buffer`` to read into: ``buffer[start:end]``
// // | :param int end: End of the slice; this index is not included. Defaults to ``len(buffer)``
// // | :param int write_value: Value to write while reading. (Usually ignored.)"""
// // | ...
// // |
//| def readinto(self, buffer: WriteableBuffer, *, start: int = 0, end: Optional[int] = None) -> None:
//| """Read into ``buffer``. If the number of bytes to read is 0, nothing happens. The buffer
//| include any data added to the fifo even if it was added before this was called.
//|
//| :param ~_typing.WriteableBuffer buffer: Read data into this buffer
//| :param int start: Start of the slice of ``buffer`` to read into: ``buffer[start:end]``
//| :param int end: End of the slice; this index is not included. Defaults to ``len(buffer)``"""
//| ...
//|
// STATIC mp_obj_t rp2pio_statemachine_readinto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
// enum { ARG_buffer, ARG_start, ARG_end, ARG_write_value };
// static const mp_arg_t allowed_args[] = {
// { MP_QSTR_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
// { MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
// { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} },
// { MP_QSTR_write_value,MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
// };
// rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
// check_for_deinit(self);
// check_lock(self);
// mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
// mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
STATIC mp_obj_t rp2pio_statemachine_readinto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_buffer, ARG_start, ARG_end };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} },
};
rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
check_for_deinit(self);
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
// mp_buffer_info_t bufinfo;
// mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_WRITE);
// int32_t start = args[ARG_start].u_int;
// size_t length = bufinfo.len;
// normalize_buffer_bounds(&start, args[ARG_end].u_int, &length);
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_WRITE);
int32_t start = args[ARG_start].u_int;
size_t length = bufinfo.len;
normalize_buffer_bounds(&start, args[ARG_end].u_int, &length);
// if (length == 0) {
// return mp_const_none;
// }
if (length == 0) {
return mp_const_none;
}
// bool ok = common_hal_rp2pio_statemachine_read(self, ((uint8_t*)bufinfo.buf) + start, length, args[ARG_write_value].u_int);
// if (!ok) {
// mp_raise_OSError(MP_EIO);
// }
// return mp_const_none;
// }
// MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_readinto_obj, 2, rp2pio_statemachine_readinto);
uint8_t* original_pointer = bufinfo.buf;
int stride_in_bytes = mp_binary_get_size('@', bufinfo.typecode, NULL);
if (stride_in_bytes > 4) {
mp_raise_ValueError(translate("Buffer elements must be 4 bytes long or less"));
}
// //| def write_readinto(self, buffer_out: ReadableBuffer, buffer_in: WriteableBuffer, *, out_start: int = 0, out_end: Optional[int] = None, in_start: int = 0, in_end: Optional[int] = None) -> None:
// //| """Write out the data in ``buffer_out`` while simultaneously reading data into ``buffer_in``.
// //| The SPI object must be locked.
// //| The lengths of the slices defined by ``buffer_out[out_start:out_end]`` and ``buffer_in[in_start:in_end]``
// //| must be equal.
// //| If buffer slice lengths are both 0, nothing happens.
// //|
// //| :param ~_typing.ReadableBuffer buffer_out: Write out the data in this buffer
// //| :param ~_typing.WriteableBuffer buffer_in: Read data into this buffer
// //| :param int out_start: Start of the slice of buffer_out to write out: ``buffer_out[out_start:out_end]``
// //| :param int out_end: End of the slice; this index is not included. Defaults to ``len(buffer_out)``
// //| :param int in_start: Start of the slice of ``buffer_in`` to read into: ``buffer_in[in_start:in_end]``
// //| :param int in_end: End of the slice; this index is not included. Defaults to ``len(buffer_in)``"""
// //| ...
// //|
bool ok = common_hal_rp2pio_statemachine_readinto(self, ((uint8_t*)bufinfo.buf) + start, length, stride_in_bytes);
if (!ok) {
mp_raise_OSError(MP_EIO);
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_readinto_obj, 2, rp2pio_statemachine_readinto);
// STATIC mp_obj_t rp2pio_statemachine_write_readinto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
// enum { ARG_buffer_out, ARG_buffer_in, ARG_out_start, ARG_out_end, ARG_in_start, ARG_in_end };
// static const mp_arg_t allowed_args[] = {
// { MP_QSTR_buffer_out, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
// { MP_QSTR_buffer_in, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
// { MP_QSTR_out_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
// { MP_QSTR_out_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} },
// { MP_QSTR_in_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
// { MP_QSTR_in_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} },
// };
// rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
// check_for_deinit(self);
// check_lock(self);
// mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
// mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
//| def write_readinto(self, buffer_out: ReadableBuffer, buffer_in: WriteableBuffer, *, out_start: int = 0, out_end: Optional[int] = None, in_start: int = 0, in_end: Optional[int] = None) -> None:
//| """Write out the data in ``buffer_out`` while simultaneously reading data into ``buffer_in``.
//| The lengths of the slices defined by ``buffer_out[out_start:out_end]`` and ``buffer_in[in_start:in_end]``
//| may be different. The function will return once both are filled.
//| If buffer slice lengths are both 0, nothing happens.
//|
//| :param ~_typing.ReadableBuffer buffer_out: Write out the data in this buffer
//| :param ~_typing.WriteableBuffer buffer_in: Read data into this buffer
//| :param int out_start: Start of the slice of buffer_out to write out: ``buffer_out[out_start:out_end]``
//| :param int out_end: End of the slice; this index is not included. Defaults to ``len(buffer_out)``
//| :param int in_start: Start of the slice of ``buffer_in`` to read into: ``buffer_in[in_start:in_end]``
//| :param int in_end: End of the slice; this index is not included. Defaults to ``len(buffer_in)``"""
//| ...
//|
// mp_buffer_info_t buf_out_info;
// mp_get_buffer_raise(args[ARG_buffer_out].u_obj, &buf_out_info, MP_BUFFER_READ);
// int32_t out_start = args[ARG_out_start].u_int;
// size_t out_length = buf_out_info.len;
// normalize_buffer_bounds(&out_start, args[ARG_out_end].u_int, &out_length);
STATIC mp_obj_t rp2pio_statemachine_write_readinto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_buffer_out, ARG_buffer_in, ARG_out_start, ARG_out_end, ARG_in_start, ARG_in_end };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_buffer_out, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_buffer_in, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_out_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_out_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} },
{ MP_QSTR_in_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_in_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} },
};
rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
check_for_deinit(self);
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
// mp_buffer_info_t buf_in_info;
// mp_get_buffer_raise(args[ARG_buffer_in].u_obj, &buf_in_info, MP_BUFFER_WRITE);
// int32_t in_start = args[ARG_in_start].u_int;
// size_t in_length = buf_in_info.len;
// normalize_buffer_bounds(&in_start, args[ARG_in_end].u_int, &in_length);
mp_buffer_info_t buf_out_info;
mp_get_buffer_raise(args[ARG_buffer_out].u_obj, &buf_out_info, MP_BUFFER_READ);
int32_t out_start = args[ARG_out_start].u_int;
size_t out_length = buf_out_info.len;
normalize_buffer_bounds(&out_start, args[ARG_out_end].u_int, &out_length);
// if (out_length != in_length) {
// mp_raise_ValueError(translate("buffer slices must be of equal length"));
// }
mp_buffer_info_t buf_in_info;
mp_get_buffer_raise(args[ARG_buffer_in].u_obj, &buf_in_info, MP_BUFFER_WRITE);
int32_t in_start = args[ARG_in_start].u_int;
size_t in_length = buf_in_info.len;
normalize_buffer_bounds(&in_start, args[ARG_in_end].u_int, &in_length);
// if (out_length == 0) {
// return mp_const_none;
// }
if (out_length == 0 && in_length == 0) {
return mp_const_none;
}
// bool ok = common_hal_rp2pio_statemachine_transfer(self,
// ((uint8_t*)buf_out_info.buf) + out_start,
// ((uint8_t*)buf_in_info.buf) + in_start,
// out_length);
// if (!ok) {
// mp_raise_OSError(MP_EIO);
// }
// return mp_const_none;
// }
// MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_write_readinto_obj, 2, rp2pio_statemachine_write_readinto);
int in_stride_in_bytes = mp_binary_get_size('@', buf_in_info.typecode, NULL);
if (in_stride_in_bytes > 4) {
mp_raise_ValueError(translate("In buffer elements must be 4 bytes long or less"));
}
int out_stride_in_bytes = mp_binary_get_size('@', buf_out_info.typecode, NULL);
if (out_stride_in_bytes > 4) {
mp_raise_ValueError(translate("Out buffer elements must be 4 bytes long or less"));
}
bool ok = common_hal_rp2pio_statemachine_write_readinto(self,
((uint8_t*)buf_out_info.buf) + out_start,
out_length,
out_stride_in_bytes,
((uint8_t*)buf_in_info.buf) + in_start,
in_length,
in_stride_in_bytes);
if (!ok) {
mp_raise_OSError(MP_EIO);
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_KW(rp2pio_statemachine_write_readinto_obj, 2, rp2pio_statemachine_write_readinto);
//| def clear_rxfifo(self) -> None:
//| """Clears any unread bytes in the rxfifo."""
//| ...
//|
STATIC mp_obj_t rp2pio_statemachine_obj_clear_rxfifo(mp_obj_t self_in) {
rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_rp2pio_statemachine_clear_rxfifo(self);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(rp2pio_statemachine_clear_rxfifo_obj, rp2pio_statemachine_obj_clear_rxfifo);
//| frequency: int
//| """The actual state machine frequency. This may not match the frequency requested
@ -413,9 +513,37 @@ STATIC mp_obj_t rp2pio_statemachine_obj_get_frequency(mp_obj_t self_in) {
}
MP_DEFINE_CONST_FUN_OBJ_1(rp2pio_statemachine_get_frequency_obj, rp2pio_statemachine_obj_get_frequency);
STATIC mp_obj_t rp2pio_statemachine_obj_set_frequency(mp_obj_t self_in, mp_obj_t frequency) {
rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self);
common_hal_rp2pio_statemachine_set_frequency(self, mp_obj_get_int(frequency));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(rp2pio_statemachine_set_frequency_obj, rp2pio_statemachine_obj_set_frequency);
const mp_obj_property_t rp2pio_statemachine_frequency_obj = {
.base.type = &mp_type_property,
.proxy = {(mp_obj_t)&rp2pio_statemachine_get_frequency_obj,
(mp_obj_t)&rp2pio_statemachine_set_frequency_obj,
(mp_obj_t)&mp_const_none_obj},
};
//| rxstall: bool
//| """True when the state machine has stalled due to a full RX FIFO since the last
//| `clear_rxfifo` call."""
//|
STATIC mp_obj_t rp2pio_statemachine_obj_get_rxstall(mp_obj_t self_in) {
rp2pio_statemachine_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self);
return MP_OBJ_NEW_SMALL_INT(common_hal_rp2pio_statemachine_get_rxstall(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(rp2pio_statemachine_get_rxstall_obj, rp2pio_statemachine_obj_get_rxstall);
const mp_obj_property_t rp2pio_statemachine_rxstall_obj = {
.base.type = &mp_type_property,
.proxy = {(mp_obj_t)&rp2pio_statemachine_get_rxstall_obj,
(mp_obj_t)&mp_const_none_obj,
(mp_obj_t)&mp_const_none_obj},
};
@ -425,12 +553,17 @@ STATIC const mp_rom_map_elem_t rp2pio_statemachine_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&rp2pio_statemachine_obj___exit___obj) },
// { MP_ROM_QSTR(MP_QSTR_restart), MP_ROM_PTR(&rp2pio_statemachine_configure_obj) },
{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&rp2pio_statemachine_stop_obj) },
{ MP_ROM_QSTR(MP_QSTR_restart), MP_ROM_PTR(&rp2pio_statemachine_restart_obj) },
{ MP_ROM_QSTR(MP_QSTR_run), MP_ROM_PTR(&rp2pio_statemachine_run_obj) },
{ MP_ROM_QSTR(MP_QSTR_clear_rxfifo), MP_ROM_PTR(&rp2pio_statemachine_clear_rxfifo_obj) },
// { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&rp2pio_statemachine_readinto_obj) },
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&rp2pio_statemachine_readinto_obj) },
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&rp2pio_statemachine_write_obj) },
// { MP_ROM_QSTR(MP_QSTR_write_readinto), MP_ROM_PTR(&rp2pio_statemachine_write_readinto_obj) },
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&rp2pio_statemachine_frequency_obj) }
{ MP_ROM_QSTR(MP_QSTR_write_readinto), MP_ROM_PTR(&rp2pio_statemachine_write_readinto_obj) },
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&rp2pio_statemachine_frequency_obj) },
{ MP_ROM_QSTR(MP_QSTR_rxstall), MP_ROM_PTR(&rp2pio_statemachine_rxstall_obj) }
};
STATIC MP_DEFINE_CONST_DICT(rp2pio_statemachine_locals_dict, rp2pio_statemachine_locals_dict_table);

View File

@ -36,36 +36,38 @@
extern const mp_obj_type_t rp2pio_statemachine_type;
// Construct an underlying SPI object.
extern void common_hal_rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self,
void common_hal_rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self,
const uint16_t* program, size_t program_len,
size_t frequency,
const uint16_t* init, size_t init_len,
const mcu_pin_obj_t * first_out_pin, uint8_t out_pin_count,
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,
const mcu_pin_obj_t * first_in_pin, uint8_t in_pin_count,
const mcu_pin_obj_t * first_set_pin, uint8_t set_pin_count,
const mcu_pin_obj_t * first_sideset_pin, uint8_t sideset_pin_count,
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,
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,
bool exclusive_pin_use,
bool auto_pull, uint8_t pull_threshold, bool out_shift_right,
bool wait_for_txstall,
bool auto_push, uint8_t push_threshold, bool in_shift_right);
extern void common_hal_rp2pio_statemachine_deinit(rp2pio_statemachine_obj_t *self);
extern bool common_hal_rp2pio_statemachine_deinited(rp2pio_statemachine_obj_t *self);
void common_hal_rp2pio_statemachine_deinit(rp2pio_statemachine_obj_t *self);
bool common_hal_rp2pio_statemachine_deinited(rp2pio_statemachine_obj_t *self);
void common_hal_rp2pio_statemachine_restart(rp2pio_statemachine_obj_t *self);
void common_hal_rp2pio_statemachine_stop(rp2pio_statemachine_obj_t *self);
void common_hal_rp2pio_statemachine_run(rp2pio_statemachine_obj_t *self, const uint16_t *instructions, size_t len);
// Writes out the given data.
extern bool common_hal_rp2pio_statemachine_write(rp2pio_statemachine_obj_t *self, const uint8_t *data, size_t len);
bool common_hal_rp2pio_statemachine_write(rp2pio_statemachine_obj_t *self, const uint8_t *data, size_t len, uint8_t stride_in_bytes);
bool common_hal_rp2pio_statemachine_readinto(rp2pio_statemachine_obj_t *self, uint8_t *data, size_t len, uint8_t stride_in_bytes);
bool common_hal_rp2pio_statemachine_write_readinto(rp2pio_statemachine_obj_t *self,
const uint8_t *data_out, size_t out_len, uint8_t out_stride_in_bytes,
uint8_t *data_in, size_t in_len, uint8_t in_stride_in_bytes);
// // Reads in len bytes while outputting zeroes.
// extern bool common_hal_rp2pio_statemachine_read(rp2pio_statemachine_obj_t *self, uint8_t *data, size_t len, uint8_t write_value);
// // Reads and write len bytes simultaneously.
// extern bool common_hal_rp2pio_statemachine_transfer(rp2pio_statemachine_obj_t *self,
// const uint8_t *data_out, size_t out_len,
// uint8_t *data_in, size_t in_len);
// Return actual SPI bus frequency.
// Return actual state machine frequency.
uint32_t common_hal_rp2pio_statemachine_get_frequency(rp2pio_statemachine_obj_t* self);
void common_hal_rp2pio_statemachine_set_frequency(rp2pio_statemachine_obj_t* self, uint32_t frequency);
// This is used by the supervisor to claim SPI devices indefinitely.
// extern void common_hal_rp2pio_statemachine_never_reset(rp2pio_statemachine_obj_t *self);
bool common_hal_rp2pio_statemachine_get_rxstall(rp2pio_statemachine_obj_t* self);
void common_hal_rp2pio_statemachine_clear_rxfifo(rp2pio_statemachine_obj_t *self);
#endif // MICROPY_INCLUDED_RASPBERRYPI_BINDINGS_RP2PIO_STATEMACHINE_H

View File

@ -0,0 +1,224 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Scott Shawcroft for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdint.h>
#include <string.h>
#include "mpconfigport.h"
#include "py/gc.h"
#include "py/mperrno.h"
#include "py/runtime.h"
#include "common-hal/audiobusio/I2SOut.h"
#include "shared-bindings/audiobusio/I2SOut.h"
#include "shared-bindings/microcontroller/Pin.h"
#include "shared-module/audiocore/__init__.h"
#include "bindings/rp2pio/StateMachine.h"
#include "supervisor/shared/translate.h"
const uint16_t i2s_program[] = {
// ; Load the next set of samples
// ; /--- LRCLK
// ; |/-- BCLK
// ; ||
// pull noblock side 0b01 ; Loads OSR with the next FIFO value or X
0x8880,
// mov x osr side 0b01 ; Save the new value in case we need it again
0xa827,
// set y 14 side 0b01
0xe84e,
// bitloop1:
// out pins 1 side 0b00 [2]
0x6201,
// jmp y-- bitloop1 side 0b01 [2]
0x0a83,
// out pins 1 side 0b10 [2]
0x7201,
// set y 14 side 0b11 [2]
0xfa4e,
// bitloop0:
// out pins 1 side 0b10 [2]
0x7201,
// jmp y-- bitloop0 side 0b11 [2]
0x1a87,
// out pins 1 side 0b00 [2]
0x6201
};
const uint16_t i2s_program_left_justified[] = {
// ; Load the next set of samples
// ; /--- LRCLK
// ; |/-- BCLK
// ; ||
// pull noblock side 0b11 ; Loads OSR with the next FIFO value or X
0x9880,
// mov x osr side 0b11 ; Save the new value in case we need it again
0xb827,
// set y 14 side 0b11
0xf84e,
// bitloop1:
// out pins 1 side 0b00 [2]
0x6201,
// jmp y-- bitloop1 side 0b01 [2]
0x0a83,
// out pins 1 side 0b10 [2]
0x6201,
// set y 14 side 0b01 [2]
0xea4e,
// bitloop0:
// out pins 1 side 0b10 [2]
0x7201,
// jmp y-- bitloop0 side 0b11 [2]
0x1a87,
// out pins 1 side 0b10 [2]
0x7201
};
void i2sout_reset(void) {
}
// Caller validates that pins are free.
void common_hal_audiobusio_i2sout_construct(audiobusio_i2sout_obj_t* self,
const mcu_pin_obj_t* bit_clock, const mcu_pin_obj_t* word_select,
const mcu_pin_obj_t* data, bool left_justified) {
if (bit_clock->number != word_select->number - 1) {
mp_raise_ValueError(translate("Bit clock and word select must be sequential pins"));
}
const uint16_t* program = i2s_program;
size_t program_len = sizeof(i2s_program) / sizeof(i2s_program[0]);
if (left_justified) {
program = i2s_program_left_justified;
program_len = sizeof(i2s_program_left_justified) / sizeof(i2s_program_left_justified[0]);;
}
// Use the state machine to manage pins.
common_hal_rp2pio_statemachine_construct(&self->state_machine,
program, program_len,
44100 * 32 * 6, // Clock at 44.1 khz to warm the DAC up.
NULL, 0,
data, 1, 0, 0xffffffff, // out pin
NULL, 0, // in pins
NULL, 0, 0, 0x1f, // set pins
bit_clock, 2, 0, 0x1f, // sideset pins
true, // exclusive pin use
false, 32, false, // shift out left to start with MSB
false, // Wait for txstall
false, 32, false); // in settings
self->playing = false;
audio_dma_init(&self->dma);
}
bool common_hal_audiobusio_i2sout_deinited(audiobusio_i2sout_obj_t* self) {
return common_hal_rp2pio_statemachine_deinited(&self->state_machine);
}
void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t* self) {
if (common_hal_audiobusio_i2sout_deinited(self)) {
return;
}
common_hal_rp2pio_statemachine_deinit(&self->state_machine);
}
void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self,
mp_obj_t sample, bool loop) {
if (common_hal_audiobusio_i2sout_get_playing(self)) {
common_hal_audiobusio_i2sout_stop(self);
}
uint8_t bits_per_sample = audiosample_bits_per_sample(sample);
// Make sure we transmit a minimum of 16 bits.
// TODO: Maybe we need an intermediate object to upsample instead. This is
// only needed for some I2S devices that expect at least 8.
if (bits_per_sample < 16) {
bits_per_sample = 16;
}
// We always output stereo so output twice as many bits.
uint16_t bits_per_sample_output = bits_per_sample * 2;
size_t clocks_per_bit = 6;
uint32_t frequency = bits_per_sample_output * audiosample_sample_rate(sample);
common_hal_rp2pio_statemachine_set_frequency(&self->state_machine, clocks_per_bit * frequency);
common_hal_rp2pio_statemachine_restart(&self->state_machine);
uint8_t channel_count = audiosample_channel_count(sample);
if (channel_count > 2) {
mp_raise_ValueError(translate("Too many channels in sample."));
}
audio_dma_result result = audio_dma_setup_playback(
&self->dma,
sample,
loop,
false, // single channel
0, // audio channel
true, // output signed
bits_per_sample,
(uint32_t) &self->state_machine.pio->txf[self->state_machine.state_machine], // output register
self->state_machine.tx_dreq); // data request line
if (result == AUDIO_DMA_DMA_BUSY) {
common_hal_audiobusio_i2sout_stop(self);
mp_raise_RuntimeError(translate("No DMA channel found"));
} else if (result == AUDIO_DMA_MEMORY_ERROR) {
common_hal_audiobusio_i2sout_stop(self);
mp_raise_RuntimeError(translate("Unable to allocate buffers for signed conversion"));
}
// Turn on the state machine's clock.
self->playing = true;
}
void common_hal_audiobusio_i2sout_pause(audiobusio_i2sout_obj_t* self) {
audio_dma_pause(&self->dma);
}
void common_hal_audiobusio_i2sout_resume(audiobusio_i2sout_obj_t* self) {
// Maybe: Clear any overrun/underrun errors
audio_dma_resume(&self->dma);
}
bool common_hal_audiobusio_i2sout_get_paused(audiobusio_i2sout_obj_t* self) {
return audio_dma_get_paused(&self->dma);
}
void common_hal_audiobusio_i2sout_stop(audiobusio_i2sout_obj_t* self) {
audio_dma_stop(&self->dma);
common_hal_rp2pio_statemachine_stop(&self->state_machine);
self->playing = false;
}
bool common_hal_audiobusio_i2sout_get_playing(audiobusio_i2sout_obj_t* self) {
bool playing = audio_dma_get_playing(&self->dma);
if (!playing && self->playing) {
common_hal_audiobusio_i2sout_stop(self);
}
return playing;
}

View File

@ -0,0 +1,47 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Scott Shawcroft for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_I2SOUT_H
#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_I2SOUT_H
#include "common-hal/microcontroller/Pin.h"
#include "common-hal/rp2pio/StateMachine.h"
#include "audio_dma.h"
#include "py/obj.h"
// We don't bit pack because we'll only have two at most. Its better to save code size instead.
typedef struct {
mp_obj_base_t base;
bool left_justified;
rp2pio_statemachine_obj_t state_machine;
bool playing;
audio_dma_t dma;
} audiobusio_i2sout_obj_t;
void i2sout_reset(void);
#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_I2SOUT_H

View File

@ -0,0 +1,172 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Scott Shawcroft for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdint.h>
#include <string.h>
#include <math.h>
#include "py/mperrno.h"
#include "py/runtime.h"
#include "shared-bindings/audiobusio/PDMIn.h"
#include "shared-bindings/microcontroller/Pin.h"
#include "supervisor/shared/translate.h"
#include "audio_dma.h"
#define OVERSAMPLING 64
#define SAMPLES_PER_BUFFER 32
// MEMS microphones must be clocked at at least 1MHz.
#define MIN_MIC_CLOCK 1000000
const uint16_t pdmin[] = {
// in pins 1 side 0b1
0x5001,
// push iffull side 0b0
0x8040
};
// Caller validates that pins are free.
void common_hal_audiobusio_pdmin_construct(audiobusio_pdmin_obj_t* self,
const mcu_pin_obj_t* clock_pin,
const mcu_pin_obj_t* data_pin,
uint32_t sample_rate,
uint8_t bit_depth,
bool mono,
uint8_t oversample) {
if (!(bit_depth == 16 || bit_depth == 8) || !mono || oversample != OVERSAMPLING) {
mp_raise_NotImplementedError(translate("Only 8 or 16 bit mono with " MP_STRINGIFY(OVERSAMPLING) "x oversampling is supported."));
}
// Use the state machine to manage pins.
common_hal_rp2pio_statemachine_construct(&self->state_machine,
pdmin, sizeof(pdmin) / sizeof(pdmin[0]),
44100 * 32 * 2, // Clock at 44.1 khz to warm the DAC up.
NULL, 0,
NULL, 1, 0, 0xffffffff, // out pin
data_pin, 1, // in pins
NULL, 0, 0, 0x1f, // set pins
clock_pin, 1, 0, 0x1f, // sideset pins
true, // exclusive pin use
false, 32, false, // out settings
false, // Wait for txstall
false, 32, true); // in settings
uint32_t actual_frequency = common_hal_rp2pio_statemachine_get_frequency(&self->state_machine);
if (actual_frequency < MIN_MIC_CLOCK) {
mp_raise_ValueError(translate("sampling rate out of range"));
}
self->sample_rate = actual_frequency / oversample;
self->bit_depth = bit_depth;
}
bool common_hal_audiobusio_pdmin_deinited(audiobusio_pdmin_obj_t* self) {
return common_hal_rp2pio_statemachine_deinited(&self->state_machine);
}
void common_hal_audiobusio_pdmin_deinit(audiobusio_pdmin_obj_t* self) {
if (common_hal_audiobusio_pdmin_deinited(self)) {
return;
}
}
uint8_t common_hal_audiobusio_pdmin_get_bit_depth(audiobusio_pdmin_obj_t* self) {
return self->bit_depth;
}
uint32_t common_hal_audiobusio_pdmin_get_sample_rate(audiobusio_pdmin_obj_t* self) {
return self->sample_rate;
}
// a windowed sinc filter for 44 khz, 64 samples
//
// This filter is good enough to use for lower sample rates as
// well. It does not increase the noise enough to be a problem.
//
// In the long run we could use a fast filter like this to do the
// decimation and initial filtering in real time, filtering to a
// higher sample rate than specified. Then after the audio is
// recorded, a more expensive filter non-real-time filter could be
// used to down-sample and low-pass.
const uint16_t sinc_filter [OVERSAMPLING] = {
0, 2, 9, 21, 39, 63, 94, 132,
179, 236, 302, 379, 467, 565, 674, 792,
920, 1055, 1196, 1341, 1487, 1633, 1776, 1913,
2042, 2159, 2263, 2352, 2422, 2474, 2506, 2516,
2506, 2474, 2422, 2352, 2263, 2159, 2042, 1913,
1776, 1633, 1487, 1341, 1196, 1055, 920, 792,
674, 565, 467, 379, 302, 236, 179, 132,
94, 63, 39, 21, 9, 2, 0, 0
};
#define REPEAT_32_TIMES(X) do { X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X } while(0)
static uint16_t filter_sample(uint32_t pdm_samples[2]) {
uint16_t running_sum = 0;
const uint16_t *filter_ptr = sinc_filter;
for (uint8_t i = 0; i < 2; i++) {
uint32_t pdm_sample = pdm_samples[i];
REPEAT_32_TIMES( {
if (pdm_sample & 0x1) {
running_sum += *filter_ptr;
}
filter_ptr++;
pdm_sample >>= 1;
}
);
}
return running_sum;
}
// output_buffer may be a byte buffer or a halfword buffer.
// output_buffer_length is the number of slots, not the number of bytes.
uint32_t common_hal_audiobusio_pdmin_record_to_buffer(audiobusio_pdmin_obj_t* self,
uint16_t* output_buffer, uint32_t output_buffer_length) {
uint32_t samples[2];
size_t output_count = 0;
common_hal_rp2pio_statemachine_clear_rxfifo(&self->state_machine);
// Do one read to get the mic going and throw it away.
common_hal_rp2pio_statemachine_readinto(&self->state_machine, (uint8_t*) samples, 2 * sizeof(uint32_t), sizeof(uint32_t));
while (output_count < output_buffer_length && !common_hal_rp2pio_statemachine_get_rxstall(&self->state_machine)) {
common_hal_rp2pio_statemachine_readinto(&self->state_machine, (uint8_t*) samples, 2 * sizeof(uint32_t), sizeof(uint32_t));
// Call filter_sample just one place so it can be inlined.
uint16_t value = filter_sample(samples);
if (self->bit_depth == 8) {
// Truncate to 8 bits.
((uint8_t*) output_buffer)[output_count] = value >> 8;
} else {
output_buffer[output_count] = value;
}
output_count++;
}
return output_count;
}
void common_hal_audiobusio_pdmin_record_to_file(audiobusio_pdmin_obj_t* self, uint8_t* buffer, uint32_t length) {
}

View File

@ -0,0 +1,50 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Scott Shawcroft for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_AUDIOOUT_H
#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_AUDIOOUT_H
#include "common-hal/microcontroller/Pin.h"
#include "bindings/rp2pio/StateMachine.h"
#include "extmod/vfs_fat.h"
#include "py/obj.h"
typedef struct {
mp_obj_base_t base;
uint32_t sample_rate;
uint8_t serializer;
uint8_t clock_unit;
uint8_t bytes_per_sample;
uint8_t bit_depth;
rp2pio_statemachine_obj_t state_machine;
} audiobusio_pdmin_obj_t;
void pdmin_reset(void);
void pdmin_background(void);
#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_AUDIOOUT_H

View File

@ -0,0 +1 @@
// No audiobusio module functions.

View File

@ -54,27 +54,25 @@ const uint16_t neopixel_program[] = {
0xa142
};
const uint16_t init_program[] = {
0xe081
};
void common_hal_neopixel_write(const digitalio_digitalinout_obj_t* digitalinout, uint8_t *pixels, uint32_t num_bytes) {
// Set everything up.
rp2pio_statemachine_obj_t state_machine;
// TODO: Cache the state machine after we create it once. We'll need a way to
// change the pins then though.
uint8_t pin_number = digitalinout->pin->number;
uint32_t pins_we_use = 1 << digitalinout->pin->number;
bool ok = rp2pio_statemachine_construct(&state_machine,
neopixel_program, sizeof(neopixel_program) / sizeof(neopixel_program[0]),
800000 * 6, // 800 khz * 6 cycles per bit
init_program, 1,
NULL, 1,
NULL, 1,
digitalinout->pin, 1,
digitalinout->pin, 1,
1 << pin_number, true, false,
NULL, 0, // init program
NULL, 1, // out
NULL, 1, // in
NULL, 1, // set
digitalinout->pin, 1, // sideset
0, pins_we_use, // initial pin state
pins_we_use, true, false,
true, 8, false, // TX, auto pull every 8 bits. shift left to output msb first
true, // Wait for txstall. If we don't, then we'll deinit too quickly.
false, 32, true, // RX setting we don't use
false); // claim pins
if (!ok) {
@ -86,7 +84,7 @@ void common_hal_neopixel_write(const digitalio_digitalinout_obj_t* digitalinout,
// two.
while (port_get_raw_ticks(NULL) < next_start_raw_ticks) {}
common_hal_rp2pio_statemachine_write(&state_machine, pixels, num_bytes);
common_hal_rp2pio_statemachine_write(&state_machine, pixels, num_bytes, 1 /* stride in bytes */);
// Use a private deinit of the state machine that doesn't reset the pin.
rp2pio_statemachine_deinit(&state_machine, true);

View File

@ -54,7 +54,6 @@ STATIC PIO pio_instances[2] = {pio0, pio1};
void _reset_statemachine(PIO pio, uint8_t sm, bool leave_pins) {
uint8_t pio_index = pio_get_index(pio);
pio_sm_unclaim(pio, sm);
uint32_t program_id = _current_program_id[pio_index][sm];
if (program_id == 0) {
return;
@ -89,6 +88,7 @@ void _reset_statemachine(PIO pio, uint8_t sm, bool leave_pins) {
}
}
_current_sm_pins[pio_index][sm] = 0;
pio_sm_unclaim(pio, sm);
}
void reset_rp2pio_statemachine(void) {
@ -130,8 +130,10 @@ bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self,
const mcu_pin_obj_t * first_in_pin, uint8_t in_pin_count,
const mcu_pin_obj_t * first_set_pin, uint8_t set_pin_count,
const mcu_pin_obj_t * first_sideset_pin, uint8_t sideset_pin_count,
uint32_t initial_pin_state, uint32_t initial_pin_direction,
uint32_t pins_we_use, bool tx_fifo, bool rx_fifo,
bool auto_pull, uint8_t pull_threshold, bool out_shift_right,
bool wait_for_txstall,
bool auto_push, uint8_t push_threshold, bool in_shift_right,
bool claim_pins) {
// Create a program id that isn't the pointer so we can store it without storing the original object.
@ -199,6 +201,11 @@ bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self,
_current_sm_pins[pio_index][state_machine] = pins_we_use;
_current_pins[pio_index] |= pins_we_use;
pio_sm_set_pins_with_mask(self->pio, state_machine, initial_pin_state, pins_we_use);
pio_sm_set_pindirs_with_mask(self->pio, state_machine, initial_pin_direction, pins_we_use);
self->initial_pin_state = initial_pin_state;
self->initial_pin_direction = initial_pin_direction;
for (size_t pin_number = 0; pin_number < TOTAL_GPIO_COUNT; pin_number++) {
if ((pins_we_use & (1 << pin_number)) == 0) {
continue;
@ -260,27 +267,38 @@ bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self,
self->out = tx_fifo;
self->out_shift_right = out_shift_right;
self->in_shift_right = in_shift_right;
self->wait_for_txstall = wait_for_txstall;
self->init = init;
self->init_len = init_len;
sm_config_set_fifo_join(&c, join);
pio_sm_init(self->pio, self->state_machine, program_offset, &c);
common_hal_rp2pio_statemachine_run(self, init, init_len);
common_hal_rp2pio_statemachine_set_frequency(self, frequency);
pio_sm_set_enabled(self->pio, self->state_machine, true);
for (size_t i = 0; i < init_len; i++) {
pio_sm_exec(self->pio, self->state_machine, init[i]);
}
return true;
}
static uint32_t mask_and_rotate(const mcu_pin_obj_t* first_pin, uint32_t bit_count, uint32_t value) {
value = value & ((1 << bit_count) - 1);
uint32_t shift = first_pin->number;
return value << shift | value >> (32 - shift);
}
void common_hal_rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self,
const uint16_t* program, size_t program_len,
size_t frequency,
const uint16_t* init, size_t init_len,
const mcu_pin_obj_t * first_out_pin, uint8_t out_pin_count,
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,
const mcu_pin_obj_t * first_in_pin, uint8_t in_pin_count,
const mcu_pin_obj_t * first_set_pin, uint8_t set_pin_count,
const mcu_pin_obj_t * first_sideset_pin, uint8_t sideset_pin_count,
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,
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,
bool exclusive_pin_use,
bool auto_pull, uint8_t pull_threshold, bool out_shift_right,
bool wait_for_txstall,
bool auto_push, uint8_t push_threshold, bool in_shift_right) {
// First, check that all pins are free OR already in use by any PIO if exclusive_pin_use is false.
@ -397,9 +415,26 @@ void common_hal_rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self,
mp_raise_ValueError_varg(translate("Program does OUT without loading OSR"));
}
if (in_pin_count > 8 || out_pin_count > 8) {
mp_raise_NotImplementedError(translate("Only IN/OUT of up to 8 supported"));
uint32_t initial_pin_state = mask_and_rotate(first_out_pin, out_pin_count, initial_out_pin_state);
uint32_t initial_pin_direction = mask_and_rotate(first_out_pin, out_pin_count, initial_out_pin_direction);
initial_set_pin_state = mask_and_rotate(first_set_pin, set_pin_count, initial_set_pin_state);
initial_set_pin_direction = mask_and_rotate(first_set_pin, set_pin_count, initial_set_pin_direction);
uint32_t set_out_overlap = mask_and_rotate(first_out_pin, out_pin_count, 0xffffffff) &
mask_and_rotate(first_set_pin, set_pin_count, 0xffffffff);
// Check that OUT and SET settings agree because we don't have a way of picking one over the other.
if ((initial_pin_state & set_out_overlap) != (initial_set_pin_state & set_out_overlap)) {
mp_raise_ValueError(translate("Initial set pin state conflicts with initial out pin state"));
}
if ((initial_pin_direction & set_out_overlap) != (initial_set_pin_direction & set_out_overlap)) {
mp_raise_ValueError(translate("Initial set pin direcion conflicts with initial out pin direction"));
}
initial_pin_state |= initial_set_pin_state;
initial_pin_direction |= initial_set_pin_direction;
// Sideset overrides OUT or SET so we always use its values.
uint32_t sideset_mask = mask_and_rotate(first_sideset_pin, sideset_pin_count, 0x1f);
initial_pin_state = (initial_pin_state & ~sideset_mask) | mask_and_rotate(first_sideset_pin, sideset_pin_count, initial_sideset_pin_state);
initial_pin_direction = (initial_pin_direction & ~sideset_mask) | mask_and_rotate(first_sideset_pin, sideset_pin_count, initial_sideset_pin_direction);
bool ok = rp2pio_statemachine_construct(self,
program, program_len,
@ -409,8 +444,10 @@ void common_hal_rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self,
first_in_pin, in_pin_count,
first_set_pin, set_pin_count,
first_sideset_pin, sideset_pin_count,
initial_pin_state, initial_pin_direction,
pins_we_use, tx_fifo, rx_fifo,
auto_pull, pull_threshold, out_shift_right,
wait_for_txstall,
auto_push, push_threshold, in_shift_right,
true /* claim pins */);
if (!ok) {
@ -418,10 +455,47 @@ void common_hal_rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self,
}
}
void common_hal_rp2pio_statemachine_restart(rp2pio_statemachine_obj_t *self) {
pio_sm_restart(self->pio, self->state_machine);
uint8_t pio_index = pio_get_index(self->pio);
uint32_t pins_we_use = _current_sm_pins[pio_index][self->state_machine];
pio_sm_set_pins_with_mask(self->pio, self->state_machine, self->initial_pin_state, pins_we_use);
pio_sm_set_pindirs_with_mask(self->pio, self->state_machine, self->initial_pin_direction, pins_we_use);
common_hal_rp2pio_statemachine_run(self, self->init, self->init_len);
pio_sm_set_enabled(self->pio, self->state_machine, true);
}
void common_hal_rp2pio_statemachine_stop(rp2pio_statemachine_obj_t *self) {
pio_sm_set_enabled(self->pio, self->state_machine, false);
}
void common_hal_rp2pio_statemachine_run(rp2pio_statemachine_obj_t *self, const uint16_t *instructions, size_t len) {
for (size_t i = 0; i < len; i++) {
pio_sm_exec(self->pio, self->state_machine, instructions[i]);
}
}
uint32_t common_hal_rp2pio_statemachine_get_frequency(rp2pio_statemachine_obj_t* self) {
return self->actual_frequency;
}
void common_hal_rp2pio_statemachine_set_frequency(rp2pio_statemachine_obj_t* self, uint32_t frequency) {
if (frequency == 0) {
frequency = clock_get_hz(clk_sys);
}
uint64_t frequency256 = ((uint64_t) clock_get_hz(clk_sys)) * 256;
uint64_t div256 = frequency256 / frequency;
if (frequency256 % div256 > 0) {
div256 += 1;
}
self->actual_frequency = frequency256 / div256;
pio_sm_set_clkdiv_int_frac(self->pio, self->state_machine, div256 / 256, div256 % 256);
// Reset the clkdiv counter in case our new TOP is lower.
pio_sm_clkdiv_restart(self->pio, self->state_machine);
}
void rp2pio_statemachine_deinit(rp2pio_statemachine_obj_t *self, bool leave_pins) {
uint8_t sm = self->state_machine;
uint8_t pio_index = pio_get_index(self->pio);
@ -445,9 +519,21 @@ bool common_hal_rp2pio_statemachine_deinited(rp2pio_statemachine_obj_t *self) {
return self->state_machine == NUM_PIO_STATE_MACHINES;
}
enum dma_channel_transfer_size _stride_to_dma_size(uint8_t stride) {
switch (stride) {
case 4:
return DMA_SIZE_32;
case 2:
return DMA_SIZE_16;
case 1:
default:
return DMA_SIZE_8;
}
}
static bool _transfer(rp2pio_statemachine_obj_t *self,
const uint8_t *data_out, size_t out_len,
uint8_t *data_in, size_t in_len) {
const uint8_t *data_out, size_t out_len, uint8_t out_stride_in_bytes,
uint8_t *data_in, size_t in_len, uint8_t in_stride_in_bytes) {
// This implementation is based on SPI but varies because the tx and rx buffers
// may be different lengths and occur at different times or speeds.
@ -472,42 +558,43 @@ static bool _transfer(rp2pio_statemachine_obj_t *self,
if (tx) {
tx_destination = (volatile uint8_t*) &self->pio->txf[self->state_machine];
if (!self->out_shift_right) {
tx_destination += 3;
tx_destination += 4 - out_stride_in_bytes;
}
}
if (rx) {
rx_source = (const volatile uint8_t*) &self->pio->rxf[self->state_machine];
if (!self->in_shift_right) {
rx_source += 3;
if (self->in_shift_right) {
rx_source += 4 - in_stride_in_bytes;
}
}
uint32_t stall_mask = 1 << (PIO_FDEBUG_TXSTALL_LSB + self->state_machine);
bool use_dma = (!rx || chan_rx >= 0) && (!tx || chan_tx >= 0);
if (use_dma) {
dma_channel_config c;
uint32_t channel_mask = 0;
if (tx) {
c = dma_channel_get_default_config(chan_tx);
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
channel_config_set_transfer_data_size(&c, _stride_to_dma_size(out_stride_in_bytes));
channel_config_set_dreq(&c, self->tx_dreq);
channel_config_set_read_increment(&c, true);
channel_config_set_write_increment(&c, false);
dma_channel_configure(chan_tx, &c,
tx_destination,
data_out,
len,
out_len / out_stride_in_bytes,
false);
channel_mask |= 1u << chan_tx;
}
if (rx) {
c = dma_channel_get_default_config(chan_rx);
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
channel_config_set_transfer_data_size(&c, _stride_to_dma_size(in_stride_in_bytes));
channel_config_set_dreq(&c, self->rx_dreq);
channel_config_set_read_increment(&c, false);
channel_config_set_write_increment(&c, true);
dma_channel_configure(chan_rx, &c,
data_in,
rx_source,
len,
in_len / in_stride_in_bytes,
false);
channel_mask |= 1u << chan_rx;
}
@ -528,7 +615,7 @@ static bool _transfer(rp2pio_statemachine_obj_t *self,
}
}
// Clear the stall bit so we can detect when the state machine is done transmitting.
self->pio->fdebug = PIO_FDEBUG_TXSTALL_BITS;
self->pio->fdebug = stall_mask;
}
// If we have claimed only one channel successfully, we should release immediately. This also
@ -542,27 +629,31 @@ static bool _transfer(rp2pio_statemachine_obj_t *self,
if (!use_dma && !mp_hal_is_interrupted()) {
// Use software for small transfers, or if couldn't claim two DMA channels
size_t rx_remaining = in_len;
size_t tx_remaining = out_len;
size_t rx_remaining = in_len / in_stride_in_bytes;
size_t tx_remaining = out_len / out_stride_in_bytes;
while (rx_remaining || tx_remaining) {
for (int i=0; i<32; i++) {
bool did_transfer = false;
if (tx_remaining && !pio_sm_is_tx_fifo_full(self->pio, self->state_machine)) {
while (tx_remaining && !pio_sm_is_tx_fifo_full(self->pio, self->state_machine)) {
if (out_stride_in_bytes == 1) {
*tx_destination = *data_out;
data_out++;
--tx_remaining;
did_transfer = true;
} else if (in_stride_in_bytes == 2) {
*((uint16_t*) tx_destination) = *((uint16_t*) data_out);
} else if (in_stride_in_bytes == 4) {
*((uint32_t*) tx_destination) = *((uint32_t*) data_out);
}
if (rx_remaining && !pio_sm_is_rx_fifo_empty(self->pio, self->state_machine)) {
data_out += out_stride_in_bytes;
--tx_remaining;
}
while (rx_remaining && !pio_sm_is_rx_fifo_empty(self->pio, self->state_machine)) {
if (in_stride_in_bytes == 1) {
*data_in = (uint8_t) *rx_source;
data_in++;
--rx_remaining;
did_transfer = true;
}
if (!did_transfer) {
break;
} else if (in_stride_in_bytes == 2) {
*((uint16_t*) data_in) = *((uint16_t*) rx_source);
} else if (in_stride_in_bytes == 4) {
*((uint32_t*) data_in) = *((uint32_t*) rx_source);
}
data_in += in_stride_in_bytes;
--rx_remaining;
}
RUN_BACKGROUND_TASKS;
if (mp_hal_is_interrupted()) {
@ -570,24 +661,55 @@ static bool _transfer(rp2pio_statemachine_obj_t *self,
}
}
// Clear the stall bit so we can detect when the state machine is done transmitting.
self->pio->fdebug = PIO_FDEBUG_TXSTALL_BITS;
self->pio->fdebug = stall_mask;
}
// Wait for the state machine to finish transmitting the data we've queued
// up.
if (tx) {
while (!pio_sm_is_tx_fifo_empty(self->pio, self->state_machine) ||
(self->pio->fdebug & PIO_FDEBUG_TXSTALL_BITS) == 0) {
(self->wait_for_txstall && (self->pio->fdebug & stall_mask) == 0)) {
RUN_BACKGROUND_TASKS;
}
}
return true;
}
// Writes out the given data.
bool common_hal_rp2pio_statemachine_write(rp2pio_statemachine_obj_t *self,
const uint8_t *data, size_t len) {
// TODO: Provide a way around these checks in case someone wants to use the FIFO
// with manually run code.
bool common_hal_rp2pio_statemachine_write(rp2pio_statemachine_obj_t *self, const uint8_t *data, size_t len, uint8_t stride_in_bytes) {
if (!self->out) {
mp_raise_RuntimeError(translate("No out in program"));
}
return _transfer(self, data, len, NULL, 0);
return _transfer(self, data, len, stride_in_bytes, NULL, 0, 0);
}
bool common_hal_rp2pio_statemachine_readinto(rp2pio_statemachine_obj_t *self, uint8_t *data, size_t len, uint8_t stride_in_bytes) {
if (!self->in) {
mp_raise_RuntimeError(translate("No in in program"));
}
return _transfer(self, NULL, 0, 0, data, len, stride_in_bytes);
}
bool common_hal_rp2pio_statemachine_write_readinto(rp2pio_statemachine_obj_t *self,
const uint8_t *data_out, size_t out_len, uint8_t out_stride_in_bytes,
uint8_t *data_in, size_t in_len, uint8_t in_stride_in_bytes) {
if (!self->in || !self->out) {
mp_raise_RuntimeError(translate("No in or out in program"));
}
return _transfer(self, data_out, out_len, out_stride_in_bytes, data_in, in_len, in_stride_in_bytes);
}
bool common_hal_rp2pio_statemachine_get_rxstall(rp2pio_statemachine_obj_t* self) {
uint32_t stall_mask = 1 << (PIO_FDEBUG_RXSTALL_LSB + self->state_machine);
return (self->pio->fdebug & stall_mask) != 0;
}
void common_hal_rp2pio_statemachine_clear_rxfifo(rp2pio_statemachine_obj_t *self) {
uint8_t level = pio_sm_get_rx_fifo_level(self->pio, self->state_machine);
uint32_t stall_mask = 1 << (PIO_FDEBUG_RXSTALL_LSB + self->state_machine);
for (size_t i = 0; i < level; i++) {
(void) self->pio->rxf[self->state_machine];
}
self->pio->fdebug = stall_mask;
}

View File

@ -36,8 +36,13 @@ typedef struct {
uint32_t pins; // Bitmask of what pins this state machine uses.
int state_machine;
PIO pio;
const uint16_t* init;
size_t init_len;
uint32_t initial_pin_state;
uint32_t initial_pin_direction;
bool in;
bool out;
bool wait_for_txstall;
uint tx_dreq;
uint rx_dreq;
bool out_shift_right;
@ -56,8 +61,10 @@ bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self,
const mcu_pin_obj_t * first_in_pin, uint8_t in_pin_count,
const mcu_pin_obj_t * first_set_pin, uint8_t set_pin_count,
const mcu_pin_obj_t * first_sideset_pin, uint8_t sideset_pin_count,
uint32_t initial_pin_state, uint32_t initial_pin_direction,
uint32_t pins_we_use, bool tx_fifo, bool rx_fifo,
bool auto_pull, uint8_t pull_threshold, bool out_shift_right,
bool wait_for_txstall,
bool auto_push, uint8_t push_threshold, bool in_shift_right,
bool claim_pins);

View File

@ -38,7 +38,7 @@ CIRCUITPY_WATCHDOG = 1
# Audio via PWM
CIRCUITPY_AUDIOIO = 0
CIRCUITPY_AUDIOBUSIO ?= 0 # add this later
CIRCUITPY_AUDIOBUSIO ?= 1
CIRCUITPY_AUDIOCORE ?= 1
CIRCUITPY_AUDIOPWMIO ?= 1

View File

@ -112,6 +112,9 @@ void reset_port(void) {
#if CIRCUITPY_AUDIOPWMIO
audiopwmout_reset();
#endif
#if CIRCUITPY_AUDIOCORE
audio_dma_reset();
#endif
reset_all_pins();
}

View File

@ -0,0 +1,36 @@
import audiocore
import audiobusio
import board
import digitalio
import array
import time
import math
import rp2pio
import adafruit_pioasm
time.sleep(10)
trigger = digitalio.DigitalInOut(board.D4)
trigger.switch_to_output(True)
# Generate one period of sine wav.
length = 8000 // 440
# signed 16 bit
s16 = array.array("h", [0] * length)
for i in range(length):
s16[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15))
print(s16[i])
sample = audiocore.RawSample(s16, sample_rate=8000)
dac = audiobusio.I2SOut(bit_clock=board.D10,
word_select=board.D11, data=board.D12)
trigger.value = False
dac.play(sample, loop=True)
time.sleep(1)
dac.stop()
trigger.value = True
print("done")

View File

@ -0,0 +1,44 @@
import audiobusio
import board
import digitalio
import array
import time
import math
trigger = digitalio.DigitalInOut(board.D4)
trigger.switch_to_output(True)
def mean(values):
return sum(values) / len(values)
def normalized_rms(values):
minbuf = int(mean(values))
samples_sum = sum(
float(sample - minbuf) * (sample - minbuf)
for sample in values
)
return math.sqrt(samples_sum / len(values))
# signed 16 bit
s16 = array.array("H", [0] * 10000)
pdm = audiobusio.PDMIn(clock_pin=board.D11, data_pin=board.D12, sample_rate=24000, bit_depth=16)
print("starting read")
trigger.value = False
count = pdm.record(s16, len(s16))
trigger.value = True
print("read done")
print("recorded {} samples".format(count))
for v in s16[:count]:
print(v)
magnitude = normalized_rms(s16)
print("magnitude", magnitude)
print("count", count)
print("done")