43.1 初学者重要提示
学习本章节前,务必优先学习需要对DMAMUX,DMA的基础知识和HAL库的几个常用API有个认识。
相比定时器本身支持的PWM,这种方式更加灵活,可以让任意IO都可以输出PWM,而且方便运行中动态修改输出状态。
43.2 定时器触发DMA驱动设计
定时器触发DMAMUX,控制DMA让GPIO输出PWM的实现思路框图如下:
下面将程序设计中的相关问题逐一为大家做个说明。
43.2.1 定时器选择
使用DMA的话,请求信号都是来自DMAMUX2,而控制DMA做周期性传输的话,可以使用定时器触发,这样的话就可以使用DMAMUX的请求发生器功能,支持如下几种触发:
- #define HAL_DMAMUX1_REQ_GEN_DMAMUX1_CH0_EVT 0U
- #define HAL_DMAMUX1_REQ_GEN_DMAMUX1_CH1_EVT 1U
- #define HAL_DMAMUX1_REQ_GEN_DMAMUX1_CH2_EVT 2U
- #define HAL_DMAMUX1_REQ_GEN_LPTIM1_OUT 3U
- #define HAL_DMAMUX1_REQ_GEN_LPTIM2_OUT 4U
- #define HAL_DMAMUX1_REQ_GEN_LPTIM3_OUT 5U
- #define HAL_DMAMUX1_REQ_GEN_EXTI0 6U
- #define HAL_DMAMUX1_REQ_GEN_TIM12_TRGO 7U
复制代码
我们这里使用的是TIM12_TRGO。
接下来就是TIM12的时钟配置问题,代码如下:
- 1. /*
- 2. ******************************************************************************************************
- 3. * 函 数 名: TIM12_Config
- 4. * 功能说明: 配置TIM12,用于触发DMAMUX的请求发生器
- 5. * 形 参: _Mode
- 6. * 0 配置为100KHz触发频率,如果DMAMUX配置为单边沿触发,那么输出PWM频率是50KHz,双边沿是
- 7. 100KHz。
- 8. * 1 配置为10KHz触发频率,如果DMAMUX配置为单边沿触发,那么输出PWM频率是5KHz,双边沿是10KHz。
- 9. * 返 回 值: 无
- 10. ******************************************************************************************************
- 11. */
- 12. void TIM12_Config(uint8_t _Mode)
- 13. {
- 14. TIM_HandleTypeDef htim ={0};
- 15. TIM_MasterConfigTypeDef sMasterConfig = {0};
- 16. TIM_OC_InitTypeDef sConfig = {0};
- 17. uint32_t Period[2] = {1999, 19999};
- 18. uint32_t Pulse[2] = {1000, 10000};
- 19.
- 20. /* 使能时钟 */
- 21. __HAL_RCC_TIM12_CLK_ENABLE();
- 22.
- 23. /*-----------------------------------------------------------------------
- 24. bsp.c 文件中 void SystemClock_Config(void) 函数对时钟的配置如下:
- 25.
- 26. System Clock source = PLL (HSE)
- 27. SYSCLK(Hz) = 400000000 (CPU Clock)
- 28. HCLK(Hz) = 200000000 (AXI and AHBs Clock)
- 29. AHB Prescaler = 2
- 30. D1 APB3 Prescaler = 2 (APB3 Clock 100MHz)
- 31. D2 APB1 Prescaler = 2 (APB1 Clock 100MHz)
- 32. D2 APB2 Prescaler = 2 (APB2 Clock 100MHz)
- 33. D3 APB4 Prescaler = 2 (APB4 Clock 100MHz)
- 34.
- 35. 因为APB1 prescaler != 1, 所以 APB1上的TIMxCLK = APB1 x 2 = 200MHz;
- 36. 因为APB2 prescaler != 1, 所以 APB2上的TIMxCLK = APB2 x 2 = 200MHz;
- 37. APB4上面的TIMxCLK没有分频,所以就是100MHz;
- 38.
- 39. APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14,LPTIM1
- 40. APB2 定时器有 TIM1, TIM8 , TIM15, TIM16,TIM17
- 41.
- 42. APB4 定时器有 LPTIM2,LPTIM3,LPTIM4,LPTIM5
- 43.
- 44. TIM12CLK = 200MHz/(Period + 1) / (Prescaler + 1)
- 45. 函数bsp_InitTimDMA1中DMAMUX1选择的是单边沿触发,每个时钟可以触发两次。
- 46. ----------------------------------------------------------------------- */
- 47. HAL_TIM_Base_DeInit(&htim);
- 48.
- 49. htim.Instance = TIM12;
- 50. htim.Init.Period = Period[_Mode];
- 51. htim.Init.Prescaler = 0;
- 52. htim.Init.ClockDivision = 0;
- 53. htim.Init.CounterMode = TIM_COUNTERMODE_UP;
- 54. htim.Init.RepetitionCounter = 0;
- 55. HAL_TIM_Base_Init(&htim);
- 56.
- 57. sConfig.OCMode = TIM_OCMODE_PWM1;
- 58. sConfig.OCPolarity = TIM_OCPOLARITY_LOW;
- 59.
- 60. /* 占空比50% */
- 61. sConfig.Pulse = Pulse[_Mode];
- 62. if(HAL_TIM_OC_ConfigChannel(&htim, &sConfig, TIM_CHANNEL_1) != HAL_OK)
- 63. {
- 64. Error_Handler(__FILE__, __LINE__);
- 65. }
- 66.
- 67. /* 启动OC1 */
- 68. if(HAL_TIM_OC_Start(&htim, TIM_CHANNEL_1) != HAL_OK)
- 69. {
- 70. Error_Handler(__FILE__, __LINE__);
- 71. }
- 72.
- 73. /* TIM12的TRGO用于触发DMAMUX的请求发生器 */
- 74. sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1REF;
- 75. sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
- 76. sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
- 77.
- 78. HAL_TIMEx_MasterConfigSynchronization(&htim, &sMasterConfig);
- 79. }
复制代码
这里把几个关键的地方阐释下:
第14 – 16行,对作为局部变量的HAL库结构体做初始化,防止不确定值配置时出问题。
第17 – 18行,定义了两组周期变量和占空比变量,用来设置TIM12。
第20 – 71行,注释已经比较详细。
当选择第1组配置时,
TIM12CLK = 200MHz / (Period + 1) / (Prescaler + 1) = 200MHz/(1999+1) = 100KHz
占空比 = Pulse / (Period + 1) = 1000 / (1999+1)= 50%
当选择第2组配置时,
TIM12CLK = 200MHz / (Period + 1) / (Prescaler + 1) = 200MHz/(19999+1) = 10KHz
占空比 = Pulse / (Period + 1) = 10000 /(19999+1)= 50%
第22 – 40行, TIM12的TRGO用于触发DMAMUX的请求发生器。
这些知识点在前面的定时器章节有更详细的说明。
43.2.2 DMAMUX和DMA配置
完整配置如下:
- 1. /*
- 2. ******************************************************************************************************
- 3. * 函 数 名: bsp_InitTimDMA
- 4. * 功能说明: 配置DMAMUX的定时器触+DMA双缓冲控制任意IO做PWM和脉冲数控制
- 5. * 形 参: 无
- 6. * 返 回 值: 无
- 7. ******************************************************************************************************
- 8. */
- 9. void bsp_InitTimDMA(void)
- 10. {
- 11. GPIO_InitTypeDef GPIO_InitStruct;
- 12. DMA_HandleTypeDef DMA_Handle = {0};
- 13. HAL_DMA_MuxRequestGeneratorConfigTypeDef dmamux_ReqGenParams = {0};
- 14.
- 15. /*##-1- 配置PB1用于PWM输出 ##################################################*/
- 16. __HAL_RCC_GPIOB_CLK_ENABLE();
- 17. GPIO_InitStruct.Pin = GPIO_PIN_1;
- 18. GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- 19. GPIO_InitStruct.Pull = GPIO_NOPULL;
- 20. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
- 21. HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
- 22.
- 23. /*##-2- 使能DMA1时钟并配置 ##################################################*/
- 24. __HAL_RCC_DMA1_CLK_ENABLE();
- 25. DMA_Handle.Instance = DMA1_Stream1; /* 使用的DMA1 Stream1 */
- 26. DMA_Handle.Init.Request = DMA_REQUEST_GENERATOR0; /* 请求类型采用的DMAMUX请求发生器通道0 */
- 27. DMA_Handle.Init.Direction = DMA_MEMORY_TO_PERIPH;/* 传输方向是从存储器到外设 */
- 28. DMA_Handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设地址自增禁止 */
- 29. DMA_Handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器地址自增使能 */
- 30. DMA_Handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; /* 外设数据传输位宽选择字,即32bit */
- 31. DMA_Handle.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; /* 存储器数据传输位宽选择字,即32bit */
- 32. DMA_Handle.Init.Mode = DMA_CIRCULAR; /* 循环模式 */
- 33. DMA_Handle.Init.Priority = DMA_PRIORITY_LOW; /* 优先级低 */
- 34. DMA_Handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; /* 禁止FIFO*/
- 35. DMA_Handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* 禁止FIFO此位不起作用,用于设置阀值 */
- 36. DMA_Handle.Init.MemBurst = DMA_MBURST_SINGLE; /* 禁止FIFO此位不起作用,用于存储器突发 */
- 37. DMA_Handle.Init.PeriphBurst = DMA_PBURST_SINGLE; /* 禁止FIFO此位不起作用,用于外设突发 */
- 38.
- 39. /* 初始化DMA */
- 40. if(HAL_DMA_Init(&DMA_Handle) != HAL_OK)
- 41. {
- 42. Error_Handler(__FILE__, __LINE__);
- 43. }
- 44.
- 45. /* 开启DMA1 Stream1的中断 */
- 46. HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 2, 0);
- 47. HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);
- 48.
- 49.
- 50. /*##-4- 配置DMAMUX ###########################################################*/
- 51. dmamux_ReqGenParams.SignalID = HAL_DMAMUX1_REQ_GEN_TIM12_TRGO; /* 请求触发器选择LPTIM2_OUT */
- 52. dmamux_ReqGenParams.Polarity = HAL_DMAMUX_REQ_GEN_RISING; /* 上升沿触发 */
- 53. dmamux_ReqGenParams.RequestNumber = 1; /* 触发后,传输进行1次DMA传输 */
- 54.
- 55. HAL_DMAEx_ConfigMuxRequestGenerator(&DMA_Handle, &dmamux_ReqGenParams);/* 配置DMAMUX */
- 56. HAL_DMAEx_EnableMuxRequestGenerator (&DMA_Handle); /* 使能DMAMUX请求发生器 */
- 57.
- 58.
- 59. /*##-4- 启动DMA双缓冲传输 ################################################*/
- 60. /*
- 61. 1、此函数会开启DMA的TC,TE和DME中断
- 62. 2、如果用户配置了回调函数DMA_Handle.XferHalfCpltCallback,那么函数HAL_DMA_Init会开启半传输完
- 63. 成中断。
- 64. 3、如果用户使用了DMAMUX的同步模式,此函数会开启同步溢出中断。
- 65. 4、如果用户使用了DMAMUX的请求发生器,此函数会开始请求发生器溢出中断。
- 66. */
- 67. HAL_DMAEx_MultiBufferStart_IT(&DMA_Handle, (uint32_t)IO_Toggle,
- 68. (uint32_t)&GPIOB->BSRRL,(uint32_t)IO_Toggle1, 8);
- 69.
- 70. /* 用不到的中断可以直接关闭 */
- 71. //DMA1_Stream1->CR &= ~DMA_IT_DME;
- 72. //DMA1_Stream1->CR &= ~DMA_IT_TE;
- 73. //DMAMUX1_RequestGenerator0->RGCR &= ~DMAMUX_RGxCR_OIE;
- 74.
- 75. TIM12_Config(0);
- 76. }
复制代码
这里把几个关键的地方阐释下:
第11 - 13行,对作为局部变量的HAL库结构体做初始化,防止不确定值配置时出问题。
第16 - 21行,配置PB1推挽输出。
第24 – 43行,配置DMA的基本参数,注释较详细。
第46 – 47行,配置DMA的中断优先级,并使能。
第50 – 56行,配置DMAMUX的请求发生器触发源选择的TIM12 TRGO,上升沿触发DMA传输。
第59行,采用双缓冲的中断方式启动DMA传输,这里中断注意第2个参数,第3个参数和第4个参数。第2个原地址,即缓冲0地址和第4个缓冲1的地址定义如下:
- uint32_t IO_Toggle[8] ={
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- };
- uint32_t IO_Toggle1[8] =
- {
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- };
复制代码
都是定义了8个uint32_t类型的变量。第3个参数非常考究,这里使用的GPIO的BSRR寄存器,这个寄存器的特点就是置1有效,而清零操作对其无效。
高16位用于控制GPIO的输出低电平,而低16位用于输出高电平工作,所以我们这里设置
GPIOB_BSRR = 0x00000002时,表示PB1输出高电平。
GPIOB_BSRR = 0x00020000时,表示PB1输出低电平。
通过这种方式就实现了PB1引脚的高低电平控制。
第71 – 73行,由于函数HAL_DMAEx_MultiBufferStart_IT会开启好几个中断(函数前面的注释比较详细),大家可以在这里关闭不需要开启的中断。
第75行,调用TIM12的初始化配置,默认TIM12配置的触发频率是100KHz,实现50KHz的方波输出(两次触发是一个周期)。
43.2.3 DMA存储器选择注意事项
由于STM32H7 Cache的存在,凡是CPU和DMA都会操作到的存储器,我们都要注意数据一致性问题。对于本章节要实现的功能,如果不需要运行中动态修改DMA源地址中的数据,可以不用管这个问题,如果要动态修改就得注意Cache所带来的的数据一致性问题,这里提供两种解决办法:
方法一:
设置DMA所使用SRAM3存储区的Cache属性为Write through, read allocate,no write allocate。保证写入的数据会立即更新到SRAM3里面。
- /* 配置SRAM3的属性为Write through, read allocate,no write allocate */
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
- MPU_InitStruct.BaseAddress = 0x38000000;
- MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
- MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
- MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
- MPU_InitStruct.Number = MPU_REGION_NUMBER2;
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
- MPU_InitStruct.SubRegionDisable = 0x00;
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
方法二:
设置SRAM3的缓冲区做32字节对齐,大小最好也是32字节整数倍,然后调用函数SCB_CleanDCache_by_Addr做Clean操作即可,保证BDMA读取到的数据是刚更新好的。
本章节配套例子是直接使用的方法一。例子中变量的定义方式如下:
- /* 方便Cache类的API操作,做32字节对齐 */
- #if defined ( __ICCARM__ )
- #pragma location = 0x38000000
- uint32_t IO_Toggle[8] =
- {
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- };
- #pragma location = 0x38000020
- uint32_t IO_Toggle1[8] =
- {
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- };
- #elif defined ( __CC_ARM )
- ALIGN_32BYTES(__attribute__((section (".RAM_D3"))) uint32_t IO_Toggle[8]) =
- {
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- };
- ALIGN_32BYTES(__attribute__((section (".RAM_D3"))) uint32_t IO_Toggle1[8]) =
- {
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- 0x00000002U,
- 0x00020000U,
- };
- #endif
复制代码
对于IAR需要#pragma location指定位置,而MDK通过分散加载即可实现,详情看前面第26章,有详细讲解。
43.2.4 DMA中断处理
前面的配置中开启了DMA的传输完成中断、传输错误中断和直接模式错误中断(半传输完成中断未做开启),其中传输完成中断里面可以实现双缓冲的处理:
- /*
- *********************************************************************************************************
- * 函 数 名: DMA1_Stream1_IRQHandler
- * 功能说明: DMA1 Stream1中断服务程序
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- void DMA1_Stream1_IRQHandler(void)
- {
- /* 传输完成中断 */
- if((DMA1->LISR & DMA_FLAG_TCIF1_5) != RESET)
- {
- /* 清除标志 */
- DMA1->LIFCR = DMA_FLAG_TCIF1_5;
- /* 当前使用的缓冲0 */
- if((DMA1_Stream1->CR & DMA_SxCR_CT) == RESET)
- {
- /*
- 1、当前正在使用缓冲0,此时可以动态修改缓冲1的数据。
- 比如缓冲区0是IO_Toggle,缓冲区1是IO_Toggle1,那么此时就可以修改IO_Toggle1。
- 2、变量所在的SRAM区已经通过MPU配置为WT模式,更新变量IO_Toggle会立即写入。
- 3、不配置MPU的话,也可以通过Cahce的函数SCB_CleanDCache_by_Addr做Clean操作。
- */
- }
- /* 当前使用的缓冲1 */
- else
- {
- /*
- 1、当前正在使用缓冲1,此时可以动态修改缓冲0的数据。
- 比如缓冲区0是IO_Toggle,缓冲区1是IO_Toggle1,那么此时就可以修改IO_Toggle。
- 2、变量所在的SRAM区已经通过MPU配置为WT模式,更新变量IO_Toggle会立即写入。
- 3、不配置MPU的话,也可以通过Cahce的函数SCB_CleanDCache_by_Addr做Clean操作。
- */
- }
- }
- /* 半传输完成中断 */
- if((DMA1->LISR & DMA_FLAG_HTIF1_5) != RESET)
- {
- /* 清除标志 */
- DMA1->LISR = DMA_FLAG_HTIF1_5;
- }
- /* 传输错误中断 */
- if((DMA1->LISR & DMA_FLAG_TEIF1_5) != RESET)
- {
- /* 清除标志 */
- DMA1->LISR = DMA_FLAG_TEIF1_5;
- }
- /* 直接模式错误中断 */
- if((DMA1->LISR & DMA_FLAG_DMEIF1_5) != RESET)
- {
- /* 清除标志 */
- DMA1->LISR = DMA_FLAG_DMEIF1_5;
- }
- }
复制代码
注释的比较清楚。如果输出的PWM频率较高,建议将DMA的缓冲区设置的大些,防止DMA中断的执行频率较高。
由于使用了DMAMUX的请求发生器功能,函数HAL_DMAEx_MultiBufferStart_IT还会开启请求发生器溢出中断,对应的中断服务程序处理如下:
- /*
- *********************************************************************************************************
- * 函 数 名: DMAMUX1_OVR_IRQHandler
- * 功能说明: DMAMUX的中断服务程序,这里用于处理请求发生器的溢出。
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- void DMAMUX1_OVR_IRQHandler(void)
- {
- if(DMAMUX1_RequestGenStatus->RGSR & DMAMUX_RGSR_OF0 != RESET)
- {
- /* 关闭溢出中断 */
- DMAMUX1_RequestGenerator0->RGCR &= ~DMAMUX_RGxCR_OIE;
- /* 清除标志 */
- DMAMUX1_RequestGenStatus->RGCFR = DMAMUX_RGSR_OF0;
- }
- }
复制代码
处理比较简单,检测到溢出中断后关闭溢出中断,同时清除标志。这个中断主要用于检测。另外,如果大家用到DMAMUX的同步模式,同步溢出中断也是在DMAMUX1_OVR_IRQHandler里面处理。
43.2.5 DMA脉冲个数控制
借助本章2.4小节的知识点,如果要实现脉冲个数的控制,在DMA中断服务程序里面动态修改缓冲区即可。比如我们配置:
DMA开启双缓冲模式。
DMA传输16次为一轮,每两次传输算一个周期的脉冲。
如果要实现100个脉冲,我们就可以在12轮,即12*8=96个脉冲的传输完成中断里面修改缓冲区1出低电平即可,再次进入传输完成中断后再缓冲区0输出低电平。
43.3 DMA板级支持包(bsp_tim_dma.c)
DMA驱动文件bsp_pwm_dma.c提供了如下两个函数:
TIM12_Config
bsp_InitTimDMA
43.3.1 函数TIM12_Config
函数原型:
void TIM12_Config(uint8_t _Mode)
函数描述:
此函数用于配置TIM12工作在OC输出比较模式,使用TIM12的TRGO作为BDMA请求发生器的触发源。
函数参数:
第1个参数用于配置TIM12的输出频率。
配置为数值0:表示配置为100KHz触发频率,配置为100KHz触发频率,如果DMAMUX配置为单边沿触发,那么输出PWM频。
配置为数值1:表示配置为10KHz触发频率,如果DMAMUX配置为单边沿触发,那么输出PWM频率是5KHz,双边沿是10KHz。
注意事项:
关于此函数的讲解在本章的2.1小节。
43.3.2 函数bsp_InitTimDMA
函数原型:
void bsp_InitTimBDMA(void)
函数描述:
此函数用于配置定时器触发DMA,可以实现任意IO做PWM输出。
注意事项:
关于此函数的讲解在本章2.2小节。
使用举例:
作为初始化函数,直接在bsp.c文件的bsp_Init函数里面调用即可。
43.4 DMA驱动移植和使用
低功耗定时器的移植比较简单:
第1步:复制bsp_tim_dma.c和bsp_tim_dma.h到自己的工程目录,并添加到工程里面。
第2步:这几个驱动文件主要用到HAL库的GPIO、TIM和DMA驱动文件,简单省事些可以添加所有HAL库.C源文件进来。
第3步,应用方法看本章节配套例子即可。
43.5 实验例程设计框架
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
第1阶段,上电启动阶段:
这部分在第14章进行了详细说明。
第2阶段,进入main函数:
第1步,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器,LED和串口。
第2步,借助按键消息实现不同的输出频率调整,方便测试。
43.6 实验例程说明(MDK)
配套例子:
V7-011_DMAMUX的定时器触+DMA双缓冲控制任意IO做PWM和脉冲数控制
实验目的:
学习DMAMUX的定时器触+DMA双缓冲控制任意IO做PWM和脉冲数控制。
实验内容:
通过TIM12触发DMAMUX的请求发生器,控制DMA给任意IO做PWM输出。
实验操作:
K2键按下,PB1输出5KHz方波,占空比50%
K2键按下,PB1输出50KHz方波,占空比50%
PB1的位置:
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1
程序设计:
系统栈大小分配:
RAM空间用的DTCM:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
- /*
- *********************************************************************************************************
- * 函 数 名: bsp_Init
- * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
- * 形 参:无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- void bsp_Init(void)
- {
- /* 配置MPU */
- MPU_Config();
- /* 使能L1 Cache */
- CPU_CACHE_Enable();
- /*
- STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
- - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
- - 设置NVIV优先级分组为4。
- */
- HAL_Init();
- /*
- 配置系统时钟到400MHz
- - 切换使用HSE。
- - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
- */
- SystemClock_Config();
- /*
- Event Recorder:
- - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
- - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
- */
- #if Enable_EventRecorder == 1
- /* 初始化EventRecorder并开启 */
- EventRecorderInitialize(EventRecordAll, 1U);
- EventRecorderStart();
- #endif
- bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
- bsp_InitTimer(); /* 初始化滴答定时器 */
- bsp_InitUart(); /* 初始化串口 */
- bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
- bsp_InitLed(); /* 初始化LED */
- bsp_InitTimDMA(); /* 初始化定时器触发DMA */
- }
复制代码
MPU配置和Cache配置:
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM),FMC的扩展IO区和SRAM4。
- /*
- *********************************************************************************************************
- * 函 数 名: MPU_Config
- * 功能说明: 配置MPU
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void MPU_Config( void )
- {
- MPU_Region_InitTypeDef MPU_InitStruct;
- /* 禁止 MPU */
- HAL_MPU_Disable();
- /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
- MPU_InitStruct.BaseAddress = 0x24000000;
- MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
- MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
- MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
- MPU_InitStruct.Number = MPU_REGION_NUMBER0;
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
- MPU_InitStruct.SubRegionDisable = 0x00;
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
- /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
- MPU_InitStruct.BaseAddress = 0x60000000;
- MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
- MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
- MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
- MPU_InitStruct.Number = MPU_REGION_NUMBER1;
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
- MPU_InitStruct.SubRegionDisable = 0x00;
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
- /* 配置SRAM4的属性为Write through, read allocate,no write allocate */
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
- MPU_InitStruct.BaseAddress = 0x38000000;
- MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
- MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
- MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
- MPU_InitStruct.Number = MPU_REGION_NUMBER2;
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
- MPU_InitStruct.SubRegionDisable = 0x00;
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
- /*使能 MPU */
- HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
- }
- /*
- *********************************************************************************************************
- * 函 数 名: CPU_CACHE_Enable
- * 功能说明: 使能L1 Cache
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void CPU_CACHE_Enable(void)
- {
- /* 使能 I-Cache */
- SCB_EnableICache();
- /* 使能 D-Cache */
- SCB_EnableDCache();
- }
复制代码
主功能:
主程序实现如下操作:
K1键按下,PB1输出5KHz方波,占空比50%。
K2键按下,PB1输出50KHz方波,占空比50%。
- /*
- *********************************************************************************************************
- * 函 数 名: main
- * 功能说明: c程序入口
- * 形 参: 无
- * 返 回 值: 错误代码(无需处理)
- *********************************************************************************************************
- */
- int main(void)
- {
- uint8_t ucKeyCode; /* 按键代码 */
- bsp_Init(); /* 硬件初始化 */
- PrintfLogo(); /* 打印例程名称和版本等信息 */
- PrintfHelp(); /* 打印操作提示 */
- bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */
- /* 进入主程序循环体 */
- while (1)
- {
- bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
- /* 判断定时器超时时间 */
- if (bsp_CheckTimer(0))
- {
- /* 每隔50ms 进来一次 */
- bsp_LedToggle(2);
- }
- /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
- ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
- if (ucKeyCode != KEY_NONE)
- {
- switch (ucKeyCode)
- {
- case KEY_DOWN_K1: /* K1键按下,PB1输出5KHz方波,占空比50% */
- TIM12_Config(1);
- break;
- case KEY_DOWN_K2: /* K2键按下,PB1输出50KHz方波,占空比50% */
- TIM12_Config(0);
- break;
- default:
- /* 其它的键值不处理 */
- break;
- }
- }
- }
- }
复制代码
43.7 实验例程说明(IAR)
配套例子:
V7-011_DMAMUX的定时器触+DMA双缓冲控制任意IO做PWM和脉冲数控制
实验目的:
学习DMAMUX的定时器触+DMA双缓冲控制任意IO做PWM和脉冲数控制。
实验内容:
通过TIM12触发DMAMUX的请求发生器,控制DMA给任意IO做PWM输出。
实验操作:
K2键按下,PB1输出5KHz方波,占空比50%
K2键按下,PB1输出50KHz方波,占空比50%
PB1的位置:
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1
程序设计:
系统栈大小分配:
RAM空间用的DTCM:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
- /*
- *********************************************************************************************************
- * 函 数 名: bsp_Init
- * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
- * 形 参:无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- void bsp_Init(void)
- {
- /* 配置MPU */
- MPU_Config();
- /* 使能L1 Cache */
- CPU_CACHE_Enable();
- /*
- STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
- - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
- - 设置NVIV优先级分组为4。
- */
- HAL_Init();
- /*
- 配置系统时钟到400MHz
- - 切换使用HSE。
- - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
- */
- SystemClock_Config();
- /*
- Event Recorder:
- - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
- - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
- */
- #if Enable_EventRecorder == 1
- /* 初始化EventRecorder并开启 */
- EventRecorderInitialize(EventRecordAll, 1U);
- EventRecorderStart();
- #endif
- bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
- bsp_InitTimer(); /* 初始化滴答定时器 */
- bsp_InitUart(); /* 初始化串口 */
- bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
- bsp_InitLed(); /* 初始化LED */
- bsp_InitTimDMA(); /* 初始化定时器触发DMA */
- }
复制代码
MPU配置和Cache配置:
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM),FMC的扩展IO区和SRAM4。
- /*
- *********************************************************************************************************
- * 函 数 名: MPU_Config
- * 功能说明: 配置MPU
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void MPU_Config( void )
- {
- MPU_Region_InitTypeDef MPU_InitStruct;
- /* 禁止 MPU */
- HAL_MPU_Disable();
- /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
- MPU_InitStruct.BaseAddress = 0x24000000;
- MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
- MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
- MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
- MPU_InitStruct.Number = MPU_REGION_NUMBER0;
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
- MPU_InitStruct.SubRegionDisable = 0x00;
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
- /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
- MPU_InitStruct.BaseAddress = 0x60000000;
- MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
- MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
- MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
- MPU_InitStruct.Number = MPU_REGION_NUMBER1;
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
- MPU_InitStruct.SubRegionDisable = 0x00;
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
- /* 配置SRAM4的属性为Write through, read allocate,no write allocate */
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
- MPU_InitStruct.BaseAddress = 0x38000000;
- MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
- MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
- MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
- MPU_InitStruct.Number = MPU_REGION_NUMBER2;
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
- MPU_InitStruct.SubRegionDisable = 0x00;
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
- /*使能 MPU */
- HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
- }
- /*
- *********************************************************************************************************
- * 函 数 名: CPU_CACHE_Enable
- * 功能说明: 使能L1 Cache
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void CPU_CACHE_Enable(void)
- {
- /* 使能 I-Cache */
- SCB_EnableICache();
- /* 使能 D-Cache */
- SCB_EnableDCache();
- }
复制代码
主功能:
主程序实现如下操作:
K1键按下,PB1输出5KHz方波,占空比50%。
K2键按下,PB1输出50KHz方波,占空比50%。
- /*
- *********************************************************************************************************
- * 函 数 名: main
- * 功能说明: c程序入口
- * 形 参: 无
- * 返 回 值: 错误代码(无需处理)
- *********************************************************************************************************
- */
- int main(void)
- {
- uint8_t ucKeyCode; /* 按键代码 */
- bsp_Init(); /* 硬件初始化 */
- PrintfLogo(); /* 打印例程名称和版本等信息 */
- PrintfHelp(); /* 打印操作提示 */
- bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */
- /* 进入主程序循环体 */
- while (1)
- {
- bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
- /* 判断定时器超时时间 */
- if (bsp_CheckTimer(0))
- {
- /* 每隔50ms 进来一次 */
- bsp_LedToggle(2);
- }
- /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
- ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
- if (ucKeyCode != KEY_NONE)
- {
- switch (ucKeyCode)
- {
- case KEY_DOWN_K1: /* K1键按下,PB1输出5KHz方波,占空比50% */
- TIM12_Config(1);
- break;
- case KEY_DOWN_K2: /* K2键按下,PB1输出50KHz方波,占空比50% */
- TIM12_Config(0);
- break;
- default:
- /* 其它的键值不处理 */
- break;
- }
- }
- }
- }
复制代码
43.8 总结
本章节就为大家讲解这么多,控制DMA让GPIO输出PWM以及脉冲数的控制,实际项目中有一定的实用价值,望初学者熟练掌握。
|