| 本帖最后由 正点原子运营 于 2024-4-9 16:24 编辑 
 
   
 1)实验平台:正点原子 M144Z-M3 STM32F103最小系统板 
 2) 章节摘自【正点原子】M144Z-M3最小系统板使用指南——STM32F103版 
 
 
 
 6)正点原子STM32技术交流QQ群:725095144 
 
 
本章将介绍使用STM32F103高级定时器输出指定个数的PWM。通过本章的学习,读者将学习到高级定时器重复计数器的使用。 本章分为如下几个小节: 21.1 硬件设计 21.2 程序设计 21.3 下载验证 
     21.1 硬件设计 21.1.1 例程功能 1. 按下KEY0按键,PC6引脚输出5个频率为2Hz的PWM 2. LED0闪烁,提示程序正在运行 
 21.1.2硬件资源 1. LED        LED0 - PB5 2. 按键        KEY0- PE4 3. TIM8        CH1 - PC6 
 21.1.3 原理图 本章实验使用的TIM8为STM32F103的片上资源,因此没有对应的连接原理图。 
 21.2 程序设计 21.2.1 HAL库的TIM驱动 本章实验将使用KEY0按键控制TIM8通过通道1(PC6引脚)输出指定个数的PWM,其具体的配置步骤如下: ①:初始化定时器PWM ②:配置定时器PWM输出通道 ③:开始定时器PWM输出 在HAL库中对应的驱动函数如下: ①:初始化定时器PWM输出 请见第18.2.1小节中初始化定时器PWM的相关内容。 ②:配置定时器PWM输出通道 请见第18.2.1小节中配置定时器PWM输出通道的相关内容。 ③:开始定时器PWM输出 请见第18.2.1小节中开始定时器PWM输出的相关内容。 
 21.2.2 高级定时器驱动 本章实验的高级定时器驱动主要负责向应用层提供高级定时器的初始化和输出指定个数PWM的函数,并实现高级定时器的中断回调函数。本章实验中,高级定时器的驱动代码包括atim.c和atim.h两个文件。 高级定时器驱动中,对TIM、GPIO的相关宏定义,如下所示: 复制代码#define ATIM_TIMX_NPWM             TIM8
#define ATIM_TIMX_NPWM_IRQn        TIM8_UP_TIM13_IRQn
#define ATIM_TIMX_NPWM_IRQHandler  TIM8_UP_TIM13_IRQHandler
#define ATIM_TIMX_NPWM_CLK_ENABLE()    \
    do {                                \
         __HAL_RCC_TIM8_CLK_ENABLE();   \
    }while (0)
#define ATIM_TIMX_NPWM_CHY             TIM_CHANNEL_1
#define ATIM_TIMX_NPWM_CHY_GPIO_PORT   GPIOC
#define ATIM_TIMX_NPWM_CHY_GPIO_PIN    GPIO_PIN_6
#define ATIM_TIMX_NPWM_CHY_GPIO_CLK_ENABLE()   \
    do {                                        \
         __HAL_RCC_GPIOC_CLK_ENABLE();          \
    }while (0)
