Skip to main content
gokhannsahin
Associate III
January 6, 2019
Question

ADC trigger with PWM and reading voltage on its state

  • January 6, 2019
  • 11 replies
  • 4086 views

Hi everyone,

I have a question about ADC trigger. I have to generate a PWM 1.1KHz and read the avarage voltage of ADC pin accoding to the On/Off state of that PWM. How can I make it.? I enclosed an image about it.

0690X000006CyKTQA0.jpg

This topic has been closed for replies.

11 replies

S.Ma
Principal
January 6, 2019

Elaborate more, especially not a timing, more like which signals feeds who? Schematics sketch would help understand better what is the system and what is really the challenge. Thanks for providing mode details, otherwise the answers may not be relevant enough...

gokhannsahin
Associate III
January 6, 2019

That PWM supplies a coil and there is a sense coil. The voltage drops on an sense coil and want to measure its voltage according to each state of supplying PWM.

gokhannsahin
Associate III
January 6, 2019

If I use a timer 1Mhz for ADC as sampling rate is 61.5, how long does it take the canversion to finish for 450 samples? I guess that if I use ADC DMA conversion complete callback, I can toggle IO just as PWM. However, I don’t how to calculate correctly.

S.Ma
Principal
January 6, 2019

If it is a kind of motor control or digital power supply, there are specific app notes and examples for this, including 3 phase sense for sensored brushless motor. Check on IHM demo boards.

