cancel
Showing results for 
Search instead for 
Did you mean: 

How to generate a sine wave using PWM and RC filter with the STM32? Part2

B.Montanari
ST Employee

How to generate a sine wave using PWM and RC filter with the STM32?

Welcome back to part2! Here we will cover the code implementation. If you missed the introduction and setting up the peripherals, please refer to part1.

In the generated code from the STM32CubeIDE, create a define to the offset as 50, since it will not change its value. Then, create the variables for the sine table, the string, buffer, and index of each frequency and amplitude, and finally set the sine amplitude variable to 100, which means that the startup amplitude is 100% if no value was given.
A flag was used to inform the pressing of the User’s Button, this flag resides inside the EXTI callback function, called ButtonON. The correspondent part of the code is shown below:
 
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#define OFFSET 50
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
volatile uint8_t Sine_Table[360] =
	{ 50, 51, 52, 53, 53, 54, 55, 56, 57, 58, 59, 60, 60, 61,
	  62, 63, 64, 65, 65, 66, 67, 68, 69, 70, 70, 71, 72, 73,
	  73, 74, 75, 76, 76, 77, 78, 79, 79, 80, 81, 81, 82, 83,
	  83, 84, 85, 85, 86, 87, 87, 88, 88, 89, 89, 90, 90, 91,
	  91, 92, 92, 93, 93, 94, 94, 95, 95, 95, 96, 96, 96, 97,
	  97, 97, 98, 98, 98, 98, 99, 99, 99, 99, 99, 99, 100, 100,
	  100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
	  100, 100, 100, 99, 99, 99, 99, 99, 99, 98, 98, 98, 98, 97,
	  97, 97, 96, 96, 96, 95, 95, 95, 94, 94, 93, 93, 92, 92, 91,
	  91, 90, 90, 89, 89, 88, 88, 87, 87, 86, 85, 85, 84, 83, 83,
	  82, 81, 81, 80, 79, 79, 78, 77, 76, 76, 75, 74, 73, 73, 72,
	  71, 70, 70, 69, 68, 67, 66, 65, 65, 64, 63, 62, 61, 60, 60,
	  59, 58, 57, 56, 55, 54, 53, 53, 52, 51, 50, 49, 48, 47, 47,
	  46, 45, 44, 43, 42, 41, 40, 40, 39, 38, 37, 36, 35, 35, 34,
	  33, 32, 31, 30, 30, 29, 28, 27, 27, 26, 25, 24, 24, 23, 22,
	  21, 21, 20, 19, 19, 18, 17, 17, 16, 15, 15, 14, 13, 13, 12,
	  12, 11, 11, 10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 5, 4, 4, 4,
	  3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
	  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2,
	  3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11,
	  11, 12, 12, 13, 13, 14, 15, 15, 16, 17, 17, 18, 19, 19, 20, 21,
	  21, 22, 23, 24, 24, 25, 26, 27, 27, 28, 29, 30, 30, 31, 32, 33,
	  34, 35, 35, 36, 37, 38, 39, 40, 40, 41, 42, 43, 44, 45, 46, 47,
	  47, 48, 49 };

uint8_t ResetString[] = "**************\r\n* Set Values *\r\n**************\r\n\n"
uint8_t FrequencyString[] = "*Choose Frequency*: \r\n";
uint8_t AmplitudeString[] = "*Choose Amplitude*: \r\n";
uint8_t FrequencyBuffer[3];
uint8_t AmplitudeBuffer[3];
uint8_t FrequencyIndex = 0;
uint8_t AmplitudeIndex = 0;
uint16_t SineAmplitude = 100; 
uint16_t ButtonON = 0;
/* USER CODE END PV */
The next step is to initialize the PWM and UART functions. After the peripherals initialization and before the main while loop, the PWM start function and the UART transmit and the receive functions will be called–please refer to the comments “USER CODE BEGIN” to facilitate the location to copy and paste:
 
