Skip to content

Commit 4f40fa5

Browse files
peterhinchdpgeorge
authored andcommitted
stm32/adc: Add read_timed_multi() static method, with docs and tests.
1 parent 0096a4b commit 4f40fa5

3 files changed

Lines changed: 187 additions & 1 deletion

File tree

docs/library/pyb.ADC.rst

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,58 @@ Methods
7676
for val in buf: # loop over all values
7777
print(val) # print the value out
7878

79-
This function does not allocate any memory.
79+
This function does not allocate any heap memory. It has blocking behaviour:
80+
it does not return to the calling program until the buffer is full.
81+
82+
.. method:: ADC.read_timed_multi((adcx, adcy, ...), (bufx, bufy, ...), timer)
83+
84+
This is a static method. It can be used to extract relative timing or
85+
phase data from multiple ADC's.
86+
87+
It reads analog values from multiple ADC's into buffers at a rate set by
88+
the *timer* object. Each time the timer triggers a sample is rapidly
89+
read from each ADC in turn.
90+
91+
ADC and buffer instances are passed in tuples with each ADC having an
92+
associated buffer. All buffers must be of the same type and length and
93+
the number of buffers must equal the number of ADC's.
94+
95+
Buffers can be ``bytearray`` or ``array.array`` for example. The ADC values
96+
have 12-bit resolution and are stored directly into the buffer if its element
97+
size is 16 bits or greater. If buffers have only 8-bit elements (eg a
98+
``bytearray``) then the sample resolution will be reduced to 8 bits.
99+
100+
*timer* must be a Timer object. The timer must already be initialised
101+
and running at the desired sampling frequency.
102+
103+
Example reading 3 ADC's::
104+
105+
adc0 = pyb.ADC(pyb.Pin.board.X1) # Create ADC's
106+
adc1 = pyb.ADC(pyb.Pin.board.X2)
107+
adc2 = pyb.ADC(pyb.Pin.board.X3)
108+
tim = pyb.Timer(8, freq=100) # Create timer
109+
rx0 = array.array('H', (0 for i in range(100))) # ADC buffers of
110+
rx1 = array.array('H', (0 for i in range(100))) # 100 16-bit words
111+
rx2 = array.array('H', (0 for i in range(100)))
112+
# read analog values into buffers at 100Hz (takes one second)
113+
pyb.ADC.read_timed_multi((adc0, adc1, adc2), (rx0, rx1, rx2), tim)
114+
for n in range(len(rx0)):
115+
print(rx0[n], rx1[n], rx2[n])
116+
117+
This function does not allocate any heap memory. It has blocking behaviour:
118+
it does not return to the calling program until the buffers are full.
119+
120+
The function returns ``True`` if all samples were acquired with correct
121+
timing. At high sample rates the time taken to acquire a set of samples
122+
can exceed the timer period. In this case the function returns ``False``,
123+
indicating a loss of precision in the sample interval. In extreme cases
124+
samples may be missed.
125+
126+
The maximum rate depends on factors including the data width and the
127+
number of ADC's being read. In testing two ADC's were sampled at a timer
128+
rate of 140KHz without overrun. Samples were missed at 180KHz. At high
129+
sample rates disabling interrupts for the duration can reduce the risk
130+
of sporadic data loss.
80131

81132
The ADCAll Object
82133
-----------------

ports/stm32/adc.c

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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_
450450
}
451451
STATIC MP_DEFINE_CONST_FUN_OBJ_3(adc_read_timed_obj, adc_read_timed);
452452

453+
// read_timed_multi((adcx, adcy, ...), (bufx, bufy, ...), timer)
454+
//
455+
// Read analog values from multiple ADC's into buffers at a rate set by the
456+
// timer. The ADC values have 12-bit resolution and are stored directly into
457+
// the corresponding buffer if its element size is 16 bits or greater, otherwise
458+
// the sample resolution will be reduced to 8 bits.
459+
//
460+
// This function should not allocate any heap memory.
461+
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) {
462+
size_t nadcs, nbufs;
463+
mp_obj_t *adc_array, *buf_array;
464+
mp_obj_get_array(adc_array_in, &nadcs, &adc_array);
465+
mp_obj_get_array(buf_array_in, &nbufs, &buf_array);
466+
467+
if (nadcs < 1) {
468+
mp_raise_ValueError("need at least 1 ADC");
469+
}
470+
if (nadcs != nbufs) {
471+
mp_raise_ValueError("length of ADC and buffer lists differ");
472+
}
473+
474+
// Get buf for first ADC, get word size, check other buffers match in type
475+
mp_buffer_info_t bufinfo;
476+
mp_get_buffer_raise(buf_array[0], &bufinfo, MP_BUFFER_WRITE);
477+
size_t typesize = mp_binary_get_size('@', bufinfo.typecode, NULL);
478+
for (uint array_index = 0; array_index < nbufs; array_index++) {
479+
mp_buffer_info_t bufinfo_curr;
480+
mp_get_buffer_raise(buf_array[array_index], &bufinfo_curr, MP_BUFFER_WRITE);
481+
if ((bufinfo.len != bufinfo_curr.len) || (bufinfo.typecode != bufinfo_curr.typecode)) {
482+
mp_raise_ValueError("size and type of buffers must match");
483+
}
484+
}
485+
486+
// Use the supplied timer object as the sampling time base
487+
TIM_HandleTypeDef *tim;
488+
tim = pyb_timer_get_handle(tim_in);
489+
490+
// Start adc; this is slow so wait for it to start
491+
pyb_obj_adc_t *adc0 = adc_array[0];
492+
adc_config_channel(&adc0->handle, adc0->channel);
493+
HAL_ADC_Start(&adc0->handle);
494+
// Wait for sample to complete and discard
495+
#define READ_TIMED_TIMEOUT (10) // in ms
496+
adc_wait_for_eoc_or_timeout(READ_TIMED_TIMEOUT);
497+
// Read (and discard) value
498+
uint value = ADCx->DR;
499+
500+
// Ensure first sample is on a timer tick
501+
__HAL_TIM_CLEAR_FLAG(tim, TIM_FLAG_UPDATE);
502+
while (__HAL_TIM_GET_FLAG(tim, TIM_FLAG_UPDATE) == RESET) {
503+
}
504+
__HAL_TIM_CLEAR_FLAG(tim, TIM_FLAG_UPDATE);
505+
506+
// Overrun check: assume success
507+
bool success = true;
508+
size_t nelems = bufinfo.len / typesize;
509+
for (size_t elem_index = 0; elem_index < nelems; elem_index++) {
510+
if (__HAL_TIM_GET_FLAG(tim, TIM_FLAG_UPDATE) != RESET) {
511+
// Timer has already triggered
512+
success = false;
513+
} else {
514+
// Wait for the timer to trigger so we sample at the correct frequency
515+
while (__HAL_TIM_GET_FLAG(tim, TIM_FLAG_UPDATE) == RESET) {
516+
}
517+
}
518+
__HAL_TIM_CLEAR_FLAG(tim, TIM_FLAG_UPDATE);
519+
520+
for (size_t array_index = 0; array_index < nadcs; array_index++) {
521+
pyb_obj_adc_t *adc = adc_array[array_index];
522+
// configure the ADC channel
523+
adc_config_channel(&adc->handle, adc->channel);
524+
// for the first sample we need to turn the ADC on
525+
// ADC is started: set the "start sample" bit
526+
#if defined(STM32F4) || defined(STM32F7)
527+
ADCx->CR2 |= (uint32_t)ADC_CR2_SWSTART;
528+
#elif defined(STM32L4)
529+
SET_BIT(ADCx->CR, ADC_CR_ADSTART);
530+
#else
531+
#error Unsupported processor
532+
#endif
533+
// wait for sample to complete
534+
#define READ_TIMED_TIMEOUT (10) // in ms
535+
adc_wait_for_eoc_or_timeout(READ_TIMED_TIMEOUT);
536+
537+
// read value
538+
value = ADCx->DR;
539+
540+
// store values in buffer
541+
if (typesize == 1) {
542+
value >>= 4;
543+
}
544+
mp_buffer_info_t bufinfo_curr; // Get buf for current ADC
545+
mp_get_buffer_raise(buf_array[array_index], &bufinfo_curr, MP_BUFFER_WRITE);
546+
mp_binary_set_val_array_from_int(bufinfo_curr.typecode, bufinfo_curr.buf, elem_index, value);
547+
}
548+
}
549+
550+
// Turn the ADC off
551+
adc0 = adc_array[0];
552+
HAL_ADC_Stop(&adc0->handle);
553+
554+
return mp_obj_new_bool(success);
555+
}
556+
STATIC MP_DEFINE_CONST_FUN_OBJ_3(adc_read_timed_multi_fun_obj, adc_read_timed_multi);
557+
STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(adc_read_timed_multi_obj, MP_ROM_PTR(&adc_read_timed_multi_fun_obj));
558+
453559
STATIC const mp_rom_map_elem_t adc_locals_dict_table[] = {
454560
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&adc_read_obj) },
455561
{ MP_ROM_QSTR(MP_QSTR_read_timed), MP_ROM_PTR(&adc_read_timed_obj) },
562+
{ MP_ROM_QSTR(MP_QSTR_read_timed_multi), MP_ROM_PTR(&adc_read_timed_multi_obj) },
456563
};
457564

458565
STATIC MP_DEFINE_CONST_DICT(adc_locals_dict, adc_locals_dict_table);

tests/pyb/adc.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,31 @@
3232
print(len(arv))
3333
for i in arv:
3434
assert i > 1000 and i < 2000
35+
36+
# Test read_timed_multi
37+
arv = bytearray(b'\xff'*50)
38+
art = bytearray(b'\xff'*50)
39+
ADC.read_timed_multi((adcv, adct), (arv, art), tim)
40+
for i in arv:
41+
assert i > 60 and i < 125
42+
# Wide range: unsure of accuracy of temp sensor.
43+
for i in art:
44+
assert i > 15 and i < 200
45+
46+
arv = array.array('i', 25 * [-1])
47+
art = array.array('i', 25 * [-1])
48+
ADC.read_timed_multi((adcv, adct), (arv, art), tim)
49+
for i in arv:
50+
assert i > 1000 and i < 2000
51+
# Wide range: unsure of accuracy of temp sensor.
52+
for i in art:
53+
assert i > 50 and i < 2000
54+
55+
arv = array.array('h', 25 * [0x7fff])
56+
art = array.array('h', 25 * [0x7fff])
57+
ADC.read_timed_multi((adcv, adct), (arv, art), tim)
58+
for i in arv:
59+
assert i > 1000 and i < 2000
60+
# Wide range: unsure of accuracy of temp sensor.
61+
for i in art:
62+
assert i > 50 and i < 2000

0 commit comments

Comments
 (0)