I did some tests with some codes calling directly into LL_ADC codes in HAL vs analogRead(). The sketch is rather lengthy as such:
Code: Select all
#include <Arduino.h>
#include <variant_BLACKPILL_F401CC.h>
void checkcmd(void);
void printtempvbat(void);
//void enablefpu(void);
void ARTtoggle();
uint16_t readADC(uint32_t pin);
void testADC();
void adcregs();
uint32_t get_adc_channel(PinName pin);
void sleep(uint16_t ms);
int ledPin = LED_BUILTIN;
int print = false;
//used by cosfade()
#define PER 15
#define REP 20
int8_t led = 0;
int n = PER, cnt = 0;
uint8_t p = 0;
//#define DEBUG
// the setup() method runs once when the sketch starts
void setup() {
// not needed fpu is enabled in the core
// enablefpu();
ARTtoggle();
//initialize the digital pin as an output:
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
//these are codes for steve's libmaple core, left here as a ref
// ADC_CCR_VBATE; // f401 allows only vbat or temp sensor read, setting VBATE reads vbat
//ADC_COMMON->CCR |= ADC_CCR_TSVREFE; //| ADC_CCR_VBATE; // enable VREFINT, VBAT or temp sensor
//adc_calibrate(ADC1);
//adc_set_sampling_time(ADC1, ADC_SMPR_480); // sample time for temp sensor
pinMode(ATEMP, INPUT_ANALOG);
pinMode(AVREF, INPUT_ANALOG);
pinMode(PA0, INPUT_ANALOG);
analogReadResolution(12);
while(!Serial.available()) {
Serial.println("press any key to start");
sleep(1000);
}
Serial.read();
}
// Enable the FPU (Cortex-M4 - STM32F4xx and higher)
// http://infocenter.arm.com/help/topic/com.arm.doc.dui0553a/BEHBJHIG.html
//void enablefpu() {
// __asm volatile
// (
// " ldr.w r0, =0xE000ED88 \n" /* The FPU enable bits are in the CPACR. */
// " ldr r1, [r0] \n" /* read CAPCR */
// " orr r1, r1, #( 0xf << 20 )\n" /* Set bits 20-23 to enable CP10 and CP11 coprocessors */
// " str r1, [r0] \n" /* Write back the modified value to the CPACR */
// " dsb \n" /* wait for store to complete */
// " isb" /* reset pipeline now the FPU is enabled */
// );
//}
//the loop() method runs over and over again,
//as long as maple has power
void loop() {
digitalToggle(LED_BUILTIN);
checkcmd();
printtempvbat();
testADC();
sleep(2000);
}
void testADC() {
const int N = 10000;
//adc test
Serial.print(N);
Serial.println(" analog reads");
float ave = 0.0F;
uint32_t start = millis();
for(uint16_t i=0; i<N; i++) {
ave += analogRead(PA0) / 4096.0F * 3.3F;
}
uint32_t dur = millis() - start;
ave = ave / (N * 1.0F);
Serial.print("dur (ms):");
Serial.println(dur);
Serial.print("ave:");
Serial.println(ave);
// fast
start = millis();
ave = 0.0F;
// enable adc clocks
__HAL_RCC_ADC1_CLK_ENABLE();
LL_ADC_REG_SetSequencerDiscont(ADC1, LL_ADC_REG_SEQ_DISCONT_1RANK);
LL_ADC_Enable(ADC1);
adcregs();
while(!LL_ADC_IsEnabled(ADC1));
//while(!(ADC1->CR2 & ADC_CR2_ADON_Msk));
for(uint16_t i=0; i<N; i++) {
ave += readADC(PA0) / 4096.0F * 3.3F;
}
dur = millis() - start;
ave = ave / (N * 1.0F);
Serial.print("dur (ms):");
Serial.println(dur);
Serial.print("ave:");
Serial.println(ave);
}
uint16_t readADC(uint32_t pin) {
// "analogRead"
PinName pn = digitalPinToPinName(analogInputToDigitalPin(pin));
uint32_t channel = get_adc_channel(pn);
LL_ADC_REG_SetSequencerLength(ADC1, LL_ADC_REG_SEQ_SCAN_DISABLE);
LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, channel);
LL_ADC_SetChannelSamplingTime(ADC1, channel, LL_ADC_SAMPLINGTIME_15CYCLES);
// start conversion
LL_ADC_ClearFlag_EOCS(ADC1);
LL_ADC_REG_StartConversionSWStart(ADC1);
// another spinlock to check end of conversion;
while (!LL_ADC_IsActiveFlag_EOCS(ADC1));
// ok we've got an end-of-conversion
LL_ADC_ClearFlag_EOCS(ADC1);
// now read the register
uint16_t value = LL_ADC_REG_ReadConversionData12(ADC1);
return value;
}
uint32_t get_adc_channel(PinName pin)
{
uint32_t function = pinmap_function(pin, PinMap_ADC);
uint32_t channel = 0;
switch (STM_PIN_CHANNEL(function)) {
case 0:
channel = LL_ADC_CHANNEL_0;
break;
case 1:
channel = LL_ADC_CHANNEL_1;
break;
case 2:
channel = LL_ADC_CHANNEL_2;
break;
case 3:
channel = LL_ADC_CHANNEL_3;
break;
case 4:
channel = LL_ADC_CHANNEL_4;
break;
case 5:
channel = LL_ADC_CHANNEL_5;
break;
case 6:
channel = LL_ADC_CHANNEL_6;
break;
case 7:
channel = LL_ADC_CHANNEL_7;
break;
case 8:
channel = LL_ADC_CHANNEL_8;
break;
case 9:
channel = LL_ADC_CHANNEL_9;
break;
case 10:
channel = LL_ADC_CHANNEL_10;
break;
case 11:
channel = LL_ADC_CHANNEL_11;
break;
case 12:
channel = LL_ADC_CHANNEL_12;
break;
case 13:
channel = LL_ADC_CHANNEL_13;
break;
case 14:
channel = LL_ADC_CHANNEL_14;
break;
case 15:
channel = LL_ADC_CHANNEL_15;
break;
case 16:
channel = LL_ADC_CHANNEL_16;
break;
case 17:
channel = LL_ADC_CHANNEL_17;
break;
case 18:
channel = LL_ADC_CHANNEL_18;
break;
default:
channel = 0;
break;
}
return channel;
}
void adcregs() {
#ifdef DEBUG
Serial.print("addr:");
Serial.println((uint32_t) ADC1, HEX);
//uint32_t control asm("control");
uint32_t control = __get_CONTROL();
Serial.print("control:");
Serial.println((uint32_t) control, HEX);
uint32_t psr = __get_APSR();
Serial.print("psr:");
Serial.println((uint32_t) psr, HEX);
Serial.print("SR:");
Serial.println(ADC1->SR, HEX);
Serial.print("CR1:");
Serial.println(ADC1->CR1, HEX);
Serial.print("CR2:");
Serial.println(ADC1->CR2, HEX);
Serial.print("SQR1:");
Serial.println(ADC1->SQR1, HEX);
Serial.print("SQR3:");
Serial.println(ADC1->SQR3, HEX);
Serial.print("SMPR2:");
Serial.println(ADC1->SMPR2, HEX);
Serial.print("CCR:");
Serial.println(ADC1_COMMON->CCR, HEX);
#endif //DEBUG
}
void printtempvbat() {
//ADC1 channel 18 is vrefint 1.23v
//uint16_t vrefint = adc_read(ADC1, 17);
uint16_t vrefint = analogRead(AVREF);
Serial.print("Vref int (1.21v):");
Serial.print(vrefint);
Serial.println();
//ADC1 channel 16 is temperature sensor
//uint16_t vtemp = adc_read(ADC1, 18);
uint16_t vtemp = analogRead(ATEMP);
Serial.print("temp sensor:");
Serial.print(vtemp);
Serial.println();
uint16_t mv = (1210 * vtemp) / vrefint;
Serial.print("mvolt:");
Serial.print(mv);
Serial.println();
// specs 5.3.22 temp sensor characteristics
// V 25 deg ~ 0.76v
// slope 2.5 mv/C
uint16_t v25 = 760;
float temp = (mv - v25) * 1.0 / 2.5 + 25.0;
Serial.print("temp:");
Serial.print(temp);
Serial.println();
/* for stm32f401 it is either temp or vbat
//ADC1 channel 17 is VBAT / 2
uint16_t vbatsens = adc_read(ADC1, 18);
Serial.print("VBAT/2 sensor:");
Serial.print(vbatsens);
Serial.println();
uint16_t vbat = vbatsens * 2.0 * 1230 / vrefint;
Serial.print("VBAT (mv):");
Serial.print(vbat);
Serial.println();
*/
}
void checkcmd() {
if(Serial.available()) {
char r = Serial.read();
if(r==' ') {
Serial.println("paused");
while(!Serial.available())
sleep(100);
Serial.read();
} else {
}
}
}
void ARTtoggle() {
if((FLASH->ACR & FLASH_ACR_ICEN)!=FLASH_ACR_ICEN) { // art enabled
/* enable the ART accelerator */
/* enable prefetch buffer */
FLASH->ACR |= FLASH_ACR_PRFTEN;
/* Enable flash instruction cache */
FLASH->ACR |= FLASH_ACR_ICEN;
/* Enable flash data cache */
FLASH->ACR |= FLASH_ACR_DCEN;
asm("wfi"); //wait for a systick interrupt i.e. delay(1)
Serial.println("ART enabled");
} else {
/* disable the ART accelerator */
/* disable flash instruction cache */
//FLASH->ACR &= ~FLASH_ACR_ICEN;
/* disable flash data cache */
//FLASH->ACR &= ~FLASH_ACR_DCEN;
/* enable prefetch buffer */
//FLASH->ACR |= FLASH_ACR_PRFTEN;
//asm("wfi"); //wait for a systick interrupt, i.e. delay(1)
//Serial.println("ART disabled");
}
}
void sleep(uint16_t ms) {
for(uint16_t i=0; i<ms; i++)
asm("wfi");
}
I debugged it a little and found out that apparently I'd need to call __HAL_RCC_ADC1_CLK_ENABLE(); to enable the ADC clocks.
Otherwise, all the updates are void and nothing happens.
The codes compare reading 10,000 adc values on pin PA0 comparing between analogRead() vs the low layer HAL functions.
Actually, this is a poor comparison as it'd probably be better to compare between the more abstracted HAL, but i used LL codes in part to avoid possible 'overlaps' in accessing ADC handles etc.
the results looks like such. The mcu is stm32f401ccu the first set of readings after 10000 analog reads() is done using analogRead(), the second set using LL HAL functions.
Code: Select all
Vref int (1.21v):1502
temp sensor:956
mvolt:770
temp:29.00
10000 analog reads
dur (ms):509
ave:1.23
dur (ms):28
ave:1.25
Vref int (1.21v):1502
temp sensor:952
mvolt:766
temp:27.40
10000 analog reads
dur (ms):509
ave:1.23
dur (ms):28
ave:1.25
paused
The differences are rather large, for 10000 ADC reads(), the duration is 509 ms using analogRead() while the same done with LL codes is 28 ms.
It turns out that much of that difference is due to that analogRead() initializes the ADC each time analogRead() is called, it clocks the ADC,
setup all the registers, enable the adc.
Then the actual sampling is done to start conversion, poll for end of conversion and read the data register.
Next it proceed to deInit() the adc before returning the results.
The ADC turn on clocks and init() is very costly in terms of processing time as the codes often need to spin lock and wait for the adc to be ready in a particular state. e.g. check that the clocks are on, then set all the relevant registers and enable the adc, then spin lock to see that the adc is enabled before proceeding to perform conversions.
while in my LL based codes, the adc initialization is done once, clock the adc and enable it, then spinlock for adc enabled.
while for each adc read(), the LL based codes only does is select the ADC channel/pin, SWSTART to start conversion and poll for EOC (end of conversion) and read results.
This accounts for the significant differences in run times.
I'd think these changes if they are to be implemented are rather large in the core codes. i.e. the adc pretty much needs to be initialized at startup / reset.
and analogRead() pretty much does selecting ADC channel/pin, SWSTART to start conversion and poll for EOC and read results. This would significantly reduce the time lost caused by the init and deinit phase of analogRead().
I think this attempt is still worth doing despite the effort as the use cases of analogRead() are pretty large, I'd think there are quite many codes that probably calls analogRead() from interrupts e.g. timer to poll the adc periodically. one could think of 3d printers (Marlin) using analogRead() to poll thermistors say every 1s and maybe audio processing apps that does pretty much the same from interrupts. And as analogRead() is pretty much the only interface spec in the Arduino / Wiring api that does analog reads, a lot of codes reference it for various sensors etc.
A trouble is due to the rather large different capabilities of different stm32 processors, this change may be 'hard to implement'. I'd guess a focus would mainly be just providing ADC1 for analogRead(). I'm not sure if after all there may be stm32 mcus that has no adc, that'd be a surprise, but it'd take checks in such a large portfolio of skus.