summaryrefslogtreecommitdiff
path: root/ports/zephyr/machine_adc.c
diff options
context:
space:
mode:
Diffstat (limited to 'ports/zephyr/machine_adc.c')
-rw-r--r--ports/zephyr/machine_adc.c217
1 files changed, 217 insertions, 0 deletions
diff --git a/ports/zephyr/machine_adc.c b/ports/zephyr/machine_adc.c
new file mode 100644
index 000000000..ffc694c81
--- /dev/null
+++ b/ports/zephyr/machine_adc.c
@@ -0,0 +1,217 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2025 NED KONZ <NED@METAMAGIX.TECH>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// This file is never compiled standalone, it's included directly from
+// extmod/machine_adc.c via MICROPY_PY_MACHINE_ADC_INCLUDEFILE.
+
+/*
+ * The ADC peripheral and pinmux is configured in the board's .dts file. Make
+ * sure that the ADC is enabled (status = "okay";).
+ *
+ * In addition to that, this driver requires an ADC channel specified in the
+ * io-channels property of the zephyr,user node. This is usually done with a
+ * devicetree overlay.
+ *
+ * Configuration of channels (settings like gain, reference, or acquisition time)
+ * also needs to be specified in devicetree, in ADC controller child nodes. Also
+ * the ADC resolution and oversampling setting (if used) need to be specified
+ * there.
+ *
+ * Here is an excerpt from a devicetree overlay that configures an ADC
+ * with one channel that would be referred to as ('adc', 0) in the constructor
+ * of the ADC object:
+ *
+ * / {
+ * zephyr,user {
+ * io-channels = <&adc 0>;
+ * };
+ * };
+ *
+ * &adc {
+ * #address-cells = <1>;
+ * #size-cells = <0>;
+ *
+ * channel@0 {
+ * reg = <0>;
+ * zephyr,gain = "ADC_GAIN_1_6";
+ * zephyr,reference = "ADC_REF_INTERNAL";
+ * zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
+ * zephyr,input-positive = <NRF_SAADC_AIN0>;
+ * zephyr,resolution = <12>;
+ * };
+ * // other channels here (1, 4, 5, 7)
+ * };
+ *
+ */
+
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <zephyr/device.h>
+#include <zephyr/devicetree.h>
+#include <zephyr/kernel.h>
+#include <zephyr/sys/printk.h>
+#include <zephyr/sys/util.h>
+#include <zephyr/drivers/adc.h>
+#include "py/mphal.h"
+#include "py/mperrno.h"
+#include "zephyr_device.h"
+
+static uint16_t sample_buffer;
+
+static struct adc_sequence adc_sequence = {
+ .options = NULL, // No options set by default
+ .channels = 0,
+ .buffer = &sample_buffer,
+ .buffer_size = sizeof(sample_buffer), // bytes, not samples
+ .resolution = 12, // Default resolution, can be changed later
+ .oversampling = 0, // Default oversampling, can be changed later
+ .calibrate = false, // Default to no calibration
+};
+
+#define USER_NODE DT_PATH(zephyr_user)
+
+#define DT_SPEC_AND_COMMA(node_id, prop, idx) \
+ ADC_DT_SPEC_GET_BY_IDX(node_id, idx),
+
+// Data of ADC io-channels specified in devicetree.
+static const struct adc_dt_spec adc_channels[] = {
+ // We require that the user node has an io-channels property.
+ #if DT_NODE_HAS_PROP(USER_NODE, io_channels)
+ DT_FOREACH_PROP_ELEM(USER_NODE, io_channels,
+ DT_SPEC_AND_COMMA)
+ #endif
+};
+
+#define NUM_CHANNELS ARRAY_SIZE(adc_channels)
+
+typedef struct _machine_adc_obj_t {
+ mp_obj_base_t base;
+ char const *name; // name given by the user in the tuple
+ const struct adc_dt_spec *spec; // ADC device channel specification
+} machine_adc_obj_t;
+
+
+static void mp_machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
+ machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in);
+
+ mp_printf(print, "ADC(%s.%d)", self->name, self->spec->channel_id);
+}
+
+// constructor((adcblock, channel) | adc_obj, ...)
+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) {
+ mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true);
+
+ machine_adc_obj_t *self = NULL;
+ if (mp_obj_is_type(args[0], &machine_adc_type)) {
+ // Already an ADC object, just return it
+ self = MP_OBJ_TO_PTR(args[0]);
+ } else if (mp_obj_is_type(args[0], &mp_type_tuple)) {
+ // Get the wanted (adcblock, channel) values.
+ mp_obj_t *items;
+ mp_obj_get_array_fixed_n(args[0], 2, &items);
+ const char *dev_name = mp_obj_str_get_str(items[0]);
+ int channel_id = mp_obj_get_int(items[1]);
+ const struct device *wanted_port = zephyr_device_find(items[0]);
+
+ // Find the desired channel by device name and channel ID
+ struct adc_dt_spec const *wanted_adc_channel = NULL;
+
+ for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) {
+ if (adc_channels[i].dev == wanted_port &&
+ adc_channels[i].channel_id == channel_id) {
+ wanted_adc_channel = adc_channels + i;
+ break;
+ }
+ }
+
+ if (!wanted_adc_channel) {
+ mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("ADC channel (%s, %d) not found"), dev_name, channel_id);
+ }
+
+ if (!adc_is_ready_dt(wanted_adc_channel)) {
+ mp_raise_OSError(MP_EIO);
+ }
+
+ int err = adc_channel_setup_dt(wanted_adc_channel);
+ if (err < 0) {
+ mp_raise_OSError(MP_EINVAL);
+ }
+
+ self = mp_obj_malloc(machine_adc_obj_t, &machine_adc_type);
+ self->spec = wanted_adc_channel;
+ self->name = dev_name;
+ } else {
+ // Unknown type, raise a ValueError
+ mp_raise_ValueError(MP_ERROR_TEXT("ADC must be initialized with a tuple of (adcblock, channel) or an ADC object"));
+ }
+
+ return MP_OBJ_FROM_PTR(self);
+}
+
+static mp_uint_t zephyr_adc_read(const struct adc_dt_spec *spec) {
+ int err = adc_sequence_init_dt(spec, &adc_sequence);
+ if (err < 0) {
+ mp_raise_OSError(MP_EOPNOTSUPP);
+ }
+
+ err = adc_read_dt(spec, &adc_sequence);
+ if (err < 0) {
+ mp_raise_OSError(MP_EIO);
+ }
+
+ return (mp_uint_t)sample_buffer; // Return the read value as a 16-bit integer
+}
+
+static mp_int_t mp_machine_adc_read_u16(machine_adc_obj_t *self) {
+ mp_uint_t raw = zephyr_adc_read(self->spec);
+ // Scale raw reading to 16 bit value using a Taylor expansion (for 8 <= bits <= 16)
+ mp_int_t bits = self->spec->resolution;
+ mp_uint_t u16 = raw << (16 - bits) | raw >> (2 * bits - 16);
+ return u16;
+}
+
+
+#if MICROPY_PY_MACHINE_ADC_READ_UV
+static mp_int_t mp_machine_adc_read_uv(machine_adc_obj_t *self) {
+ int32_t raw = (int32_t)zephyr_adc_read(self->spec);
+ int err = adc_raw_to_millivolts_dt(self->spec, &raw);
+ if (err < 0) {
+ mp_raise_OSError(MP_EOPNOTSUPP);
+ }
+ return (mp_int_t)raw * 1000UL; // Convert to microvolts
+}
+#endif
+
+
+#if MICROPY_PY_MACHINE_ADC_READ
+static mp_int_t mp_machine_adc_read(machine_adc_obj_t *self) {
+ mp_uint_t raw = zephyr_adc_read(self->spec);
+ return (mp_int_t)raw;
+}
+#endif
+
+#define MICROPY_PY_MACHINE_ADC_CLASS_CONSTANTS