DMA + PWM + GPIO

This example uses DMA + PWM + GPIO to achieve arbitrary waveform output of digital signals.

The example uses TIM5 with a timing period of 10us, generating DMA requests periodically to transfer data to the GPIO register. By changing the duration of the high and low levels of the GPIO output (number of data transfer times by DMA source * TIM period), it simulates the function of dynamically changing the PWM waveform duty cycle.

For example: if the data is defined as {0xFFFFFFFF, 0xFFFFFFFF, 0x0}, the high-level output time of the GPIO is 2*10us=20us, the low-level output time of the GPIO is 1*10us=10us, the PWM period is 30us, and the duty cycle is 2/3. Here, 0xFFFFFFFF represents a high level, and 0x0 represents a low level.

In the example, you can choose whether to repeat the waveform output by configuring the macro GPIO_OUT_REPEAT.

Requirements

The sample supports the following development kits:

Development Kits

Hardware Platforms

Board Name

RTL8752H HDK

RTL8752H EVB

For more requirements, please refer to Quick Start.

Wiring

Connect P2_3 (PWM output) and P2_5 (GPIO output) to the logic analyzer to observe the waveform.

Configurations

The macros that can be configured in this example are as follows:

GPIO_OUT_REPEAT :Configure this macro to choose whether to repeat the output waveform.

Building and Downloading

This sample can be found in the SDK folder:

Project file: board\evb\io_sample\TIM\GDMA+PWM+GPIO\mdk

Project file: board\evb\io_sample\TIM\GDMA+PWM+GPIO\gcc

Please follow these steps to build and run the example:

  1. Open sample project file.

  2. To build the target, follow the steps listed on the Generating App Image in Quick Start.

  3. After a successful compilation, the app bin app_MP_xxx.bin will be generated in the directory mdk\bin or gcc\bin.

  4. To download app bin into EVB board, follow the steps listed on the MP Tool Download in Quick Start.

  5. Press reset button on EVB board and it will start running.

Experimental Verification

  1. When the macro GPIO_OUT_REPEAT is enabled, the duty cycle of the GPIO output waveform can be observed to match the settings in the GPIO_Ctl_AutoReload array. The number of periods that the PWM waveform remains high are {1, 2, 3, 5, 3, 2, 1}, cycling in sequence.

    This should be an image of the PWM output

    PWM Output Waveform - Repeated Output

  2. When the macro GPIO_OUT_REPEAT is not enabled, the duty cycle of the GPIO output waveform can be observed to match the settings in the GPIO_Ctl_LLI array. After all values in the GPIO_Ctl_LLI array are processed, the GPIO pin stops outputting the waveform.

    This should be an image of the PWM output

    PWM Output Waveform - Non-Repeated Output

Code Overview

This chapter will be introduced according to the following several parts:

  1. Source Code Directory.

  2. Peripheral initialization will be introduced in chapter Initialization.

  3. Functional implementation after initialization will be introduced in chapter Function Implementation.

Source Code Directory

  • Project directory: sdk\board\evb\io_sample\TIM\GDMA+PWM+GPIO

  • Source code directory: sdk\src\sample\io_sample\TIM\GDMA+PWM+GPIO

Source files are currently categorized into several groups as below.

└── Project: gdma_pwm_gpio
    └── secure_only_app
        └── include
            ├── app_define.h
            └── rom_uuid.h
        ├── cmsis                    includes CMSIS header files and startup files
            ├── overlay_mgr.c
            ├── system_rtl876x.c
            └── startup_rtl876x.s
        ├── lib                      includes all binary symbol files that user application is built on
            ├── rtl8752h_sdk.lib
            ├── gap_utils.lib
            └── ROM.lib
        ├── peripheral               includes all peripheral drivers and module code used by the application
            ├── rtl876x_rcc.c
            ├── rtl876x_pinmux.c
            ├── rtl876x_nvic.c
            ├── rtl876x_gdma.c
            ├── rtl876x_gpio.c
            └── rtl876x_tim.c
        ├── profile
        └── app                      includes the ble_peripheral user application implementation
            └── main.c

Initialization

When the EVB is reset, the main function is executed, following these steps:

int main(void)
{
    extern uint32_t random_seed_value;
    srand(random_seed_value);
    __enable_irq();

    gdma_pwm_gpio_demo();

    while (1)
    {

    }
}

The gdma_pwm_gpio_demo includes PAD/PINMUX settings, and the initialization of GPIO, TIM, and DMA peripherals.

void gdma_pwm_gpio_demo(void)
{
    board_gpio_init();
    board_pwm_init();
    driver_gpio_init();
    driver_pwm_init();
    driver_gdma_multiblock_init();
}

board_gpio_init is for PAD/PINMUX settings related to GPIO, including the following process:

  1. Configure PAD: Set the pin, PINMUX mode, PowerOn, internal pull-none, output high.

  2. Configure PINMUX: Configure the pin for DWGPIO function.

