学堂 学堂 学堂公众号手机端

【STM32H7教程】第41章STM32H7的BDMA应用之控制任意IO做PWM和脉冲数控制

lewis 1年前 (2024-04-16) 阅读数 12 #技术

​​​​

第41章 STM32H7的BDMA应用之控制任意IO做PWM和脉冲数控制

本章教程为大家讲解定时器触发DMAMUX,控制BDMA让GPIO输出PWM以及脉冲数的控制,实际项目中有一定的使用价值。

41.1 初学者重要提示


41.2 定时器触发BDMA驱动设计

41.3 BDMA板级支持包(bsp_tim_dma.c)

41.4 BDMA驱动移植和使用

41.5 实验例程设计框架

41.6 实验例程说明(MDK)

41.7 实验例程说明(IAR)

41.8 总结

41.1 初学者重要提示学习本章节前,务必优先学习第39章和40章,需要对DMAMUX,BDMA的基础知识和HAL库的几个常用API有个认识。使用半传输完成中断和传输完成中断实现的双缓冲效果跟BDMA本身支持的双缓冲模式实现的效果是一样的。只是最大传输个数只能达到32767次。相比定时器本身支持的PWM,这种方式更加灵活,可以让任意IO都可以输出PWM,而且方便运行中动态修改输出状态。41.2 定时器触发BDMA驱动设计

定时器触发DMAMUX,控制BDMA让GPIO输出PWM的实现思路框图如下:


下面将程序设计中的相关问题逐一为大家做个说明。

41.2.1 定时器选择

使用BDMA的话,请求信号都是来自DMAMUX2,而控制DMA做周期性传输的话,可以使用定时器触发,这样的话就可以使用DMAMUX的请求发生器功能,支持如下几种触发:

#define HAL_DMAMUX2_REQ_GEN_DMAMUX2_CH0_EVT   0U   
#define HAL_DMAMUX2_REQ_GEN_DMAMUX2_CH1_EVT 1U
#define HAL_DMAMUX2_REQ_GEN_DMAMUX2_CH2_EVT 2U
#define HAL_DMAMUX2_REQ_GEN_DMAMUX2_CH3_EVT 3U
#define HAL_DMAMUX2_REQ_GEN_DMAMUX2_CH4_EVT 4U
#define HAL_DMAMUX2_REQ_GEN_DMAMUX2_CH5_EVT 5U
#define HAL_DMAMUX2_REQ_GEN_DMAMUX2_CH6_EVT 6U
#define HAL_DMAMUX2_REQ_GEN_LPUART1_RX_WKUP 7U
#define HAL_DMAMUX2_REQ_GEN_LPUART1_TX_WKUP 8U
#define HAL_DMAMUX2_REQ_GEN_LPTIM2_WKUP 9U
#define HAL_DMAMUX2_REQ_GEN_LPTIM2_OUT 10U
#define HAL_DMAMUX2_REQ_GEN_LPTIM3_WKUP 11U
#define HAL_DMAMUX2_REQ_GEN_LPTIM3_OUT 12U
#define HAL_DMAMUX2_REQ_GEN_LPTIM4_WKUP 13U
#define HAL_DMAMUX2_REQ_GEN_LPTIM5_WKUP 14U
#define HAL_DMAMUX2_REQ_GEN_I2C4_WKUP 15U
#define HAL_DMAMUX2_REQ_GEN_SPI6_WKUP 16U
#define HAL_DMAMUX2_REQ_GEN_COMP1_OUT 17U
#define HAL_DMAMUX2_REQ_GEN_COMP2_OUT 18U
#define HAL_DMAMUX2_REQ_GEN_RTC_WKUP 19U
#define HAL_DMAMUX2_REQ_GEN_EXTI0 20U
#define HAL_DMAMUX2_REQ_GEN_EXTI2 21U
#define HAL_DMAMUX2_REQ_GEN_I2C4_IT_EVT 22U
#define HAL_DMAMUX2_REQ_GEN_SPI6_IT 23U
#define HAL_DMAMUX2_REQ_GEN_LPUART1_TX_IT 24U
#define HAL_DMAMUX2_REQ_GEN_LPUART1_RX_IT 25U
#define HAL_DMAMUX2_REQ_GEN_ADC3_IT 26U
#define HAL_DMAMUX2_REQ_GEN_ADC3_AWD1_OUT 27U
#define HAL_DMAMUX2_REQ_GEN_BDMA_CH0_IT 28U
#define

