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:
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:
Open sample project file.
To build the target, follow the steps listed on the Generating App Image in Quick Start.
After a successful compilation, the app bin
app_MP_xxx.bin
will be generated in the directorymdk\bin
orgcc\bin
.To download app bin into EVB board, follow the steps listed on the MP Tool Download in Quick Start.
Press reset button on EVB board and it will start running.
Experimental Verification
When the macro
GPIO_OUT_REPEAT
is enabled, the duty cycle of the GPIO output waveform can be observed to match the settings in theGPIO_Ctl_AutoReload
array. The number of periods that the PWM waveform remains high are {1, 2, 3, 5, 3, 2, 1}, cycling in sequence.PWM Output Waveform - Repeated Output
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 theGPIO_Ctl_LLI
array. After all values in theGPIO_Ctl_LLI
array are processed, the GPIO pin stops outputting the waveform.PWM Output Waveform - Non-Repeated Output
Code Overview
This chapter will be introduced according to the following several parts:
Peripheral initialization will be introduced in chapter Initialization.
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:
Configure PAD: Set the pin, PINMUX mode, PowerOn, internal pull-none, output high.
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:
Configure PAD: Set the pin, PINMUX mode, PowerOn, internal pull-none, output high.
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:
Enable RCC clock.
Set the GPIO pin to output mode and disable interrupts.
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:
Enable the RCC clock.
Configure TIM for user-defined mode.
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:
Set the DMA transfer direction from memory to peripheral.
Set the source address to increment, and the destination address to fixed.
Set the data size for transfer to 32 bits, with each burst transferring a single data unit.
Set the source address to
GPIO_Ctl_AutoReload
and the destination address to0x40011200
(GPIO Data Register).Configure Handshake to
GDMA_Handshake_TIM5
, which triggers a transfer once per PWM cycle.Enable Multi-Block transfer.
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
).Set the parameters information of the LLI structure. For details, refer to the DMA Multi-Block in the Initialization section.
Configure and enable the DMA IRQ channel.
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);
}