cancel
Showing results for 
Search instead for 
Did you mean: 

**[STM32N6 / CubeMX Bug] ADC Oversampling RightBitShift field not generated

mraehle
Associate II

Hi 
I had some problems to setup the ADC1 and ADC2 on STM32N6 with CubeMX. I use 8 channels of ADC1 and 2 channels of ADC2.

Environment
- MCU: STM32N645
- CubeMX version: 6.17 (latest)
- HAL Driver: STM32CubeN6
- IDE: STM32CubeIDE v2.1.1

---

Problem Description

When configuring ADC1 (or ADC2) with Oversampling enabled in CubeMX and selecting a RightBitShift (e.g. 6-bit shift for Ratio=64), the generated `MX_ADC1_Init()` code is **missing the `RightBitShift` field** in the `Oversampling` struct.

CubeMX correctly generates:

hadc1.Init.OversamplingMode = ENABLE;
hadc1.Init.Oversampling.Ratio = 64;
hadc1.Init.Oversampling.TriggeredMode = ADC_TRIGGEREDMODE_SINGLE_TRIGGER;
hadc1.Init.Oversampling.OversamplingStopReset = ADC_REGOVERSAMPLING_CONTINUED_MODE;

But **does NOT generate**:
```c
hadc1.Init.Oversampling.RightBitShift = ADC_RIGHTBITSHIFT_6;
```

As a result, `RightBitShift` defaults to `0` (no shift), and the DMA buffer receives the raw **accumulated** oversampled value instead of the divided 12-bit result.

---

Symptoms

- With Ratio=64 and RightBitShift=6 configured in CubeMX, the expected DMA value for a 1V input (VDDA=1.8V) would be ~2275 (12-bit).
- Actual DMA value received: ~11'876 — which is the raw accumulated value without the hardware right-shift applied.
- The value in the DMA buffer is always larger than 4095, indicating no shift is being applied.

---

Root Cause

The `ADC_OversamplingTypeDef` struct contains a `RightBitShift` field:

typedef struct {
uint32_t Ratio; // Oversampling ratio
uint32_t RightBitShift; // ← NOT generated by CubeMX
uint32_t TriggeredMode;
uint32_t OversamplingStopReset;
} ADC_OversamplingTypeDef;

Since C/C++ does not zero-initialize struct members that are not explicitly set in this context, `RightBitShift` remains `0` (= `ADC_RIGHTBITSHIFT_NONE`), overriding the CubeMX UI setting at runtime.

---

Workaround

The only reliable fix is to write the `CFGR2` register directly after `MX_ADC1_Init()`. Place this in the `USER CODE BEGIN 0` block (preserved on CubeMX regeneration) using wrapper functions:

main.c enhanced

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void ADC1_Reinit(void) {
    MX_ADC1_Init();
    MODIFY_REG(ADC1->CFGR2, ADC_CFGR2_OVSS, (6UL << ADC_CFGR2_OVSS_Pos));
}
void ADC2_Reinit(void) {
    MX_ADC2_Init();
    MODIFY_REG(ADC2->CFGR2, ADC_CFGR2_OVSS, (6UL << ADC_CFGR2_OVSS_Pos));
}
/* USER CODE END 0 */

Use the Reinit wrapper, where you Calibrate the ADC Channel

extern "C"
{
    void ADC1_Reinit(void);
    void ADC2_Reinit(void);
}


//******************************************************************************
// description:
//   Constructor
//******************************************************************************
AdcHandler::AdcHandler()
{
    m_adc1Hdl_p = new AnalogDigitalConverter(&hadc1, ADC1Ch_Nof, ADC1_Reinit);
    m_adc2Hdl_p = new AnalogDigitalConverter(&hadc2, ADC2Ch_Nof, ADC2_Reinit);
}
AnalogDigitalConverter::AnalogDigitalConverter(ADC_HandleTypeDef* hadc_p, uint8_t nofChannels, ReinitCallback reinitCb)
{
   this->m_adcHdl_p = hadc_p;
	this->m_nofChannels = nofChannels;
    m_reinitCb = reinitCb;
}

