1. GD32A50x芯片的DMA搬运数据到PWM的CCR寄存器控制脉冲详解
在嵌入式硬件开发中,精确控制PWM输出是电机控制、电源管理等应用的核心需求。GD32A50x系列芯片提供了强大的DMA+PWM联动机制,可以实现无需CPU干预的精确脉冲控制。本文将深入解析如何利用DMA将数据搬运到PWM的CCR寄存器,实现高效的脉冲控制。
1.1 硬件架构概述
GD32A50x的定时器模块(TIMER)与DMA控制器协同工作时,具有以下关键特性:
- 多CCR同步更新:通过DMA一次触发可同时更新多个通道的CCR值
- 硬件自动分发:定时器硬件根据配置自动将数据分发到指定CCR寄存器
- 互补输出支持:主通道和互补通道共享CCR寄存器,硬件自动生成互补信号
这种架构特别适合需要精确同步的多通道PWM控制场景,如三相电机驱动。
2. 初始化配置详解
2.1 GPIO初始化配置
PWM输出需要正确配置GPIO的复用功能。以TIMER0为例:
c复制// 配置TIMER0_CH0 (PC8) 和 TIMER0_CH1 (PA4) 为复用功能
gpio_init(PC8, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_AF_1);
gpio_init(PA4, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_AF_1);
// 配置互补通道 TIMER0_MCH0 (PC7) 和 TIMER0_MCH1 (PA3)
gpio_init(PC7, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_AF_1);
gpio_init(PA3, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_AF_1);
注意:GPIO速度等级应根据实际PWM频率选择,高频PWM(>10kHz)建议使用50MHz配置
2.2 TIMER0基础配置
定时器的核心配置包括时钟分频、计数模式和周期设置:
c复制timer_init_para timer_init_struct;
timer_init_struct.prescaler = 99; // 预分频值
timer_init_struct.alignedmode = TIMER_COUNTER_EDGE; // 边沿对齐模式
timer_init_struct.counterdirection = TIMER_COUNTER_UP; // 向上计数
timer_init_struct.period = 999; // 自动重载值
timer_init_struct.clockdivision = TIMER_CKDIV_DIV1; // 时钟分频
timer_init(TIMER0, &timer_init_struct);
计算实际PWM频率:
- 系统时钟:100MHz
- 定时器时钟 = 100MHz/(99+1) = 1MHz
- PWM周期 = (999+1) * 1μs = 1ms → PWM频率=1kHz
2.3 PWM通道配置
配置通道0和通道1为PWM模式:
c复制timer_oc_init_para oc_init_struct;
oc_init_struct.outputstate = TIMER_CCX_ENABLE; // 使能主输出
oc_init_struct.outputnstate = TIMER_CCXN_ENABLE; // 使能互补输出
oc_init_struct.ocpolarity = TIMER_OC_POLARITY_HIGH; // 输出极性
oc_init_struct.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
oc_init_struct.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
oc_init_struct.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
oc_init_struct.pulse = 100; // 初始占空比
timer_channel_output_config(TIMER0, TIMER_CH_0, &oc_init_struct);
timer_channel_output_config(TIMER0, TIMER_CH_1, &oc_init_struct);
2.4 DMA触发配置
关键配置寄存器说明:
-
TIMER0_DMACFG寄存器:
- DMATA[5:0] = 13 → 起始地址偏移 = 13×4 = 0x34
- DMATC[13:8] = 1 → 传输次数 = 1+1 = 2
-
DMA传输配置:
c复制dma_init_para dma_init_struct;
dma_init_struct.periph_addr = (uint32_t)&TIMER0->DMATB; // 外设地址
dma_init_struct.memory_addr = (uint32_t)buffer; // 内存地址
dma_init_struct.direction = DMA_DIR_MEMORY_TO_PERIPHERAL; // 传输方向
dma_init_struct.buffer_size = 8; // 传输数据量
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 外设地址不递增
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 内存地址递增
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT; // 外设数据宽度
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT; // 内存数据宽度
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; // 优先级
dma_init_struct.loop_mode = DMA_MODE_CIRCULAR; // 循环模式
dma_channel_init(DMA0, DMA_CH4, &dma_init_struct);
// 配置DMAMUX触发源
dmamux_init(DMA0, DMA_CH4, DMAMUX_DMA_REQUEST_TIMER0_UP);
3. 数据传输流程解析
3.1 硬件自动触发流程
-
TIMER0计数溢出:
- 计数器从0递增到999(ARR值)
- 硬件置位UPIF标志,触发DMA请求
-
DMA传输启动:
- DMA控制器从内存(buffer)读取第一个数据(100)
- 将数据写入TIMER0_DMATB寄存器
-
硬件自动分发:
- 定时器检测到DMATB被写入
- 根据DMATA值计算第一个CCR地址(TIMER0_CH0CV)
- 将DMATB[15:0]写入TIMER0_CH0CV
- 根据DMATC值请求第二个数据,写入TIMER0_CH1CV
3.2 数据同步机制
GD32A50x的独特优势在于其硬件级同步:
- 多个CCR寄存器更新是原子操作,无时间差
- 主通道和互补通道同步更新
- 整个过程中CPU无需干预
这种机制特别适合需要严格同步的应用,如:
- 电机驱动的三相PWM输出
- 多路同步电源控制
- 精密时序控制应用
4. 实际应用示例
4.1 电机控制应用
假设我们需要控制一个双路电机,PWM数据缓冲区定义如下:
c复制uint16_t motor_buffer[8] = {
// 通道0 通道1
100, 200, // 状态1
300, 400, // 状态2
500, 600, // 状态3
700, 800 // 状态4
};
工作流程:
- 初始化后,系统自动循环输出4种状态
- 每种状态的PWM占空比同步更新
- 切换间隔为PWM周期(1ms)
4.2 动态调整策略
如需动态改变PWM模式,可通过以下方式:
- 修改buffer内容:
c复制motor_buffer[0] = new_value1;
motor_buffer[1] = new_value2;
- 调整传输数据量:
c复制DMA_CHCNT(DMA0, DMA_CH4) = new_buffer_size;
注意:修改buffer内容时,应确保DMA当前未在访问要修改的位置
5. 常见问题与调试技巧
5.1 典型问题排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无PWM输出 | GPIO配置错误 | 检查复用功能和输出模式 |
| 占空比不正确 | DMA数据传输错误 | 检查buffer内容和传输宽度 |
| 通道不同步 | DMATC配置错误 | 确保DMATC=通道数-1 |
| 输出抖动 | 时钟配置不当 | 检查定时器时钟分频 |
5.2 性能优化建议
-
内存对齐优化:
- 确保buffer数组地址32位对齐
- 可使用
__attribute__((aligned(4)))修饰
-
DMA优先级设置:
- 高实时性要求时设为ULTRA_HIGH
- 避免被其他DMA通道打断
-
缓存一致性:
- 如果使用Cache,需手动维护缓存一致性
- 修改buffer后执行SCB_CleanDCache_by_Addr()
5.3 高级应用技巧
- 与中断配合使用:
c复制// 配置DMA传输完成中断
dma_interrupt_enable(DMA0, DMA_CH4, DMA_INT_FTF);
nvic_irq_enable(DMA0_Channel4_IRQn, 0, 0);
// 中断服务程序中更新buffer
void DMA0_Channel4_IRQHandler(void)
{
if(dma_interrupt_flag_get(DMA0, DMA_CH4, DMA_INT_FLAG_FTF)) {
// 更新buffer内容
dma_interrupt_flag_clear(DMA0, DMA_CH4, DMA_INT_FLAG_FTF);
}
}
- 动态调整PWM频率:
- 通过修改TIMER0_PSC和TIMER0_CAR实现
- 注意:修改前应禁用定时器,修改后重新使能
6. 关键参数计算参考
6.1 PWM频率与分辨率
计算公式:
code复制PWM频率 = Timer_CLK / (PSC + 1) / (ARR + 1)
PWM分辨率 = log2(ARR + 1)
示例配置:
- 期望PWM频率:20kHz
- 系统时钟:100MHz
- 计算:
- 取ARR=499 → 周期=500
- PSC = 100MHz/20kHz/500 - 1 = 9
- 实际频率 = 100MHz/(9+1)/500 = 20kHz
- 分辨率 = log2(500) ≈ 9bit
6.2 死区时间计算
互补PWM需配置死区时间:
code复制死区时间 = DTCFG[7:0] * (1/Timer_CLK)
例如:
- 需要1μs死区
- Timer_CLK = 10MHz (PSC=9)
- DTCFG = 1μs * 10MHz = 10 → 0x0A
配置代码:
c复制timer_deadtime_config(TIMER0, 0x0A, TIMER_DTG_DIV_1);
7. 实测波形分析
使用逻辑分析仪捕获的典型波形:
-
正常PWM输出:
- 通道间延迟 < 10ns
- 占空比误差 < 0.1%
-
互补输出波形:
- 死区时间精确符合配置
- 上升/下降时间约20ns(与GPIO速度相关)
-
模式切换瞬态:
- 无毛刺或异常脉冲
- 切换过程同步完成
在实际项目中,这种硬件级同步机制可以显著提高系统可靠性,特别是在高精度电机控制应用中,避免了软件干预带来的时序不确定性问题。通过合理配置DMA和定时器参数,可以实现极其精确的PWM控制,同时大大减轻CPU负担。