Otherwise, take a timer to generate the pwm(s) and use the spare channel to generate a pulse which you can shape and position during the PWM period time. This trigger goes to a pin which feeds the ADC trgger input pin. It will enable you to time position the sampling at the correct place (assuming the ADC input signall is low impedence, otherwise, get ready for voltage droops as the ADC behave like an RC filter during the sampling time. If you use an injected ADC channel, you can directly read the voltage as LSB or have a digital min/max comparator if the target is to be within a specific range.

gokhannsahin
Associate III
January 6, 2019

Well, how can I calculate the conversion timing for reading ADC via DMA? Are the followings correct?

TIM2 clock 32Mhz and set the prescaler 31 it will be 1Mhz. Counter Period is 1. The timing will be 1us.

The sample cycle 61.5, then total cycle 12+61.5=73.5 cycle.

The number of sample is 450, then it takes 33075 cycles.

So, it takes 33,075ms, it gets interrupt after 33ms.

S.Ma
Principal
January 6, 2019

Maybe still the understanding of what you'd like to implement is still subject to various interpretations.

  1. What is the PWM frequency and duty cycle range
  2. After how many PWM periods an ADC conversion is being done
  3. After how many PWM periods the PWM duty cycle is being updated?
  4. How accurate the firering of the ADC sampling time respective to PWM pulse? (position and precision) or it does not matter.

Thanks.

gokhannsahin
Associate III
January 6, 2019

Thanks ​ @S.Ma​ for your answers.

  1. PWM frequency is 1.1KHz and duty 50%.
  2. For each 50% duty, so in condition On/Off.
  3. Duty is never changed, it's allways 5%0.
  4. After changed the state, the first 2 or 3 value is assumed as dummy, not calculating these.

S.Ma
Principal
January 6, 2019

So if I understand correctly: STM32 generates 1.1kHz with 50% duty cycle

==> So this is one of the timer output channel : Let's say the timer runs at 4MHz, so its toggle is 1800 ticks, period is 3600 ticks with overflow.

==> For example, create a second channel which output will toggle based on compares coming from a DMA scanned table, say toggle at 4, 8, 12, 16, 20, 24 (that's the first 3 pulses for 3 usec, go on until 3600 is done. You could use another timer to generate a 1MHz pulse triggering the ADC, however, you probably want the fine shifting convenience and synchronisation of the ADC firering with 1/8 usec accuracy (or twice better is using 16MHz timer). DMA for Timer can run in cyclical mode.

==> These 1 MHz time shitable pulses are used to trigger the ADC conversion, one sample by one sample. Use the physical trigger pin to monitor what's going on.

Then use another DMA channel to save the ADC results into a RAM table, which you will have to handle by SW its backup every 900 usec.

You will only have to make sure that the ADC total sampling time is same or shorter than the ADC trigger period. DMA transfer complete will give you the event to analyse and perform the average. If you;d like to run it continuously, you'll probably have to implement double buffering.

If not known exactly the ADC sampling time, run the code using debugger and count how many samples were collected over 900 usec.

There are probably simpler implementations, however, if there are some system fine tuning required down the road, a bit more complex HW use provides extra tuning knobs.

The earlier Invested time the shorter debug time...

gokhannsahin
Associate III
January 7, 2019

I don't understand what you meant.

I have tried something but confused the calculation of ADC DMA conversion time. I'm using the following code.

TIM2:

static void MX_TIM2_Init(void)
{
 
 /* USER CODE BEGIN TIM2_Init 0 */
 
 /* USER CODE END TIM2_Init 0 */
 
 TIM_ClockConfigTypeDef sClockSourceConfig = {0};
 TIM_MasterConfigTypeDef sMasterConfig = {0};
 
 /* USER CODE BEGIN TIM2_Init 1 */
 
 /* USER CODE END TIM2_Init 1 */
 htim2.Instance = TIM2;
 htim2.Init.Prescaler = 63;
 htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
 htim2.Init.Period = 1;
 htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
 htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
 if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
 {
 Error_Handler();
 }
 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
 if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
 {
 Error_Handler();
 }
 sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
 if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
 {
 Error_Handler();
 }
 /* USER CODE BEGIN TIM2_Init 2 */
 
 /* USER CODE END TIM2_Init 2 */
 
}

ADC:

static void MX_ADC1_Init(void)
{
 
 /* USER CODE BEGIN ADC1_Init 0 */
 
 /* USER CODE END ADC1_Init 0 */
 
 ADC_ChannelConfTypeDef sConfig = {0};
 
 /* USER CODE BEGIN ADC1_Init 1 */
 
 /* USER CODE END ADC1_Init 1 */
 /**Common config 
 */
 hadc1.Instance = ADC1;
 hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV1;
 hadc1.Init.Resolution = ADC_RESOLUTION_12B;
 hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
 hadc1.Init.ContinuousConvMode = DISABLE;
 hadc1.Init.DiscontinuousConvMode = DISABLE;
 hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
 hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
 hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
 hadc1.Init.NbrOfConversion = 1;
 hadc1.Init.DMAContinuousRequests = ENABLE;
 hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
 hadc1.Init.LowPowerAutoWait = DISABLE;
 hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
 if (HAL_ADC_Init(&hadc1) != HAL_OK)
 {
 Error_Handler();
 }
 /**Configure Regular Channel 
 */
 sConfig.Channel = ADC_CHANNEL_4;
 sConfig.Rank = ADC_REGULAR_RANK_1;
 sConfig.SingleDiff = ADC_SINGLE_ENDED;
 sConfig.SamplingTime = ADC_SAMPLETIME_19CYCLES_5;
 sConfig.OffsetNumber = ADC_OFFSET_NONE;
 sConfig.Offset = 0;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }
 /* USER CODE BEGIN ADC1_Init 2 */
 
 /* USER CODE END ADC1_Init 2 */
 
}

Callback:

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
 
	uint32_t wResult1 = 0, wResult2 = 0, wResult3 = 0;
	uint32_t dwHighTotal = 0, dwLowTotatl = 0;
	static uint32_t dwCounter;
 
		HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_2);
		if (flag != 2) {
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2)) {
				for (uint32_t i = 0; i < 225; i++) {
					sADCValues.wHighData[i] = data[i];
					dwHighTotal += data[i];
				}
				sADCValues.wHighAverage = dwHighTotal / 225;
			} else {
				for (uint32_t i = 0; i < 225; i++) {
					sADCValues.wLowData[i] = data[i];
					dwLowTotatl += data[i];
				}
				sADCValues.wLowAverage = dwLowTotatl / 225;
			}
			flag++;
		}
}

In main, the ADC is started via DMA.

if (HAL_ADC_Start_DMA(&hadc1, (uint32_t*) &data, 225) != HAL_OK){

Error_Handler();

}

And it generate 1.1KHz signal but I can't calculate how to do that.

S.Ma
Principal
January 7, 2019

The way you generate the 1.1kHz is by the time it takes to perform all the ADC required conversions, half of them with GPIO low, half of them with GPIO high.

Because the callback is within interrupt, jitter will come if other interrupts delays from time to time yours. (USB, etc...). The PWM frequency is not accurate and it will definitely stop if you put a breakpoint in the callback.

Usually, we try to rely on the HW so that SW is non critical for the signals generation with precise timings.

In this case, we would generate the PWM and check how many ADC samples filled the buffer. Half of them would be for high level, the other half for the low level (exluding the ones near the boundaries).

This way, if the SW stops, the PWM may continue. And if you adjust the ADC sampling time, the number of conversions will vary and you'll still be able to calculate an average.

Make sense? There would be still different scenarios to make it better, this is the way to look at the implementation.