我们这里使用的是LPTIM2_OUT,因为BDMA,LPTIM2和GPIO都在D3域。

接下来就是LPTIM的时钟配置问题,由前面的LPTIM章节,我们知道LPTIM2的时钟可以由LSE,LSI,APB或者外部输入时钟提供。使用LSE,LSI或者外部输入的好处是停机状态下,LPTIM1也可以正常工作。

V7开发板使用的LSE晶振是32768Hz。STM32H743的LSI频率约32KHz。LPTIM1 – LPTIM5的频率都是100MHz。
System Clock source       = PLL (HSE)
SYSCLK(Hz) = 400000000 (CPU Clock)
HCLK(Hz) = 200000000 (AXI and AHBs Clock)
AHB Prescaler = 2
D1 APB3 Prescaler = 2 (APB3 Clock 100MHz)
D2 APB1 Prescaler = 2 (APB1 Clock 100MHz)
D2 APB2 Prescaler = 2 (APB2 Clock 100MHz)
D3 APB4 Prescaler = 2 (APB4 Clock 100MHz)

因为APB1 prescaler != 1, 所以 APB1上的TIMxCLK = APB1 x 2 = 200MHz; 不含这个总线下的LPTIM1
因为APB2 prescaler != 1, 所以 APB2上的TIMxCLK = APB2 x 2 = 200MHz;

APB4上面的TIMxCLK没有分频,所以就是100MHz;

APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14,LPTIM1
APB2 定时器有 TIM1, TIM8 , TIM15, TIM16,TIM17

APB4 定时器有 LPTIM2,LPTIM3,LPTIM4,LPTIM5

如果选择APB时钟的话,配置如下:

RCC_PeriphCLKInitTypeDef   RCC_PeriphCLKInitStruct = {0};

RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LPTIM2;
RCC_PeriphCLKInitStruct.Lptim2ClockSelection = RCC_LPTIM2CLKSOURCE_D3PCLK1;
HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct);

使用APB作为LPTIM系统时钟注意以下两点:

LPTIM1 – LPTIM5的最高主频都是100MHz。注意参数RCC_LPTIM2CLKSOURCE_D3PCLK1。

LPTIM1使用的RCC_LPTIM1CLKSOURCE_D2PCLK1。

LPTIM2使用的RCC_LPTIM2CLKSOURCE_D3PCLK1。

LPTIM3-LPTIM5使用的RCC_LPTIM345CLKSOURCE_D3PCLK1。

LPTIM2的配置代码如下:

