cancel
Showing results for 
Search instead for 
Did you mean: 

Changing a PWM complementary pins to GPIO functionality and then back to PWM at runtime

Ricko
Senior II

Hi,

I am running TMR1 CH1 and CH1N in complementary mode (so they are the opposite of each other).

 

Using a HAL function, how do I change each (separately) from PWM to GPIO and back to PWM?

 

Just so to explain what I am trying to do... I am driving a H-bridge, I want to be able to:

- run the PWM in complementary mode (i.e. biphase/bipolar bridge)

- fix one half of the bridge to high or low while the other side still runs in PWM

- switch both to either high or low

- go back to full PWM complementary

 

Thank you

1 ACCEPTED SOLUTION

Accepted Solutions
TDK
Guru

Hmm, pins have to be initialized somewhere, if they're working.

I created a project to test this, it looks like they're being initialized in HAL_TIM_MspPostInit. Can you check for that?

void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(htim->Instance==TIM8)
  {
  /* USER CODE BEGIN TIM8_MspPostInit 0 */

  /* USER CODE END TIM8_MspPostInit 0 */

    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    /**TIM8 GPIO Configuration
    PA7     ------> TIM8_CH1N
    PC6     ------> TIM8_CH1
    */
    GPIO_InitStruct.Pin = GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF3_TIM8;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF3_TIM8;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  /* USER CODE BEGIN TIM8_MspPostInit 1 */

  /* USER CODE END TIM8_MspPostInit 1 */
  }

}

If that doesn't work, please attach your IOC file and I will take a look..

 

If you feel a post has answered your question, please click "Accept as Solution".

View solution in original post

10 REPLIES 10
TDK
Guru

Use HAL_GPIO_Init to initialize them as GPIO_MODE_OUTPUT_PP, and control their states with HAL_GPIO_WritePin.

When you want to switch back to PWM mode, call HAL_GPIO_Init again and set them up in alternate function TIM mode. If you've set the timer up in PWM mode in CubeMX, the initialization calls to set the pins in PWM mode are in HAL_TIM_MspInit, call that again or call it within your own code.

If you feel a post has answered your question, please click "Accept as Solution".

Thank you @TDK,

 

I also just came across HAL_TIM_PWM_DeInit(). Do I need to use it or is it better not to?

 

In the documentation it just says that it deinitialises the timer and I am not sure what that means exactly... it is not clear which functionality is actually reset.

This is the API document I am looking at, is there a more comprehensive one perhaps?

 

Thank you :)

 

> I also just came across HAL_TIM_PWM_DeInit(). Do I need to use it or is it better not to?

I wouldn't. You can change the pins and keep the timer going in the background. That seems easiest for your use case.

> This is the API document I am looking at, is there a more comprehensive one perhaps?

The reference manual will detail exactly how the timers work. If you want the intimate details, that is the reference to look at.

If you feel a post has answered your question, please click "Accept as Solution".

Thank you @TDK 

I just tried implementing what you suggested i.e. calling HAL_TIM_MspInit() but there is no sign of it in the generated code anywhere in the project. There are other MspInit() functions as you can see from the screenshot here but not that one.

 

Ricko_3-1722822788563.png

 

The only functions generated by CubeMX are these:

 

Ricko_4-1722822815619.png

 

I am new to STM32 so I am wondering if it could be because I am using CH1 and CH1N with TMR1 configured like in the screenshot below? The generated complementary PWM outputs are correct on the oscilloscope but perhaps I configured it incorrectly or in a less than ideal manner (although )? Maybe using different settings would generate the HAL_TIM_MspInit() you are referring to?  Or perhaps I need to #include some header?

 

Ricko_0-1722822285588.png

 

Thank you again! :)

TDK
Guru

HAL_TIM_PWM_MspInit probably. Or maybe HAL_TIM_Base_MspInit

Whatever function the pins are initialized in.