void board_gpio_init(void)
{
    Pad_Config(GPIO_OUTPUT_PIN_0, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_NONE, PAD_OUT_ENABLE, PAD_OUT_HIGH);
    Pinmux_Config(GPIO_OUTPUT_PIN_0, DWGPIO);
}

board_pwm_init is for PAD/PINMUX settings related to PWM, including the following process:

  1. Configure PAD: Set the pin, PINMUX mode, PowerOn, internal pull-none, output high.

  2. Configure PINMUX: Configure the pin for TIM_PWM5 function.

void board_pwm_init(void)
{
    Pad_Config(PWM_OUT_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_NONE, PAD_OUT_ENABLE, PAD_OUT_HIGH);
    Pinmux_Config(PWM_OUT_PIN, PWM_OUT_PIN_PINMUX);
}

driver_gpio_init is the initialization of the GPIO peripheral, which includes the following processes:

  1. Enable RCC clock.

  2. Set the GPIO pin to output mode and disable interrupts.

  3. Set the GPIO control mode to Hardware mode.

Note

After setting GPIO to Hardware Mode, the address 0x40011200 is used as the GPIO Data Register (in Software Mode, GPIO_DR is used as the Data Register), with BIT0 ~ BIT31 mapped to GPIO0 ~ GPIO31 respectively.

