cancel
Showing results for 
Search instead for 
Did you mean: 

Repeating PWM pulse in DMA array to generate audio

Claydonkey
Senior

Using the STM's CubeMx HAL, I generate audio using PWM from a raw wav file at 8bit 44100hz but I am clearly using the DMA the wrong way. The resulting audio is fine but the solution has an unnecessary overhead.

I am using a circular DMA with one 8bit value in it's buffer array, and repeat this pulse until Timer5 clock is reset. The resulting audio is fine but the Timer interrupt is fired and processed too often at 44.1khz, hogging precious cycles.

audio_t audio_ch1;
#define SOMESIZE somesize
 
uint8_t audio_buf[SOMESIZE]={.........};
uint32_t audio_bufsize = SOMESIZE;
 
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  PeriphCommonClock_Config();
 
  MX_GPIO_Init();
  MX_TIM1_Init();
  MX_TIM5_Init();
 
	HAL_TIM_RegisterCallback(&htim5, HAL_TIM_PERIOD_ELAPSED_CB_ID,
			&TIM5_PeriodElapsedCallback);
 
/* currently redundant
	HAL_TIM_RegisterCallback(&htim1, HAL_TIM_PWM_PULSE_FINISHED_CB_ID,
			&PWM_PulseFinishedCallback); */
 
	HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t*) &audio_ch1.ptr, 1);
	HAL_TIM_Base_Start_IT(&htim5);
	HAL_TIM_Base_Start_IT(&htim1);
 
 playSound(audio_buf, audio_bufsize, PLAY_ONCE, 1);
	return 0;
	while (1) {
   
	}
}
 
/*
currently redundant
void PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) {
	if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
		audio_ch1.pulseFinished = true;
	if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3)
		audio_ch2.pulseFinished = true;
	if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
	{
			audio_ch1.pulseFinished = true;
 
	}
}
*/
int8_t emptyArray[10];
void TIM5_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
	if ( audio_ch1.mode != NOT_PLAYING) {
		if (audio_ch1.cnt >= audio_ch1.length) {
			switch (audio_ch1.mode) {
			case PLAY_LOOP:
				audio_ch1.cnt = 0;
				(*audio_ch1.state) = false;
				break;
			case PLAY_ONCE:
				audio_ch1.cnt = 0;
				audio_ch1.buffer = emptyArray;
				audio_ch1.length = 10;
				audio_ch1.mode = NOT_PLAYING;
				(*audio_ch1.state) = false;
				break;
			default:
				audio_ch1.cnt = 0;
				break;		
		}
		if (audio_ch1.length && audio_ch1.mode != NOT_PLAYING) {
			audio_ch1.ptr = audio_ch1.buffer[audio_ch1.cnt++] ;
		}
	}
}
 
void playSound(const uint8_t *wav, uint32_t len, SfxMode mode, audio_t *audio) {
 
	        audio->cnt = 0;
		audio->length = len;
		audio->buffer = (uint8_t*) wav;
		audio->mode = mode;
		audio->state = state;
}
 
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
 
  /*AXI clock gating */
  RCC->CKGAENR = 0xFFFFFFFF;
 
  /** Supply configuration update enable
  */
  HAL_PWREx_ConfigSupply(PWR_DIRECT_SMPS_SUPPLY);
 
  /** Configure the main internal regulator output voltage
  */
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);
 
  while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
 
  /** Configure LSE Drive Capability
  */
  HAL_PWR_EnableBkUpAccess();
  __HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_LOW);
 
  /** Macro to configure the PLL clock source
  */
  __HAL_RCC_PLL_PLLSOURCE_CONFIG(RCC_PLLSOURCE_HSE);
 
  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48|RCC_OSCILLATORTYPE_LSI
                              |RCC_OSCILLATORTYPE_HSE|RCC_OSCILLATORTYPE_LSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.LSEState = RCC_LSE_ON;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  RCC_OscInitStruct.HSI48State = RCC_HSI48_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 12;
  RCC_OscInitStruct.PLL.PLLN = 280;
  RCC_OscInitStruct.PLL.PLLP = 2;
  RCC_OscInitStruct.PLL.PLLQ = 2;
  RCC_OscInitStruct.PLL.PLLR = 2;
  RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_1;
  RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
  RCC_OscInitStruct.PLL.PLLFRACN = 0;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
 
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
                              |RCC_CLOCKTYPE_D3PCLK1|RCC_CLOCKTYPE_D1PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
  RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;
 
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_7) != HAL_OK)
  {
    Error_Handler();
  }
  HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_HSE, RCC_MCODIV_1);
}
 