If you feel a post has answered your question, please click "Accept as Solution".

Thank you

and really sorry about this but I have been looking at all those functions, but unless I am missing something, it does not appear the pins are initialised in any of them.

I am new to STM32, perhaps initialisation is there but in a form I don't recognise?

 

The PWM pin names are OUTPUT_PWM_2 and OUTPUT_PWM_1 on PA7 and PA8. But I don't see them addressed anywhere in any of the project files.

 

Thank you

 

 

static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();

/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, EXT_FLASH_CS_Pin|LED1_Pin|LED_POWER_Pin|LED3_Pin, GPIO_PIN_RESET);

/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, LED2_Pin|STPR_ENABLE_Pin|STPR_RESET_Pin|STPR_SLEEP_Pin
|STPR_DIR_Pin|uSD_CS_Pin, GPIO_PIN_RESET);

/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOC, ENZYME_SNS_RST_Pin|LED_BATT_LOW_Pin|LED_CARTRIDGE_Pin|LED_TEMP_Pin, GPIO_PIN_RESET);

/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED_ENZYME_LOW_GPIO_Port, LED_ENZYME_LOW_Pin, GPIO_PIN_SET);

/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(OUTPUT_EN_1_GPIO_Port, OUTPUT_EN_1_Pin, GPIO_PIN_SET);

/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(OUTPUT_EN_2_GPIO_Port, OUTPUT_EN_2_Pin, GPIO_PIN_SET);

/*Configure GPIO pins : USER_BTN_1_Pin KEYPAD_I2C_INT_AN_Pin ENZYME_SNS_OUT_2_Pin ENZYME_SNS_OUT_1_Pin
USER_BTN_2_Pin */
GPIO_InitStruct.Pin = USER_BTN_1_Pin|KEYPAD_I2C_INT_AN_Pin|ENZYME_SNS_OUT_2_Pin|ENZYME_SNS_OUT_1_Pin
|USER_BTN_2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

/*Configure GPIO pins : EXT_FLASH_CS_Pin LED1_Pin LED_POWER_Pin LED3_Pin */
GPIO_InitStruct.Pin = EXT_FLASH_CS_Pin|LED1_Pin|LED_POWER_Pin|LED3_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

/*Configure GPIO pins : FLOW_SW_Pin CARTRIDGE_SW_Pin INT_TEMP_ALRM_Pin uSD_CD_Pin */
GPIO_InitStruct.Pin = FLOW_SW_Pin|CARTRIDGE_SW_Pin|INT_TEMP_ALRM_Pin|uSD_CD_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

/*Configure GPIO pins : LED2_Pin STPR_ENABLE_Pin STPR_RESET_Pin STPR_SLEEP_Pin
STPR_DIR_Pin OUTPUT_EN_2_Pin uSD_CS_Pin */
GPIO_InitStruct.Pin = LED2_Pin|STPR_ENABLE_Pin|STPR_RESET_Pin|STPR_SLEEP_Pin
|STPR_DIR_Pin|OUTPUT_EN_2_Pin|uSD_CS_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

/*Configure GPIO pins : ENZYME_SNS_RST_Pin LED_BATT_LOW_Pin LED_ENZYME_LOW_Pin LED_CARTRIDGE_Pin
LED_TEMP_Pin */
GPIO_InitStruct.Pin = ENZYME_SNS_RST_Pin|LED_BATT_LOW_Pin|LED_ENZYME_LOW_Pin|LED_CARTRIDGE_Pin
|LED_TEMP_Pin;
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);

/*Configure GPIO pin : OUTPUT_EN_1_Pin */
GPIO_InitStruct.Pin = OUTPUT_EN_1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(OUTPUT_EN_1_GPIO_Port, &GPIO_InitStruct);

/*Configure GPIO pin : PB4 */
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

