cancel
Showing results for 
Search instead for 
Did you mean: 

Found 2 bugs with DMA for Input Capture and Sigma Delta ADC

DMårt
Senior II

Hi!

I just want to report that I have found two bugs. There is no errors with the HAL_DMA functions. I assume there is some errors with the predefined constants or enumerations.

Introduction

I'm using STM32CubeIDE 1.7.0 with STM32F373VBTx and firmware V1.11.3

I'm going to enable DMA for two timers. TIM17 and TIM16.

I'm going also to enable DMA for Sigma Delta ADC.

Goal

The goal is that with TIM17 and TIM16, they will work as Input Capture and save the counter value into arrays.

For Sigma Delta ADC, I want to store the ADC values into arrays.

Method - Input Capture

First I enable the input capture with this code.

volatile static uint16_t input_capture0[2] = {0};
volatile static uint16_t input_capture1[2] = {0};
 
void STM32_PLC_Start_Input_Capture(TIM_HandleTypeDef* htim17, TIM_HandleTypeDef* htim16) {
	/*
	 * Input capture for measuring frequency
	 * For TIM17 and TIM16
	 * Timer clock: 48 Mhz
	 * Prescaler: 4799
	 * Counter: 65535 (0xffff)
	 * Update frequency: 0.1526 Hz (1/0.1526 = 6.5535 seconds)
	 * Example: For every second, it will count 10000
	 * Lowest frequency measurement: 1/(0xFFFF*0.0001) = 0.1526 Hz
	 * Highest frequency measurement: 1/(1*0.0001) = 10000 Hz
	 */
	if(HAL_TIM_IC_Start_DMA(htim16, TIM_CHANNEL_1, (uint32_t*)input_capture1, 2) != HAL_OK)
		Error_Handler();
	if(HAL_TIM_IC_Start_DMA(htim17, TIM_CHANNEL_1, (uint32_t*)input_capture0, 2) != HAL_OK)
		Error_Handler();
}

They both DMA are for circular modes.

0693W00000D2UKvQAN.png0693W00000D2ULFQA3.pngThe purpose with the arrays above, is that I can compute the freqeuency by calling the function

static float compute_frequency(uint16_t input_capture[]) {
	/*
	 * Typical worst case scenarios:
	 * T1: 0xFFFF - T0: 0x0
	 * T1: 0x0    - T0: 0xFFFF
	 * T1: 0x7FFF - T0: 0x7FFF
	 * T1: 0x0	  -	T0: 0x0
	 */
	if(input_capture[1] > input_capture[0]) {
		return (float) 1/((input_capture[1] - input_capture[0])*0.0001f);
	} else if(input_capture[1] < input_capture[0]) {
		return (float) 1/((input_capture[1] + 0xFFFF - input_capture[0])*0.0001f);
	} else if(input_capture[1] == 0x7FFF && input_capture[0] == 0x7FFF){
		return (float) 1/(0xFFFF*0.0001f);
	} else {
		return 0;
	}
}
 
float STM32_PLC_Input_Capture_Get(uint8_t i) {
	if(i == 0)
		return compute_frequency((uint16_t*)input_capture0);
	else
		return compute_frequency((uint16_t*)input_capture1);
}

TIM17 and TIM16 have the same configurations.

0693W00000D2UMSQA3.png 

0693W00000D2UMcQAN.pngMethod - Sigma Delta ADC

To enable Sigma Delta ADC, I calling this function.

volatile static int16_t SDADC1_Single[9];
volatile static int16_t SDADC2_Single[3];
volatile static int16_t SDADC3_Differential[5];
static float SDADC_Single_Calibration_Gain[12] = {0};
static float SDADC_Single_Calibration_Bias[12] = {0};
static float SDADC_Differential_Calibration_Gain[5] = {0};
static float SDADC_Differential_Calibration_Bias[5] = {0};
TIM_HandleTypeDef *handler_tim12;
TIM_HandleTypeDef *handler_tim13;
SDADC_HandleTypeDef *handler_sdadc1;
SDADC_HandleTypeDef *handler_sdadc2;
SDADC_HandleTypeDef *handler_sdadc3;
 