/* USER CODE BEGIN 2 */

  //Start the PWM in Timer 3
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
  
  //Transmit the restart message in the UART
  HAL_UART_Transmit(&huart2, ResetString, sizeof(ResetString), 100);
  
  //Transmit the Frequency requirement string
  HAL_UART_Transmit(&huart2, FrequencyString, sizeof(FrequencyString), 100);
  
  //Receive the Frequency value
  HAL_UART_Receive_IT(&huart2, FrequencyBuffer, 1);

/* USER CODE END 2 */
Now, it is time to recreate the callback functions. In the /* USER CODE BEGIN 4 */ area, use the PWM callback with the following characteristics:
 
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
	static uint16_t i = 0;
	
	//If the index is higher than the sine table size,
	//it returns to the initial position in the table
	if(i > 360)
		i = 0;
	
	//If the index is lower than the sine table size,
	//it keeps walking through it
	else
		i++;
	
	//Put the sine table's values in the pulse pointer, as the Duty Cycle
	TIM3->CCR2 = Sine_Table[i];
}
The pulse value is updated to complete the loop cycle of the sine wave with a direct register access instead of calling the HAL function, thus the TIM3 CCR2 manipulation.
The next callback to recreate is the TIM6’s period elapsed, used to modify the frequency and amplitude of the sine wave:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) 
{
	static uint16_t i = 0;
	
	//Temporary Sine variable
	static signed int TempSine = 0;
	
	//If the index is higher than the sine table size,
	//it returns to the initial position in the table
	if(i > 360)
		i = 0;
	
	//If the index is lower than the sine table size,
	//it keeps walking through it
	else
		i++;
	
	//Put aside the offset so the sine table can be multiplied
	TempSine = Sine_Table[i] - OFFSET;
	
	//Multiply the sine values to the desirable amplitude percentage
	TempSine *= SineAmplitude;
	
	//As the values will be received in percentage,
	//it is necessary to divide it by 100
	TempSine /= 100;
	
	//Add the offset again
	TempSine += OFFSET;
	
	//Put the new values in the pulse pointer, as the Duty Cycle
	TIM3->CCR2 = TempSine;
}
The next callback function will be the User’s Button external interrupt callback. As mentioned before, the button alternates from “frequency” to “amplitude” parameter when pressed:
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
	//There will be a string for visual organization purpose
	HAL_UART_Transmit(&huart2, (uint8_t *)"----\r\n\n", size of("----\r\n\n"), 100);
