前言
本文使用的芯片型号是STM32G030,写本文的目前是想记录学习下Timer借助DMA生成可变占空比PWM时的底层配置过程。
一、CUBEMUX生成项目
1. Timer配置
使用TIM1,配置就只改了图上的配置,系统时钟用的16M,分频选择15(16-1),自动重装载寄存器ARR选择999(1000-1),那么生成的就是1kHz的PWM,这里为什么要减1,因为这俩是从0开始计数,想知道公式计算的可以去搜一下,介绍的有很多。Clock Source(时钟源)选择内部时钟,也就是系统时钟。通道1选择PWM。
这里再解释下通道选择PWM模式有几个,CH1、CH1N还有CH1和CH1N的组合。对应到下边一张图的右边,通道指Capture/Compare x register,而每个通道有两个输出口,OC1和OC1N,这俩是相反的波形,就是对应选择的选项了CH1、CH1N,选择他俩就是同时输出了。
2. DMA配置
这是DMA的配置参数,这里选择循环发送,如果只发送一次,就看不出来PWM变化了,所有我改成了循环发送。这里的数据宽度为什么要选择半字是根据TIM1的CCR寄存器定的,看下边一张图,CCR寄存器就是控制PWM占空比的,他的大小就是16位的。
二、代码流程
1.代码
代码如下:
int main(void)
{
/* USER CODE BEGIN 1 */
uint16_t aDutyCycleArray[9] = {100, 200, 300, 400, 600, 700, 800, 900};
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t*)aDutyCycleArray, 9);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
2.流程
MX_GPIO_Init()、MX_DMA_Init() 这两个函数就不讲了。
1) MX_TIM1_Init()
该函数大部分都和不使用DMA一样,主要分析下添加的部分,这个全局变量是添加DMA需要的。
DMA_HandleTypeDef hdma_tim1_ch1;
/* TIM1 DMA Init */
/* TIM1_CH1 Init */
hdma_tim1_ch1.Instance = DMA1_Channel1;
hdma_tim1_ch1.Init.Request = DMA_REQUEST_TIM1_CH1;
hdma_tim1_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_tim1_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tim1_ch1.Init.MemInc = DMA_MINC_ENABLE;
hdma_tim1_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_tim1_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_tim1_ch1.Init.Mode = DMA_CIRCULAR;
hdma_tim1_ch1.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_tim1_ch1) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(tim_baseHandle,hdma[TIM_DMA_ID_CC1],hdma_tim1_ch1);
进入HAL_DMA_Init 函数,
1、hdma->ChannelIndex = (((uint32_t)hdma->Instance - (uint32_t)DMA1_Channel1) / ((uint32_t)DMA1_Channel2 - (uint32_t)DMA1_Channel1)) << 2U;
/* Clear PL, MSIZE, PSIZE, MINC, PINC, CIRC, DIR and MEM2MEM bits */
2、CLEAR_BIT(hdma->Instance->CCR, (DMA_CCR_PL | DMA_CCR_MSIZE | DMA_CCR_PSIZE | \
DMA_CCR_MINC | DMA_CCR_PINC | DMA_CCR_CIRC | \
DMA_CCR_DIR | DMA_CCR_MEM2MEM));
/* Set the DMA Channel configuration */
3、SET_BIT(hdma->Instance->CCR, (hdma->Init.Direction | \
hdma->Init.PeriphInc | hdma->Init.MemInc | \
hdma->Init.PeriphDataAlignment | hdma->Init.MemDataAlignment | \
hdma->Init.Mode | hdma->Init.Priority));
/* Initialize parameters for DMAMUX channel :
DMAmuxChannel, DMAmuxChannelStatus and DMAmuxChannelStatusMask
*/
4、DMA_CalcDMAMUXChannelBaseAndMask(hdma);
/* Set peripheral request to DMAMUX channel */
5、hdma->DMAmuxChannel->CCR = (hdma->Init.Request & DMAMUX_CxCR_DMAREQ_ID);
/* Clear the DMAMUX synchro overrun flag */
6、hdma->DMAmuxChannelStatus->CFR = hdma->DMAmuxChannelStatusMask;
- 绑定当前hdma_tim1_ch1变量用的是通道几,在配置阶段我们配置的是通道1,这款芯片总共有5个DMA通道。
- 第2点和第3点一起讲,就是DMA的CCR寄存器先清空再根据我们上一张图设置的参数来设置该寄存器。
- 总结到第2点。
- DMA_CalcDMAMUXChannelBaseAndMask 主要用于建立DMA和DMAMUX通道的连接,DMAMUX相当于DMA和Timer之间加的一个请求器,DMA可以帮很多模块搬东西,但怎么去与不同的模块连接就靠DMAMUX这个中间件来完成。
- 建立DMAMUX和Timer的连接,这就相当于,上一步把DMAMUX的一头连接到了DMA,这一步把DMAMUX的另一头连接到了Timer。
- 清除标志位,这个同步标志位我也不懂,如果有看懂的也可以留言教教我,哈哈哈。
再回头看看这行代码
__HAL_LINKDMA(tim_baseHandle,hdma[TIM_DMA_ID_CC1],hdma_tim1_ch1);
tim_baseHandle就是传入的TIM1 ,看下TIM1 的结构体定义,hdma 是个DMA_HandleTypeDef类型的指针数组。
他的宏是这样的,
#define __HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD__, __DMA_HANDLE__) \
do{ \
(__HANDLE__)->__PPP_DMA_FIELD__ = &(__DMA_HANDLE__); \
(__DMA_HANDLE__).Parent = (__HANDLE__); \
} while(0U)
TIM_DMA_ID_CC1这个的值是1,带入参数展开后就是,就是这个指针数组的1号位置 = hdma_tim1_ch1变量的地址。
TIM1->hdma[TIM_DMA_ID_CC1] = &hdma_tim1_ch1;
hdma_tim1_ch1.Parent = TIM1;
2)HAL_TIM_PWM_Start_DMA()
HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t*)aDutyCycleArray, 9);
这里有个疑点就是我们前边设置的搬运Memory的宽度是16位,为什么这里要转成32位呢,其实就是这个函数要求要传32位的,但搬运的时候是按照16位来搬,除非代码里用到地址自增自减,否则是不会有影响的。
进入到函数,跳过不重要的部分。
- 这里的hdma[TIM_DMA_ID_CC1] 就等于hdma_tim1_ch1。先是设置了DMA传输的回调函数,当DMA传输到相应状态时就会触发中断,从而调用这里设置的回调函数。然后开启了DMA通道HAL_DMA_Start_IT,最后打开Timer的DMA请求使能 __HAL_TIM_ENABLE_DMA, 当Timer开启后就会通知DMA搬运数据过来。
...
switch (Channel)
{
case TIM_CHANNEL_1:
{
/* Set the DMA compare callbacks */
htim->hdma[TIM_DMA_ID_CC1]->XferCpltCallback = TIM_DMADelayPulseCplt;
htim->hdma[TIM_DMA_ID_CC1]->XferHalfCpltCallback = TIM_DMADelayPulseHalfCplt;
/* Set the DMA error callback */
htim->hdma[TIM_DMA_ID_CC1]->XferErrorCallback = TIM_DMAError ;
/* Enable the DMA channel */
if (HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC1], (uint32_t)pData, (uint32_t)&htim->Instance->CCR1, Length) != HAL_OK)
{
/* Return error status */
return HAL_ERROR;
}
/* Enable the TIM Capture/Compare 1 DMA request */
__HAL_TIM_ENABLE_DMA(htim, TIM_DMA_CC1);
break;
}
...
进入到HAL_DMA_Start_IT,省略了一些过程,就是开启DMA连接Memory和Timer通道以及DMA中断。文章来源:https://www.toymoban.com/news/detail-845640.html
/* Disable the peripheral */
__HAL_DMA_DISABLE(hdma);
/* Configure the source, destination address and the data length & clear flags*/
DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);
__HAL_DMA_DISABLE_IT(hdma, DMA_IT_HT);
__HAL_DMA_ENABLE_IT(hdma, (DMA_IT_TC | DMA_IT_TE));
/* Enable the Peripheral */
__HAL_DMA_ENABLE(hdma);
- 返回到上一层函数,接着往Timer的DMA请求使能往后讲,开启比较通道寄存器,开启输出,最后开启Timer,开始生成PWM,并从DMA出传入数据到CCR寄存器。
/* Enable the Capture compare channel */
TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
/* Enable the main output */
__HAL_TIM_MOE_ENABLE(htim);
__HAL_TIM_ENABLE(htim);
三、结果展示
最后给张结果展示图,我设置的CCR的值从100到900不断循环,ARR的值又为1000,所以占空比就是10%~90%不断变化。
文章来源地址https://www.toymoban.com/news/detail-845640.html
到了这里,关于STM32 HAL库 Timer(定时器)+DMA输出PWM底层配置过程学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!