1.    /*
2. ******************************************************************************************************
3. * 函 数 名: LPTIM_Config
4. * 功能说明: 配置LPTIM,用于触发DMAMUX的请求发生器
5. * 形 参: 无
6. * 返 回 值: 无
7. ******************************************************************************************************
8. */
9. void LPTIM_Config(void)
10. {
11.
12. RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
13.
14.
15. /*##-1- 配置LPTIM2使用PCLK时钟 ##################################################*/
16. PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LPTIM2;
17. PeriphClkInitStruct.Lptim2ClockSelection = RCC_LPTIM2CLKSOURCE_D3PCLK1;
18. HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
19.
20.
21. /*##-2- 使能LPTIM2时钟并配置 ####################################################*/
22. __HAL_RCC_LPTIM2_CLK_ENABLE();
23.
24. LptimHandle.Instance = LPTIM2;
25. LptimHandle.Init.CounterSource = LPTIM_COUNTERSOURCE_INTERNAL;
26. LptimHandle.Init.UpdateMode = LPTIM_UPDATE_ENDOFPERIOD;
27. LptimHandle.Init.OutputPolarity = LPTIM_OUTPUTPOLARITY_HIGH;
28. LptimHandle.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK_LPOSC;
29. LptimHandle.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV1;
30. LptimHandle.Init.UltraLowPowerClock.Polarity = LPTIM_CLOCKPOLARITY_RISING;
31. LptimHandle.Init.UltraLowPowerClock.SampleTime = LPTIM_CLOCKSAMPLETIME_DIRECTTRANSITION;
32. LptimHandle.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE;
33. LptimHandle.Init.Trigger.ActiveEdge = LPTIM_ACTIVEEDGE_RISING;
34. LptimHandle.Init.Trigger.SampleTime = LPTIM_TRIGSAMPLETIME_DIRECTTRANSITION;
35.
36. /*##-3- 初始化LPTIM2 ##########################################################*/
37. if(HAL_LPTIM_Init(&LptimHandle) != HAL_OK)
38. {
39. Error_Handler(__FILE__, __LINE__);
40. }
41.
42. /*##-4- 启动LPTIM2的PWM模式,但使用输出引脚,仅用于DMAMUX的触发 ##############*/
43. /* LPTIM2的时钟主频是100MHz,这里配置触发是100MHz / (10000 - 1 + 1) = 10KHz */
44. if (HAL_LPTIM_PWM_Start(&LptimHandle, 10000-1, 5000 - 1) != HAL_OK)
45. {
46. Error_Handler(__FILE__, __LINE__);
47. }
48. }

这里把几个关键的地方阐释下:

第16 – 18行,配置LPTIM2使用APB时钟。第22 – 40行, 配置LPTIM2的相关参数,具体每个参数代表的含义可以看前面LPTIM章节的讲解。第44 – 47行,配置LPTIM2工作在PWM模式,频率10KHz,占空比50%。这里仅仅是用到LPTIM2_OUT的输出信号作为DMAMUX的请求发生器触发源,所以用不到PWM的输出引脚。41.2.2 DMMUX和BDMA配置

完整配置如下:

