普冉(PUYA)单片机开发笔记(6): 呼吸灯

2023-12-13 03:36:15

概述

上一篇的实验中,分别正确地配置了 TIM16 和 TIM1,TIM16 的中断服务程序中每隔 500ms 翻转板载 LED 一次;TIM1 的 CHANNEL_1 用于输出一个固定占空比的 PWM 信号。这一次我们进一小步:使用 TIM16 的中断设置 TIM1 CHANNEL_1 的 PWM 输出呈现从小到大,再从大到小的循环,即实现呼吸灯效果。

为此,PWM 输出改用千分之一精度的调节,TIM16 的定时周期设置为 8 毫秒,PWM 从 0% 到 100%,耗时 8秒。

对于 TIM1,PWM_PERIOD 定义为 2400,PWM 调节步长设置为 2.4F,和 1000 步调节到位相对应。PWM_PERIOD 参数用于 TIM1 的 ARR 装填,PWM 输出频率为 10KHz。

实现代码

修改 main.h 文件

将 PWM 分频参数 PWM_PERIOD 搬到 mian.h 中

新增一个千分之一 PWM 输出的函数:

void TIM1_PWM_Output_Permill(const uint16_t duty_permill);

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __MAIN_H
#define __MAIN_H

#ifdef __cplusplus
extern "C"
{
#endif

#ifndef PWM_PERIOD
#define PWM_PERIOD 2400
#endif

/* Includes ------------------------------------------------------------------*/
#include "py32f0xx_hal.h"
#include "py32f003xx_Start_Kit.h"
#include <stdbool.h>

/* Exported functions prototypes ---------------------------------------------*/
HAL_StatusTypeDef SystemClock_Config(void);
HAL_StatusTypeDef GPIO_Config(void);
HAL_StatusTypeDef USART_Config(void);
HAL_StatusTypeDef DBG_UART_Start(void);
HAL_StatusTypeDef TIM16_Config(void);
HAL_StatusTypeDef TIM16_Start(void);

HAL_StatusTypeDef TIM1_PWM_Config(void);
HAL_StatusTypeDef TIM1_PWM_Start(uint32_t duty);
HAL_StatusTypeDef TIM1_PWM_Stop(void);
void TIM1_PWM_Output(const uint8_t duty_percent);
void TIM1_PWM_Output_Permill(const uint16_t duty_permill);

void Debug_Info(const char* msg);

...
...

在 app_pwm.c 中实现 TIM1_PWM_Output_Permill 函数

void TIM1_PWM_Output_Permill(const uint16_t duty_permill)
{
    uint16_t tmp_duty = 0;
    uint32_t duty     = 0;
    
    tmp_duty = duty_permill;
    if(duty_permill > 1000) tmp_duty = 999;
    duty = (uint32_t)(tmp_duty * PWM_PERIOD / 1000.0F + 0.5F) + 1;
    TIM1_PWM_Start(duty);
}

计算 duty 时,加的那个 0.5F 是处理四舍五入用的。

修改 app_timer.c 文件

在原有代码上增加三个全局变量

#include "main.h"

TIM_HandleTypeDef htim16;

#define  gPwmStep            2.4F
uint16_t gCurrentDutyPermill = 0;
int8_t   gPwmDir             = 1;

...
...

初始化代码修改如下,Period 和 Prescaler 的设定值使 TIM16 的定时周期为 0.008s ,即8毫秒。

HAL_StatusTypeDef TIM16_Config(void)
{
    HAL_StatusTypeDef conf_res = HAL_OK;
    
    htim16.Instance = TIM16;                                       // 选择 TIM16
    htim16.Init.Period            = 12000 - 1;                     // 自动重装载值 500us
    htim16.Init.Prescaler         = 16 - 1;                        // 预分频为 2-1,两者确定定时器中断周期为1ms
    htim16.Init.ClockDivision     = TIM_CLOCKDIVISION_DIV1;        // 时钟不分频
    htim16.Init.CounterMode       = TIM_COUNTERMODE_UP;            // 向上计数
    htim16.Init.RepetitionCounter = 1 - 1;                         // 不重复计数
    htim16.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; // TIMx ARR 缓冲
    conf_res = HAL_TIM_Base_Init(&htim16);                         // TIMx 初始化
    if ( conf_res != HAL_OK)                         
        return conf_res;
    
    return HAL_OK;
}

在 HAL_TIM_PeriodElapsedCallback 函数中计算出呼吸灯的调节量,并调用 TIM1_PWM_Output_Permill 函数执行 PWM 输出。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance != TIM16) return;

    TIM1_PWM_Output_Permill(gCurrentDutyPermill);
    gCurrentDutyPermill = (uint16_t)(gCurrentDutyPermill + gPwmDir * gPwmStep);

    if(gPwmDir == 1)
    {
        if(gCurrentDutyPermill >= 1000)
        {
            gPwmDir = -1;
            gCurrentDutyPermill = 1000;
        }
    }
    else {
        if(gCurrentDutyPermill <= (uint16_t)(gPwmStep + 0.5))
        {
            gPwmDir = 1;
            gCurrentDutyPermill = 0;
        }
    }
}

代码中原有的翻转 LED 灯的那一句去掉了:闪烁频率太高,看着不舒服了。

稍微修改一下 main() 函数

注意要将 TIM16 的初始化函数放到 TIM1_PWM 初始化函数前面。

int main(void)
{
    HAL_Init();             // systick初始化
    SystemClock_Config();   // 配置系统时钟
    
    if(USART_Config() != HAL_OK) Error_Handler();         
    printf("[SYS_INIT] Debug port initilaized.\r\n");
    
    if(GPIO_Config() != HAL_OK) Error_Handler();          
    printf("[SYS_INIT] Board LED initilaized.\r\n");
    
    if(TIM16_Config() != HAL_OK) Error_Handler();
    printf("[SYS_INIT] Timer initialized.\r\n");
    
    if(TIM1_PWM_Config() != HAL_OK) Error_Handler();
    printf("[SYS_INIT] PWM initialized.\r\n");

    if (TIM16_Start() != HAL_OK) Error_Handler();
    printf("[SYS_INIT] Timer started.\r\n");
    
    printf("\r\n+---------------------------------------+"
           "\r\n|        PY32F003 MCU is ready.         |"
           "\r\n+---------------------------------------+"
           "\r\n");

    if (DBG_UART_Start() != HAL_OK) Error_Handler();
    
    // TIM1_PWM_Output_Permill(250);
        
    while (1) { }
}

总结

示波器的输出波形已通过视频上传,达到设计预期。

视频链接:PY32F003 的呼吸灯效果-CSDN直播

要点:

  • 根据 PWM 输出精度的要求设置 TIM1 对应通道 PWM 的频率,例如,要实现千分之一的逐级调节,PWM_PERIOD 就要设置得大于1000才好。
  • 根据 PWM 调节时长限制确定 TIM16 的定时周期,例如,要实现 8秒完成一轮调节,每一轮调节需要 1000 次递增/递减,则需要将 TIM16 的定时周期设置为 8 毫秒。
  • TIM16 的初始化和启动应放在 TIM1 的初始化之后。
  • 在 TIM16 的中断服务程序中,以最少的时间代价完成 TIM1 PWM 通道的输出。

问题点:在 PWM 波形变化过程中,会出现少许凹陷的毛刺,还没有找到原因。

文章来源:https://blog.csdn.net/Tomcat0130/article/details/134874010
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。