cancel
Showing results for 
Search instead for 
Did you mean: 

STM32G431 (STSPIN32G4), OPV High Offset Voltage

oeser
Associate II

Hi,

i have a custom design with a STSPIN32G4 SoC. However, I think my question does refer to the internal microcontroller (STM32G431VBTx) and not the motor controller.

  • Timer TIM3 does trigger an interrupt every 10 ms.
  • The Interrupt starts a conversion of ADC2, which reads the channels IN3 (single ended), IN4 (signle ended), IN8 (single ended), and VOPAMP3 channel
  • A single conversation should take about 60 µs (ADC_CLK = 170 MHz / 4; 640.5 Cycles per Channel; 4 * 640.5 / 42.5 MHz = 60 µs)
  • VOPAMP3 is configured as PGA Internally Connected with a gain factor of 32.
  • The measured voltages of IN3, IN4, and IN8 are ok.
  • The output voltage of VOPAMP3 is different than expected.
  • According to the datasheet of the microcontroller (5.3.22 Operational amplifiers characteristics) the OPV has
    • an input common mode range from 0 to VDDA
    • a maximum offset Voltage of 3 mV over the entire temperature range
    • the low saturation voltage is at 100 mV
  •  
  • I would expect the maximum measured output voltage to be somewhere around 3 mV * 32 (PGA gain) = 96 mV
  • The adc reads a voltage of 450 mV.

Do you have any ideas what can cause this issue?

I initialize the OPV with CubeMX generated Code:

 

 

 

 

  hopamp3.Instance = OPAMP3;
  hopamp3.Init.PowerMode = OPAMP_POWERMODE_NORMALSPEED;
  hopamp3.Init.Mode = OPAMP_PGA_MODE;
  hopamp3.Init.NonInvertingInput = OPAMP_NONINVERTINGINPUT_IO0;
  hopamp3.Init.InternalOutput = ENABLE;
  hopamp3.Init.TimerControlledMuxmode = OPAMP_TIMERCONTROLLEDMUXMODE_DISABLE;
  hopamp3.Init.PgaConnect = OPAMP_PGA_CONNECT_INVERTINGINPUT_NO;
  hopamp3.Init.PgaGain = OPAMP_PGA_GAIN_32_OR_MINUS_31;
  hopamp3.Init.UserTrimming = OPAMP_TRIMMING_FACTORY;
  if (HAL_OPAMP_Init(&hopamp3) != HAL_OK)
  {
    Error_Handler();
  }

 

 

 

