|
| 1 | +/* |
| 2 | + * This file is part of the MicroPython project, http://micropython.org/ |
| 3 | + * |
| 4 | + * The MIT License (MIT) |
| 5 | + * |
| 6 | + * Copyright (c) 2025 NED KONZ <[email protected]> |
| 7 | + * |
| 8 | + * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 9 | + * of this software and associated documentation files (the "Software"), to deal |
| 10 | + * in the Software without restriction, including without limitation the rights |
| 11 | + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 12 | + * copies of the Software, and to permit persons to whom the Software is |
| 13 | + * furnished to do so, subject to the following conditions: |
| 14 | + * |
| 15 | + * The above copyright notice and this permission notice shall be included in |
| 16 | + * all copies or substantial portions of the Software. |
| 17 | + * |
| 18 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 19 | + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 20 | + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 21 | + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 22 | + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 23 | + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 24 | + * THE SOFTWARE. |
| 25 | + */ |
| 26 | + |
| 27 | +// This file is never compiled standalone, it's included directly from |
| 28 | +// extmod/machine_adc.c via MICROPY_PY_MACHINE_ADC_INCLUDEFILE. |
| 29 | + |
| 30 | +/* |
| 31 | + * The ADC peripheral and pinmux is configured in the board's .dts file. Make |
| 32 | + * sure that the ADC is enabled (status = "okay";). |
| 33 | + * |
| 34 | + * In addition to that, this driver requires an ADC channel specified in the |
| 35 | + * io-channels property of the zephyr,user node. This is usually done with a |
| 36 | + * devicetree overlay. |
| 37 | + * |
| 38 | + * Configuration of channels (settings like gain, reference, or acquisition time) |
| 39 | + * also needs to be specified in devicetree, in ADC controller child nodes. Also |
| 40 | + * the ADC resolution and oversampling setting (if used) need to be specified |
| 41 | + * there. |
| 42 | + * |
| 43 | + * Here is an excerpt from a devicetree overlay that configures an ADC |
| 44 | + * with one channel that would be referred to as ('adc', 0) in the constructor |
| 45 | + * of the ADC object: |
| 46 | + * |
| 47 | + * / { |
| 48 | + * zephyr,user { |
| 49 | + * io-channels = <&adc 0>; |
| 50 | + * }; |
| 51 | + * }; |
| 52 | + * |
| 53 | + * &adc { |
| 54 | + * #address-cells = <1>; |
| 55 | + * #size-cells = <0>; |
| 56 | + * |
| 57 | + * channel@0 { |
| 58 | + * reg = <0>; |
| 59 | + * zephyr,gain = "ADC_GAIN_1_6"; |
| 60 | + * zephyr,reference = "ADC_REF_INTERNAL"; |
| 61 | + * zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>; |
| 62 | + * zephyr,input-positive = <NRF_SAADC_AIN0>; |
| 63 | + * zephyr,resolution = <12>; |
| 64 | + * }; |
| 65 | + * // other channels here (1, 4, 5, 7) |
| 66 | + * }; |
| 67 | + * |
| 68 | + */ |
| 69 | + |
| 70 | +#include <inttypes.h> |
| 71 | +#include <stddef.h> |
| 72 | +#include <stdint.h> |
| 73 | +#include <zephyr/device.h> |
| 74 | +#include <zephyr/devicetree.h> |
| 75 | +#include <zephyr/kernel.h> |
| 76 | +#include <zephyr/sys/printk.h> |
| 77 | +#include <zephyr/sys/util.h> |
| 78 | +#include <zephyr/drivers/adc.h> |
| 79 | +#include "py/mphal.h" |
| 80 | +#include "py/mperrno.h" |
| 81 | +#include "zephyr_device.h" |
| 82 | + |
| 83 | +static uint16_t sample_buffer; |
| 84 | + |
| 85 | +static struct adc_sequence adc_sequence = { |
| 86 | + .options = NULL, // No options set by default |
| 87 | + .channels = 0, |
| 88 | + .buffer = &sample_buffer, |
| 89 | + .buffer_size = sizeof(sample_buffer), // bytes, not samples |
| 90 | + .resolution = 12, // Default resolution, can be changed later |
| 91 | + .oversampling = 0, // Default oversampling, can be changed later |
| 92 | + .calibrate = false, // Default to no calibration |
| 93 | +}; |
| 94 | + |
| 95 | +#define USER_NODE DT_PATH(zephyr_user) |
| 96 | + |
| 97 | +#define DT_SPEC_AND_COMMA(node_id, prop, idx) \ |
| 98 | + ADC_DT_SPEC_GET_BY_IDX(node_id, idx), |
| 99 | + |
| 100 | +// Data of ADC io-channels specified in devicetree. |
| 101 | +static const struct adc_dt_spec adc_channels[] = { |
| 102 | + // We require that the user node has an io-channels property. |
| 103 | + #if DT_NODE_HAS_PROP(USER_NODE, io_channels) |
| 104 | + DT_FOREACH_PROP_ELEM(USER_NODE, io_channels, |
| 105 | + DT_SPEC_AND_COMMA) |
| 106 | + #endif |
| 107 | +}; |
| 108 | + |
| 109 | +#define NUM_CHANNELS ARRAY_SIZE(adc_channels) |
| 110 | + |
| 111 | +typedef struct _machine_adc_obj_t { |
| 112 | + mp_obj_base_t base; |
| 113 | + char const *name; // name given by the user in the tuple |
| 114 | + const struct adc_dt_spec *spec; // ADC device channel specification |
| 115 | +} machine_adc_obj_t; |
| 116 | + |
| 117 | + |
| 118 | +static void mp_machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { |
| 119 | + machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); |
| 120 | + |
| 121 | + mp_printf(print, "ADC(%s.%d)", self->name, self->spec->channel_id); |
| 122 | +} |
| 123 | + |
| 124 | +// constructor((adcblock, channel) | adc_obj, ...) |
| 125 | +static mp_obj_t mp_machine_adc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { |
| 126 | + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); |
| 127 | + |
| 128 | + machine_adc_obj_t *self = NULL; |
| 129 | + if (mp_obj_is_type(args[0], &machine_adc_type)) { |
| 130 | + // Already an ADC object, just return it |
| 131 | + self = MP_OBJ_TO_PTR(args[0]); |
| 132 | + } else if (mp_obj_is_type(args[0], &mp_type_tuple)) { |
| 133 | + // Get the wanted (adcblock, channel) values. |
| 134 | + mp_obj_t *items; |
| 135 | + mp_obj_get_array_fixed_n(args[0], 2, &items); |
| 136 | + const char *dev_name = mp_obj_str_get_str(items[0]); |
| 137 | + int channel_id = mp_obj_get_int(items[1]); |
| 138 | + const struct device *wanted_port = zephyr_device_find(items[0]); |
| 139 | + |
| 140 | + // Find the desired channel by device name and channel ID |
| 141 | + struct adc_dt_spec const *wanted_adc_channel = NULL; |
| 142 | + |
| 143 | + for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) { |
| 144 | + if (adc_channels[i].dev == wanted_port && |
| 145 | + adc_channels[i].channel_id == channel_id) { |
| 146 | + wanted_adc_channel = adc_channels + i; |
| 147 | + break; |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + if (!wanted_adc_channel) { |
| 152 | + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("ADC channel (%s, %d) not found"), dev_name, channel_id); |
| 153 | + } |
| 154 | + |
| 155 | + if (!adc_is_ready_dt(wanted_adc_channel)) { |
| 156 | + mp_raise_OSError(MP_EIO); |
| 157 | + } |
| 158 | + |
| 159 | + int err = adc_channel_setup_dt(wanted_adc_channel); |
| 160 | + if (err < 0) { |
| 161 | + mp_raise_OSError(MP_EINVAL); |
| 162 | + } |
| 163 | + |
| 164 | + self = mp_obj_malloc(machine_adc_obj_t, &machine_adc_type); |
| 165 | + self->spec = wanted_adc_channel; |
| 166 | + self->name = dev_name; |
| 167 | + } else { |
| 168 | + // Unknown type, raise a ValueError |
| 169 | + mp_raise_ValueError(MP_ERROR_TEXT("ADC must be initialized with a tuple of (adcblock, channel) or an ADC object")); |
| 170 | + } |
| 171 | + |
| 172 | + return MP_OBJ_FROM_PTR(self); |
| 173 | +} |
| 174 | + |
| 175 | +static mp_uint_t zephyr_adc_read(const struct adc_dt_spec *spec) { |
| 176 | + int err = adc_sequence_init_dt(spec, &adc_sequence); |
| 177 | + if (err < 0) { |
| 178 | + mp_raise_OSError(MP_EOPNOTSUPP); |
| 179 | + } |
| 180 | + |
| 181 | + err = adc_read_dt(spec, &adc_sequence); |
| 182 | + if (err < 0) { |
| 183 | + mp_raise_OSError(MP_EIO); |
| 184 | + } |
| 185 | + |
| 186 | + return (mp_uint_t)sample_buffer; // Return the read value as a 16-bit integer |
| 187 | +} |
| 188 | + |
| 189 | +static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) { |
| 190 | + mp_uint_t raw = zephyr_adc_read(self->spec); |
| 191 | + // Scale raw reading to 16 bit value using a Taylor expansion (for 8 <= bits <= 16) |
| 192 | + mp_int_t bits = self->spec->resolution; |
| 193 | + mp_uint_t u16 = raw << (16 - bits) | raw >> (2 * bits - 16); |
| 194 | + return u16; |
| 195 | +} |
| 196 | + |
| 197 | + |
| 198 | +#if MICROPY_PY_MACHINE_ADC_READ_UV |
| 199 | +static mp_int_t mp_machine_adc_read_uv(machine_adc_obj_t *self) { |
| 200 | + int32_t raw = (int32_t)zephyr_adc_read(self->spec); |
| 201 | + int err = adc_raw_to_millivolts_dt(self->spec, &raw); |
| 202 | + if (err < 0) { |
| 203 | + mp_raise_OSError(MP_EOPNOTSUPP); |
| 204 | + } |
| 205 | + return (mp_int_t)raw * 1000UL; // Convert to microvolts |
| 206 | +} |
| 207 | +#endif |
| 208 | + |
| 209 | + |
| 210 | +#if MICROPY_PY_MACHINE_ADC_READ |
| 211 | +static mp_int_t mp_machine_adc_read(machine_adc_obj_t *self) { |
| 212 | + mp_uint_t raw = zephyr_adc_read(self->spec); |
| 213 | + return (mp_int_t)raw; |
| 214 | +} |
| 215 | +#endif |
| 216 | + |
| 217 | +#define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS |
0 commit comments