STM32 TIM DMA burst 输出变频 PWM 波形

1. 问题背景

客户需要 MCU 输出一组变频的 PWM 波形来控制外围器件,并且不同频率脉冲的个数也不同。STM32U5 芯片拥有 TIM1/TIM8 高级定时器,还有通用定时器TIM2/TIM3/TIM4/TIM5 以及 TIM15/TIM16/TIM17。TIM 模块中,可通过修改 ARR 寄存器的值来修改 PWM 的频率。如果使用 TIM1/TIM8 或者 TIM15/TIM16/TIM17,则可以通过修改 RCR 与 CCR 寄存器,来控制脉冲个数及占空比。由于要同时修改多个 TIM 寄存器,需要使用 TIM 的 DMA burst 功能来实现此需求。

2. TIM DMA burst

STM32 片内部分 TIMER 在产生单个定时器事件情况下可以基于特定硬件机制触发多个 DMA 请求,这样产生多个连续的 DMA 传输来实现对多个 TIMER 寄存器的批量访问。

这就是所谓的 TIM DMA burst 功能,这里有两个专用寄存器:

  • TIMx_DCR :
  • DBSS : 触发 DMA burst 的事件源
  • DBL : DMA burst 传输个数
  • DBA : DMA burst 传输的 TIM 寄存器基地址索引
  • TIMx_DMAR :
  • TIM DMA Burst 时,DMA 访问此寄存器

3. 产生 PWM

本文使用 TIM1 来产生 PWM,在 U575 NECLEO 板上测试,MCU 主频为 100MHz。
使用两个频率分别对应 TIM 寄存器组的值如下:ARR/ RCR/ CCR1

uint32_t pulse1[3] = {1000, 2, 500} ;
uint32_t pulse2[3] = {5000, 1, 2500} ;

即输出 3 个 pulse1 的脉冲后,再输出 2 个 pulse2 脉冲,这样交替输出。

3.1. TIM 与 GPDMA 配置

3.1.1. TIM1 配置

TIM1 配置如下,使能寄存器预装载功能。

图1.TIM1 配置
在这里插入图片描述

3.1.2. GPDMA 配置

使用 GPDMA 通道 12 的 linked list 模式,并配置为循环模式:

图2.GPDMA 配置
在这里插入图片描述
Linked List 配置中,创建一个 list queue,并添加两个 list node,选择 GPDMA 来执行此 list queue,同样配置为循环模式,指定循环起始节点为 TN1,如下图。

图3.Linked List 配置
在这里插入图片描述
Linked List 节点配置中,使用 TIM1 update 事件来产生 DMA 请求,指定 DMA 目的地址为 TIMx_DMAR 寄存器,源地址为 pulse1 数组地址。TN2 只需将 pulse1 修改为 pulse2即可。

图4.Linked List Node 配置
在这里插入图片描述

3.1.3. TIMx_DCR 寄存器配置

在 CubeMX 生成代码后,添加以下代码,将 TIM 与 DMA 通道绑定,并配置TIMx_DCR 寄存器:

MX_TQ1_Config();
 /* Link created queue to DMA channel #######################################*/
 if (HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel12, &TQ1) != HAL_OK)
 {
 	Error_Handler();
 }
 
 __HAL_LINKDMA(&htim1, hdma[TIM_DMA_ID_CC1], handle_GPDMA1_Channel12);
 __HAL_TIM_ENABLE_DMA(&htim1, TIM_DMA_UPDATE);
 HAL_DMAEx_List_Start_IT(&handle_GPDMA1_Channel12);
 
 // update 事件触发 DMA burst
// 3 个 DMA transfer,分别修改 ARR/ RCR/ CCR1 寄存器
 // TIM 寄存器作为基地址的索引,ARR 寄存器索引为 11
 htim1.Instance->DCR = (1<<16) | ((3-1)<<8) | (11<<0);
 HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);

3.1.4. 测试结果

测试结果如下图,可以看到两个频率的 PWM 波形交替输出,且脉冲个数也符合需求:

图5.PWM 波形输出
在这里插入图片描述

3.2. TIM 无 RCR 寄存器情况

3.2.1. TIM2/TIM3/TIM4/TIM5 无 RCR 寄存器

当使用的 TIM 无 RCR 寄存器时,上述方式无法配置每个频率的 PWM 脉冲个数。而在U5 系列上,GPDMA 的 12-15 通道具有 2D 寻址能力,同时也有 repeat 功能。利用repeat 特性同样可以实现上述需求,下面以 TIM2 为例。

在前面配置基础上,使能 DMA 通道的 2D 功能,并添加 2D 寻址配置:

图6.GPDMA 通道 2D 功能
在这里插入图片描述
图7.GPDMA 通道 2D 寻址配置
在这里插入图片描述

