diff --git a/docs/library/pyb.ADC.rst b/docs/library/pyb.ADC.rst index 58aae6129a..c2d09ad402 100644 --- a/docs/library/pyb.ADC.rst +++ b/docs/library/pyb.ADC.rst @@ -76,7 +76,58 @@ Methods for val in buf: # loop over all values print(val) # print the value out - This function does not allocate any memory. + This function does not allocate any heap memory. It has blocking behaviour: + it does not return to the calling program until the buffer is full. + + .. method:: ADC.read_timed_multi((adcx, adcy, ...), (bufx, bufy, ...), timer) + + This is a static method. It can be used to extract relative timing or + phase data from multiple ADC's. + + It reads analog values from multiple ADC's into buffers at a rate set by + the *timer* object. Each time the timer triggers a sample is rapidly + read from each ADC in turn. + + ADC and buffer instances are passed in tuples with each ADC having an + associated buffer. All buffers must be of the same type and length and + the number of buffers must equal the number of ADC's. + + Buffers can be ``bytearray`` or ``array.array`` for example. The ADC values + have 12-bit resolution and are stored directly into the buffer if its element + size is 16 bits or greater. If buffers have only 8-bit elements (eg a + ``bytearray``) then the sample resolution will be reduced to 8 bits. + + *timer* must be a Timer object. The timer must already be initialised + and running at the desired sampling frequency. + + Example reading 3 ADC's:: + + adc0 = pyb.ADC(pyb.Pin.board.X1) # Create ADC's + adc1 = pyb.ADC(pyb.Pin.board.X2) + adc2 = pyb.ADC(pyb.Pin.board.X3) + tim = pyb.Timer(8, freq=100) # Create timer + rx0 = array.array('H', (0 for i in range(100))) # ADC buffers of + rx1 = array.array('H', (0 for i in range(100))) # 100 16-bit words + rx2 = array.array('H', (0 for i in range(100))) + # read analog values into buffers at 100Hz (takes one second) + pyb.ADC.read_timed_multi((adc0, adc1, adc2), (rx0, rx1, rx2), tim) + for n in range(len(rx0)): + print(rx0[n], rx1[n], rx2[n]) + + This function does not allocate any heap memory. It has blocking behaviour: + it does not return to the calling program until the buffers are full. + + The function returns ``True`` if all samples were acquired with correct + timing. At high sample rates the time taken to acquire a set of samples + can exceed the timer period. In this case the function returns ``False``, + indicating a loss of precision in the sample interval. In extreme cases + samples may be missed. + + The maximum rate depends on factors including the data width and the + number of ADC's being read. In testing two ADC's were sampled at a timer + rate of 140KHz without overrun. Samples were missed at 180KHz. At high + sample rates disabling interrupts for the duration can reduce the risk + of sporadic data loss. The ADCAll Object ----------------- diff --git a/ports/stm32/adc.c b/ports/stm32/adc.c index 3f00ad875e..ba1ca5ce59 100644 --- a/ports/stm32/adc.c +++ b/ports/stm32/adc.c @@ -450,9 +450,116 @@ STATIC mp_obj_t adc_read_timed(mp_obj_t self_in, mp_obj_t buf_in, mp_obj_t freq_ } STATIC MP_DEFINE_CONST_FUN_OBJ_3(adc_read_timed_obj, adc_read_timed); +// read_timed_multi((adcx, adcy, ...), (bufx, bufy, ...), timer) +// +// Read analog values from multiple ADC's into buffers at a rate set by the +// timer. The ADC values have 12-bit resolution and are stored directly into +// the corresponding buffer if its element size is 16 bits or greater, otherwise +// the sample resolution will be reduced to 8 bits. +// +// This function should not allocate any heap memory. +STATIC mp_obj_t adc_read_timed_multi(mp_obj_t adc_array_in, mp_obj_t buf_array_in, mp_obj_t tim_in) { + size_t nadcs, nbufs; + mp_obj_t *adc_array, *buf_array; + mp_obj_get_array(adc_array_in, &nadcs, &adc_array); + mp_obj_get_array(buf_array_in, &nbufs, &buf_array); + + if (nadcs < 1) { + mp_raise_ValueError("need at least 1 ADC"); + } + if (nadcs != nbufs) { + mp_raise_ValueError("length of ADC and buffer lists differ"); + } + + // Get buf for first ADC, get word size, check other buffers match in type + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_array[0], &bufinfo, MP_BUFFER_WRITE); + size_t typesize = mp_binary_get_size('@', bufinfo.typecode, NULL); + for (uint array_index = 0; array_index < nbufs; array_index++) { + mp_buffer_info_t bufinfo_curr; + mp_get_buffer_raise(buf_array[array_index], &bufinfo_curr, MP_BUFFER_WRITE); + if ((bufinfo.len != bufinfo_curr.len) || (bufinfo.typecode != bufinfo_curr.typecode)) { + mp_raise_ValueError("size and type of buffers must match"); + } + } + + // Use the supplied timer object as the sampling time base + TIM_HandleTypeDef *tim; + tim = pyb_timer_get_handle(tim_in); + + // Start adc; this is slow so wait for it to start + pyb_obj_adc_t *adc0 = adc_array[0]; + adc_config_channel(&adc0->handle, adc0->channel); + HAL_ADC_Start(&adc0->handle); + // Wait for sample to complete and discard + #define READ_TIMED_TIMEOUT (10) // in ms + adc_wait_for_eoc_or_timeout(READ_TIMED_TIMEOUT); + // Read (and discard) value + uint value = ADCx->DR; + + // Ensure first sample is on a timer tick + __HAL_TIM_CLEAR_FLAG(tim, TIM_FLAG_UPDATE); + while (__HAL_TIM_GET_FLAG(tim, TIM_FLAG_UPDATE) == RESET) { + } + __HAL_TIM_CLEAR_FLAG(tim, TIM_FLAG_UPDATE); + + // Overrun check: assume success + bool success = true; + size_t nelems = bufinfo.len / typesize; + for (size_t elem_index = 0; elem_index < nelems; elem_index++) { + if (__HAL_TIM_GET_FLAG(tim, TIM_FLAG_UPDATE) != RESET) { + // Timer has already triggered + success = false; + } else { + // Wait for the timer to trigger so we sample at the correct frequency + while (__HAL_TIM_GET_FLAG(tim, TIM_FLAG_UPDATE) == RESET) { + } + } + __HAL_TIM_CLEAR_FLAG(tim, TIM_FLAG_UPDATE); + + for (size_t array_index = 0; array_index < nadcs; array_index++) { + pyb_obj_adc_t *adc = adc_array[array_index]; + // configure the ADC channel + adc_config_channel(&adc->handle, adc->channel); + // for the first sample we need to turn the ADC on + // ADC is started: set the "start sample" bit + #if defined(STM32F4) || defined(STM32F7) + ADCx->CR2 |= (uint32_t)ADC_CR2_SWSTART; + #elif defined(STM32L4) + SET_BIT(ADCx->CR, ADC_CR_ADSTART); + #else + #error Unsupported processor + #endif + // wait for sample to complete + #define READ_TIMED_TIMEOUT (10) // in ms + adc_wait_for_eoc_or_timeout(READ_TIMED_TIMEOUT); + + // read value + value = ADCx->DR; + + // store values in buffer + if (typesize == 1) { + value >>= 4; + } + mp_buffer_info_t bufinfo_curr; // Get buf for current ADC + mp_get_buffer_raise(buf_array[array_index], &bufinfo_curr, MP_BUFFER_WRITE); + mp_binary_set_val_array_from_int(bufinfo_curr.typecode, bufinfo_curr.buf, elem_index, value); + } + } + + // Turn the ADC off + adc0 = adc_array[0]; + HAL_ADC_Stop(&adc0->handle); + + return mp_obj_new_bool(success); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(adc_read_timed_multi_fun_obj, adc_read_timed_multi); +STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(adc_read_timed_multi_obj, MP_ROM_PTR(&adc_read_timed_multi_fun_obj)); + STATIC const mp_rom_map_elem_t adc_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&adc_read_obj) }, { MP_ROM_QSTR(MP_QSTR_read_timed), MP_ROM_PTR(&adc_read_timed_obj) }, + { MP_ROM_QSTR(MP_QSTR_read_timed_multi), MP_ROM_PTR(&adc_read_timed_multi_obj) }, }; STATIC MP_DEFINE_CONST_DICT(adc_locals_dict, adc_locals_dict_table); diff --git a/tests/pyb/adc.py b/tests/pyb/adc.py index 7834c7520b..0bd9b9d53a 100644 --- a/tests/pyb/adc.py +++ b/tests/pyb/adc.py @@ -32,3 +32,31 @@ adcv.read_timed(arv, tim) print(len(arv)) for i in arv: assert i > 1000 and i < 2000 + +# Test read_timed_multi +arv = bytearray(b'\xff'*50) +art = bytearray(b'\xff'*50) +ADC.read_timed_multi((adcv, adct), (arv, art), tim) +for i in arv: + assert i > 60 and i < 125 +# Wide range: unsure of accuracy of temp sensor. +for i in art: + assert i > 15 and i < 200 + +arv = array.array('i', 25 * [-1]) +art = array.array('i', 25 * [-1]) +ADC.read_timed_multi((adcv, adct), (arv, art), tim) +for i in arv: + assert i > 1000 and i < 2000 +# Wide range: unsure of accuracy of temp sensor. +for i in art: + assert i > 50 and i < 2000 + +arv = array.array('h', 25 * [0x7fff]) +art = array.array('h', 25 * [0x7fff]) +ADC.read_timed_multi((adcv, adct), (arv, art), tim) +for i in arv: + assert i > 1000 and i < 2000 +# Wide range: unsure of accuracy of temp sensor. +for i in art: + assert i > 50 and i < 2000