Skip to content

Commit

Permalink
Merge pull request #9438 from timchinowsky/analogbufio-loop
Browse files Browse the repository at this point in the history
add looping to rp2040 analogbufio.BufferedIn
  • Loading branch information
tannewt authored Jul 22, 2024
2 parents 98982c6 + d554c01 commit f3b9b35
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 71 deletions.
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])

0 comments on commit f3b9b35

Please sign in to comment.