1.    /*
2. ******************************************************************************************************
3. * 函 数 名: bsp_InitTimBDMA
4. * 功能说明: 配置DMAMUX的定时器触+DMA控制任意IO做PWM和脉冲数控制
5. * 形 参: 无
6. * 返 回 值: 无
7. ******************************************************************************************************
8. */
9. void bsp_InitTimBDMA(void)
10. {
11. GPIO_InitTypeDef GPIO_InitStruct;
12. DMA_HandleTypeDef DMA_Handle = {0};
13. HAL_DMA_MuxRequestGeneratorConfigTypeDef dmamux_ReqGenParams ={0};
14.
15.
16. /*##-1- ##################################################*/
17. __HAL_RCC_GPIOB_CLK_ENABLE();
18.
19. GPIO_InitStruct.Pin = GPIO_PIN_1;
20. GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
21. GPIO_InitStruct.Pull = GPIO_NOPULL;
22. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
23. HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
24.
25.
26. /*##-2- Configure the DMA ##################################################*/
27. __HAL_RCC_BDMA_CLK_ENABLE();
28.
29. DMA_Handle.Instance = BDMA_Channel0; /* 使用的BDMA通道0 */
30. DMA_Handle.Init.Request = BDMA_REQUEST_GENERATOR0; /* 请求类型采用的DMAMUX请求发生器通道0 */
31. DMA_Handle.Init.Direction = DMA_MEMORY_TO_PERIPH; /* 传输方向是从存储器到外设 */
32. DMA_Handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设地址自增禁止 */
33. DMA_Handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器地址自增使能 */
34. DMA_Handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; /* 外设数据传输位宽选择字,即32bit */
35. DMA_Handle.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; /* 存储器数据传输位宽选择字,即32bit */
36. DMA_Handle.Init.Mode = DMA_CIRCULAR; /* 循环模式 */
37. DMA_Handle.Init.Priority = DMA_PRIORITY_LOW; /* 优先级低 */
38. DMA_Handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; /* BDMA不支持FIFO */
39. DMA_Handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* BDMA不支持FIFO阀值设置 */
40. DMA_Handle.Init.MemBurst = DMA_MBURST_SINGLE; /* BDMA不支持存储器突发 */
41. DMA_Handle.Init.PeriphBurst = DMA_PBURST_SINGLE; /* BDMA不支持外设突发 */
42.
43. HAL_DMA_Init(&DMA_Handle);
44.
45. /* 开启BDMA Channel0的中断 */
46. HAL_NVIC_SetPriority(BDMA_Channel0_IRQn, 2, 0);
47. HAL_NVIC_EnableIRQ(BDMA_Channel0_IRQn);
48.
49. /*##-3- 配置DMAMUX #########################################################*/
50. dmamux_ReqGenParams.SignalID = HAL_DMAMUX2_REQ_GEN_LPTIM2_OUT; /* 请求触发器选择LPTIM2_OUT */
51. dmamux_ReqGenParams.Polarity = HAL_DMAMUX_REQ_GEN_RISING_FALLING; /* 上升沿和下降沿均可触发 */
52. dmamux_ReqGenParams.RequestNumber = 1; /* 触发后,传输进行1次DMA传输 */
53.
54. HAL_DMAEx_ConfigMuxRequestGenerator(&DMA_Handle, &dmamux_ReqGenParams); /* 配置DMAMUX */
55.
56. HAL_DMAEx_EnableMuxRequestGenerator (&DMA_Handle); /* 使能DMAMUX请求发生器 */
57.
58. /*##-4- 启动DMA传输 ################################################*/
59. HAL_DMA_Start_IT(&DMA_Handle, (uint32_t)IO_Toggle, (uint32_t)&GPIOB->BSRRL, 8);
60.
61. /*
62. 默认情况下,用户通过注册回调函数DMA_Handle.XferHalfCpltCallback,然后函数HAL_DMA_Init会开启半
63. 传输完成中断,
64. 由于这里没有使用HAL库默认的中断管理函数HAL_DMA_IRQHandler,直接手动开启。
65. */
66. BDMA_Channel0->CCR |= BDMA_CCR_HTIE;
67.
68. LPTIM_Config(); /* 配置LPTIM触发DMAMUX */
69. }

这里把几个关键的地方阐释下:

第12 - 13行,对作为局部变量的HAL库结构体做初始化,防止不确定值配置时出问题。第17 - 23行,配置PB1推挽输出。第27 – 43行,配置BDMA的基本参数,注释较详细。第46 – 47行,配置BDMA的中断优先级,并使能。第50 – 56行,配置DMAMUX的请求发生器触发源选择的LPTIM2_OUT,上升沿和下降沿均触发BDMA传输。第59行,采用中断方式启动BDAM传输,这里中断注意第2个参数和第3个参数。第2个原地址,定义如下:
uint32_t IO_Toggle[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引脚的高低电平控制。

第66行,这里比较特殊,默认情况下,用户通过注册回调函数DMA_Handle.XferHalfCpltCallback,然后函数HAL_DMA_Init会开启半传输完成中断,由于这里没有使用HAL库默认的中断管理函数HAL_DMA_IRQHandler,直接手动开启。第68行,调用LPTIM的初始化配置。41.2.3 BDMA存储器选择注意事项

由于STM32H7 Cache的存在,凡是CPU和DMA都会操作到的存储器,我们都要注意数据一致性问题。对于本章节要实现的功能,如果不需要运行中动态修改BDMA源地址中的数据,可以不用管这个问题,如果要动态修改,就得注意Cache所带来的的数据一致性问题,这里提供两种解决办法:

方法一:

设置BDMA所使用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,
};

#elif defined ( __CC_ARM )
ALIGN_32BYTES(__attribute__((section (".RAM_D3"))) uint32_t IO_Toggle[8]) =
{
0x00000002U,
0x00020000U,
0x00000002U,
0x00020000U,
0x00000002U,
0x00020000U,
0x00000002U,
0x00020000U,
};
#endif

