cancel
Showing results for 
Search instead for 
Did you mean: 

STM32L475V: Using ADC without HAL fails miserably

EThom.3
Senior II

I am officially confused.

I'm trying to use an ADC without the HAL layer, and for some reason I just don't get it to work. With HAL, it works fine.

I have tried comparing register contents, and I've now run into a wall. There must be something the HAL function is doing that I am missing or misunderstanding.

In this test, I am trying to sample ADC2, channel 16, just once, without DMA or anything. With my own code, ADCSTART never goes low after I've started the ADC.

When I check what the HAL code does differently, I simply don't get it.

The HAL setup code (which works):

      ADC2Handle->Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
      ADC2Handle->Init.ContinuousConvMode = DISABLE;
      ADC2Handle->Init.DMAContinuousRequests = DISABLE;
      ADC2Handle->Init.DataAlign = ADC_DATAALIGN_RIGHT;
      ADC2Handle->Init.DiscontinuousConvMode = DISABLE;
      ADC2Handle->Init.EOCSelection = ADC_EOC_SINGLE_CONV;
      ADC2Handle->Init.ExternalTrigConv = ADC_SOFTWARE_START;
      ADC2Handle->Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
      ADC2Handle->Init.LowPowerAutoWait = DISABLE;
      ADC2Handle->Init.NbrOfConversion = 1;
      ADC2Handle->Init.NbrOfDiscConversion = 0;
      ADC2Handle->Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
      ADC2Handle->Init.OversamplingMode = DISABLE;
      ADC2Handle->Init.Resolution = ADC_RESOLUTION_12B;
      ADC2Handle->Init.ScanConvMode = ADC_SCAN_ENABLE;
      HAL_ADC_Init(ADC2Handle);

      ChannelCfg.Channel = 16;
      ChannelCfg.Offset = 0;
      ChannelCfg.OffsetNumber = ADC_OFFSET_NONE;
      ChannelCfg.Rank = ADC_REGULAR_RANK_1;
      ChannelCfg.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
      ChannelCfg.SingleDiff = ADC_SINGLE_ENDED;
      HAL_ADC_ConfigChannel(ADC2Handle, &ChannelCfg);
      HAL_ADC_Start(ADC2Handle);

In short, I just want to sample channel 16 once, with 12 bit resolution and 640.5 cycles.

The main registers I have checked are these:

CR = 0x10000005 (ADC regulator on; regular conversion started; ADC enabled)

CFGR = 0x80001000 (Injected queue disabled; overrun mode)

