Menu Close

PWM engineering code and example waveform

1. Engineering code


Related reference articles:

RISC-V teaching plan


The interrupt caused by PWM is also an external interrupt, and its interrupt request is passed to the PLIC for processing. The corresponding software processing of PWM interrupt is very similar to GPIO interrupt (click here for details). So only the parts of the PWM interrupt code that are different from the previously introduced code are listed here.


1.1. fii_pwm.c

#include <stdio.h>
#include <stdint.h>
#include "fii_pwm.h"
#include "platform.h"

//Implement PWM_IRQ_register function, configure PWM enable, period, duty cycle, count
void PWM_IRQ_register(  u32_t pwm_pin, u32_t pwm_period, u32_t pwm_period_DC,
                        u32_t pwm_cnt,E_PWM_IRQ_SW pwm_sw)
    //verilog source code is located in "RIB_fii_pwm"
    switch(pwm_pin)           //PWM0/1/2/3
    case 0x00:                //pwm0
        PWM_REG(PWM_PERIOD + 32)= pwm_period;         //period
        PWM_REG(PWM_CNT + 32) = pwm_cnt;              //count
        PWM_REG(PWM_PERIOD_DC + 32) = pwm_period_DC;  //duty cycle
    case 0x08:               //pwm1
        PWM_REG(PWM_PERIOD + 64)= pwm_period;         //period
        PWM_REG(PWM_CNT + 64) = pwm_cnt;              //count
        PWM_REG(PWM_PERIOD_DC + 64) = pwm_period_DC;  //duty cycle
    case 0x10:               //pwm2
        PWM_REG(PWM_PERIOD + 96)= pwm_period;         //period
        PWM_REG(PWM_CNT + 96) = pwm_cnt;              //count
        PWM_REG(PWM_PERIOD_DC + 96) = pwm_period_DC;  //duty cycle
    case 0x18:              //pwm3
        PWM_REG(PWM_PERIOD + 128)= pwm_period;        //period
        PWM_REG(PWM_CNT + 128) = pwm_cnt;             //count
        PWM_REG(PWM_PERIOD_DC + 128) = pwm_period_DC; //duty cycle
        printf("No valid pwm pin");

    if(pwm_sw == PWM_IRQ_DIS)      //disable interrupt     
        PWM_REG(PWM_IRQ_ENA) &= ~(1UL<<pwm_pin);        //Interrupt enable
        PWM_REG(PWM_IRQ_MASK) &= ~(1UL<<pwm_pin);       //interrupt mask
    else                          //enable interrupt  
        PWM_REG(PWM_IRQ_ENA) |= 1UL<<pwm_pin;           //Interrupt enable
        PWM_REG(PWM_IRQ_MASK) |= 1UL<<pwm_pin;          //interrupt mask





1.2. platform.h

//Define a simple expression for PWM address access
#define PWM_REG(offset) (*(volatile unsigned int *)(PWM_ADDR + offset))

//Define PWM interrupt ID
//In the current PWM group, only the first PWM of each group is implemented
#define INT_PWM0_BASE 44          
#define INT_PWM1_BASE 48
#define INT_PWM2_BASE 52
#define INT_PWM3_BASE 56

//Define the PWM offset
#define PWM_0_OFFSET (0)
#define PWM_1_OFFSET (8)
#define PWM_2_OFFSET (16)
#define PWM_3_OFFSET (24)

//Redefine PWM interrupt ID



2. PWM experimental waveform

Set the IRQ_register function in main.c to generate waveforms with the same frequency, 100 KHz, and duty cycles of 3/5 and 1/5, respectively.

//Set interrupt related registers
void IRQ_register (){
    // Disable all interrupts until interrupt setup is complete
    clear_csr(mie, MIP_MEIP);                // disable external interrupt
    clear_csr(mie, MIP_MTIP);                // disable timer interrupt

    //Create an interrupt handler for each external interrupt source
    for (int ii = 0; ii < PLIC_NUM_INTERRUPTS; ii ++){
        g_ext_interrupt_handlers[ii] = no_interrupt_handler;

    //Create an interrupt handler for PWM
    g_ext_interrupt_handlers[INT_DEVICE_PWM_0] = PWM_0_handler;
    g_ext_interrupt_handlers[INT_DEVICE_PWM_1] = PWM_1_handler;

    // Enable interrupts at both the PLIC level and the GPIO level, here is the PLIC enable

    // The priority must be set to > 0 to effectively trigger the interrupt
    PLIC_set_priority(&g_plic, INT_DEVICE_PWM_0, 3);
    PLIC_set_priority(&g_plic, INT_DEVICE_PWM_1, 2);

    //Set the period, count, and duty cycle of the PWM interrupt
    PWM_IRQ_register(PWM_0_OFFSET, 500, 300, 0, PWM_IRQ_EN);
    PWM_IRQ_register(PWM_1_OFFSET, 500, 100, 0, PWM_IRQ_EN);

    //PWM interrupt module initialization ends




As you can see, PWM0 and PWM1 are set to waveforms with a frequency of 50 MHz/500 = 100 KHz, respectively. A count of 0 means that the PWM will continue and work forever. The only difference is that the duty cycle of PWM0 is 300/500 = 60%, and the duty cycle of PWM1 is 100/500 = 20%.

Observe PWM0 and PWM through an oscilloscope, as shown in Figure 1, the yellow signal is PWM0, and the red signal is PWM1. It can be seen that the actual waveform is consistent with the setting above.



Figure 1 Oscilloscope to observe PWM0 and PWM1 outputs


Engineering code download:


Posted in FPGA, RISC-V, RISC-V Textbook, Textbook and Training Project

Related Articles

Leave a Reply

Your email address will not be published.

Leave the field below empty!