I initialize the ADC with CubeMX generated Code:

 

 

 

  hadc2.Instance = ADC2;
  hadc2.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc2.Init.Resolution = ADC_RESOLUTION_12B;
  hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc2.Init.GainCompensation = 0;
  hadc2.Init.ScanConvMode = ADC_SCAN_ENABLE;
  hadc2.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc2.Init.LowPowerAutoWait = DISABLE;
  hadc2.Init.ContinuousConvMode = ENABLE;
  hadc2.Init.NbrOfConversion = 4;
  hadc2.Init.DiscontinuousConvMode = DISABLE;
  hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc2.Init.DMAContinuousRequests = DISABLE;
  hadc2.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc2.Init.OversamplingMode = DISABLE;
  if (HAL_ADC_Init(&hadc2) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure Regular Channel
  */
  sConfig.Channel = ADC_CHANNEL_3;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure Regular Channel
  */
  sConfig.Channel = ADC_CHANNEL_4;
  sConfig.Rank = ADC_REGULAR_RANK_2;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure Regular Channel
  */
  sConfig.Channel = ADC_CHANNEL_8;
  sConfig.Rank = ADC_REGULAR_RANK_3;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

 

 

 

These are the functions I use to read out the adc. Before I do anything with the peripherals I give the MCU 100 ms to settle.

 

 

 

HAL_OPAMP_SelfCalibrate(&hopamp3);
HAL_OPAMP_Start(&hopamp3);
HAL_ADCEx_Calibration_Start(&hadc2, ADC_SINGLE_ENDED);

// Retriggered in TIM3 Callback
HAL_ADC_Start_DMA(&hadc2, (uint32_t*)&adc_buffer, 4); ​

 

 

 

Best regards.
1 ACCEPTED SOLUTION

Accepted Solutions

Fortunately, I also had a NUCLEO-G474RE flying around. The observed behaviour can be reproduced by shorting PB0 (posiitve input of OPAMP3) to ground. It turns out, that the line

HAL_OPAMP_SelfCalibrate(&hopamp3);

 does make a difference. Without the calibration function I get the expected output. When I call the function after the initialization, the output is somewhere between 0.4 and 0.5 Volts. So I think that I might use this function wrong or there is some bug inside. For me, I could live without the calibration function.

For everyone else, I attached the NUCLEO-G474 Project to reproduce the observed behaviour. The form does not allow me to upload the exported *.zip-File from STM32CubeIDE. So I can only provide the main.c and the CubeMX configuration.

 

View solution in original post

18 REPLIES 18
MasterT
Lead

"The adc reads a voltage of 450 mV."

Is input+ connected to anything?

You can configure external GPIO pin as OPA output (connected to both, GPIO and internal ADC) and verify voltage with voltmeter if there 450mV

Thank you for your fast response. OPAMP3_INP is my input signal, which is 0 Volt (measured). Unfortunately my design has no connection to the output pin of OPAMP3. However, I was able to contact the pin directly with a needle. I measured a voltage of 3.6 mV at the output pin of OPAMP3, which is within the range of what is possible.

 

So the error must be from a wrong configuration of the adc.? What can be the reason?

Best regards.

I don't see this part in code:

/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_VOPAMP3_ADC2;
sConfig.Rank = ADC_REGULAR_RANK_4;
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
{
Error_Handler();
}

 

Thank you very much. Now it works as intended, although I do not know, why CubeMX does not Auto-Generate this Code.

For bugfixing, this are the software versions I used:


STM32CubeIDE

Version: 1.13.0

Build: 17399_20230707_0829 (UTC)


STM32CubeMX

Version: 6.11.1


MCSDK_v6.3.0-Full

 

oeser
Associate II

Ok. I only thought that this was the solution...

I only measured a smaller voltage for one time. Now, I am still getting the 0.45 V.

Common practice to throubsleshoot issues with HAL driver, if it's doesn't generate code  properly:

get RM0444, inspect content of the ADC  registers.  I'd start from ADC_CHSELR, see what channels are really activated in sequence. Same apply to OPA,  see what bits are set and if it's correct configuration 

Now I had a closer look in the registers. For my device the RM0440 is the right one. My ADC does not have an ADC_CHSELR Register.

  • According to Figure 85, OPAMP3 is connected to Channel 18. The Other Inputs I use are Channel 3, 4, and 8.
  • All the inputs are selected correctly as single-ended inputs. (DIFSEL=0)
  • Channel Selection seems correct. This is given by SQR1. Bits SQ1 are 0x3 (Channel 3), Bits SQ2 are 0x4 (Channel 4), Bits SQ2 are 0x8 (Channel 8), and Bits SQ4 are 0x12 (Channel 18). Bits L are set to 0x03 (Corresponds to 4 conversions).
  • All other Sequence Regsiters are 0x0
  • Channel-wise programmable sampling time seems correct. SMP3, SMP4, SMP8, and SMP18 are set to 0x7 in SMPR1 and SMPR2.
  • All other registers also seem ok to me,

I wonder a little bit what the second switch in figure 85 is about. As far as I understand, the channel select bits only control the switches on the right side in the following picture:

oeser_0-1726238732714.png

I wasn't able to find further information on this. According to table 201, the adc input is connected internally to the OPAMP3_VOUT when OPAINTOEN bit is set. Do you think that this might be the second switch? However, the bit is set in OPAMP3_CSR. So everything seems fine here.

I also read Chapter 11 about the periphals interconnect matrix. But unfortunately I did not find anything here (Some connections are disabled in low power modes, which I do not use).

 

 

 

I think so, swith is likely the same as shown in this picture:Figure 170. PGA mode, internal gain setting (x2-x64).png

I don't have G431, but I tested nucleo-G474re, and all perfectly works.

Setings:

/*OPA-3
0x12980145 0x12880000 0x11880000 0x12900000
*/
/*ADC-2

0x0000000B 0x00000010 0x10000005 0x80003003 0x00000000 0x07007E00 0x07000000 0x00000000
0x0FFF0000 0x00FF0000 0x00FF0000 0x00000000 0x122040C3 0x00000000 0x00000000 0x00000000
0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000037 0x00000000 0x00000000

ADC_SQR1:
Reg: 50000130 0001 0010 0010 0000 0100 0000 1100 0011 (0x122040C3)
ADC_SMPR1:
Reg: 50000114 0000 0111 0000 0000 0111 1110 0000 0000 (0x07007E00)

*/

Results with all 4 inputs in parallel with small DC voltage applyed:

PGA: 2
*0 113 116 117 228
PGA: 4
*0 116 104 113 455
PGA: 8
*0 119 111 119 908
PGA: 16
*0 116 115 113 1827
PGA: 32
*0 118 113 116 3646
PGA: 64
*0 115 104 117 4095

Here is the soft, generated by CubeMX but re-configured to run in arduino IDE:

void setup()
{
  Serial.begin(115200);
  in_String.reserve(200);
  Serial.print(F("\n\n\tSketch: g474_pga32. Date: 13 sept. 2024"));
  //  SystemClock_Config();
  Serial.print(F("\n\tSys_clock: "));
  Serial.print(((uint32_t) SystemCoreClock /(float) 1000000.0), 3);
  Serial.print(F(" MHz."));

  Serial.print(F("\n\tcfg_Pins..."));
  delay(100);      
  cfg_Pins1(); 
  Serial.print(F("\tdone."));

  Serial.print(F("\n\tDMA..."));
  delay(100); 
  hadc2.Instance = ADC2;         
  my_DMA_Init( &hadc2 );
  Serial.print(F("\tdone."));

  Serial.print(F("\n\tADC..."));
  delay(100);      
  my_ADC2_Init();
  Serial.print(F("\tdone."));
  
  Serial.print(F("\n\topa3_config..."));
  delay(100);      
  OPA_Config3();
  pga_adjust(pga_input);
  Serial.print(F("\tdone."));

  Serial.print("\n\tADC_start_dma.");  
  delay(100);      
  if (HAL_ADC_Start_DMA(&hadc2, (uint32_t *)inp_1, (2 * INP_BUFF)) != HAL_OK) {
    Error_Handler();
    Serial.print("\n\tError-start-dma.");  
    }
  else 
    Serial.print("\tDone.");  

  Serial.print(F("\n\tSetup complete."));
}

 

void cfg_Pins1(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = { 0, 0, 0, 0, 0};

    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOD_CLK_ENABLE();

    /**ADC2 GPIO Configuration
    PC2     ------> ADC2_IN8
    PA6     ------> ADC2_IN3
    PA7     ------> ADC2_IN4
    */
    GPIO_InitStruct.Pin   = GPIO_PIN_2;
    GPIO_InitStruct.Mode  = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull  = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    GPIO_InitStruct.Pin   = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode  = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull  = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);


    /**OPAMP3 GPIO Configuration
    PB13     ------> OPAMP3_VINP
    */
    GPIO_InitStruct.Pin   = GPIO_PIN_13;
    GPIO_InitStruct.Mode  = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull  = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

//DEBUG  
    GPIO_InitStruct.Pin       = GPIO_PIN_0|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12;
    GPIO_InitStruct.Mode      = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull      = GPIO_NOPULL;
    GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
static void OPA_Config3( void )
{
  hopamp3.Instance                        = OPAMP3;
//  hopamp3.Init.PowerMode                  = OPAMP_POWERMODE_HIGHSPEED;
  hopamp3.Init.PowerMode                  = OPAMP_POWERMODE_NORMALSPEED;
  hopamp3.Init.Mode                       = OPAMP_PGA_MODE;
  hopamp3.Init.NonInvertingInput          = OPAMP_NONINVERTINGINPUT_IO1;
  hopamp3.Init.InternalOutput             = ENABLE;//DISABLE;
  
  hopamp3.Init.TimerControlledMuxmode     = OPAMP_TIMERCONTROLLEDMUXMODE_DISABLE;
  hopamp3.Init.PgaConnect                 = OPAMP_PGA_CONNECT_INVERTINGINPUT_NO;
//  hopamp3.Init.PgaConnect                 = OPAMP_PGA_CONNECT_INVERTINGINPUT_IO0_BIAS;
  hopamp3.Init.PgaGain                    = OPAMP_PGA_GAIN_32_OR_MINUS_31;
  //hopamp3.Init.PgaGain                    = OPAMP_PGA_GAIN_64_OR_MINUS_63;
  hopamp3.Init.UserTrimming               = OPAMP_TRIMMING_FACTORY;
  if (HAL_OPAMP_Init(&hopamp3) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_OPAMP_Start(&hopamp3) != HAL_OK)
  {
    Error_Handler();
  }  
}

void pga_adjust(uint16_t gain_set)
{
  uint32_t reg_set = 0x00000001;

  gain_set &= 0x0000007E;
  
  if(gain_set ==  2) reg_set = OPAMP_PGA_GAIN_2_OR_MINUS_1;
  if(gain_set ==  4) reg_set = OPAMP_PGA_GAIN_4_OR_MINUS_3;
  if(gain_set ==   reg_set = OPAMP_PGA_GAIN_8_OR_MINUS_7;
  if(gain_set == 16) reg_set = OPAMP_PGA_GAIN_16_OR_MINUS_15;
  if(gain_set == 32) reg_set = OPAMP_PGA_GAIN_32_OR_MINUS_31;
  if(gain_set == 64) reg_set = OPAMP_PGA_GAIN_64_OR_MINUS_63;
  
  hopamp3.Init.PgaGain = reg_set;
  if(HAL_OK != HAL_OPAMP_Init(&hopamp3)) {
    Error_Handler();
    }
}

void my_DMA_Init(ADC_HandleTypeDef *hadc)
{
  RCC_PeriphCLKInitTypeDef PeriphClkInit  = {0};

  PeriphClkInit.PeriphClockSelection      = RCC_PERIPHCLK_ADC12;
  PeriphClkInit.Adc12ClockSelection       = RCC_ADC12CLKSOURCE_SYSCLK;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
    {
      Error_Handler();
    }
  
  __HAL_RCC_ADC12_CLK_ENABLE();
  
  __HAL_RCC_DMAMUX1_CLK_ENABLE();
  __HAL_RCC_DMA1_CLK_ENABLE();

    /* ADC2 DMA Init */
    /* ADC2 Init */
    hdma_adc2.Instance                  = DMA1_Channel1;
    hdma_adc2.Init.Request              = DMA_REQUEST_ADC2;
    hdma_adc2.Init.Direction            = DMA_PERIPH_TO_MEMORY;
    hdma_adc2.Init.PeriphInc            = DMA_PINC_DISABLE;
    hdma_adc2.Init.MemInc               = DMA_MINC_ENABLE;
    hdma_adc2.Init.PeriphDataAlignment  = DMA_PDATAALIGN_HALFWORD;
    hdma_adc2.Init.MemDataAlignment     = DMA_MDATAALIGN_HALFWORD;
    hdma_adc2.Init.Mode                 = DMA_CIRCULAR;
    hdma_adc2.Init.Priority             = DMA_PRIORITY_VERY_HIGH;
    
    if (HAL_DMA_Init(&hdma_adc2) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA( hadc, DMA_Handle, hdma_adc2);

  HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}

void my_ADC2_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};

  hadc2.Instance                        = ADC2;
  hadc2.Init.ClockPrescaler             = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc2.Init.Resolution                 = ADC_RESOLUTION_12B;
  hadc2.Init.DataAlign                  = ADC_DATAALIGN_RIGHT;
  hadc2.Init.GainCompensation           = 0;
//  hadc2.Init.ScanConvMode               = ADC_SCAN_DISABLE;
  hadc2.Init.ScanConvMode               = ADC_SCAN_ENABLE;
  hadc2.Init.EOCSelection               = ADC_EOC_SINGLE_CONV;
  hadc2.Init.LowPowerAutoWait           = DISABLE;
  hadc2.Init.ContinuousConvMode         = ENABLE;
//  hadc2.Init.ContinuousConvMode         = DISABLE;
  hadc2.Init.NbrOfConversion            = 4; //1;
  hadc2.Init.DiscontinuousConvMode      = DISABLE;
  hadc2.Init.ExternalTrigConv           = ADC_SOFTWARE_START;
//  hadc2.Init.ExternalTrigConv           = ADC_EXTERNALTRIG_T3_TRGO;
//  hadc2.Init.ExternalTrigConvEdge       = ADC_EXTERNALTRIGCONVEDGE_RISING; //ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc2.Init.ExternalTrigConvEdge       = ADC_EXTERNALTRIGCONVEDGE_NONE; //ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc2.Init.DMAContinuousRequests      = ENABLE;
  hadc2.Init.Overrun                    = ADC_OVR_DATA_OVERWRITTEN;
//  hadc2.Init.OversamplingMode           = ENABLE;
  hadc2.Init.OversamplingMode           = DISABLE;
/*
  hadc2.Init.Oversampling.Ratio         = ADC_OVERSAMPLING_RATIO_256;
  hadc2.Init.Oversampling.RightBitShift = ADC_RIGHTBITSHIFT_4;
  hadc2.Init.Oversampling.TriggeredMode = ADC_TRIGGEREDMODE_SINGLE_TRIGGER;
  hadc2.Init.Oversampling.OversamplingStopReset = ADC_REGOVERSAMPLING_CONTINUED_MODE;
*/
  if (HAL_ADC_Init(&hadc2) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel       = ADC_CHANNEL_3;
  sConfig.Rank          = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime  = ADC_SAMPLETIME_640CYCLES_5;
  sConfig.SingleDiff    = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber  = ADC_OFFSET_NONE;
  sConfig.Offset        = 0;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel       = ADC_CHANNEL_4;
  sConfig.Rank          = ADC_REGULAR_RANK_2;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel       = ADC_CHANNEL_8;
  sConfig.Rank          = ADC_REGULAR_RANK_3;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel       = ADC_CHANNEL_VOPAMP3_ADC2;
  sConfig.Rank          = ADC_REGULAR_RANK_4;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  
  //if (HAL_ADCEx_Calibration_Start(&hadc2, ADC_DIFFERENTIAL_ENDED) != HAL_OK)
  if (HAL_ADCEx_Calibration_Start(&hadc2, ADC_SINGLE_ENDED) != HAL_OK)
  {
    Error_Handler();
  }  
}

 

 

 

 

Fortunately, I also had a NUCLEO-G474RE flying around. The observed behaviour can be reproduced by shorting PB0 (posiitve input of OPAMP3) to ground. It turns out, that the line

HAL_OPAMP_SelfCalibrate(&hopamp3);

 does make a difference. Without the calibration function I get the expected output. When I call the function after the initialization, the output is somewhere between 0.4 and 0.5 Volts. So I think that I might use this function wrong or there is some bug inside. For me, I could live without the calibration function.

For everyone else, I attached the NUCLEO-G474 Project to reproduce the observed behaviour. The form does not allow me to upload the exported *.zip-File from STM32CubeIDE. So I can only provide the main.c and the CubeMX configuration.