|
| 1 | +/* |
| 2 | + * ADC measurement sample with work queue. |
| 3 | + * |
| 4 | + * - Performs an ADC measurement in a work queue item scheduled every 5 seconds. |
| 5 | + * - Updates a global variable with the latest ADC value (raw and millivolts). |
| 6 | + * - If the ADC value (mV) is below a configured threshold, triggers a |
| 7 | + * low-threshold callback (e.g. for alert/interrupt handling). |
| 8 | + * - After each measurement, schedules the same work again to repeat. |
| 9 | + */ |
| 10 | + |
| 11 | +#include <zephyr/device.h> |
| 12 | +#include <zephyr/kernel.h> |
| 13 | +#include <zephyr/devicetree.h> |
| 14 | +#include <zephyr/drivers/adc.h> |
| 15 | +#include <zephyr/sys/printk.h> |
| 16 | + |
| 17 | +#if !DT_NODE_EXISTS(DT_PATH(zephyr_user)) || \ |
| 18 | + !DT_NODE_HAS_PROP(DT_PATH(zephyr_user), io_channels) |
| 19 | +#error "No suitable devicetree overlay specified" |
| 20 | +#endif |
| 21 | + |
| 22 | +#define DT_SPEC_AND_COMMA(node_id, prop, idx) \ |
| 23 | + ADC_DT_SPEC_GET_BY_IDX(node_id, idx), |
| 24 | + |
| 25 | +/* Data of ADC io-channels specified in devicetree. */ |
| 26 | +static const struct adc_dt_spec adc_channels[] = { |
| 27 | + DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, |
| 28 | + DT_SPEC_AND_COMMA) |
| 29 | +}; |
| 30 | + |
| 31 | +/* Delay between measurements */ |
| 32 | +#define MEASUREMENT_INTERVAL_SEC 5 |
| 33 | + |
| 34 | +/* Threshold in millivolts: trigger callback when ADC (mV) is below this */ |
| 35 | +#define ADC_LOW_THRESHOLD_MV 1500 |
| 36 | + |
| 37 | +/** Global variable: latest ADC value in millivolts (updated by work item). */ |
| 38 | +volatile int32_t g_adc_value_mv; |
| 39 | + |
| 40 | +/** Global variable: latest raw ADC value (updated by work item). */ |
| 41 | +volatile int32_t g_adc_value_raw; |
| 42 | + |
| 43 | +/** Set when ADC value is below threshold (can be used like an interrupt flag). */ |
| 44 | +volatile bool g_adc_below_threshold; |
| 45 | + |
| 46 | +/** Optional: count how many times low threshold was triggered. */ |
| 47 | +volatile uint32_t g_adc_low_trigger_count; |
| 48 | + |
| 49 | +static int32_t adc_buffer; |
| 50 | +static struct adc_sequence sequence = { |
| 51 | + .buffer = &adc_buffer, |
| 52 | + .buffer_size = sizeof(adc_buffer), |
| 53 | +#if CONFIG_SAMPLE_ADC_CALIBRATE_REQUIRED |
| 54 | + .calibrate = true, |
| 55 | +#endif |
| 56 | +}; |
| 57 | + |
| 58 | +static void adc_work_handler(struct k_work *work); |
| 59 | + |
| 60 | +K_WORK_DELAYABLE_DEFINE(adc_work, adc_work_handler); |
| 61 | + |
| 62 | +/** |
| 63 | + * Called when ADC value is below threshold (treat as software "interrupt" handler). |
| 64 | + * Can be extended to set a GPIO, wake a thread, or raise a poll signal. |
| 65 | + */ |
| 66 | +static void adc_low_threshold_trigger(void) |
| 67 | +{ |
| 68 | + g_adc_below_threshold = true; |
| 69 | + g_adc_low_trigger_count++; |
| 70 | + printk("ADC below threshold: %d mV (raw %d)\n", |
| 71 | + (int)g_adc_value_mv, (int)g_adc_value_raw); |
| 72 | +} |
| 73 | + |
| 74 | +static int adc_setup() |
| 75 | +{ |
| 76 | + int err = 0; |
| 77 | + /* Configure channels individually prior to sampling. */ |
| 78 | + for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) { |
| 79 | + printk("Is ready %s \n", adc_channels[i].dev->name); |
| 80 | + if (!adc_is_ready_dt(&adc_channels[i])) { |
| 81 | + printk("ADC controller device %s not ready\n", adc_channels[i].dev->name); |
| 82 | + return 0; |
| 83 | + } |
| 84 | + printk("Configure %s \n", adc_channels[i].dev->name); |
| 85 | + err = adc_channel_setup_dt(&adc_channels[i]); |
| 86 | + if (err < 0) { |
| 87 | + printk("Could not setup channel #%d (%d)\n", i, err); |
| 88 | + return 0; |
| 89 | + } |
| 90 | + } |
| 91 | + return 0; |
| 92 | +} |
| 93 | + |
| 94 | +static void adc_work_handler(struct k_work *work) |
| 95 | +{ |
| 96 | + int err; |
| 97 | + |
| 98 | + (void)work; |
| 99 | + |
| 100 | + for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) { |
| 101 | + int32_t val_mv; |
| 102 | + |
| 103 | + printk("- %s, channel %d: ", |
| 104 | + adc_channels[i].dev->name, |
| 105 | + adc_channels[i].channel_id); |
| 106 | + |
| 107 | + (void)adc_sequence_init_dt(&adc_channels[i], &sequence); |
| 108 | + |
| 109 | + err = adc_read_dt(&adc_channels[i], &sequence); |
| 110 | + if (err < 0) { |
| 111 | + printk("Could not read (%d)\n", err); |
| 112 | + continue; |
| 113 | + } |
| 114 | + |
| 115 | + val_mv = adc_buffer; |
| 116 | + |
| 117 | + printk("%"PRId32, val_mv); |
| 118 | + err = adc_raw_to_millivolts_dt(&adc_channels[i], |
| 119 | + &val_mv); |
| 120 | + /* conversion to mV may not be supported, skip if not */ |
| 121 | + if (err < 0) { |
| 122 | + printk(" (value in mV not available)\n"); |
| 123 | + } else { |
| 124 | + printk(" = %"PRId32" mV\n", val_mv); |
| 125 | + } |
| 126 | + } |
| 127 | + k_work_schedule(&adc_work, K_MSEC(CONFIG_VBAT_MONITOR_INTERVAL_MS)); |
| 128 | + |
| 129 | +// /* Update global variable (raw) */ |
| 130 | +// g_adc_value_raw = adc_buffer; |
| 131 | + |
| 132 | +// /* Convert to millivolts and update global */ |
| 133 | +// ret = adc_raw_to_millivolts_dt(&adc_channel, &adc_buffer); |
| 134 | +// if (ret == 0) { |
| 135 | +// g_adc_value_mv = adc_buffer; |
| 136 | +// } else { |
| 137 | +// g_adc_value_mv = 0; |
| 138 | +// } |
| 139 | + |
| 140 | +// /* If below threshold, trigger "interrupt" (callback) */ |
| 141 | +// if (g_adc_value_mv < ADC_LOW_THRESHOLD_MV) { |
| 142 | +// adc_low_threshold_trigger(); |
| 143 | +// } else { |
| 144 | +// g_adc_below_threshold = false; |
| 145 | +// } |
| 146 | + |
| 147 | +// printk("ADC: %d mV (raw %d)%s\n", |
| 148 | +// (int)g_adc_value_mv, (int)g_adc_value_raw, |
| 149 | +// g_adc_below_threshold ? " [LOW]" : ""); |
| 150 | + |
| 151 | +// reschedule: |
| 152 | +// /* Schedule this work again in 5 seconds to repeat the process */ |
| 153 | +// k_work_schedule(&adc_work, K_SECONDS(MEASUREMENT_INTERVAL_SEC)); |
| 154 | +} |
| 155 | + |
| 156 | + |
| 157 | + |
| 158 | +int health_monitoring_setup(void) |
| 159 | +{ |
| 160 | + |
| 161 | + printk("ADC work queue sample: measure every %d s, low threshold %d mV\n", |
| 162 | + MEASUREMENT_INTERVAL_SEC, ADC_LOW_THRESHOLD_MV); |
| 163 | + |
| 164 | + adc_setup(); |
| 165 | + |
| 166 | + /* Start the cycle: first run in 5 seconds */ |
| 167 | + k_work_schedule(&adc_work, K_MSEC(CONFIG_VBAT_MONITOR_INTERVAL_MS)); |
| 168 | + |
| 169 | + // for (;;) { |
| 170 | + // k_sleep(K_SECONDS(10)); |
| 171 | + // /* Optional: print global state from main loop */ |
| 172 | + // printk("Global ADC: %d mV, low_trigger_count %u\n", |
| 173 | + // (int)g_adc_value_mv, (unsigned)g_adc_low_trigger_count); |
| 174 | + // } |
| 175 | + |
| 176 | + return 0; |
| 177 | +} |
0 commit comments