From 6cc66e26c127ac3beabbec48f0fec35c6386a8ac Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sat, 28 Jan 2023 13:58:55 +0800 Subject: [PATCH] Initial STM32F4xx fast ADC read implementation --- CommandStation-EX.ino | 3 + DCCTimer.h | 14 +++-- DCCTimerSAMD.cpp | 19 +------ DCCTimerSTM32.cpp | 129 ++++++++++++++++++++++++++++++++++++++++-- DCCWaveform.cpp | 1 - 5 files changed, 136 insertions(+), 30 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index f003ef6..67a7e58 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -99,6 +99,9 @@ void setup() // Initialise HAL layer before reading EEprom or setting up MotorDrivers IODevice::begin(); + // As the setup of a motor shield may require a read of the current sense input from the ADC, + // let's make sure to initialise the ADCee class! + ADCee::begin(); // Responsibility 3: Start the DCC engine. // Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s) // Standard supported devices have pre-configured macros but custome hardware installations require diff --git a/DCCTimer.h b/DCCTimer.h index ed87e1f..75be8cd 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -1,5 +1,5 @@ /* - * © 2022 Paul M. Antoine + * © 2022-2023 Paul M. Antoine * © 2021 Mike S * © 2021-2022 Harald Barth * © 2021 Fred Decker @@ -102,9 +102,14 @@ private: // that an offset can be initialized. class ADCee { public: - // init does add the pin to the list of scanned pins (if this + // begin is called for any setup that must be done before + // **init** can be called. On some architectures this involves ADC + // initialisation and clock routing, sampling times etc. + static void begin(); + // init adds the pin to the list of scanned pins (if this // platform's implementation scans pins) and returns the first - // read value. It is called before the regular scan is started. + // read value (which is why it required begin to have been called first!) + // It must be called before the regular scan is started. static int init(uint8_t pin); // read does read the pin value from the scanned cache or directly // if this is a platform that does not scan. fromISR is a hint if @@ -117,9 +122,6 @@ private: // On platforms that scan, it is called from waveform ISR // only on a regular basis. static void scan(); - // begin is called for any setup that must be done before - // scan can be called. - static void begin(); // bit array of used pins (max 16) static uint16_t usedpins; // cached analog values (malloc:ed to actual number of ADC channels) diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp index 7f10169..463cc64 100644 --- a/DCCTimerSAMD.cpp +++ b/DCCTimerSAMD.cpp @@ -168,23 +168,6 @@ int ADCee::init(uint8_t pin) { if (id > NUM_ADC_INPUTS) return -1023; - // Dummy read using Arduino library - analogReadResolution(12); - value = analogRead(pin); - - // Reconfigure ADC - ADC->CTRLA.bit.ENABLE = 0; // disable ADC - while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization - - ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits - ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 16 - ADC_CTRLB_RESSEL_12BIT; // Result 12 bits, 10 bits possible - ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time - ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0 - ADC->SAMPCTRL.reg = 0x00ul; // sampling Time Length = 0 - ADC->CTRLA.bit.ENABLE = 1; // enable ADC - while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization - // Permanently configure SAMD IO MUX for that pin pinPeripheral(pin, PIO_ANALOG); ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber; // Selection for the positive ADC input @@ -205,9 +188,11 @@ int ADCee::init(uint8_t pin) { return value; } + int16_t ADCee::ADCmax() { return 4095; } + /* * Read function ADCee::read(pin) to get value instead of analogRead(pin) */ diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 0f60507..b1a90ac 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -131,31 +131,148 @@ void DCCTimer::reset() { while(true) {}; } +#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS + +// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs! +uint16_t ADCee::usedpins = 0; +int * ADCee::analogvals = NULL; +uint32_t * analogchans = NULL; +bool adc1configured = false; + int16_t ADCee::ADCmax() { return 4095; } int ADCee::init(uint8_t pin) { - return analogRead(pin); + uint id = pin - A0; + int value = 0; + PinName stmpin = digitalPin[analogInputPin[id]]; + uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO port group on STM32 + uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!) + GPIO_TypeDef * gpioBase; + + // Port config - find which port we're on and power it up + switch(stmgpio) { + case 0x00: + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA + gpioBase = GPIOA; + break; + case 0x01: + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; //Power up PORTB + gpioBase = GPIOB; + break; + case 0x02: + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC + gpioBase = GPIOC; + break; + } + + // Set pin mux mode to analog input + gpioBase->MODER |= (0b011 << (stmpin << 1)); // Set pin mux to analog mode + + // Set the sampling rate for that analog input + if (adcchan < 10) + ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles. 16MHz bus clock for ADC. 1/16MHz = 62.5ns. 480*62.5ns=30us + else + ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles. 16MHz bus clock for ADC. 1/16MHz = 62.5ns. 480*62.5ns=30us + + // Read the inital ADC value for this analog input + ADC1->SQR3 = adcchan; // 1st conversion in regular sequence + ADC1->CR2 |= (1 << 30); // Start 1st conversion SWSTART + while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete + value = ADC1->DR; // Read value from register + + if (analogvals == NULL) + { + analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); + analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t)); + } + analogvals[id] = value; // Store sampled value + analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin + usedpins |= (1 << id); // This pin is now ready + + return value; } + /* * Read function ADCee::read(pin) to get value instead of analogRead(pin) */ int ADCee::read(uint8_t pin, bool fromISR) { - int current; - if (!fromISR) noInterrupts(); - current = analogRead(pin); - if (!fromISR) interrupts(); - return current; + uint8_t id = pin - A0; + // Was this pin initialised yet? + if ((usedpins & (1<SR & (1 << 1))) + return; // no result, continue to wait + // found value + analogvals[id] = ADC1->DR; + // advance at least one track + // for scope debug TrackManager::track[1]->setBrake(0); + waiting = false; + id++; + mask = mask << 1; + if (id == NUM_ADC_INPUTS+1) { + id = 0; + mask = 1; + } + } + if (!waiting) { + if (usedpins == 0) // otherwise we would loop forever + return; + // look for a valid track to sample or until we are around + while (true) { + if (mask & usedpins) { + // start new ADC aquire on id + ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence + ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART + // for scope debug TrackManager::track[1]->setBrake(1); + waiting = true; + return; + } + id++; + mask = mask << 1; + if (id == NUM_ADC_INPUTS+1) { + id = 0; + mask = 1; + } + } + } } +#pragma GCC pop_options void ADCee::begin() { noInterrupts(); + //ADC1 config sequence + // TODO: currently defaults to ADC1, may need more to handle other members of STM32F4xx family + RCC->APB2ENR |= (1 << 8); //Enable ADC1 clock (Bit8) + // Set ADC prescaler - DIV8 ~ 40ms, DIV6 ~ 30ms, DIV4 ~ 20ms, DIV2 ~ 11ms + ADC->CCR = (0 << 16); // Set prescaler 0=DIV2, 1=DIV4, 2=DIV6, 3=DIV8 + ADC1->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8) + ADC1->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00) + ADC1->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion + ADC1->CR2 &= ~(1 << 1); //Single conversion + ADC1->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0 + ADC1->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register + ADC1->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register + ADC1->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register + ADC1->CR2 |= (1 << 0); // Switch on ADC1 interrupts(); } #endif \ No newline at end of file diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index f807c34..e065648 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -62,7 +62,6 @@ const bool signalTransform[]={ /* WAVE_PENDING (should not happen) -> */ LOW}; void DCCWaveform::begin() { - ADCee::begin(); DCCTimer::begin(DCCWaveform::interruptHandler); }