void AnalogDigitalConverter::Calibrate()
{
    uint32_t tmpNbrOfConversion = m_adcHdl_p->Init.NbrOfConversion;

    /* EOC temporär auf Single-Conversion für Kalibrierung */
    m_adcHdl_p->Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    m_adcHdl_p->Init.NbrOfConversion = 1;          /* nur 1 Kanal aktiv    */
    m_adcHdl_p->Init.ScanConvMode    = DISABLE;
    m_adcHdl_p->Init.ContinuousConvMode = DISABLE;
    if (HAL_ADC_Init(m_adcHdl_p) != HAL_OK) {
        Error_Handler();
    }

    /* Kalibrierung */
    if (HAL_ADCEx_Calibration_Start(m_adcHdl_p, ADC_SINGLE_ENDED) != HAL_OK)
    {
        Error_Handler();
    }

    LL_ADC_Disable(m_adcHdl_p->Instance);
    /* Warten bis disabled */
    uint32_t tick = HAL_GetTick();
    while (LL_ADC_IsEnabled(m_adcHdl_p->Instance))
    {
        if ((HAL_GetTick() - tick) > 10U) { Error_Handler(); break; }
    }


    /* Vollständige Konfiguration via CubeMX-Wrapper wiederherstellen  */
    /* Setzt korrekt: Oversampling, Channel-Ranks, DMA-Link, SampTimes */
    if (m_reinitCb != nullptr)
    {
        m_reinitCb();
    }
}

---

Additional STM32N6-specific ADC pitfalls not covered by CubeMX

During the investigation, several other issues were discovered:

1. **`HAL_PWREx_EnableVddA()` missing from `HAL_ADC_MspInit()`**
CubeMX does not generate this call. Without it, ADC calibration hangs in timeout (ADSTART never clears). Must be added manually to `HAL_ADC_MspInit()`. See also ST Community thread #815299.

2. **`ADC_CR_ADVREGEN` must be set before `HAL_ADC_Init()`**
The internal ADC voltage regulator needs explicit activation and a stabilization time of at least 10µs before calibration can start:
```c
SET_BIT(hadc->Instance->CR, ADC_CR_ADVREGEN);
HAL_Delay(1);
```

3. **`HAL_ADCEx_Calibration_Start()` hangs when `NbrOfConversion > 1`**
The calibration routine internally calls `LL_ADC_REG_StartConversion()` and polls for EOC after each single conversion. With `EOCSelection = ADC_EOC_SEQ_CONV` and `NbrOfConversion > 1`, the EOC flag is only set after the complete sequence, causing a timeout.
Solution: temporarily set `NbrOfConversion=1`, `ScanConvMode=DISABLE`, `ContinuousConvMode=DISABLE` before calibration, then restore the full configuration via the re-init wrapper.

4. **ADC remains ENABLED after `HAL_ADCEx_Calibration_Start()` on STM32N6**
Unlike other STM32 families, the STM32N6 ADC is not automatically disabled after calibration. `LL_ADC_Disable()` must be called explicitly before reconfiguring registers:

HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
LL_ADC_Disable(hadc1.Instance);
uint32_t tick = HAL_GetTick();
while (LL_ADC_IsEnabled(hadc1.Instance)) {
if ((HAL_GetTick() - tick) > 10U) { Error_Handler(); break; }
}

---

Expected Fix from ST

- CubeMX should generate the `RightBitShift` field when Oversampling with RightBitShift != 0 is configured in the UI.
- `HAL_ADC_Init()` should reliably update `CFGR2` on repeated calls, or at minimum document that a state reset is required.

---

Hope this helps others hitting the same issue. Please confirm if this is a known CubeMX bug and whether a fix is planned.

Best regards
Markus

1 REPLY 1
Souhaib MAZHOUD
ST Employee

Hello @mraehle 

Thank you for reporting this issue; your detailed explanation is very helpful.

I will escalate it to the appropriate team for further investigation and review.

BR, Souhaib

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.