void PeriphCommonClock_Config(void)
{
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
 
  /** Initializes the peripherals clock
  */
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_OSPI|RCC_PERIPHCLK_ADC
                              |RCC_PERIPHCLK_SDMMC;
  PeriphClkInitStruct.PLL2.PLL2M = 12;
  PeriphClkInitStruct.PLL2.PLL2N = 280;
  PeriphClkInitStruct.PLL2.PLL2P = 4;
  PeriphClkInitStruct.PLL2.PLL2Q = 4;
  PeriphClkInitStruct.PLL2.PLL2R = 14;
  PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_1;
  PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE;
  PeriphClkInitStruct.PLL2.PLL2FRACN = 0;
  PeriphClkInitStruct.OspiClockSelection = RCC_OSPICLKSOURCE_PLL2;
  PeriphClkInitStruct.SdmmcClockSelection = RCC_SDMMCCLKSOURCE_PLL2;
  PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
}
 
 void MX_TIM1_Init(void)
{
 
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0}
 
  htim1.Instance = TIM1;
  htim1.Init.Prescaler = PWM_PRESCALER;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = PWM_PERIOD;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1;
  sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_ASSYMETRIC_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_ENABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_SET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
  {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 0;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_LOW;
  sBreakDeadTimeConfig.BreakFilter = 0;
  sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
  sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_LOW;
  sBreakDeadTimeConfig.Break2Filter = 0;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_ENABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }
 
  HAL_TIM_MspPostInit(&htim1);
 
}
 
 
 
static void MX_TIM5_Init(void)
{
 
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
 
  htim5.Instance = TIM5;
  htim5.Init.Prescaler = OC_PRESCALER;
  htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim5.Init.Period = OC_PERIOD;
  htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim5.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim5) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim5, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
}
 
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
 
  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOI_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOG_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOK_CLK_ENABLE();
  __HAL_RCC_GPIOJ_CLK_ENABLE();
 
  /*Configure GPIO pin : PA8 */
  GPIO_InitStruct.Pin = GPIO_PIN_8;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  GPIO_InitStruct.Alternate = GPIO_AF0_MCO;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
}
 

11 REPLIES 11

Which STM32?

> DMA with a one element in the buffer

That's nonsense, buffer is supposed to hold individual sample values and DMA is supposed to write each sample only once.

From the Cube gibberish I don't understand what do you do, but if TIM1 generates the PWM, then set it up to a period which is integer division of the audio sampling period, set the ratio between audio sampling and chosen PWM period to TIM1_RCR, and set DMA targeting its CCRx register(s) to be triggered from its Update. You don't need a second timer.

JW

Claydonkey
Senior

Thanks for the quick reply and sorry about my nonsense. This is my first attempt at using the PWM on an STM32Mx (st32mxh7a). So What you're proposing is that rather than writing a pulse stream like this:

111...,222...,333....,444...,555, etc.

I should using the DMA with x elements (how many?)

and stream like this

1234567...(n),1234567..,1234567,,,,1234567...,

89ABCD..,89ABCD...,89ABCD...,89ABCD... etc.

and also if I understand correctly the TIM_RCR is the repeat counter - yes? and this would correspond to the times the elements in the DMA are repeated...?

> st32mxh7a

What's that?

> TIM_RCR is the repeat counter

Yes. It's described in the TIM chapter of RM. It does nothing else just prevents the Update event to happen upon every "overflow" of the timer, and it makes it happen upon every Nth "overflow".

JW

Claydonkey
Senior

sorry its an STM32H7B3xI + How should I calculate the length of the DMA buffer?

Do you know of any examples of this kind of application that I could peruse?

Open and read examples in repository folder .

And next nonsense is

playSound(audio_buf, audio_bufsize, PLAY_ONCE, 1);

last param is pointer to struct and cant be 1

Try to look around in the examples which came with Cube.

The 'H7 is an overtly complicated beast and I am not using it, nor am I using Cube.

JW

[EDIT] https://github.com/STMicroelectronics/STM32CubeH7/tree/master/Projects/STM32H7B3I-DK/Examples/TIM/TIM_DMA ?

Claydonkey
Senior

Thanks that's extremely helpful + I'm getting there.

Just a quick question. Am I right in saying (and I realise this is hidden under HAL code) that the PWM_PulseFinishedCallback is triggered by n number of pulses where is n is defined by the repeatcounter. It seems to be the case as it is triggered by "htim->hdma[TIM_DMA_ID_CCx]->XferCpltCallback" and I presuppose that would be after the last element of the DMA buffer is read...