对于IAR需要#pragma location指定位置,而MDK通过分散加载即可实现,详情看前面第26章,有详细讲解。

41.2.4 BDMA中断处理

前面的配置中开启了BDMA的传输完成中断、半传输完成中断和传输错误中断。通过半传输完整中断和传输完成中断可以实现双缓冲的效果:

/*
*********************************************************************************************************
* 函 数 名: BDMA_Channel0_IRQHandler
* 功能说明: BDMA通道0
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void BDMA_Channel0_IRQHandler(void)
{
/* 传输完成中断 */
if((BDMA->ISR & BDMA_FLAG_TC0) != RESET)
{
BDMA->IFCR = BDMA_FLAG_TC0;

/*
1、传输完成开始使用DMA缓冲区的前半部分,此时可以动态修改后半部分数据
比如缓冲区大小是IO_Toggle[0] 到 IO_Toggle[7]
那么此时可以修改IO_Toggle[4] 到 IO_Toggle[7]
2、变量所在的SRAM区已经通过MPU配置为WT模式,更新变量IO_Toggle会立即写入。
3、不配置MPU的话,也可以通过Cahce的函数SCB_CleanDCache_by_Addr做Clean操作。
*/
}

/* 半传输完成中断 */
if((BDMA->ISR & BDMA_FLAG_HT0) != RESET)
{
BDMA->IFCR = BDMA_FLAG_HT0;

/*
1、半传输完成开始使用DMA缓冲区的后半部分,此时可以动态修改前半部分数据
比如缓冲区大小是IO_Toggle[0] 到 IO_Toggle[7]
那么此时可以修改IO_Toggle[0] 到 IO_Toggle[3]
2、变量所在的SRAM区已经通过MPU配置为WT模式,更新变量IO_Toggle会立即写入。
3、不配置MPU的话,也可以通过Cahce的函数SCB_CleanDCache_by_Addr做Clean操作。
*/
}

/* 传输错误中断 */
if((BDMA->ISR & BDMA_FLAG_TE0) != RESET)
{
BDMA->IFCR = BDMA_FLAG_TE0;
}
}

注释的比较清楚。如果输出的PWM频率较高,建议将BDMA的缓冲区设置的大些,防止BDMA中断的执行频率较高。

41.2.5 BDMA脉冲个数控制

借助本章2.4小节的知识点,如果要实现脉冲个数的控制,在BDMA中断服务程序里面动态修改缓冲区即可。比如我们配置:

BDMA开启半传输完成中断和传输完成中断。BDMA传输16次为一轮,每两次传输算一个周期的脉冲。

如果要实现100个脉冲,我们就可以在12轮,即12*8=96个脉冲后的传输完成中断里面修改后半部分输出低电平即可,进入半传输完成中断后再修改前半部分数据输出低电平。

41.3 BDMA板级支持包(bsp_tim_dma.c)

BDMA驱动文件bsp_pwm_dma.c提供了如下两个函数:

LPTIM_Configbsp_InitTimBDMA

函数LPTIM_Config是文件内部调用的,而函数bsp_InitTimBDMA是供用户调用的。

41.3.1 函数LPTIM_Config

函数原型:

static void LPTIM_Config(void)

函数描述:

此函数用于配置LPTIM2工作在PWM模式,但不初始化GPIO,使用内部的LPTIM2_OUT即可作为BDMA请求发生器的触发源。

注意事项:

函数前面static用于限制作用域,表示仅在本文件里面调用。关于此函数的讲解在本章的2.1小节41.3.2 函数bsp_InitTimBDMA

函数原型:

void bsp_InitTimBDMA(void)

函数描述:

此函数用于配置定时器触发BDMA,可以实现任意IO做PWM输出。

注意事项:

关于此函数的讲解在本章2.2小节。

使用举例:

作为初始化函数,直接在bsp.c文件的bsp_Init函数里面调用即可。

41.4 BDMA驱动移植和使用

低功耗定时器的移植比较简单:

