From 13700b96ca6bc5446d8ec1b915e5ad16f91d6cb4 Mon Sep 17 00:00:00 2001 From: true Date: Thu, 19 Oct 2023 19:53:04 -0700 Subject: [PATCH] WIP Code ADC now works in interrupt mode. No idea why DMA was unreliable. Initial button push / release detection and mode switch detection working. No callbacks implemented yet. --- include/adc.h | 10 ++++- src/adc.c | 110 +++++++++++++++++++++++++++++++------------------- src/main.c | 27 ++++--------- src/userio.c | 66 +++++++++++++++++++++++++++--- 4 files changed, 147 insertions(+), 66 deletions(-) diff --git a/include/adc.h b/include/adc.h index f238d5d..213f73a 100644 --- a/include/adc.h +++ b/include/adc.h @@ -13,8 +13,16 @@ -void adc_init(); +#define ADC_CHANNELS 6 +#define ADC_HISTLEN 16 + + +extern uint16_t adc_avg[ADC_CHANNELS]; + + + +void adc_init(); uint8_t adc_next(); diff --git a/src/adc.c b/src/adc.c index d60aedd..7401f43 100644 --- a/src/adc.c +++ b/src/adc.c @@ -12,6 +12,12 @@ * sampling time is increased. this results in a final ADC sample rate * of about 33KHz, still well beyond what is needed in this application. * this value may change in the future. + * + * note: attempted to use DMA for this and scanning all channels + * at once, but that didn't work. would not reliably read channels. + * tried many different variations and it just wasn't working. + * because of this I moved to using interrupt and the built-in + * wait mode just in case things went slow. * * file creation: 20231015 0121 */ @@ -19,14 +25,15 @@ #include +#include "adc.h" #include "testo.h" #define SAMPLE_TIME LL_ADC_SAMPLINGTIME_239CYCLES_5 -#define ADC_CHANNELS 6 -#define ADC_HISTLEN 16 +#define ADC_SEQ_RDY 0xff +#define ADC_SEQ_STARTUP 0xfe #define CONF_CALIBRATE (1 << 0) #define CONF_USEPROBE (1 << 1) @@ -35,8 +42,10 @@ static uint16_t adc_read[ADC_CHANNELS]; static uint16_t adc_hist[ADC_CHANNELS][ADC_HISTLEN]; -static uint16_t adc_avg[ADC_CHANNELS]; +uint16_t adc_avg[ADC_CHANNELS]; + +static uint8_t adc_seq = 0; static uint8_t adc_idx = ADC_HISTLEN + 1; static int16_t vref; @@ -61,13 +70,15 @@ void adc_init() ADC_CHSELR_CHSEL11 | ADC_CHSELR_CHSEL12; ADC1->SMPR = SAMPLE_TIME; - // run in non-circular DMA mode, with wait mode enabled - ADC1->CFGR1 = ADC_CFGR1_OVRMOD | ADC_CFGR1_WAIT | LL_ADC_REG_DMA_TRANSFER_LIMITED; - - // by default, DMA selection for all channels is ADC - // so all we need to do is set the peripheral address - DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR; - DMA1_Channel1->CMAR = (uint32_t)adc_read; + // run in one-shot mode (scan all channels) but with wait mode active + ADC1->CFGR1 = ADC_CFGR1_OVRMOD | ADC_CFGR1_WAIT; + + // enable end of conversion interrupt + ADC1->IER = ADC_IER_EOCIE; + + // and enable the interrupt source in the NVIC + NVIC_EnableIRQ(ADC_COMP_IRQn); + NVIC_SetPriority(ADC_COMP_IRQn, 3); } /* @@ -89,16 +100,8 @@ void adc_go() // stop if conversion in progress (it shouldn't ever be) adc_stop(); - // configure and enable DMA - DMA1_Channel1->CCR = 0; - DMA1_Channel1->CNDTR = ADC_CHANNELS; - DMA1_Channel1->CCR = LL_DMA_PRIORITY_HIGH | - LL_DMA_MDATAALIGN_HALFWORD | - LL_DMA_PDATAALIGN_HALFWORD | - LL_DMA_MEMORY_INCREMENT | - LL_DMA_MODE_NORMAL | - LL_DMA_DIRECTION_PERIPH_TO_MEMORY | - DMA_CCR_EN; + // restart sequence + adc_seq = 0; // enable and start ADC ADC1->ISR = 0x1e; // clear all interrupt flags (per DS; mistranslated) @@ -122,6 +125,7 @@ void adc_switch0() adc_hist[0][i] = 0; } + // reset history index adc_idx = 0; // force ADC read to get fresh data @@ -131,34 +135,46 @@ void adc_switch0() uint8_t adc_next() { uint8_t i, j; - int16_t w; - - // stop if conversion in progress (it shouldn't ever be) - adc_stop(); + int16_t w; - // start ADC calibration - if (calibrate) { - calibrate = 0; - - // disable ADC and start calibration - ADC1->CR &= ~(ADC_CR_ADEN); - ADC1->CR |= ADC_CR_ADCAL; - } - - // copy data to history from last read + // on first boot, do a calibration if (adc_idx > ADC_HISTLEN) { // nothing to copy when first starting adc_idx = 0; calibrate = 1; - } else { - // copy data to history + } + + // start ADC calibration + if (calibrate) { + adc_stop(); + + // disable ADC and start calibration + ADC1->CR &= ~(ADC_CR_ADEN); + ADC1->CR |= ADC_CR_ADCAL; + + // clear cal flag; reset sequence to allow conversions to start + calibrate = 0; + adc_seq = ADC_SEQ_RDY; + + // wait for calibration to complete, if started + while (ADC1->CR & ADC_CR_ADCAL) {}; + + // do our first round of conversions + adc_go(); + + return ADC_SEQ_STARTUP; + } + + if (adc_seq == ADC_SEQ_RDY) { + // copy read data to history for (i = 0; i < ADC_CHANNELS; i++) { adc_hist[i][adc_idx] = adc_read[i]; } - // next one - // if we're looping, then average our last reads + // and start over again adc_idx++; + + // if we're looping, then average our last reads if (adc_idx >= ADC_HISTLEN) { adc_idx = 0; @@ -182,10 +198,22 @@ uint8_t adc_next() } } - // wait for calibration to complete, if started - while (ADC1->CR & ADC_CR_ADCAL) {}; - adc_go(); return adc_idx; +} + +void ADC_COMP_IRQHandler() +{ + if (adc_seq != 0xff) { + adc_read[adc_seq++] = ADC1->DR; + + // is this the end of conversions? + if (ADC1->ISR & ADC_ISR_EOSEQ) { + adc_seq = ADC_SEQ_RDY; + } + } + + // clear all interrupt flags + ADC1->ISR = 0x1e; } \ No newline at end of file diff --git a/src/main.c b/src/main.c index 4cbde96..193d17b 100644 --- a/src/main.c +++ b/src/main.c @@ -81,12 +81,6 @@ static inline void systick_init() // configure timebase with interrupt at 4096Hz // this assumes we'll always be running at 8MHz SysTick_Config((8000000 / 4096) - 1); - - // configure unused DMA channels to not be on ADC - // (otherwise ADC can shows reads of 0... it's a hardware bug) - SYSCFG->CFGR3 = LL_SYSCFG_DMA_MAP_ADC | // ch0 - LL_SYSCFG_DMA_MAP_TIM17_UP << 8 | // ch1 - LL_SYSCFG_DMA_MAP_TIM17_UP << 16; // ch2 } /* @@ -124,6 +118,7 @@ int main() /* * main application interrupt */ + void SysTick_Handler(void) { uint16_t cs; @@ -144,18 +139,12 @@ void SysTick_Handler(void) // shifted counter for use in the program cs = ctr >> 2; - // adc (512Hz/16 avg = 32Hz update rate) - if (cs & 1) { - if (!adc_next()) { - // adc has new computed results, - // so we can do userio, probe, etc - - // but the results are only valid - // after our first cycle - if (uptime || cs) { - userio_parse(); - } - } - } + // adc tested to result in about 61 reads/second + if (!adc_next()) { + // adc has new computed results, so we can do userio, probe, etc + if (uptime || cs) { + userio_parse(); + } + } } } \ No newline at end of file diff --git a/src/userio.c b/src/userio.c index 0ccd13c..82ac5e6 100644 --- a/src/userio.c +++ b/src/userio.c @@ -25,19 +25,24 @@ #include +#include + +#include "adc.h" +#include "testo.h" #define MODE_CONT 0 -#define MODE_CONT_TARGET (4096 * (1 - (1 / 4))) +#define MODE_CONT_TARGET 3072 // (4096 * (1 - (1 / 4))) #define MODE_FUN 1 -#define MODE_FUN_TARGET (4096 * (1 - (1 / 3))) +#define MODE_FUN_TARGET 2731 // (4096 * (1 - (1 / 3))) #define MODE_DIODE 2 -#define MODE_DIODE_TARGET (4096 * (1 - (1 / 2))) +#define MODE_DIODE_TARGET 2048 // (4096 * 1 - ((1 / 2))) -#define MODE_HYSTERESIS 60 // 3x worst case expected (1% tol) +#define MODE_SET_LIMIT 60 // 3x worst case expected (1% tol) +#define MODE_CHANGE_TICKS 4 // how many samples before switching modes #define MODE_ANALOG_MIN 20 @@ -46,7 +51,7 @@ uint8_t mode; uint8_t mode_next; uint8_t mode_count; -static const uint16_t mode_targets[] = { +static const int16_t mode_targets[] = { MODE_CONT_TARGET, MODE_FUN_TARGET, MODE_DIODE_TARGET }; @@ -64,5 +69,56 @@ void userio_update() void userio_parse() { + uint8_t i; + volatile int16_t m, w; + + // button + // debounce is handled by the fact that we've had to have averaged + // about 16ms of measured hold time below the zero threshold. + m = adc_avg[ADC_SET_MODE]; + if (m < MODE_ANALOG_MIN) { + // button is pushed + btn = 1; + if (btn_held != 0xffff) btn_held++; + } else if (btn == 1) { + // button is released + btn = 0; + } + // mode + if (!btn) { + for (i = 0; i < 4; i++) { + // clear mode_count if we aren't actively changing modes + if (i == 3) { + mode_count = 0; + break; + } + + // normalize mode selection + w = abs(m - mode_targets[i]); + + // determine if we mode switch + if (w < MODE_SET_LIMIT) { + // currently in this mode? if so, ignore it + if (mode == i) continue; + + // is the next mode set to this item? + if (mode_next == i) { + // increment the verification count + mode_count++; + if (mode_count >= MODE_CHANGE_TICKS) { + // we've got enough data to do a change of mode + mode = i; + mode_count = 0; + } + } else { + // set the next mode to the newly selected mode + mode_next = i; + mode_count = 0; + } + + break; + } + } + } } \ No newline at end of file