//Transmit the Amplitude String every time the button is pressed
	HAL_UART_Transmit(&huart2, AmplitudeString, sizeof(AmplitudeString), 100);
	
	//Abort the reception not to overwrite the buffer
	HAL_UART_AbortReceive_IT(&huart2);
	
	//Receive the new amplitude value
	HAL_UART_Receive_IT(&huart2, &AmplitudeBuffer[0], 1);
	
	//This callback is associated with the flag created
	ButtonON = 1;
}
The last callback to be implemented is the UARTs receive complete, this one collects the values of frequency and amplitude:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //UART
{
uint16_t SineFrequency;
	
   //If the User's Button is not activated, then this loop happens
   if (ButtonON != 1){
	HAL_UART_Transmit(&huart2, &FrequencyBuffer[FrequencyIndex], 1,100);
		
	//if the frequency index is the same size as the buffer's value, then:
	If(FrequencyIndex >= (sizeof(FrequencyBuffer)-1)){
		//The index returns to the initial position
		FrequencyIndex = 0;
			
		//There will be a string for visual organization purpose
		HAL_UART_Transmit(&huart2, (uint8_t *)"\r\n\n", size of("\r\n\n"), 100);
			
		//The frequency variable updates its value to the value received
		SineFrequency = ((FrequencyBuffer[0]-0x30)*100) + ((FrequencyBuffer[1]-0x30)*10) + (FrequencyBuffer[2]-0x30);
			
		//The auxiliary timer frequency is modified accordingly
		uint16_t AuxTimerFreq = SineFrequency * 360;
			
		//The counter period is modified based on the new auxiliary timer frequency
		uint16_t CounterPeriod = (1000000)/(AuxTimerFreq - 1);
			
		//The new value will officially updated
		htim6.Init.Period = CounterPeriod;
			
		//There will be an initialization function
		HAL_TIM_Base_Init(&htim6);
			
		//The timer is started with the new values
		HAL_TIM_Base_Start_IT(&htim6);
			
		//The frequency string is activated again to keeps it in loop
		HAL_UART_Transmit(&huart2, FrequencyString, sizeof(FrequencyString), 100);

		//Receive the Frequency value
		HAL_UART_Receive_IT(&huart2, FrequencyBuffer, 1);
			
	//If the frequency index is lower than the buffer's value, then:
	} else
	{
	     //The index keeps walking through the buffer size until it is completed
	     FrequencyIndex++;
			
	     //The receive frequency function is activated until the buffer is completed 
	     HAL_UART_Receive_IT(&huart2, &FrequencyBuffer[FrequencyIndex], 1);
	}
   }
   //If the User's Button is activated, then this loop happens
   else{
	HAL_UART_Transmit(&huart2, &AmplitudeBuffer[AmplitudeIndex], 1, 100);
		
	//if the amplitude index is the same size as the buffer's value, then:
	if(AmplitudeIndex >= (sizeof(AmplitudeBuffer)-1)){
		//The index returns to the initial position
		AmplitudeIndex = 0;
			
		//There will be a string for visual organization purpose
		HAL_UART_Transmit(&huart2, (uint8_t *)"\r\n\n", sizeof("\r\n\n"), 100);
			
		//The amplitude variable updates its value to the value received
		SineAmplitude = ((AmplitudeBuffer[0]-0x30)*100) + ((AmplitudeBuffer[1]-0x30)*10) + (AmplitudeBuffer[2]-0x30);
			
		//This loop takes any value higher than 100 and set it to 100, which is the maximum value possible
		if(SineAmplitude > 100) 
			SineAmplitude = 100;
			
		//This will return the flag to 0, so it can switch back to the frequency loop
		ButtonON = 0;
			
		//The frequency string is activated again to keeps it in loop
		HAL_UART_Transmit(&huart2, FrequencyString, sizeof(FrequencyString), 100);
			
		//The value of the frequency can be updated again
		HAL_UART_Receive_IT(&huart2, FrequencyBuffer, 1);
		}
	//If the amplitude index is lower than the buffer's value, then:
	else
	{
		//The index keeps walking through the buffer size until it is completed
		AmplitudeIndex++;
			
		//The receive amplitude function is activated until the buffer is completed 
		HAL_UART_Receive_IT(&huart2, &AmplitudeBuffer[AmplitudeIndex], 1);
	}
   }
}

Results:

For this article implementation, we used Tera Term’s software to receive the input serial data, but any other terminal can be used:
433.png
The “Choose Frequency” string continues to repeat until the user’s button is pressed. Upon pressing, the “Choose Amplitude” string appears and this other parameter can be modified:
436.png
Now, it is possible to control the sine wave’s frequency and amplitude as you wish with minor limitations when it comes to the final frequency due to resolution.
Below you can see a few value demonstration and the corresponding sine wave signal generated after the LPF:
439.png
442.png444.png447.png

References:


NUCLEO-G070RB - STM32 Nucleo-64 development board with STM32G070RB MCU, supports Arduino and ST morpho connectivity
STM32 Nucleo-64 boards (MB1360) - User manual
Arm® Cortex®-M0+ 32-bit MCU, 128 KB Flash, 36 KB RAM, 4x USART, timers, ADC, DAC, comm. I/Fs, 1.7-3.6V
STM32G0x0 advanced Arm®-based 32-bit MCUs - Reference manual
Version history
Last update:
‎2022-09-06 12:57 AM
Updated by: