Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add looping to rp2040 analogbufio.BufferedIn #9438

Merged
merged 3 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ports/espressif/common-hal/analogbufio/BufferedIn.c
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ static bool check_valid_data(const adc_digi_output_data_t *data, const mcu_pin_o
return true;
}

uint32_t common_hal_analogbufio_bufferedin_readinto(analogbufio_bufferedin_obj_t *self, uint8_t *buffer, uint32_t len, uint8_t bytes_per_sample) {
uint32_t common_hal_analogbufio_bufferedin_readinto(analogbufio_bufferedin_obj_t *self, uint8_t *buffer, uint32_t len, uint8_t bytes_per_sample, bool loop) {
uint8_t result[NUM_SAMPLES_PER_INTERRUPT * SOC_ADC_DIGI_DATA_BYTES_PER_CONV] __attribute__ ((aligned(4))) = {0};
uint32_t captured_samples = 0;
uint32_t captured_bytes = 0;
Expand Down
153 changes: 103 additions & 50 deletions ports/raspberrypi/common-hal/analogbufio/BufferedIn.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,36 @@ void common_hal_analogbufio_bufferedin_construct(analogbufio_bufferedin_obj_t *s
float clk_div = (float)ADC_CLOCK_INPUT / (float)sample_rate - 1;
adc_set_clkdiv(clk_div);

// Set up the DMA to start transferring data as soon as it appears in FIFO
uint dma_chan = dma_claim_unused_channel(true);
self->dma_chan = dma_chan;
self->dma_chan[0] = dma_claim_unused_channel(true);
self->dma_chan[1] = dma_claim_unused_channel(true);

// Set Config
self->cfg = dma_channel_get_default_config(dma_chan);
// Set up the DMA to start transferring data as soon as it appears in FIFO

// Channel 0 reads from ADC data register and writes to buffer
self->cfg[0] = dma_channel_get_default_config(self->dma_chan[0]);
// Reading from constant address, writing to incrementing byte addresses
channel_config_set_read_increment(&(self->cfg), false);
channel_config_set_write_increment(&(self->cfg), true);

channel_config_set_read_increment(&(self->cfg[0]), false);
channel_config_set_write_increment(&(self->cfg[0]), true);
// Pace transfers based on availability of ADC samples
channel_config_set_dreq(&(self->cfg), DREQ_ADC);
channel_config_set_dreq(&(self->cfg[0]), DREQ_ADC);
channel_config_set_chain_to(&(self->cfg[0]), self->dma_chan[0]);

// If we want to loop, later we'll set channel 0 to chain to channel 1 instead.
// Channel 1 resets channel 0's write address and restarts it

self->cfg[1] = dma_channel_get_default_config(self->dma_chan[1]);
// Read from incrementing address
channel_config_set_read_increment(&(self->cfg[1]), true);
// Write to constant address (data dma write address register)
channel_config_set_write_increment(&(self->cfg[1]), false);
// Writing to 32-bit register
channel_config_set_transfer_data_size(&(self->cfg[1]), DMA_SIZE_32);
// Run as fast as possible
channel_config_set_dreq(&(self->cfg[1]), 0x3F);
// set ring to read one 32-bit value (the starting write address) over and over
channel_config_set_ring(&(self->cfg[1]), false, 2); // ring is 1<<2 = 4 bytes
// Chain to adc channel
channel_config_set_chain_to(&(self->cfg[1]), self->dma_chan[0]);

// clear any previous activity
adc_fifo_drain();
Expand All @@ -86,15 +103,22 @@ void common_hal_analogbufio_bufferedin_deinit(analogbufio_bufferedin_obj_t *self
return;
}

// stop DMA
dma_channel_abort(self->dma_chan[0]);
dma_channel_abort(self->dma_chan[1]);

// Release ADC Pin
reset_pin_number(self->pin->number);
self->pin = NULL;

// Release DMA Channel
dma_channel_unclaim(self->dma_chan);
dma_channel_unclaim(self->dma_chan[0]);
dma_channel_unclaim(self->dma_chan[1]);
}

uint32_t common_hal_analogbufio_bufferedin_readinto(analogbufio_bufferedin_obj_t *self, uint8_t *buffer, uint32_t len, uint8_t bytes_per_sample) {
uint8_t *active_buffer;

uint32_t common_hal_analogbufio_bufferedin_readinto(analogbufio_bufferedin_obj_t *self, uint8_t *buffer, uint32_t len, uint8_t bytes_per_sample, bool loop) {
// RP2040 Implementation Detail
// Fills the supplied buffer with ADC values using DMA transfer.
// If the buffer is 8-bit, then values are 8-bit shifted and error bit is off.
Expand All @@ -120,48 +144,77 @@ uint32_t common_hal_analogbufio_bufferedin_readinto(analogbufio_bufferedin_obj_t

uint32_t sample_count = len / bytes_per_sample;

channel_config_set_transfer_data_size(&(self->cfg), dma_size);

dma_channel_configure(self->dma_chan, &(self->cfg),
buffer, // dst
&adc_hw->fifo, // src
sample_count, // transfer count
true // start immediately
);

// Start the ADC
adc_run(true);

// Once DMA finishes, stop any new conversions from starting, and clean up
// the FIFO in case the ADC was still mid-conversion.
uint32_t remaining_transfers = sample_count;
while (dma_channel_is_busy(self->dma_chan) &&
!mp_hal_is_interrupted()) {
RUN_BACKGROUND_TASKS;
}
remaining_transfers = dma_channel_hw_addr(self->dma_chan)->transfer_count;
channel_config_set_transfer_data_size(&(self->cfg[0]), dma_size);

if (!loop) { // Set DMA to stop after one one set of transfers
channel_config_set_chain_to(&(self->cfg[0]), self->dma_chan[0]);
dma_channel_configure(self->dma_chan[0], &(self->cfg[0]),
buffer, // dst
&adc_hw->fifo, // src
sample_count, // transfer count
true // start immediately
);

// Start the ADC
adc_run(true);

// Wait for DMA to finish, then stop any new conversions from starting,
// and clean up the FIFO in case the ADC was still mid-conversion.
uint32_t remaining_transfers = sample_count;
while (dma_channel_is_busy(self->dma_chan[0]) &&
!mp_hal_is_interrupted()) {
RUN_BACKGROUND_TASKS;
}
remaining_transfers = dma_channel_hw_addr(self->dma_chan[0])->transfer_count;

// Clean up
adc_run(false);
// Stopping early so abort.
if (dma_channel_is_busy(self->dma_chan)) {
dma_channel_abort(self->dma_chan);
}
adc_fifo_drain();
// Clean up
adc_run(false);

size_t captured_count = sample_count - remaining_transfers;
if (dma_size == DMA_SIZE_16) {
uint16_t *buf16 = (uint16_t *)buffer;
for (size_t i = 0; i < captured_count; i++) {
uint16_t value = buf16[i];
// Check the error bit and "truncate" the buffer if there is an error.
if ((value & ADC_FIFO_ERR_BITS) != 0) {
captured_count = i;
break;
// If we stopped early, stop DMA
if (dma_channel_is_busy(self->dma_chan[0])) {
dma_channel_abort(self->dma_chan[0]);
}
adc_fifo_drain();

// Scale the values to the standard 16 bit range.
size_t captured_count = sample_count - remaining_transfers;
if (dma_size == DMA_SIZE_16) {
uint16_t *buf16 = (uint16_t *)buffer;
for (size_t i = 0; i < captured_count; i++) {
uint16_t value = buf16[i];
// Check the error bit and "truncate" the buffer if there is an error.
if ((value & ADC_FIFO_ERR_BITS) != 0) {
captured_count = i;
break;
}
buf16[i] = (value << 4) | (value >> 8);
}
// Scale the values to the standard 16 bit range.
buf16[i] = (value << 4) | (value >> 8);
}
return captured_count;
} else { // Set DMA to repeat transfers indefinitely
dma_channel_configure(self->dma_chan[1], &(self->cfg[1]),
&dma_hw->ch[self->dma_chan[0]].al2_write_addr_trig, // write address
&active_buffer, // read address
1, // transfer count
false // don't start yet
);

// put the buffer start address into a global so that it can be read by DMA
// and written into channel 0's write address
active_buffer = buffer;

channel_config_set_chain_to(&(self->cfg[0]), self->dma_chan[1]);
dma_channel_configure(self->dma_chan[0], &(self->cfg[0]),
buffer, // write address
&adc_hw->fifo, // read address
sample_count, // transfer count
true // start immediately
);

// Start the ADC
adc_run(true);

return 0;

}
return captured_count;
}
4 changes: 2 additions & 2 deletions ports/raspberrypi/common-hal/analogbufio/BufferedIn.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ typedef struct {
mp_obj_base_t base;
const mcu_pin_obj_t *pin;
uint8_t chan;
uint dma_chan;
dma_channel_config cfg;
uint dma_chan[2];
dma_channel_config cfg[2];
} analogbufio_bufferedin_obj_t;
39 changes: 22 additions & 17 deletions shared-bindings/analogbufio/BufferedIn.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
//| ``reference_voltage`` to read the configured setting.
//| (TODO) Provide mechanism to read CPU Temperature."""
//|

//| def __init__(self, pin: microcontroller.Pin, *, sample_rate: int) -> None:
//| """Create a `BufferedIn` on the given pin and given sample rate.
//|
Expand Down Expand Up @@ -96,47 +95,53 @@ static mp_obj_t analogbufio_bufferedin___exit__(size_t n_args, const mp_obj_t *a
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(analogbufio_bufferedin___exit___obj, 4, 4, analogbufio_bufferedin___exit__);

//| def readinto(self, buffer: WriteableBuffer) -> int:
//| def readinto(self, buffer: WriteableBuffer, loop: bool = False) -> int:
//| """Fills the provided buffer with ADC voltage values.
//|
//| ADC values will be read into the given buffer at the supplied sample_rate.
//| Depending on the buffer typecode, 'B', 'H', samples are 8-bit byte-arrays or
//| 16-bit half-words and are always unsigned.
//| The ADC most significant bits of the ADC are kept. (See
//| https://docs.circuitpython.org/en/latest/docs/library/array.html)
//| (See https://docs.circuitpython.org/en/latest/docs/library/array.html)
//| For 8-bit samples, the most significant bits of the 12-bit ADC values are kept.
//| For 16-bit samples, if loop=False, the 12-bit ADC values are scaled up to fill the 16 bit range.
//| If loop=True, ADC values are stored without scaling.
//|
//| :param ~circuitpython_typing.WriteableBuffer buffer: buffer: A buffer for samples"""
//| :param ~circuitpython_typing.WriteableBuffer buffer: buffer: A buffer for samples
//| :param ~bool loop: loop: Set to true for continuous conversions, False to fill buffer once then stop
//| """
//| ...
//|
static mp_obj_t analogbufio_bufferedin_obj_readinto(mp_obj_t self_in, mp_obj_t buffer_obj) {
analogbufio_bufferedin_obj_t *self = MP_OBJ_TO_PTR(self_in);
static mp_obj_t analogbufio_bufferedin_obj_readinto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_buffer, ARG_loop };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_buffer, MP_ARG_OBJ | MP_ARG_REQUIRED, {} },
{ MP_QSTR_loop, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} },
};
analogbufio_bufferedin_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_obj_t buffer = args[ARG_buffer].u_obj;
// Buffer defined and allocated by user
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(buffer_obj, &bufinfo, MP_BUFFER_READ);

mp_get_buffer_raise(buffer, &bufinfo, MP_BUFFER_READ);
uint8_t bytes_per_sample = 1;

// Bytes Per Sample
if (bufinfo.typecode == 'H') {
bytes_per_sample = 2;
} else if (bufinfo.typecode != 'B' && bufinfo.typecode != BYTEARRAY_TYPECODE) {
mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be a bytearray or array of type 'H' or 'B'"), MP_QSTR_buffer);
}

mp_uint_t captured = common_hal_analogbufio_bufferedin_readinto(self, bufinfo.buf, bufinfo.len, bytes_per_sample);
mp_uint_t captured = common_hal_analogbufio_bufferedin_readinto(self, bufinfo.buf, bufinfo.len, bytes_per_sample, args[ARG_loop].u_bool);
return MP_OBJ_NEW_SMALL_INT(captured);
}
MP_DEFINE_CONST_FUN_OBJ_2(analogbufio_bufferedin_readinto_obj, analogbufio_bufferedin_obj_readinto);
MP_DEFINE_CONST_FUN_OBJ_KW(analogbufio_bufferedin_readinto_obj, 1, analogbufio_bufferedin_obj_readinto);

static const mp_rom_map_elem_t analogbufio_bufferedin_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&analogbufio_bufferedin_deinit_obj) },
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&analogbufio_bufferedin_deinit_obj) },
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&analogbufio_bufferedin___exit___obj) },
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&analogbufio_bufferedin_readinto_obj)},