void driver_gpio_init(void)
{
    /* Initialize gpio peripheral */
    RCC_PeriphClockCmd(APBPeriph_GPIO, APBPeriph_GPIO_CLOCK, ENABLE);

    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_StructInit(&GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin    = GPIO_PIN_OUTPUT;
    GPIO_InitStruct.GPIO_Mode   = GPIO_Mode_OUT;
    GPIO_InitStruct.GPIO_ITCmd  = DISABLE;
    GPIO_InitStruct.GPIO_ControlMode = GPIO_HARDWARE_MODE;
    GPIO_Init(&GPIO_InitStruct);

}

driver_pwm_init is the initialization of the TIM peripheral, which includes the following processes:

  1. Enable the RCC clock.

  2. Configure TIM for user-defined mode.

  3. Enable PWM output, configure the PWM output period and duty cycle.

void driver_pwm_init(void)
{
    /* Initialize tim peripheral */
    RCC_PeriphClockCmd(APBPeriph_TIMER, APBPeriph_TIMER_CLOCK, ENABLE);

    TIM_TimeBaseInitTypeDef TIM_InitStruct;
    TIM_StructInit(&TIM_InitStruct);

    TIM_InitStruct.TIM_PWM_En = PWM_ENABLE;
    TIM_InitStruct.TIM_PWM_High_Count   = PWM_HIGH_COUNT;
    TIM_InitStruct.TIM_PWM_Low_Count    = PWM_LOW_COUNT;
    TIM_InitStruct.TIM_Mode = TIM_Mode_UserDefine;
    TIM_TimeBaseInit(PWM_TIMER_NUM, &TIM_InitStruct);

}

driver_gdma_multiblock_init is the initialization of the DMA peripheral, including the following steps:

  1. Set the DMA transfer direction from memory to peripheral.

  2. Set the source address to increment, and the destination address to fixed.

  3. Set the data size for transfer to 32 bits, with each burst transferring a single data unit.

  4. Set the source address to GPIO_Ctl_AutoReload and the destination address to 0x40011200 (GPIO Data Register).

  5. Configure Handshake to GDMA_Handshake_TIM5, which triggers a transfer once per PWM cycle.

  6. Enable Multi-Block transfer.

  7. If the macro GPIO_OUT_REPEAT is enabled, set the transfer mode to auto reload the source address (GPIO_Ctl_AutoReload); otherwise, set the transfer mode to auto reload the source address in the LLI structure (GPIO_Ctl_LLI).

  8. Set the parameters information of the LLI structure. For details, refer to the DMA Multi-Block in the Initialization section.

  9. Configure and enable the DMA IRQ channel.

  10. Configure the DMA transfer complete interrupt GDMA_INT_Transfer, enable DMA transfer, and enable TIM.

void driver_gdma_multiblock_init(void)
{
    /* Set gdma with GDMA_Handshake_TIM5, msize=1, transfer width = 32 */
    RCC_PeriphClockCmd(APBPeriph_GDMA, APBPeriph_GDMA_CLOCK, ENABLE);

    GDMA_InitTypeDef GDMA_InitStruct;
    GDMA_StructInit(&GDMA_InitStruct);

    GDMA_InitStruct.GDMA_ChannelNum          = GDMA_CHANNEL_NUM;
    GDMA_InitStruct.GDMA_DIR                 = GDMA_DIR_MemoryToPeripheral;
    GDMA_InitStruct.GDMA_BufferSize          = GDMA_TRANSFER_SIZE;//determine total transfer size
    GDMA_InitStruct.GDMA_SourceInc           = DMA_SourceInc_Inc;
    GDMA_InitStruct.GDMA_DestinationInc      = DMA_DestinationInc_Fix;
    GDMA_InitStruct.GDMA_SourceDataSize      = GDMA_DataSize_Word;
    GDMA_InitStruct.GDMA_DestinationDataSize = GDMA_DataSize_Word;
    GDMA_InitStruct.GDMA_SourceMsize         = GDMA_Msize_1;
    GDMA_InitStruct.GDMA_DestinationMsize    = GDMA_Msize_1;
    GDMA_InitStruct.GDMA_SourceAddr          = (uint32_t)GPIO_Ctl_AutoReload;
    GDMA_InitStruct.GDMA_DestinationAddr     = (uint32_t)(0x40011200);//vendor gpio reg address
    GDMA_InitStruct.GDMA_DestHandshake       = GDMA_Handshake_TIM5;
    GDMA_InitStruct.GDMA_Multi_Block_En     = 1;
    GDMA_InitStruct.GDMA_Multi_Block_Struct = (uint32_t)GDMA_LLIStruct;
#if (GPIO_OUT_REPEAT == 1)
    GDMA_InitStruct.GDMA_Multi_Block_Mode   = LLI_WITH_AUTO_RELOAD_SAR;
#else
    GDMA_InitStruct.GDMA_Multi_Block_Mode   = LLI_TRANSFER;
#endif
    for (int i = 0; i < GDMA_LLI_SIZE; i++)
    {
        GDMA_LLIStruct[i].SAR = (uint32_t)(&(GPIO_Ctl_LLI[i]));
        GDMA_LLIStruct[i].DAR = (uint32_t)(0x40011200);
        if (i == (GDMA_LLI_SIZE - 1))
        {
            GDMA_LLIStruct[i].LLP = 0;  //link back to beginning
            /* Configure low 32 bit of CTL register */
            GDMA_LLIStruct[i].CTL_LOW = BIT(0)
                                        | (GDMA_InitStruct.GDMA_DestinationDataSize << 1)
                                        | (GDMA_InitStruct.GDMA_SourceDataSize << 4)
                                        | (GDMA_InitStruct.GDMA_DestinationInc << 7)
                                        | (GDMA_InitStruct.GDMA_SourceInc << 9)
                                        | (GDMA_InitStruct.GDMA_DestinationMsize << 11)
                                        | (GDMA_InitStruct.GDMA_SourceMsize << 14)
                                        | (GDMA_InitStruct.GDMA_DIR << 20);
            /* Configure high 32 bit of CTL register */
            GDMA_LLIStruct[i].CTL_HIGH = GDMA_InitStruct.GDMA_BufferSize;
        }
        else
        {
            GDMA_LLIStruct[i].LLP = (uint32_t)&GDMA_LLIStruct[i + 1];
            /* Configure low 32 bit of CTL register */
            GDMA_LLIStruct[i].CTL_LOW = BIT(0)
                                        | (GDMA_InitStruct.GDMA_DestinationDataSize << 1)
                                        | (GDMA_InitStruct.GDMA_SourceDataSize << 4)
                                        | (GDMA_InitStruct.GDMA_DestinationInc << 7)
                                        | (GDMA_InitStruct.GDMA_SourceInc << 9)
                                        | (GDMA_InitStruct.GDMA_DestinationMsize << 11)
                                        | (GDMA_InitStruct.GDMA_SourceMsize << 14)
                                        | (GDMA_InitStruct.GDMA_DIR << 20)
                                        | (GDMA_InitStruct.GDMA_Multi_Block_Mode & LLP_SELECTED_BIT);//BIT(28) | BIT(27)
            /* Configure high 32 bit of CTL register */
            GDMA_LLIStruct[i].CTL_HIGH = GDMA_InitStruct.GDMA_BufferSize;
        }
    }

    GDMA_Init(GDMA_Channel, &GDMA_InitStruct);

    /* GDMA irq config */
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel         = GDMA_Channel_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelCmd      = (FunctionalState)ENABLE;
    NVIC_InitStruct.NVIC_IRQChannelPriority = 3;
    NVIC_Init(&NVIC_InitStruct);

    GDMA_INTConfig(GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);

    GDMA_Cmd(GDMA_CHANNEL_NUM, ENABLE);

    /* Set timer toggle */
    TIM_Cmd(PWM_TIMER_NUM, ENABLE);
}

Functional Implementation

When PWM outputs a waveform for each cycle, it triggers a DMA transfer, moving the data from the structure to the GPIO Data Register and outputting high and low levels based on the data content.

When the DMA total transfer is completed, it triggers the GDMA_INT_Transfer interrupt. In the interrupt handler, the DMA interrupt pending bit is cleared.

void GDMA_Channel_Handler(void)
{
    GDMA_ClearINTPendingBit(GDMA_CHANNEL_NUM, GDMA_INT_Transfer);
}