高级定时器驱动中TIM8的初始化函数,如下所示: 复制代码/**
*@brief   初始化高级定时器输出指定个数PWM
*@param   arr: 自动重装载值
*@param   psc: 预分频系数
*@retval  无
*/
voidatim_timx_chy_npwm_init(uint16_t arr, uint16_t psc)
{
   GPIO_InitTypeDef gpio_init_struct;
   TIM_OC_InitTypeDef timx_oc_init_struct = {0};
   
    /* 使能相关时钟 */
   ATIM_TIMX_NPWM_CHY_GPIO_CLK_ENABLE();
   ATIM_TIMX_NPWM_CLK_ENABLE();
   
    /* 配置PWM输出引脚 */
   gpio_init_struct.Pin = ATIM_TIMX_NPWM_CHY_GPIO_PIN;
   gpio_init_struct.Mode = GPIO_MODE_AF_PP;
   gpio_init_struct.Pull = GPIO_PULLUP;
   gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
   HAL_GPIO_Init(ATIM_TIMX_NPWM_CHY_GPIO_PORT, &gpio_init_struct);
   
    /* 配置中断优先级并使能中断 */
   HAL_NVIC_SetPriority(ATIM_TIMX_NPWM_IRQn, 0, 0);
   HAL_NVIC_EnableIRQ(ATIM_TIMX_NPWM_IRQn);
   
    /* 初始化定时器PWM */
   g_timx_npwm_handle.Instance = ATIM_TIMX_NPWM;
   g_timx_npwm_handle.Init.Prescaler = psc;
   g_timx_npwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
   g_timx_npwm_handle.Init.Period = arr;
   g_timx_npwm_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
   g_timx_npwm_handle.Init.RepetitionCounter = 0;
   HAL_TIM_PWM_Init(&g_timx_npwm_handle);
   
    /* 配置定时器PWM输出通道 */
   timx_oc_init_struct.OCMode = TIM_OCMODE_PWM1;
   timx_oc_init_struct.Pulse = (arr + 1) >> 1;
   timx_oc_init_struct.OCPolarity = TIM_OCPOLARITY_HIGH;
   HAL_TIM_PWM_ConfigChannel(  &g_timx_npwm_handle,
                                 &timx_oc_init_struct,
                                 ATIM_TIMX_NPWM_CHY);
   
    /* 开始定时器PWM输出 */
   __HAL_TIM_ENABLE_IT(&g_timx_npwm_handle, TIM_IT_UPDATE);
   HAL_TIM_PWM_Start(&g_timx_npwm_handle, ATIM_TIMX_NPWM_CHY);
}
从TIM8的初始化代码中可以看到,初始化函数中初始化了TIM8的PWM并配置了TIM8的PWM输出通道1,最后开始TIM8的PWM输出。 高级定时器驱动中,开启高级定时器输出指定个数PWM的函数,如下所示: 复制代码/**
*@brief   设置高级定时器输出指定个数PWM
*@paramn  pwm: 指定PWM个数
*@retval  无
*/
voidatim_timx_chy_npwm_set(uint32_t npwm)
{
    if (npwm == 0)
    {
         return;
    }
   
    /* 产生更新事件来输出指定个数PWM */
   g_timx_chy_npwm_remain = npwm;
   HAL_TIM_GenerateEvent(&g_timx_npwm_handle, TIM_EVENTSOURCE_UPDATE);
   __HAL_TIM_ENABLE(&g_timx_npwm_handle);
}
该函数记录下了需要产生的PWM个数,因为会在TIM8的更新中断中处理输出PWM,因此手动产生了一次更新事件,由于初始化函数中开启了TIM8的更新中断,因此随后会进入TIM的中断服务函数中。 高级定时器驱动中,TIM8的中断回调函数,如下所示: 复制代码/**
*@brief   输出指定个数PWM高级定时器中断回调函数
*@param   无
*@retval  无
*/
voidATIM_TIMX_NPWM_IRQHandler(void)
{
    uint16_t npwm = 0;
   
    /* 定时器更新事件中断 */
    if (__HAL_TIM_GET_FLAG(&g_timx_npwm_handle, TIM_FLAG_UPDATE) != RESET)
    {
         /* 每次最多输出256个PWM(TIMx_RCR只有[7:0]有效) */
         if (g_timx_chy_npwm_remain > 256)
         {
             g_timx_chy_npwm_remain -= 256;
             npwm = 256;
         }
         else if ((g_timx_chy_npwm_remain % 256) != 0)
         {
             npwm = g_timx_chy_npwm_remain % 256;
             g_timx_chy_npwm_remain = 0;
         }
         
         if (npwm != 0)
         {
             /* 利用高级定时器的重复计数器来输出PWM */
             ATIM_TIMX_NPWM->RCR = npwm - 1;
             HAL_TIM_GenerateEvent(&g_timx_npwm_handle,TIM_EVENTSOURCE_UPDATE);
             __HAL_TIM_ENABLE(&g_timx_npwm_handle);
         }
         else
         {
             ATIM_TIMX_NPWM->CR1 &= ~(TIM_CR1_CEN);
         }
         
         /* 清除更新事件标志 */
         __HAL_TIM_CLEAR_IT(&g_timx_npwm_handle, TIM_IT_UPDATE);
    }
}
因为高级定时器的重复计数寄存器只有低八位有效,因此需要在需要产生大于255个PWM的情况下做特殊处理,随后将需要产生的PWM个数值减1写入TIM8的重复计数寄存器,并且由于写入重复计数寄存器的新值只有在下一次更新事件发生时才生效,因此需要手动产生一次更新事件,最后使能TIM8,这么一来,TIM8就会持续地输出PWM直到重复计数寄存器的数值为0后,产生一次更新中断,若此时已经输出完了指定个数的PWM,那么就会关闭高级定时器。 
 21.2.3 实验应用代码 本章实验的应用代码,如下所示: 复制代码int main(void)
{
    uint8_t key;
    uint8_t t = 0;
   
   HAL_Init();                                 /* 初始化HAL库 */
   sys_stm32_clock_init(RCC_PLL_MUL9);         /* 配置时钟,72MHz */
   delay_init(72);                             /* 初始化延时 */
   usart_init(115200);                         /* 初始化串口 */
   led_init();                                 /* 初始化LED */
   key_init();                                 /* 初始化按键 */
   atim_timx_chy_npwm_init(5000-1, 7200-1);   /* 初始化高级定时器输出指定个数PWM */
   
    while (1)
    {
         key = key_scan(0);
         if (key == KEY0_PRES)
         {
             /* 设置高级定时器输出5个PWM */
             atim_timx_chy_npwm_set(5);
         }
         
         if (++t == 20)
         {
             t = 0;
             LED0_TOGGLE();
         }
         
         delay_ms(10);
    }
}
从上面的代码中可以看到,TIM8的自动重装载值配置为(5000-1),TIM8的预分频器数值配置为(7200-1),并且TIM8的时钟频率为72MHz,因此TIM8的计数频率为10KHz,且TIM8每计数5000次溢出一次,因此溢出频率为2Hz,也就是输出PWM的频率为2Hz。 初始化完TIM8后,就重复地扫描按键,若KEY0按键被按下,则输出5个PWM。 
 21.3 下载验证 在完成编译和烧录操作后,按下KEY0按键,此时可以通过示波器或外接LED的方式观察PC6引脚输出了5个频率为2Hz,占空比为50%的PWM。 |