{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&analogbufio_bufferedin_readinto_obj)},
};

static MP_DEFINE_CONST_DICT(analogbufio_bufferedin_locals_dict, analogbufio_bufferedin_locals_dict_table);
Expand Down
2 changes: 1 addition & 1 deletion shared-bindings/analogbufio/BufferedIn.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ extern const mp_obj_type_t analogbufio_bufferedin_type;
void common_hal_analogbufio_bufferedin_construct(analogbufio_bufferedin_obj_t *self, const mcu_pin_obj_t *pin, uint32_t sample_rate);
void common_hal_analogbufio_bufferedin_deinit(analogbufio_bufferedin_obj_t *self);
bool common_hal_analogbufio_bufferedin_deinited(analogbufio_bufferedin_obj_t *self);
uint32_t common_hal_analogbufio_bufferedin_readinto(analogbufio_bufferedin_obj_t *self, uint8_t *buffer, uint32_t len, uint8_t bytes_per_sample);
uint32_t common_hal_analogbufio_bufferedin_readinto(analogbufio_bufferedin_obj_t *self, uint8_t *buffer, uint32_t len, uint8_t bytes_per_sample, bool loop);
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import analogbufio
import array
import audiocore
import audiopwmio
import board

samples = 100
buffer = array.array("H", [0x0000] * samples)
adc = analogbufio.BufferedIn(board.A0, sample_rate=100000)

adc.readinto(buffer)

print("Sample,Print 1, Print 2,Print 3, Print 4")
for i in range(4):
for j in range(samples):
print(j, "," * (i + 1), buffer[j])

adc.readinto(buffer, loop=True)

print("Sample,Print 1, Print 2,Print 3, Print 4")
for i in range(4):
for j in range(samples):
print(j, "," * (i + 1), buffer[j])