Skip to content

Commit b80048b

Browse files
authored
Merge pull request #2213 from dhalbert/cpu-voltage
Measure voltage supplied to chip
2 parents 9de155a + a0d18ac commit b80048b

File tree

10 files changed

+181
-45
lines changed

10 files changed

+181
-45
lines changed

lib/tinyusb

ports/atmel-samd/common-hal/microcontroller/Processor.c

Lines changed: 68 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@
7272
#define INT1V_DIVIDER_1000 1000.0
7373
#define ADC_12BIT_FULL_SCALE_VALUE_FLOAT 4095.0
7474

75+
// channel argument (ignored in calls below)
76+
#define IGNORED_CHANNEL 0
7577

7678
// Decimal to fraction conversion. (adapted from ASF sample).
7779
STATIC float convert_dec_to_frac(uint8_t val) {
@@ -196,14 +198,12 @@ float common_hal_mcu_processor_get_temperature(void) {
196198

197199
adc_sync_set_resolution(&adc, ADC_CTRLB_RESSEL_12BIT_Val);
198200
adc_sync_set_reference(&adc, ADC_REFCTRL_REFSEL_INT1V_Val);
199-
// Channel passed in adc_sync_enable_channel is actually ignored (!).
200-
adc_sync_enable_channel(&adc, ADC_INPUTCTRL_MUXPOS_TEMP_Val);
201+
// Channel arg is ignored.
202+
adc_sync_enable_channel(&adc, IGNORED_CHANNEL);
201203
adc_sync_set_inputs(&adc,
202204
ADC_INPUTCTRL_MUXPOS_TEMP_Val, // pos_input
203205
ADC_INPUTCTRL_MUXNEG_GND_Val, // neg_input
204-
ADC_INPUTCTRL_MUXPOS_TEMP_Val); // channel channel (this arg is ignored (!))
205-
206-
adc_sync_set_resolution(&adc, ADC_CTRLB_RESSEL_12BIT_Val);
206+
IGNORED_CHANNEL); // channel (ignored)
207207

208208
hri_adc_write_CTRLB_PRESCALER_bf(adc.device.hw, ADC_CTRLB_PRESCALER_DIV32_Val);
209209
hri_adc_write_SAMPCTRL_SAMPLEN_bf(adc.device.hw, ADC_TEMP_SAMPLE_LENGTH);
@@ -222,62 +222,100 @@ float common_hal_mcu_processor_get_temperature(void) {
222222
// like voltage reference / ADC channel change"
223223
// Empirical observation shows the first reading is quite different than subsequent ones.
224224

225-
// The channel listed in adc_sync_read_channel is actually ignored(!).
226-
// Must be set as above with adc_sync_set_inputs.
227-
adc_sync_read_channel(&adc, ADC_INPUTCTRL_MUXPOS_TEMP_Val, ((uint8_t*) &value), 2);
228-
adc_sync_read_channel(&adc, ADC_INPUTCTRL_MUXPOS_TEMP_Val, ((uint8_t*) &value), 2);
225+
// Channel arg is ignored.
226+
adc_sync_read_channel(&adc, IGNORED_CHANNEL, ((uint8_t*) &value), 2);
227+
adc_sync_read_channel(&adc, IGNORED_CHANNEL, ((uint8_t*) &value), 2);
229228

230229
adc_sync_deinit(&adc);
231230
return calculate_temperature(value);
232231
#endif // SAMD21
233232

234233
#ifdef SAMD51
235234
adc_sync_set_resolution(&adc, ADC_CTRLB_RESSEL_12BIT_Val);
236-
// Reference voltage choice is a guess. It's not specified in the datasheet that I can see.
235+
// Using INTVCC0 as the reference voltage.
237236
// INTVCC1 seems to read a little high.
238-
// INTREF doesn't work: ADC hangs BUSY.
237+
// INTREF doesn't work: ADC hangs BUSY. It's supposed to work, but does not.
238+
// The SAME54 example from Atmel START implicitly uses INTREF.
239239
adc_sync_set_reference(&adc, ADC_REFCTRL_REFSEL_INTVCC0_Val);
240240

241-
// If ONDEMAND=1, we don't need to use the VREF.TSSEL bit to choose PTAT and CTAT.
242241
hri_supc_set_VREF_ONDEMAND_bit(SUPC);
242+
// Enable temperature sensor.
243243
hri_supc_set_VREF_TSEN_bit(SUPC);
244+
hri_supc_set_VREF_VREFOE_bit(SUPC);
244245

245-
// Channel passed in adc_sync_enable_channel is actually ignored (!).
246-
adc_sync_enable_channel(&adc, ADC_INPUTCTRL_MUXPOS_PTAT_Val);
246+
// Channel arg is ignored.
247+
adc_sync_enable_channel(&adc, IGNORED_CHANNEL);
247248
adc_sync_set_inputs(&adc,
248249
ADC_INPUTCTRL_MUXPOS_PTAT_Val, // pos_input
249250
ADC_INPUTCTRL_MUXNEG_GND_Val, // neg_input
250-
ADC_INPUTCTRL_MUXPOS_PTAT_Val); // channel (this arg is ignored (!))
251+
IGNORED_CHANNEL); // channel (ignored)
251252

252253
// Read both temperature sensors.
253254
volatile uint16_t ptat;
254255
volatile uint16_t ctat;
255256

256-
// The channel listed in adc_sync_read_channel is actually ignored(!).
257-
// Must be set as above with adc_sync_set_inputs.
258-
// Read twice for stability (necessary?)
259-
adc_sync_read_channel(&adc, ADC_INPUTCTRL_MUXPOS_PTAT_Val, ((uint8_t*) &ptat), 2);
260-
adc_sync_read_channel(&adc, ADC_INPUTCTRL_MUXPOS_PTAT_Val, ((uint8_t*) &ptat), 2);
257+
// Read twice for stability (necessary?).
258+
adc_sync_read_channel(&adc, IGNORED_CHANNEL, ((uint8_t*) &ptat), 2);
259+
adc_sync_read_channel(&adc, IGNORED_CHANNEL, ((uint8_t*) &ptat), 2);
261260

262261
adc_sync_set_inputs(&adc,
263262
ADC_INPUTCTRL_MUXPOS_CTAT_Val, // pos_input
264263
ADC_INPUTCTRL_MUXNEG_GND_Val, // neg_input
265-
ADC_INPUTCTRL_MUXPOS_CTAT_Val); // channel (this arg is ignored (!))
266-
267-
// Channel passed in adc_sync_enable_channel is actually ignored (!).
268-
adc_sync_enable_channel(&adc, ADC_INPUTCTRL_MUXPOS_CTAT_Val);
269-
// The channel listed in adc_sync_read_channel is actually ignored(!).
270-
// Must be set as above with adc_sync_set_inputs.
271-
// Read twice for stability (necessary?)
272-
adc_sync_read_channel(&adc, ADC_INPUTCTRL_MUXPOS_CTAT_Val, ((uint8_t*) &ctat), 2);
273-
adc_sync_read_channel(&adc, ADC_INPUTCTRL_MUXPOS_CTAT_Val, ((uint8_t*) &ctat), 2);
274-
hri_supc_set_VREF_ONDEMAND_bit(SUPC);
264+
IGNORED_CHANNEL); // channel (ignored)
265+
266+
adc_sync_read_channel(&adc, IGNORED_CHANNEL, ((uint8_t*) &ctat), 2);
267+
adc_sync_read_channel(&adc, IGNORED_CHANNEL, ((uint8_t*) &ctat), 2);
268+
269+
// Turn off temp sensor.
270+
hri_supc_clear_VREF_TSEN_bit(SUPC);
275271