void STM32_PLC_Start_Analog_Input(TIM_HandleTypeDef* htim12, TIM_HandleTypeDef* htim13, SDADC_HandleTypeDef* hsdadc1, SDADC_HandleTypeDef* hsdadc2, SDADC_HandleTypeDef* hsdadc3) {
	/*
	 * For TIM12, TIM13
	 * Timer clock: 48 Mhz
	 * Prescaler: 0
	 * Counter: 48000 (0xbb80)
	 * Update frequency: 1000 Hz
	 */
	HAL_TIM_OC_Start(htim13, TIM_CHANNEL_1); /* TIM13 Channel 1 is trigger source for SDADC1 */
	HAL_TIM_OC_Start(htim12, TIM_CHANNEL_1); /* TIM12 Channel 1 is trigger source for SDADC2 */
	HAL_TIM_OC_Start(htim12, TIM_CHANNEL_2); /* TIM12 Channel 2 is trigger source for SDADC3 */
	if (HAL_SDADC_CalibrationStart(hsdadc1, SDADC_CALIBRATION_SEQ_1) != HAL_OK)
		Error_Handler();
	if (HAL_SDADC_CalibrationStart(hsdadc2, SDADC_CALIBRATION_SEQ_1) != HAL_OK)
		Error_Handler();
	if (HAL_SDADC_CalibrationStart(hsdadc3, SDADC_CALIBRATION_SEQ_1) != HAL_OK)
		Error_Handler();
	if (HAL_SDADC_PollForCalibEvent(hsdadc1, HAL_MAX_DELAY) != HAL_OK)
		Error_Handler();
	if (HAL_SDADC_PollForCalibEvent(hsdadc2, HAL_MAX_DELAY) != HAL_OK)
		Error_Handler();
	if (HAL_SDADC_PollForCalibEvent(hsdadc3, HAL_MAX_DELAY) != HAL_OK)
		Error_Handler();
	if(HAL_SDADC_InjectedStart_DMA(hsdadc1, (uint32_t*)SDADC1_Single, 9) != HAL_OK)
		Error_Handler();
	if(HAL_SDADC_InjectedStart_DMA(hsdadc2, (uint32_t*)SDADC2_Single, 3) != HAL_OK)
		Error_Handler();
	if(HAL_SDADC_InjectedStart_DMA(hsdadc3, (uint32_t*)SDADC3_Differential, 5) != HAL_OK)
		Error_Handler();
 
	/* Save */
	handler_tim13 = htim13;
	handler_tim12 = htim12;
	handler_sdadc1 = hsdadc1;
	handler_sdadc2 = hsdadc2;
	handler_sdadc3 = hsdadc3;
 
}

Here is DMA configurations for SDADC1, SDADC2 and SDADC3.0693W00000D2UMrQAN.pngI have also selected Injected Conversion and using a timer for calling the SDADC function.

0693W00000D2UMwQAN.pngAnd the timers TIM12 and TIM13 for SDADC.

0693W00000D2UNGQA3.pngI also can change gain on the Sigma Delta ADC while the STM32 processor is running.

/* Inline is only for optimization */
static inline int16_t* array_to_pointer(int16_t array[]){
	return array; /* This return the address */
}
 
