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.
This commit is contained in:
true 2023-10-19 19:53:04 -07:00
parent ecc478661f
commit 13700b96ca
4 changed files with 147 additions and 66 deletions

View File

@ -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();

110
src/adc.c
View File

@ -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 <stdlib.h>
#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;
}

View File

@ -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();
}
}
}
}

View File

@ -25,19 +25,24 @@
#include <stdint.h>
#include <stdlib.h>
#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;
}
}
}
}