/*Configure GPIO pin : uC_BOOT0_Pin */
GPIO_InitStruct.Pin = uC_BOOT0_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(uC_BOOT0_GPIO_Port, &GPIO_InitStruct);

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

 

 

 

 

 

 

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
{
  if(htim_base->Instance==TIM1)
  {
  /* USER CODE BEGIN TIM1_MspInit 0 */

  /* USER CODE END TIM1_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_TIM1_CLK_ENABLE();
    /* TIM1 interrupt Init */
    HAL_NVIC_SetPriority(TIM1_UP_TIM16_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn);
  /* USER CODE BEGIN TIM1_MspInit 1 */

  /* USER CODE END TIM1_MspInit 1 */
  }
}

 

 

 

 

 

void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim_pwm)
{
  if(htim_pwm->Instance==TIM2)
  {
  /* USER CODE BEGIN TIM2_MspInit 0 */

  /* USER CODE END TIM2_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_TIM2_CLK_ENABLE();
  /* USER CODE BEGIN TIM2_MspInit 1 */

  /* USER CODE END TIM2_MspInit 1 */
  }
  else if(htim_pwm->Instance==TIM3)
  {
  /* USER CODE BEGIN TIM3_MspInit 0 */

  /* USER CODE END TIM3_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_TIM3_CLK_ENABLE();
  /* USER CODE BEGIN TIM3_MspInit 1 */

  /* USER CODE END TIM3_MspInit 1 */
  }

}

 

 

 

 

TDK
Guru

Hmm, pins have to be initialized somewhere, if they're working.

I created a project to test this, it looks like they're being initialized in HAL_TIM_MspPostInit. Can you check for that?

void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(htim->Instance==TIM8)
  {
  /* USER CODE BEGIN TIM8_MspPostInit 0 */

  /* USER CODE END TIM8_MspPostInit 0 */

    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    /**TIM8 GPIO Configuration
    PA7     ------> TIM8_CH1N
    PC6     ------> TIM8_CH1
    */
    GPIO_InitStruct.Pin = GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF3_TIM8;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF3_TIM8;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  /* USER CODE BEGIN TIM8_MspPostInit 1 */

  /* USER CODE END TIM8_MspPostInit 1 */
  }

}

If that doesn't work, please attach your IOC file and I will take a look..

 

If you feel a post has answered your question, please click "Accept as Solution".

 

Thank you @TDK 

yes I found it and it works. :thumbs_up:

 

I then extracted that code and implemented in my own so I don't have to call that function. The code works and the outputs work as I expected.

 

Could I ask you to have a look at the code below to double check that I did not leave anything out (anything I might not be aware of). There are two switch case statements, one to set both channels as PWM complementary outputs and one to set one as PWM and the other as GPIO high.

 

Thank you again for your help! :folded_hands: :smiling_face_with_smiling_eyes:

 