276272
adc_sync_deinit(&adc);
277273
return calculate_temperature(ptat, ctat);
278274
#endif // SAMD51
279275
}
280276

277+
float common_hal_mcu_processor_get_voltage(void) {
278+
struct adc_sync_descriptor adc;
279+
280+
static Adc* adc_insts[] = ADC_INSTS;
281+
samd_peripherals_adc_setup(&adc, adc_insts[0]);
282+
283+
#ifdef SAMD21
284+
adc_sync_set_reference(&adc, ADC_REFCTRL_REFSEL_INT1V_Val);
285+
#endif
286+
287+
#ifdef SAMD51
288+
hri_supc_set_VREF_SEL_bf(SUPC, SUPC_VREF_SEL_1V0_Val);
289+
// ONDEMAND must be clear, and VREFOE must be set, or else the ADC conversion will not complete.
290+
// See https://community.atmel.com/forum/samd51-using-intref-adc-voltage-reference
291+
hri_supc_clear_VREF_ONDEMAND_bit(SUPC);
292+
hri_supc_set_VREF_VREFOE_bit(SUPC);
293+
adc_sync_set_reference(&adc, ADC_REFCTRL_REFSEL_INTREF_Val);
294+
#endif
295+
296+
adc_sync_set_resolution(&adc, ADC_CTRLB_RESSEL_12BIT_Val);
297+
// Channel arg is ignored.
298+
adc_sync_set_inputs(&adc,
299+
ADC_INPUTCTRL_MUXPOS_SCALEDIOVCC_Val, // IOVCC/4 (nominal 3.3V/4)
300+
ADC_INPUTCTRL_MUXNEG_GND_Val, // neg_input
301+
IGNORED_CHANNEL); // channel (ignored).
302+
adc_sync_enable_channel(&adc, IGNORED_CHANNEL);
303+
304+
volatile uint16_t reading;
305+
306+
// Channel arg is ignored.
307+
// Read twice and discard first result, as recommended in section 14 of
308+
// http://www.atmel.com/images/Atmel-42645-ADC-Configurations-with-Examples_ApplicationNote_AT11481.pdf
309+
// "Discard the first conversion result whenever there is a change in ADC configuration
310+
// like voltage reference / ADC channel change"
311+
// Empirical observation shows the first reading is quite different than subsequent ones.
312+
adc_sync_read_channel(&adc, IGNORED_CHANNEL, ((uint8_t*) &reading), 2);
313+
adc_sync_read_channel(&adc, IGNORED_CHANNEL, ((uint8_t*) &reading), 2);
314+
315+
adc_sync_deinit(&adc);
316+
// Multiply by 4 to compensate for SCALEDIOVCC division by 4.
317+
return (reading / 4095.0f) * 4.0f;
318+
}
281319

282320
uint32_t common_hal_mcu_processor_get_frequency(void) {
283321
// TODO(tannewt): Determine this dynamically.

ports/cxd56/common-hal/microcontroller/Processor.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,20 @@
2626

2727
#include <sys/boardctl.h>
2828

29+
// For NAN: remove when not needed.
30+
#include <math.h>
2931
#include "py/mphal.h"
3032

3133
uint32_t common_hal_mcu_processor_get_frequency(void) {
3234
return mp_hal_ticks_cpu();
3335
}
3436

3537
float common_hal_mcu_processor_get_temperature(void) {
36-
return 0;
38+
return NAN;
39+
}
40+
41+
float common_hal_mcu_processor_get_voltage(void) {
42+
return NAN;
3743
}
3844

3945
void common_hal_mcu_processor_get_uid(uint8_t raw_id[]) {

ports/nrf/common-hal/analogio/AnalogIn.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,21 @@
2929
#include "py/runtime.h"
3030
#include "supervisor/shared/translate.h"
3131

32-
#include "nrfx_saadc.h"
32+
#include "nrf_saadc.h"
3333
#include "nrf_gpio.h"
3434

3535
#define CHANNEL_NO 0
3636

37+
void analogin_init(void) {
38+
// Calibrate the ADC once, on startup.
39+
nrf_saadc_enable();
40+
nrf_saadc_event_clear(NRF_SAADC_EVENT_CALIBRATEDONE);
41+
nrf_saadc_task_trigger(NRF_SAADC_TASK_CALIBRATEOFFSET);
42+
while (nrf_saadc_event_check(NRF_SAADC_EVENT_CALIBRATEDONE) == 0) { }
43+
nrf_saadc_event_clear(NRF_SAADC_EVENT_CALIBRATEDONE);
44+
nrf_saadc_disable();
45+
}
46+
3747
void common_hal_analogio_analogin_construct(analogio_analogin_obj_t *self, const mcu_pin_obj_t *pin) {
3848
if (pin->adc_channel == 0)
3949
mp_raise_ValueError(translate("Pin does not have ADC capabilities"));

ports/nrf/common-hal/analogio/AnalogIn.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,6 @@ typedef struct {
3636
const mcu_pin_obj_t * pin;
3737
} analogio_analogin_obj_t;
3838

39+
void analogin_init(void);
40+
3941
#endif // MICROPY_INCLUDED_NRF_COMMON_HAL_ANALOGIO_ANALOGIN_H

ports/nrf/common-hal/microcontroller/Processor.c

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "py/runtime.h"
2929
#include "supervisor/shared/translate.h"
3030

31+
#include "nrfx_saadc.h"
3132
#ifdef BLUETOOTH_SD
3233
#include "nrf_sdm.h"
3334
#endif
@@ -47,27 +48,73 @@ float common_hal_mcu_processor_get_temperature(void) {
4748
if (err_code != NRF_SUCCESS) {
4849
mp_raise_OSError_msg(translate("Cannot get temperature"));
4950
}
50-
}
51+
return temp / 4.0f;
52+
} // Fall through if SD not enabled.
5153
#endif
52-
5354
NRF_TEMP->TASKS_START = 1;
54-
55-
while (NRF_TEMP->EVENTS_DATARDY == 0)
56-
;
57-
55+
while (NRF_TEMP->EVENTS_DATARDY == 0) { }
5856
NRF_TEMP->EVENTS_DATARDY = 0;
59-
6057
temp = NRF_TEMP->TEMP;
61-
6258
NRF_TEMP->TASKS_STOP = 1;
63-
6459
return temp / 4.0f;
6560
}
6661

62+
63+
6764
uint32_t common_hal_mcu_processor_get_frequency(void) {
6865
return 64000000ul;
6966
}
7067

68+
float common_hal_mcu_processor_get_voltage(void) {
69+
nrf_saadc_value_t value;
70+
71+
const nrf_saadc_channel_config_t config = {
72+
.resistor_p = NRF_SAADC_RESISTOR_DISABLED,
73+
.resistor_n = NRF_SAADC_RESISTOR_DISABLED,
74+
.gain = NRF_SAADC_GAIN1_6,
75+
.reference = NRF_SAADC_REFERENCE_INTERNAL,
76+
.acq_time = NRF_SAADC_ACQTIME_10US,
77+
.mode = NRF_SAADC_MODE_SINGLE_ENDED,
78+
.burst = NRF_SAADC_BURST_DISABLED,
79+
.pin_p = NRF_SAADC_INPUT_VDD,
80+
.pin_n = NRF_SAADC_INPUT_VDD,
81+
};
82+
83+
nrf_saadc_resolution_set(NRF_SAADC_RESOLUTION_14BIT);
84+
nrf_saadc_oversample_set(NRF_SAADC_OVERSAMPLE_DISABLED);
85+
nrf_saadc_enable();
86+
87+
for (uint32_t i = 0; i < NRF_SAADC_CHANNEL_COUNT; i++) {
88+
nrf_saadc_channel_input_set(i, NRF_SAADC_INPUT_DISABLED, NRF_SAADC_INPUT_DISABLED);
89+
}
90+
91+
nrf_saadc_channel_init(0, &config);
92+
nrf_saadc_buffer_init(&value, 1);
93+
94+
nrf_saadc_task_trigger(NRF_SAADC_TASK_START);
95+
while (nrf_saadc_event_check(NRF_SAADC_EVENT_STARTED) == 0) { }
96+
nrf_saadc_event_clear(NRF_SAADC_EVENT_STARTED);
97+
98+
nrf_saadc_task_trigger(NRF_SAADC_TASK_SAMPLE);
99+
while (nrf_saadc_event_check(NRF_SAADC_EVENT_END) == 0) { }
100+
nrf_saadc_event_clear(NRF_SAADC_EVENT_END);
101+
102+
nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
103+
while (nrf_saadc_event_check(NRF_SAADC_EVENT_STOPPED) == 0) { }
104+
nrf_saadc_event_clear(NRF_SAADC_EVENT_STOPPED);
105+
106+
nrf_saadc_disable();
107+
108+
if (value < 0) {
109+
value = 0;
110+
}
111+
112+
// The ADC reading we expect if VDD is 3.3V.
113+
#define NOMINAL_VALUE_3_3 (((3.3f/6)/0.6f)*16383)
114+
return (value/NOMINAL_VALUE_3_3) * 3.3f;
115+
}
116+
117+
71118
void common_hal_mcu_processor_get_uid(uint8_t raw_id[]) {
72119
for (int i=0; i<2; i++) {
73120
((uint32_t*) raw_id)[i] = NRF_FICR->DEVICEID[i];

ports/nrf/supervisor/port.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "shared-module/gamepad/__init__.h"
4040
#include "common-hal/microcontroller/Pin.h"
4141
#include "common-hal/_bleio/__init__.h"
42+
#include "common-hal/analogio/AnalogIn.h"
4243
#include "common-hal/busio/I2C.h"
4344
#include "common-hal/busio/SPI.h"
4445
#include "common-hal/busio/UART.h"
@@ -83,6 +84,10 @@ safe_mode_t port_init(void) {
8384
// Configure millisecond timer initialization.
8485
tick_init();
8586

87+
#if CIRCUITPY_ANALOGIO
88+
analogin_init();
89+
#endif
90+
8691
#if CIRCUITPY_RTC
8792
rtc_init();
8893
#endif
@@ -102,11 +107,11 @@ void reset_port(void) {
102107
spi_reset();
103108
uart_reset();
104109

105-
#ifdef CIRCUITPY_AUDIOBUSIO
110+
#if CIRCUITPY_AUDIOBUSIO
106111
i2s_reset();
107112
#endif
108113

109-
#ifdef CIRCUITPY_AUDIOPWMIO
114+
#if CIRCUITPY_AUDIOPWMIO
110115
audiopwmout_reset();
111116
#endif
112117

ports/stm32f4/common-hal/microcontroller/Processor.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
* THE SOFTWARE.
2626
*/
2727

28+
#include <math.h>
2829
#include "common-hal/microcontroller/Processor.h"
2930
#include "py/runtime.h"
3031
#include "supervisor/shared/translate.h"
@@ -34,7 +35,11 @@
3435
#define STM32_UUID ((uint32_t *)0x1FFF7A10)
3536

3637
float common_hal_mcu_processor_get_temperature(void) {
37-
return 0;
38+
return NAN;
39+
}
40+
41+
float common_hal_mcu_processor_get_voltage(void) {
42+
return NAN;
3843
}
3944

4045
uint32_t common_hal_mcu_processor_get_frequency(void) {

shared-bindings/microcontroller/Processor.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,32 @@ const mp_obj_property_t mcu_processor_uid_obj = {
113113
},
114114
};
115115

116+
//| .. attribute:: voltage
117+
//|
118+
//| The input voltage to the microcontroller, as a float. (read-only)
119+
//|
120+
//| Is `None` if the voltage is not available.
121+
//|
122+
STATIC mp_obj_t mcu_processor_get_voltage(mp_obj_t self) {
123+
float voltage = common_hal_mcu_processor_get_voltage();
124+
return isnan(voltage) ? mp_const_none : mp_obj_new_float(voltage);
125+
}
126+
127+
MP_DEFINE_CONST_FUN_OBJ_1(mcu_processor_get_voltage_obj, mcu_processor_get_voltage);
128+
129+
const mp_obj_property_t mcu_processor_voltage_obj = {
130+
.base.type = &mp_type_property,
131+
.proxy = {(mp_obj_t)&mcu_processor_get_voltage_obj, // getter
132+
(mp_obj_t)&mp_const_none_obj, // no setter
133+
(mp_obj_t)&mp_const_none_obj, // no deleter
134+
},
135+
};
136+
116137
STATIC const mp_rom_map_elem_t mcu_processor_locals_dict_table[] = {
117138
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&mcu_processor_frequency_obj) },
118139
{ MP_ROM_QSTR(MP_QSTR_temperature), MP_ROM_PTR(&mcu_processor_temperature_obj) },
119140
{ MP_ROM_QSTR(MP_QSTR_uid), MP_ROM_PTR(&mcu_processor_uid_obj) },
141+
{ MP_ROM_QSTR(MP_QSTR_voltage), MP_ROM_PTR(&mcu_processor_voltage_obj) },
120142
};
121143

122144
STATIC MP_DEFINE_CONST_DICT(mcu_processor_locals_dict, mcu_processor_locals_dict_table);

shared-bindings/microcontroller/Processor.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@ const mp_obj_type_t mcu_processor_type;
3636
uint32_t common_hal_mcu_processor_get_frequency(void);
3737
float common_hal_mcu_processor_get_temperature(void);
3838
void common_hal_mcu_processor_get_uid(uint8_t raw_id[]);
39+
float common_hal_mcu_processor_get_voltage(void);
3940

4041
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_MICROCONTROLLER_PROCESSOR_H

0 commit comments

Comments
 (0)