void STM32_PLC_Analog_Input_Set_Gain_Offset(uint8_t sdadc, uint8_t configuration_index, uint8_t gain, uint16_t offset) {
	/* Initial */
	SDADC_ConfParamTypeDef ConfParamStruct = {0};
	ConfParamStruct.CommonMode = SDADC_COMMON_MODE_VSSA;
	SDADC_HandleTypeDef *handler;
	uint8_t length_DMA;
	uint32_t *array_DMA;
 
	/* Stop DMA */
	switch(sdadc){
	case 1:
		/* Stop DMA for SDADC1 */
		if(HAL_SDADC_InjectedStop_DMA(handler_sdadc1) != HAL_OK)
			Error_Handler();
		handler = handler_sdadc1;
		length_DMA = 9;
		array_DMA = (uint32_t*)array_to_pointer((int16_t*)SDADC1_Single);
		ConfParamStruct.InputMode = SDADC_INPUT_MODE_SE_ZERO_REFERENCE;
		break;
	case 2:
		/* Stop DMA for SDADC2 */
		if(HAL_TIM_OC_Stop(handler_tim12, TIM_CHANNEL_1))
			Error_Handler();
		if(HAL_SDADC_InjectedStop_DMA(handler_sdadc2) != HAL_OK)
			Error_Handler();
		handler = handler_sdadc2;
		length_DMA = 3;
		array_DMA = (uint32_t*)array_to_pointer((int16_t*)SDADC2_Single);
		ConfParamStruct.InputMode = SDADC_INPUT_MODE_SE_ZERO_REFERENCE;
		break;
	case 3:
		/* Stop DMA for SDADC3 */
		if(HAL_SDADC_InjectedStop_DMA(handler_sdadc3) != HAL_OK)
			Error_Handler();
		handler = handler_sdadc3;
		length_DMA = 5;
		array_DMA = (uint32_t*)array_to_pointer((int16_t*)SDADC3_Differential);
		ConfParamStruct.InputMode = SDADC_INPUT_MODE_DIFF;
		break;
	default:
		/* Stop DMA for SDADC1 */
		if(HAL_SDADC_InjectedStop_DMA(handler_sdadc1) != HAL_OK)
			Error_Handler();
		handler = handler_sdadc1;
		length_DMA = 9;
		array_DMA = (uint32_t*)array_to_pointer((int16_t*)SDADC1_Single);
		ConfParamStruct.InputMode = SDADC_INPUT_MODE_SE_ZERO_REFERENCE;
		break;
	}
 
	/* Set gain and offset */
	switch(gain){
	case 0:
		ConfParamStruct.Gain = SDADC_GAIN_1_2;
		break;
	case 1:
		ConfParamStruct.Gain = SDADC_GAIN_1;
		break;
	case 2:
		ConfParamStruct.Gain = SDADC_GAIN_2;
		break;
	case 3:
		ConfParamStruct.Gain = SDADC_GAIN_4;
		break;
	case 4:
		ConfParamStruct.Gain = SDADC_GAIN_8;
		break;
	case 5:
		ConfParamStruct.Gain = SDADC_GAIN_16;
		break;
	case 6:
		ConfParamStruct.Gain = SDADC_GAIN_32;
		break;
	default:
		ConfParamStruct.Gain = SDADC_GAIN_1;
		offset = 0;
		break;
	}
	ConfParamStruct.Offset = offset;
 
	/* Set calibration */
	if (HAL_SDADC_PrepareChannelConfig(handler, configuration_index, &ConfParamStruct) != HAL_OK)
	    Error_Handler();
 
	/* Start ADC again */
	if (HAL_SDADC_CalibrationStart(handler, SDADC_CALIBRATION_SEQ_1) != HAL_OK)
		Error_Handler();
	if (HAL_SDADC_PollForCalibEvent(handler, HAL_MAX_DELAY) != HAL_OK)
		Error_Handler();
	if(HAL_SDADC_InjectedStart_DMA(handler, array_DMA, length_DMA) != HAL_OK)
		Error_Handler();
}

Result - Input Capture

These arrays gets different values when the STM32 processor runs.

volatile static uint16_t input_capture0[2] = {0};
volatile static uint16_t input_capture1[2] = {0};

I can read them, but ONLY input_capture0 (TIM17) get updates values. ONLY input_capture1 (TIM16) get values once, then nothing happens any more.

Result - Sigma Delta ADC

When I try to change the gain for the Sigma Delta ADC, I call this function. This is just an example for illusration:

if(HAL_SDADC_InjectedStop_DMA(mySDADCHandler) != HAL_OK)
	Error_Handler();

Then inside the function, this calls to abort the HAL_DMA.

