CH32V003 MCU can generate PWM signals which are commonly used for various use cases like buzzer control, dc motor control, generating audio signals, backlight control for LCD, etc.

What is PWM?
Pulse Width Modulation (PWM) is a method where the width of digital pulse is adjusted keeping it’s frequency same. Most microcontrollers have a built-in timer that can be used to generate a PWM signal.
Pinouts

If you go through the datasheet, you will be able to find that it has two timers, 1x advanced timer where you can generate PWM signals with dead time control, complementary output which are generally used in Power Electronics for motor control or power supplies. MCU has another timer which is a general purpose time which can also be used for PWM signal generation.
Configure PWM on CH32V003
In order to set pin for PWM output, you need configure it for Alternate Function for that pin and then you need configure the timer with all the settings like prescaler, period, pulse value and then enable it.
Let us see how that can be done with an example.
There is an example code given in the example code folder TIM folder >> PWM_Output, it configures Timer 1 at Port D Pin 2 for PWM output.
PWM output will be an alternate function so D2 need to configured to GPIO_Mode_AF_PP
. Output speed you can set to 2Mhz, 10Mhz or 50Mhz, here we have configured it to 10Mhz.
GPIO_InitTypeDef GPIO_InitStructure={0};
TIM_OCInitTypeDef TIM_OCInitStructure={0};
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure={0};
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOD, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init( GPIOD, &GPIO_InitStructure );
Here is the configuration for Timer 1
Timer to needs count up (TIM_CounterMode_Up
), the main system clock is divided by 1 and given to Timer module, this can be changed to reduce the frequency.
arr (this defines the total number of count in the period) and psc (this defines the period of the PWM signal)
RCC_APB2PeriphClockCmd( RCC_APB2Periph_TIM1, ENABLE );
TIM_TimeBaseInitStructure.TIM_Period = arr;
TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit( TIM1, &TIM_TimeBaseInitStructure);
Output control is configured as shown below. PWM_MODE1 and PWM_MODE2 are for if you need Edge aligned PWM output or centered aligned.
TIM_OCPolarity_High
sets the polarity of PWM signal if it will be High or Low.
ccp variable defines the pulse width of PWM signal.
#if (PWM_MODE == PWM_MODE1)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
#elif (PWM_MODE == PWM_MODE2)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
#endif
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = ccp;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init( TIM1, &TIM_OCInitStructure );
TIM_CtrlPWMOutputs(TIM1, ENABLE );
TIM_OC1PreloadConfig( TIM1, TIM_OCPreload_Disable );
TIM_ARRPreloadConfig( TIM1, ENABLE );
TIM_Cmd( TIM1, ENABLE );
Now, let us see how we can get the PWM signal on the Pin, which we can check with the help of an oscilloscope.
Main Function is as shown below:
As you can see, 1st parameter in TIM1_PWMOut_Init
is count of period and 3rd parameter 50 is how much pulse width will be, so in this case it will be 50% and Frequency is defined by system freq is 48000000, now divide that by 1st and second parameter. 48000000/(100*120) = 4000[4KHz]
/*********************************************************************
* @fn main
*
* @brief Main program.
*
* @return none
*/
int main(void)
{
SystemCoreClockUpdate();
//USART_Printf_Init(115200);
//printf("SystemClk:%d\r\n",SystemCoreClock);
//printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );
Delay_Ms(10);
TIM1_PWMOut_Init( 100, 120-1, 50);
while(1);
}
My setup looks like this:

See this is output we get on D2.

Now, if we call TIM1_PWMOut_Init( 100, 120-1, 25);
the pulse width will be for 75% and frequency will same 4Khz as explained above.

Now, if we want to generate say 1Khz 25% duty cycle, we have to set as shown below:
Frequency = 48000000/(100*4800)
Pulse Width = 100%-75% = 25%
TIM1_PWMOut_Init( 100, 480-1, 75);

Full code is here
#include "debug.h"
/* PWM Output Mode Definition */
#define PWM_MODE1 0
#define PWM_MODE2 1
/* PWM Output Mode Selection */
//#define PWM_MODE PWM_MODE1
#define PWM_MODE PWM_MODE2
/*********************************************************************
* @fn TIM1_OutCompare_Init
*
* @brief Initializes TIM1 output compare.
*
* @param arr - the period value.
* psc - the prescaler value.
* ccp - the pulse value.
*
* @return none
*/
void TIM1_PWMOut_Init(u16 arr, u16 psc, u16 ccp)
{
GPIO_InitTypeDef GPIO_InitStructure={0};
TIM_OCInitTypeDef TIM_OCInitStructure={0};
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure={0};
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOD, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init( GPIOD, &GPIO_InitStructure );
RCC_APB2PeriphClockCmd( RCC_APB2Periph_TIM1, ENABLE );
TIM_TimeBaseInitStructure.TIM_Period = arr;
TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit( TIM1, &TIM_TimeBaseInitStructure);
#if (PWM_MODE == PWM_MODE1)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
#elif (PWM_MODE == PWM_MODE2)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
#endif
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = ccp;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init( TIM1, &TIM_OCInitStructure );
TIM_CtrlPWMOutputs(TIM1, ENABLE );
TIM_OC1PreloadConfig( TIM1, TIM_OCPreload_Disable );
TIM_ARRPreloadConfig( TIM1, ENABLE );
TIM_Cmd( TIM1, ENABLE );
}
/*********************************************************************
* @fn main
*
* @brief Main program.
*
* @return none
*/
int main(void)
{
SystemCoreClockUpdate();
Delay_Ms(10);
TIM1_PWMOut_Init( 100, 480-1, 75);
while(1);
}
If we want to generate PWM pulse on any other pin we need to use remapping function as well.
I am still trying to learn that and once I have tested it I will add details here so that you can learn quickly how we can setup PWM on other pins.
I hope with this article you understood how you can generate PWM signal on CH32V003.
See also:
- Setting up the Development Environment for CH32V003: Compile and Run first example code
- CH32V003: GPIO as Output
- CH32V003: GPIO as Input (Polling, Interrupt)
- CH32V003: UART Transmit / Receive data
- CH32V003: PWM output
- CH32V003: ADC for analog signal measurement
- CH32V003: I2C Interface
- CH32V003: SPI Interface
- CH32V003: Timer Interrupt
- CH32V003: How to read 64-bit Unique ID
I am currently working as an embedded systems design consultant and helping companies build custom embedded products, develop test automation solution for their PCB.
If you have any feedback about the blog, you can share in the comments below or you can also contact me directly.
Read more interesting articles on Embedded Systems Design.