DMA + PWM + GPIO
该示例使用 DMA + PWM + GPIO 实现数字信号任意波形输出。
示例中使用 TIM5,定时周期 10us,定时产生 DMA 请求,实现 DMA 搬运数据到 GPIO 寄存器。通过改变 GPIO 输出高低电平时间 (DMA 源数据搬运次数*TIM 周期),模拟动态改变 PWM 波形占空比的功能。
例如:如果定义数据:{0xFFFFFFFF,0xFFFFFFFF,0x0},则 GPIO 输出高电平时间为 2*10us=20us,GPIO 输出低电平时间为 1*10us=10us,PWM 周期为 30us,占空比 2/3。其中 0xFFFFFFFF 表示高电平,0x0 表示低电平。
示例中可以通过配置宏 GPIO_OUT_REPEAT
选择是否重复输出波形。
环境需求
该示例支持以下开发套件:
Hardware Platforms |
Board Name |
---|---|
RTL8752H HDK |
RTL8752H EVB |
更多信息请参考 快速入门。
硬件连线
连接 P2_3(PWM 输出)和 P2_5(GPIO 输出)至逻辑分析仪观察波形。
配置选项
该示例可配置的宏如下:
GPIO_OUT_REPEAT
:配置该宏可选择是否重复输出波形。
编译和下载
该示例的工程路径如下:
Project file: board\evb\io_sample\TIM\GDMA+PWM+GPIO\mdk
Project file: board\evb\io_sample\TIM\GDMA+PWM+GPIO\gcc
请按照以下步骤操作构建并运行该示例:
打开工程文件。
按照 快速入门 中 编译 APP Image 给出的步骤构建目标文件。
编译成功后,在路径
mdk\bin
或gcc\bin
下会生成 app binapp_MP_xxx.bin
文件。按下 reset 按键,开始运行。
测试验证
代码介绍
该章节分为以下几个部分:
源码路径
工程路径:
sdk\board\evb\io_sample\TIM\GDMA+PWM+GPIO
源码路径:
sdk\src\sample\io_sample\TIM\GDMA+PWM+GPIO
该工程的工程文件代码结构如下:
└── 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
初始化
当 EVB 复位启动时,执行 main
函数,执行以下流程:
int main(void)
{
extern uint32_t random_seed_value;
srand(random_seed_value);
__enable_irq();
gdma_pwm_gpio_demo();
while (1)
{
}
}
在 gdma_pwm_gpio_demo
中,包含 PAD/PINMUX 设置,GPIO,TIM 和 DMA 外设的初始化。
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
为 GPIO 相关的 PAD/PINMUX 设置,包含如下流程:
配置 PAD:设置引脚,PINMUX 模式,PowerOn,无内部上拉,输出高。
配置 PINMUX:配置引脚为 DWGPIO 功能。
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
为 PWM 相关的 PAD/PINMUX 设置,包含如下流程:
配置 PAD:设置引脚,PINMUX 模式,PowerOn,无内部上拉,输出高。
配置 PINMUX:配置引脚为 TIM_PWM5 功能。
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
为 GPIO 外设的初始化,包含如下流程:
使能 RCC 时钟。
设置 GPIO 的引脚为输出模式,失能中断。
设置 GPIO 的控制模式为 Hardware 模式。
备注
GPIO 设定为 Hardware Mode 后,0x40011200 地址作为 GPIO Data Register(Software Mode 时 GPIO_DR 作为 Data Register),BIT0 ~ BIT31 分别 Mapping 到 GPIO0 ~ GPIO31;
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
为 TIM 外设的初始化,包含如下流程:
使能 RCC 时钟。
配置 TIM 为用户自定义模式。
使能 PWM 输出,配置 PWM 输出周期和占空比。
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
为 DMA 外设的初始化,包含如下流程:
设置 DMA 传输方向为内存到外设。
设置源端地址自增,目的端地址固定。
设置传输数据大小为 32 位,每次 burst 固定传输一个数据。
设置源端地址为
GPIO_Ctl_AutoReload
,目的端地址为0x40011200
(GPIO Data Register)。配置 Handshake 为
GDMA_Handshake_TIM5
,即每个 PWM 周期触发一次搬运。使能 Multi-Block 传输。
若开启宏
GPIO_OUT_REPEAT
,则设置传输模式为自动加载源端地址(GPIO_Ctl_AutoReload
),否则设置传输模式为自动加载 LLI 结构体中源端地址(GPIO_Ctl_LLI
)。设置 LLI 结构体的参数信息,详情可见 DMA Multi-Block 中的 初始化 章节。
配置和使能 DMA 的 IRQ 通道。
配置 DMA 总传输完成中断
GDMA_INT_Transfer
,使能 DMA 传输,使能 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); }
功能实现
当 PWM 每输出一周期的波形时,触发一次 DMA 搬运,将数组内的数据搬运至 GPIO Data Register 中,并根据数据内容输出高低电平。
当 DMA 总传输完成时,触发 GDMA_INT_Transfer
中断,在中断处理函数内,清除 DMA 的中断挂起位。
void GDMA_Channel_Handler(void)
{
GDMA_ClearINTPendingBit(GDMA_CHANNEL_NUM, GDMA_INT_Transfer);
}