第1步:复制bsp_tim_dma.c和bsp_tim_dma.h到自己的工程目录,并添加到工程里面。第2步:这几个驱动文件主要用到HAL库的GPIO、LPTIM和DMA驱动文件,简单省事些可以添加所有HAL库.C源文件进来。第3步,应用方法看本章节配套例子即可。41.5 实验例程设计框架

通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:

第1阶段,上电启动阶段:

这部分在第14章进行了详细说明。

第2阶段,进入main函数:

第1步,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器,LED和串口。第2步,借助按键消息实现不同的输出频率调整,方便测试。41.6 实验例程说明(MDK)

配套例子:

V7-010_DMAMUX的定时器触+BDMA控制任意IO做PWM和脉冲数控制

实验目的:

学习DMAMUX的定时器触+BDMA控制任意IO做PWM和脉冲数控制。

实验内容:

通过LPTIM2触发DMAMUX的请求发生器,控制DMA给任意IO做PWM输出。

实验操作:

K1键按下,PB1输出20KHz方波,占空比50%。K2键按下,PB1输出10KHz方波,占空比50%。K3键按下,PB1输出5KHz方波,占空比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_InitTimBDMA(); /* 初始化BDMA控制PB1做的PWM输出 */
}

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输出20KHz方波,占空比50%。K2键按下,PB1输出10KHz方波,占空比50%。K3键按下,PB1输出5KHz方波,占空比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))
{
/* 每隔100ms 进来一次 */
bsp_LedToggle(2);
}

/* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{
case KEY_DOWN_K1: /* K1键按下,PB1输出20KHz方波,占空比50% */
if (HAL_LPTIM_PWM_Start(&LptimHandle, 5000-1, 2500 - 1) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
break;

case KEY_DOWN_K2: /* K2键按下,PB1输出10KHz方波,占空比50% */
if (HAL_LPTIM_PWM_Start(&LptimHandle, 10000-1, 5000 - 1) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
break;

case KEY_DOWN_K3: /* K3键按下,PB1输出5KHz方波,占空比50% */
if (HAL_LPTIM_PWM_Start(&LptimHandle, 20000-1, 10000 - 1) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
break;

default:
/* 其它的键值不处理 */
break;
}
}
}
}

41.7 实验例程说明(IAR)

配套例子:

V7-010_DMAMUX的定时器触+BDMA控制任意IO做PWM和脉冲数控制

实验目的:

学习DMAMUX的定时器触+BDMA控制任意IO做PWM和脉冲数控制。

实验内容:

通过LPTIM2触发DMAMUX的请求发生器,控制DMA给任意IO做PWM输出。

实验操作:

K1键按下,PB1输出20KHz方波,占空比50%。K2键按下,PB1输出10KHz方波,占空比50%。K3键按下,PB1输出5KHz方波,占空比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_InitTimBDMA(); /* 初始化BDMA控制PB1做的PWM输出 */
}

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输出20KHz方波,占空比50%。K2键按下,PB1输出10KHz方波,占空比50%。K3键按下,PB1输出5KHz方波,占空比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))
{
/* 每隔100ms 进来一次 */
bsp_LedToggle(2);
}

/* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{
case KEY_DOWN_K1: /* K1键按下,PB1输出20KHz方波,占空比50% */
if (HAL_LPTIM_PWM_Start(&LptimHandle, 5000-1, 2500 - 1) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
break;

case KEY_DOWN_K2: /* K2键按下,PB1输出10KHz方波,占空比50% */
if (HAL_LPTIM_PWM_Start(&LptimHandle, 10000-1, 5000 - 1) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
break;

case KEY_DOWN_K3: /* K3键按下,PB1输出5KHz方波,占空比50% */
if (HAL_LPTIM_PWM_Start(&LptimHandle, 20000-1, 10000 - 1) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
break;

default:
/* 其它的键值不处理 */
break;
}
}
}
}

41.8 总结

本章节就为大家讲解这么多,控制BDMA让GPIO输出PWM以及脉冲数的控制,实际项目中有一定的实用价值,望初学者熟练掌握。

微信公众号:armfly_com


版权声明

本文仅代表作者观点,不代表博信信息网立场。

热门