CH32V003 Programming: How to generate PWM Output

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.

CH32V003 Programming: How to generate PWM Output 1

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

CH32V003 Programming: How to generate PWM Output 2

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:

CH32V003 Programming: How to generate PWM Output 3

See this is output we get on D2.

CH32V003 Programming: How to generate PWM Output 4

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.

CH32V003 Programming: How to generate PWM Output 5

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);
CH32V003 Programming: How to generate PWM Output 6

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:

  1. Setting up the Development Environment for CH32V003: Compile and Run first example code
  2. CH32V003: GPIO as Output
  3. CH32V003: GPIO as Input (Polling, Interrupt)
  4. CH32V003: UART Transmit / Receive data
  5. CH32V003: PWM output
  6. CH32V003: ADC for analog signal measurement
  7. CH32V003: I2C Interface
  8. CH32V003: SPI Interface
  9. CH32V003: Timer Interrupt
  10. 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.


Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.