本帖最后由 Peixu 于 2025-5-30 14:35 编辑
#申请原创# @21小跑堂
APM32F4 定时器和DMA输出指定数量的 PWM 脉冲功能
APM32F4 系列(适用APM32402/411/405/407)微控制器提供了两种典型解决方案:高级定时器重复计数寄存器(RCR)和DMA(直接内存访问)。本文结合硬件特性与应用场景,对比分析两种方案的技术差异与优化策略,为开发者提供选型依据。
1. 高级定时器重复计数寄存器(RCR)方案
原理:利用高级定时器内置的重复计数寄存器(仅低 8 位有效,最大值 255),通过配置 TMRx_REPCNT 为 “脉冲数 - 1”,结合更新事件(UEV)触发计数递减。当 TMRx_REPCNT 从 1 减至 0 时,触发更新中断,通知脉冲输出完成。
局限性分析:
脉冲数限制:单次最大输出 255 个脉冲,超过需分段处理(如输出 1000 个脉冲需分 4 次配置)。
实时性瓶颈:
每 256 个脉冲触发一次中断(以 100kHz 频率为例,中断间隔 2.56ms),若中断处理耗时较长(如 > 10μs),可能导致脉冲间隔误差。
分段处理时需依赖中断响应速度,存在丢数风险
2. DMA 方案(定时器 / TIMx + DMA)
原理:通过 DMA 控制器将内存缓冲区中的数据(如定时器自动重装载值)批量写入寄存器,利用 DMA 传输完成标志(如 TCIF)触发中断,无需 CPU 干预传输过程。
关键优势:
脉冲数无硬性限制:仅受内存缓冲区大小限制(如配置MAX_BUFFER_SIZE=2000,可输出 2000 个脉冲)。
实时性优势:
硬件级数据传输,CPU 利用率低(仅中断处理耗时)。
避免 RCR 分段处理的中断频繁触发问题,适合高频连续脉冲输出(如 1MHz 频率下输出 1000 个脉冲仅需 1ms,无中断干扰)。
以下是两种方式的对比:
维度
| 重复计数寄存器RCR(只有高级定时器才有)
| DMA 方式
| 脉冲数限制
| 单次≤255,超过需分段处理
| 仅受内存限制(如缓冲区设为 2000,可输出 2000 个)
| 占用率
| 高(需处理中断和分段逻辑)
| 低(DMA 硬件自动传输,CPU 仅处理完成中断)
| 实时性
| 依赖中断响应速度(软件处理延迟)
| 硬件加速,延迟仅取决于 DMA 传输时间
| 资源占用
| 仅用定时器寄存器(无额外硬件)
| 需 DMA 通道 + 内存缓冲区(消耗更多 RAM)
|
3、典型场景分析
场景 1:输出 200 个 PWM 脉冲
RCR 方式:单次配置 RCR=199(200-1),触发一次更新事件即可完成,无需分段。优势:代码简单,资源占用少。
DMA 方式:需配置 DMA 缓冲区大小为 200,传输完成后触发中断。劣势:杀鸡用牛刀,配置复杂度高于需求。
场景 2:输出 1000 个 PWM 脉冲
RCR 方式:需分 4 次处理(255×3+235),每次传输完成后通过中断重新配置 RCR 并触发 UEV。风险:中断处理不及时可能导致脉冲数误差。
DMA 方式:一次性配置 DMA 缓冲区为 1000,硬件自动传输,CPU 仅在完成时响应一次中断。优势:实时性高,无需分段逻辑,可靠性更强。
4、总结
APM32F4 的 RCR 与 DMA 方案分别针对中小规模脉冲控制与大规模实时脉冲输出场景提供了高效解决方案:
1.RCR 方案以轻量级设计见长,适合脉冲数≤255 且对实时性要求不高的场景。
2.DMA 方案凭借硬件加速与批量传输特性,成为高频、大规模脉冲输出的首选。
开发者可根据脉冲数量、实时性要求及硬件资源(如 DMA 通道、SRAM 容量)灵活选择,必要时结合两者特性(如 RCR 分段 + DMA 传输)实现复杂需求。
// 最大缓冲区大小(可根据需要调整)
#define MAX_PULSE_COUNT 2000
uint16_t dmaBuffer[MAX_PULSE_COUNT];
// ----------------- 函数声明 ----------------- //
void GPIO_Configuration(void);
void TMR_Configuration(void);
void DMA_Configuration(uint16_t count);
void GenerateDMABuffer(uint16_t count);
void OutputPWMPulses(uint16_t count);
/*!
* [url=home.php?mod=space&uid=247401]@brief[/url] Main program
*
* @param None
*
* @retval None
*/
int main(void)
{
GPIO_Configuration();
// 示例:输出不同数量的脉冲
OutputPWMPulses(500); // 输出500个脉冲
while(1)
{
}
}
// 输出指定个数的PWM脉冲
void OutputPWMPulses(uint16_t count)
{
TMR_Reset(TMR1);
TMR_Configuration();
// 检查脉冲数是否超过最大值
if (count > MAX_PULSE_COUNT) {
count = MAX_PULSE_COUNT;
}
// 生成DMA缓冲区数据并配置DMA
GenerateDMABuffer(count);
DMA_Configuration(count);
// 启动定时器和DMA
TMR_Enable(TMR1);
DMA_Enable(DMA2_Stream5);
// 等待DMA传输完成
while (!DMA_ReadStatusFlag(DMA2_Stream5, DMA_FLAG_TCI**5)); // 确保标志位正确
// 停止定时器(完整停止流程)
TMR_Disable(TMR1);
DMA_Disable(DMA2_Stream5); // 新增:禁用DMA流
DMA_ClearStatusFlag(DMA2_Stream5, DMA_FLAG_TCI**5); // 清除标志位
}
// 配置GPIO
void GPIO_Configuration(void)
{
GPIO_Config_T gpioConfig;
// 使能GPIOA时钟
RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOA);
// 配置PA8为TMR1_CH1
GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_8, GPIO_AF_TMR1);
gpioConfig.pin = GPIO_PIN_8;
gpioConfig.mode = GPIO_MODE_AF;
gpioConfig.otype = GPIO_OTYPE_PP;
gpioConfig.speed = GPIO_SPEED_100MHz;
GPIO_Config(GPIOA, &gpioConfig);
}
// 配置定时器1(100kHz PWM)
void TMR_Configuration(void)
{
TMR_BaseConfig_T tmrBaseConfig;
TMR_OCConfig_T tmrOCConfig;
// 使能TMR1时钟
RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_TMR1);
// 配置定时器(100kHz PWM频率)
tmrBaseConfig.clockDivision = TMR_CLOCK_DIV_1;
tmrBaseConfig.countMode = TMR_COUNTER_MODE_UP;
tmrBaseConfig.division = 1; // 预分频:168MHz/(1+1)=84MHz
tmrBaseConfig.period = 839; // 周期:84MHz/840=100kHz
TMR_ConfigTimeBase(TMR1, &tmrBaseConfig);
// 配置PWM通道1(50%占空比)
tmrOCConfig.mode = TMR_OC_MODE_PWM1;
tmrOCConfig.pulse = 420; // 占空比=420/840=50%
tmrOCConfig.outputState = TMR_OC_STATE_ENABLE;
tmrOCConfig.polarity = TMR_OC_POLARITY_HIGH;
TMR_ConfigOC1(TMR1, &tmrOCConfig);
// 使能预装载和主输出
TMR_ConfigOC1Preload(TMR1, TMR_OC_PRELOAD_ENABLE);
TMR_EnablePWMOutputs(TMR1);
// 使能定时器更新事件的DMA请求
TMR_EnableDMASoure(TMR1, TMR_DMA_SOURCE_UPDATE);
}
// 配置DMA
void DMA_Configuration(uint16_t count)
{
DMA_Config_T dmaConfig;
// 使能DMA2时钟
RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_DMA2);
// 配置DMA
dmaConfig.channel = DMA_CHANNEL_6; // TMR1更新事件对应通道6
dmaConfig.dir = DMA_DIR_MEMORYTOPERIPHERAL; // 内存到外设
dmaConfig.peripheralBaseAddr = (uint32_t)&TMR1->AUTORLD; // 自动重装载寄存器
dmaConfig.memoryBaseAddr = (uint32_t)dmaBuffer; // 缓冲区地址
dmaConfig.bufferSize = count; // 传输次数=脉冲数
dmaConfig.memoryInc = DMA_MEMORY_INC_ENABLE; // 内存地址递增
dmaConfig.peripheralInc = DMA_PERIPHERAL_INC_DISABLE; // 外设地址固定
dmaConfig.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD;
dmaConfig.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD;
dmaConfig.loopMode = DMA_MODE_NORMAL; // 非循环模式
dmaConfig.priority = DMA_PRIORITY_HIGH; // 高优先级
dmaConfig.fifoMode = DMA_FIFOMODE_DISABLE; // 禁用FIFO
// 初始化并使能DMA流
DMA_Reset(DMA2_Stream5);
DMA_Config(DMA2_Stream5, &dmaConfig);
}
// 生成DMA缓冲区数据
void GenerateDMABuffer(uint16_t count)
{
uint16_t period = 839; // 与定时器配置的周期值一致
for (uint16_t i = 0; i < count; i++)
{
// 前(count-1)个元素为周期值,最后一个为0(停止定时器)
dmaBuffer[i] = (i < count - 1) ? period : 0;
}
}
|
APM32F4 中定时器和DMA的结合,实现可控的PWM脉冲输出。