3.2.2. 配置 TIM2

配置 TIM2 CH1 输出 PWM,使用 PA5 引脚:

图8.TIM2 配置
在这里插入图片描述

3.2.3. 修改代码

在 CubeMX 生成代码后,修改 pulse1/pulse2 的值,将 TIM2 与 DMA 通道绑定,并配置 TIMx_DCR 寄存器,这样也可实现两个频率,不同脉冲个数 PWM 交替输出的需求。

uint32_t pulse1[3] = {1000, 0, 500};
uint32_t pulse2[3] = {5000, 0, 2500};
MX_TQ1_Config();
 /* Link created queue to DMA channel #######################################*/
 if (HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel12, &TQ1) != HAL_OK)
 {
 	Error_Handler();
 }
 
 __HAL_LINKDMA(&htim2, hdma[TIM_DMA_ID_CC1], handle_GPDMA1_Channel12);
 __HAL_TIM_ENABLE_DMA(&htim2, TIM_DMA_UPDATE);
 HAL_DMAEx_List_Start_IT(&handle_GPDMA1_Channel12);
 
 // update 事件触发 DMA burst
// 3 个 DMA transfer,分别修改 ARR/ CCR1 寄存器,RCR 寄存器值被忽略
 // TIM 寄存器作为基地址的索引,ARR 寄存器索引为 11
 htim2.Instance->DCR = (1<<16) | ((3-1)<<8) | (11<<0);
 HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

4. 小结

使用 TIM DMA burst 功能,结合 STM32U5 的 GPDMA Linked list 模式及 2D 寻址特性,能灵活的输出 PWM 波形满足客户需求。

文档中所用到的工具及版本

1,STM32CubeMX 6.6.1
2,IAR 9.20.2


本文档参考ST官方的《【应用笔记】LAT1202+TIM+DMA+burst+输出变频+PWM+波形》文档。

  • 23
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一份基于STM32的高速ADC数据DMA转移采集代码,仅供参考: ```c #include "stm32f4xx.h" #include "stm32f4xx_dma.h" #include "stm32f4xx_adc.h" #define ADC1_DR_ADDRESS ((uint32_t)0x4001204C) uint16_t ADCConvertedValues[2]; // 存储ADC转换结果的数组 void DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); // 使能DMA2时钟 DMA_InitStructure.DMA_Channel = DMA_Channel_0; // DMA通道0 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_ADDRESS; // 外设地址,即ADC数据寄存器地址 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&ADCConvertedValues; // 存储ADC转换结果的数组地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; // 数据传输方向,从外设到内存 DMA_InitStructure.DMA_BufferSize = 2; // 数据传输量为2个 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 关闭外设地址自增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 开启存储数组地址自增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设数据大小为半字(16位) DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 存储数组数据大小为半字(16位) DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式 DMA_InitStructure.DMA_Priority = DMA_Priority_High; // DMA传输优先级为高 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; // 关闭FIFO模式 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; // 存储数组burst为单次传输 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; // 外设burst为单次传输 DMA_Init(DMA2_Stream0, &DMA_InitStructure); // 初始化DMA2 Stream0 DMA_Cmd(DMA2_Stream0, ENABLE); // 使能DMA2 Stream0 } void ADC_Config(void) { ADC_CommonInitTypeDef ADC_CommonInitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // 使能ADC时钟 ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2; // 分频系数为2 ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; // 关闭DMA访问模式 ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; // 两个采样阶段之间的延迟为5个时钟周期 ADC_CommonInit(&ADC_CommonInitStructure); // 初始化ADC公共部分 ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; // ADC分辨率为12位 ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 禁止扫描模式 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 开启连续转换模式 ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; // 禁止外部触发 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; // 外部触发信号TIM1的CC1 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐 ADC_InitStructure.ADC_NbrOfConversion = 1; // 转换通道数量为1 ADC_Init(ADC1, &ADC_InitStructure); // 初始化ADC1 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_3Cycles); // 配置ADC1的转换通道为通道0,采样时间为3个时钟周期 ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE); // DMA传输请求在连续转换模式下结束后开启 ADC_DMACmd(ADC1, ENABLE); // 开启ADC1的DMA转换模式 ADC_Cmd(ADC1, ENABLE); // 使能ADC1 } int main(void) { DMA_Config(); // 配置DMA ADC_Config(); // 配置ADC while (1); } ``` 这份代码的主要思路是使用DMA实现高速ADC数据转移和采集。其中,ADC转换结果存储在一个数组中,由DMA定期地将其从ADC数据寄存器中读取并传输到数组中。这样,我们就可以在不中断CPU的情况下实现高速ADC数据的采集。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值