CFGR2 = 0 (Makes sense, as I don't use oversampling)

SMPR1 = 0x00000007 (This confuses me a lot. Doesn't this just set channel 0 sampling time to 640.5 cycles?

SMPR2 = 0 (... and why is this 0? Shouldn't it have been used for setting the channel 16 sampling time?)

SQR1 = 0 (Even more confusing. Surely, this should be pointing at channel 16 as the one and only channel in the sequence. Instead, it points at channel 0.)

I have also checked that the DMA channel if off, as I don't use DMA for this single sample:

DMA2->CCR4 = 0x00000580 (The main thing here is that the DMA channel is off. Which it should be.)

 

I would have expected SMPR2 to be (at least) 0x001C0000, to set the channel 16 sampling time to 640.5 cycles. Also, I expected SQR1 to be 0x00000400, to indicate channel 16 for the first and only conversion. Instead, both registers read 0, which I simply don't understand.

Have I completely misunderstood how the ADC works? When I read the manual, the SMPRx registers contain the sample times for each channel (regardless of sequence), and the SQRx registers are used for specifying the sample sequence and the number of conversions.

Can anytone shed some light on this?

12 REPLIES 12
Andrew Neil
Super User

@EThom.3 wrote:

With HAL, it works fine


So why not just use HAL ?

A complex system that works is invariably found to have evolved from a simple system that worked.
A complex system designed from scratch never works and cannot be patched up to make it work.

Fair point.

I need to understand how this stuff works. Especially when I'm going to do some more advanced stuff.

In my opinion, the settings that HAL creates do not correspond with the device manual.

LCE
Principal II

The HAL init functions do a lot of "hidden" stuff, like activating the peripheral clock and setting up the GPIOs.

Have you taken care of that?

PS: I don't like using HAL and try to avoid it, but for the H7 ADC with the "ranks" & mux I gave up and used HAL. Mostly because in that app the ADC is used almost only for system-irrelevant info.
For another project with a L031 with only 32kB flash and the ADC being an important feature (and much simpler) I used direct register access.

Thanks. Although I didn't write it in my post, I did read the RCC_AHB2ENR register to check that the ADC clock is enabled. And it is.

The GPIOs should be set up in main.c, but I'll check the registers.

At the moment, my main concern is that HAL sets the SMPR and SQR registers completely differently from what I would have expected – and to something that, according to the manual, doesn't make any sense.

P.S. I also have a dertain dislike of HAL, as I think it is often too...opaque. It is a bit like Arduino, I think. "You don't need to know how technical stuff works. Just use this, and with minimal knowledge and effort, you are programming Yaaay!"

TDK
Super User

HAL source is freely available and can be easily inspected in the project you use it in. It's only opaque if you don't look at it.

Can you post the IOC you're using? The SMPR and SQR registers should be configured differently than what you say, as you note, so something is amiss. If they are working then perhaps you're looking at them before they're configured.

If you feel a post has answered your question, please click "Accept as Solution".

Just checked GPIOx_MODER and GPIOx_ASCR. They are set up correctly.

Actually, I find the HAL code pretty hard to follow.

I have attached the IOC file, manually modified to remove customer information.

No, I most definitely look at them after configuration. I have used different methods, such as copying the register contents to a debug array, which I could then show in a display. Or halting the processor and reading the registers with STM32CubeProgrammer. The results from these two methods match.

An example of register content reading:

      HAL_ADC_DeInit(ADC2Handle);
      ADC2Handle->Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
      ADC2Handle->Init.ContinuousConvMode = DISABLE;
      ADC2Handle->Init.DMAContinuousRequests = DISABLE;
      ADC2Handle->Init.DataAlign = ADC_DATAALIGN_RIGHT;
      ADC2Handle->Init.DiscontinuousConvMode = DISABLE;
      ADC2Handle->Init.EOCSelection = ADC_EOC_SINGLE_CONV;
      ADC2Handle->Init.ExternalTrigConv = ADC_SOFTWARE_START;
      ADC2Handle->Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
      ADC2Handle->Init.LowPowerAutoWait = DISABLE;
      ADC2Handle->Init.NbrOfConversion = 1;
      ADC2Handle->Init.NbrOfDiscConversion = 0;
      ADC2Handle->Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
      ADC2Handle->Init.OversamplingMode = DISABLE;
      ADC2Handle->Init.Resolution = ADC_RESOLUTION_12B;
      ADC2Handle->Init.ScanConvMode = ADC_SCAN_ENABLE;
      HAL_ADC_Init(ADC2Handle);

      ChannelCfg.Channel = 16;
      ChannelCfg.Offset = 0;
      ChannelCfg.OffsetNumber = ADC_OFFSET_NONE;
      ChannelCfg.Rank = ADC_REGULAR_RANK_1;
      ChannelCfg.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
      ChannelCfg.SingleDiff = ADC_SINGLE_ENDED;
      HAL_ADC_ConfigChannel(ADC2Handle, &ChannelCfg);
      HAL_ADC_Start(ADC2Handle);

      slDebug[1] = ADC2->CR;
      slDebug[2] = ADC2->CFGR;
      slDebug[3] = ADC2->CFGR2;
      slDebug[4] = ADC2->SMPR1;
      slDebug[5] = ADC2->SMPR2;
      slDebug[6] = ADC2->SQR1;
      //slDebug[5] = ADC2DMAHandle->Instance->CCR;
      //slDebug[6] = ADC2DMAHandle->Instance->CNDTR;
      slDebug[7] = ADC2->SQR2;

 


@EThom.3 wrote:

I find the HAL code pretty hard to follow.


Might be easiest to follow in the debugger?

A complex system that works is invariably found to have evolved from a simple system that worked.
A complex system designed from scratch never works and cannot be patched up to make it work.

Quite possibly.