HAL_StatusTypeDef HAL_SDADC_InjectedStop_DMA(SDADC_HandleTypeDef *hsdadc)
{
  HAL_StatusTypeDef status;
 
  /* Check parameters */
  assert_param(IS_SDADC_ALL_INSTANCE(hsdadc->Instance));
 
  /* Check SDADC state */
  if((hsdadc->State != HAL_SDADC_STATE_INJ) && \
     (hsdadc->State != HAL_SDADC_STATE_REG_INJ))
  {
    /* Return error status */
    status = HAL_ERROR;
  }
  else
  {
    /* Clear JDMAEN bit in SDADC_CR1 register */
    hsdadc->Instance->CR1 &= ~(SDADC_CR1_JDMAEN);
 
    /* Stop current DMA transfer */
    if(HAL_DMA_Abort(hsdadc->hdma) != HAL_OK)
    {
      /* Set SDADC in error state */
      hsdadc->State = HAL_SDADC_STATE_ERROR;
      status = HAL_ERROR;
    }

Continue below:

1 ACCEPTED SOLUTION

Accepted Solutions

Hi!

I found a solution for Input Capture. It's a bug inside STM32CubeIDE 1.7.0.

Solution:

Move MX_DMA_Init();

/* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init(); <--- To here
  MX_DAC1_Init();
  MX_DAC2_Init();
  MX_RTC_Init();
  MX_SPI2_Init();
  MX_TIM2_Init();
  MX_TIM5_Init();
  MX_SPI1_Init();
  MX_TIM4_Init();
  MX_SDADC1_Init();
  MX_SDADC2_Init();
  MX_SDADC3_Init();
  MX_CAN_Init();
  MX_USART1_UART_Init();
  MX_TIM6_Init();
  MX_TIM12_Init();
  MX_TIM13_Init();
  MX_TIM16_Init();
  MX_USB_DEVICE_Init();
  MX_FATFS_Init();
  MX_TIM17_Init();
  <--- From here
  MX_TIM19_Init();
  /* USER CODE BEGIN 2 */

This results:

  • Input Capture + DMA works
  • SDADC + DMA works, but I only get -0x8000 = -32768 (int16_t) because all my inputs are attached to GND. It seems that even if I have circular buffer, it still acts like it's Normal Mode. I can't change these values if I apply higher voltage onto the SDADC inputs. Hm...

0693W00000DpkK1QAJ.png 

View solution in original post

12 REPLIES 12
DMårt
Senior II

This completes the fist post:

And here is the HAL_ERROR code, returning back. The reason is that hdma->State is equal to HAL_DMA_STATE_READY

HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma)
{
  if(hdma->State != HAL_DMA_STATE_BUSY)
  {
    /* no transfer ongoing */
    hdma->ErrorCode = HAL_DMA_ERROR_NO_XFER;
    
    /* Process Unlocked */
    __HAL_UNLOCK(hdma);
    
    return HAL_ERROR;
  }

Discussion - Input Capture

I thoughts about this is that if the code works for TIM17 but not for TIM16, that means Circular Mode is only activated for TIM17, but Normal Mode is activated for TIM16, even if I selected Circular Mode for TIM16. I assume that DMA for TIM16 only calls once.

Discussion - Sigma Delta ADC

Due to I get HAL_ERROR when I aborting the DMA for Sigma Delta ADC handler, that means that the DMA was not activated at the first time.

Because I can't get any values into the arrays. Nothing happens here. So I assume that DMA is not activated for SDADC.

volatile static int16_t SDADC1_Single[9];
volatile static int16_t SDADC2_Single[3];
volatile static int16_t SDADC3_Differential[5];

NOTICE: There is nothing wrong with Sigma Delta ADC. It works perfect if I using interrupts with this code. https://pastebin.com/xWXDHKAe

T J
Lead

do you need to calibrate every time ? it is not quick to calibrate...

this line looks suspect:

else if(input_capture[1] == 0x7FFF && input_capture[0] == 0x7FFF)

should be more parentheses in there, the && is not guarded

Tim16 and Tim17 are they synchronized ? , can you use 2 channels from the same timer ?

not sure why you have to start and stop the DMAs

I set my DMAs at startup, and let them run forever in circular buffers,

just track the DMA Count to know where it is.

this whole routine looks a bit odd, considering you cant guarantee the timers are synchronized

	if(input_capture[1] > input_capture[0]) {
		return (float) 1/((input_capture[1] - input_capture[0])*0.0001f);
	} else if(input_capture[1] < input_capture[0]) {
		return (float) 1/((input_capture[1] + 0xFFFF - input_capture[0])*0.0001f);
	} else if(input_capture[1] == 0x7FFF && input_capture[0] == 0x7FFF){
		return (float) 1/(0xFFFF*0.0001f);
	} else {
		return 0;
	}

do you need to calibrate every time ? it is not quick to calibrate...

I don't know. I thought it would be best to calibrate for every time I start up DMA.

this line looks suspect:

Actually, no. If the counter hits half of 0xFFFF twice, them the difference is 0xFFFF.

should be more parentheses in there, the && is not guarded

&& is dominat. Just as * goes before + and -

Tim16 and Tim17 are they synchronized ? , can you use 2 channels from the same timer ?

Why not? In this case, TIM16 and TIM17 does not be synchronized. They are two different Input Captures.

not sure why you have to start and stop the DMAs

Because HAL_SDADC_PrepareChannelConfig says in the comment above the function.

This function should be called only when SDADC instance is in idle state
  *         (neither calibration nor regular or injected conversion ongoing)
this whole routine looks a bit odd, considering you cant guarantee the timers are synchronized

Assume that you have a counter value from 0 to 65535. The first counter value is going to be saved at index 0 and the second counter value is going to be saved at index 1. When I call the function, I read the difference between index 1 and index 0.

Going from 0 to 65535 takes 6.5535 seconds due to the timer is 48 Mhz and 48*10^6/(1 + 4799) is 10000 couting values every second. That's 10kHz.

Assume that we have an Input Capture value of 10 at index 0 and next time it's 25 at index 1. The difference is the 25 - 10 = 15.

Next time we have a higher value at index 0. Let's say 35. Now the difference is 25 - 35 = - 10.

I changed the code a little bit.

static float compute_frequency(uint16_t input_capture[]) {
	/*
	 * Typical worst case scenarios:
	 * T1: 0xFFFF - T0: 0x0
	 * T1: 0x0    - T0: 0xFFFF
	 * T1: 0x7FFF - T0: 0x7FFF
	 * T1: 0x0	  -	T0: 0x0
	 */
	uint16_t difference;
	if(input_capture[1] > input_capture[0]) {
		difference = input_capture[1] - input_capture[0];
		return (float) 1/(difference*0.0001f);
	} else if(input_capture[1] < input_capture[0]) {
		difference = -input_capture[1] + 0xFFFF + input_capture[0] + 1;
		return (float) 1/(difference*0.0001f);
	} else {
		return 1/(65335*0.0001f);
	}
}

Try to run this code.

#include <stdio.h>
#include <stdint.h>
 
void func(uint16_t x, uint16_t y){
    uint16_t difference;
    if(x < y){
        difference = y - x;
    }else if (x > y){
        difference = -y + 0xFFFF + x + 1;
    }else{
        difference = 0xFFFF;
    }
    printf("Difference = %i\n", difference);
}
 
int main()
{
 
    func(10, 25); // 15
    func(10, 0xFFFF); // 65525
    func(50, 20); // 30
    func(0xFFFF, 10); // 65525
    func(0x7FFF, 0x7FFF); // 65535
    func(0xFFFF, 0); // 65535
    func(0, 0xFFFF); // 65535
    func(30, 30); // 65535
 
    return 0;
}

Hi!

I found a solution for Input Capture. It's a bug inside STM32CubeIDE 1.7.0.

Solution:

Move MX_DMA_Init();

/* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init(); <--- To here
  MX_DAC1_Init();
  MX_DAC2_Init();
  MX_RTC_Init();
  MX_SPI2_Init();
  MX_TIM2_Init();
  MX_TIM5_Init();
  MX_SPI1_Init();
  MX_TIM4_Init();
  MX_SDADC1_Init();
  MX_SDADC2_Init();
  MX_SDADC3_Init();
  MX_CAN_Init();
  MX_USART1_UART_Init();
  MX_TIM6_Init();
  MX_TIM12_Init();
  MX_TIM13_Init();
  MX_TIM16_Init();
  MX_USB_DEVICE_Init();
  MX_FATFS_Init();
  MX_TIM17_Init();
  <--- From here
  MX_TIM19_Init();
  /* USER CODE BEGIN 2 */

This results:

  • Input Capture + DMA works
  • SDADC + DMA works, but I only get -0x8000 = -32768 (int16_t) because all my inputs are attached to GND. It seems that even if I have circular buffer, it still acts like it's Normal Mode. I can't change these values if I apply higher voltage onto the SDADC inputs. Hm...

0693W00000DpkK1QAJ.png 

I found a solution to the SDADC arrays.

Don't use the volatile keyword. That causing problems. Is that a bug too?

Now I get values when I apply voltage to the SDADC inputs.

0693W00000DpkMMQAZ.png

DMårt
Senior II

Hello @Imen DAHMEN​  Can ST fix this issue? I still need to declare the DMA_INIT() at the beginning as alwasy. CubeMX does not generate DMA_INIT() function directly after GPIO_INIT() function.

This is an issue and the selected answer above is the right answer.

Thank you.

Imen.D
ST Employee

Hello @Daniel Mårtensson​ and thank you for flagging this for me!

I escalated this issue to CubeMx team for update.

Hi @Sara BEN HADJ YAHYA​, Can you please share the status of this issue.

Thanks

Imen

When your question is answered, please close this topic by clicking "Accept as Solution".
Thanks
Imen

Hello @Daniel Mårtensson​ ,

Unfortunately there are still some use cases where the DMA_init order is not generated correctly. The dev team is aware of this issue and they are working on it.

The Workaround for this issue is well described in the following thread MX_DMA_Init order in the main.c file generated by STM32CubeMX, How to fix?. There is a second Workaround mentioned in this thread.

I will let you know once the fix is properly done.

Sara.

Sara BEN HADJ YAHYA
ST Employee

Hello @Daniel Mårtensson​ , @HArth.1​ ,

This issue is fixed in STM32CubeMX latest release.

V6.6.0 is now available under this Link.

Thanks for your contribution.

Sara.