void SetAndRunOutputMode(uint8_t mode)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};

	//Turn off drivers EN pins
	HAL_GPIO_WritePin(OUTPUT_EN_1_GPIO_Port, OUTPUT_EN_1_Pin, GPIO_PIN_RESET); //Disable driver IC for CH1 PWM (i.e. Output 1)
	HAL_GPIO_WritePin(OUTPUT_EN_2_GPIO_Port, OUTPUT_EN_2_Pin, GPIO_PIN_RESET); //Disable driver IC for CH1N PWM (i.e. Output 2 - complementary signal)

	//Stop timer1
	HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1);		//Stop timer for CH1 PWM (i.e. Output 1)
	HAL_TIMEx_PWMN_Stop(&htim1, TIM_CHANNEL_1);	//Start timer for CH1N PWM (i.e. Output 2 - complementary signal)

	switch (mode)
	{
		// ##### MODE 0 - BOTH OUTPUT CHANNELS OFF: Output 1 #####
		case OUTPUT_MODE__BOTH_CHANNELS_OFF:
			//Turn off drivers EN pins - already done at function entry
			//Stop timer1 - Already done at function entry
			break;


		// ##### MODE 1 - COMPLEMENTARY OUTPUTS: Output 1 is opposite than output 2 #####
		case OUTPUT_MODE__COMPLEMENTARY_PWM:
			//Configure BOTH GPIO pins for PWM - They are both on Port A so a single call to HAL_GPIO_Init is used
			GPIO_InitStruct.Pin = OUTPUT_PWM_1_Pin | OUTPUT_PWM_2_Pin;
			GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
			GPIO_InitStruct.Pull = GPIO_NOPULL;
			GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
			GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
			HAL_GPIO_Init(OUTPUT_PWM_1_GPIO_Port, &GPIO_InitStruct);

			//Start PWMs
			HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);		//Start timer for CH1 PWM (i.e. Output 1)
			HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);	//Start timer for CH1N PWM (i.e. Output 2 - complementary signal)

			//Enable both channels EN pins
			HAL_GPIO_WritePin(OUTPUT_EN_1_GPIO_Port, OUTPUT_EN_1_Pin, GPIO_PIN_SET); //Enable driver IC for CH1 PWM (i.e. Output 1)
			HAL_GPIO_WritePin(OUTPUT_EN_2_GPIO_Port, OUTPUT_EN_2_Pin, GPIO_PIN_SET); //Enable driver IC for CH1N PWM (i.e. Output 2 - complementary signal)
			break;


		// ##### MODE 2 - OUTPUT 1 = PWM and OUTPUT 2 = HIGH #####
		// Effectively "negative" PWM (0V to -24V PWM)
		// NOTE: Mode 1 and Mode 2 are pretty much the same. They have been implemented as two variation of the same
		// just to test if the current flowing in opposite direction tot he water flow makes any difference
		case OUTPUT_MODE__OUT1_PWM__OUT2_HIGH:
			//Configure OUTPUT_PWM_1 pin for PWM
			GPIO_InitStruct.Pin = OUTPUT_PWM_1_Pin; //Only OUT1PWM is configured as PWM
			GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
			GPIO_InitStruct.Pull = GPIO_NOPULL;
			GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
			GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
			HAL_GPIO_Init(OUTPUT_PWM_1_GPIO_Port, &GPIO_InitStruct);

			//Configure OUTPUT_PWM_2 pin as OUTPUT
			GPIO_InitStruct.Pin = OUTPUT_PWM_2_Pin;
			GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
			GPIO_InitStruct.Pull = GPIO_NOPULL;
			GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
			HAL_GPIO_Init(OUTPUT_PWM_2_GPIO_Port, &GPIO_InitStruct);

			//Start PWM (for OUTPUT_PWM_1 only)
			HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);	//Start timer for CH1N PWM (i.e. Output 2 - complementary signal)

			//Set OUTPUT_PWM_2 to HIGH
			HAL_GPIO_WritePin(OUTPUT_PWM_2_GPIO_Port, OUTPUT_PWM_2_Pin, GPIO_PIN_SET);

			//Enable both channels EN pins
			HAL_GPIO_WritePin(OUTPUT_EN_1_GPIO_Port, OUTPUT_EN_1_Pin, GPIO_PIN_SET); //Enable driver IC for CH1 PWM (i.e. Output 1)
			HAL_GPIO_WritePin(OUTPUT_EN_2_GPIO_Port, OUTPUT_EN_2_Pin, GPIO_PIN_SET); //Enable driver IC for CH1N PWM (i.e. Output 2 - complementary signal)
			break;

		default:
			break;
	}
}

 

TDK
Guru

Looks about right to me.

You don't have to stop the timer, you can keep it running and just do the HAL_GPIO_Init statements to change the behavior of the pins. Up to you. If you stop/restart the timer, note that the PWM signal will be interrupted briefly. Maybe this isn't an issue for you.

If you feel a post has answered your question, please click "Accept as Solution".