关于软件PWM介绍

1.PWM概念介绍

PWM叫脉宽调制,它可以实现用软件来模拟模拟量的变化,比如正弦波。本质就是在一定的周期里调节脉宽,使输出类似模拟量。

概念一:什么是脉宽时间,就是高电平的时间

概念二:什么是周期,从低电平变到高电平,在从高电平变到低电平所用的总时间,算是一个完整周期。

概念三:什么是占空比?占空比就是高电平占总周期的比例

2.案例一:用PWM控制电机速度(这里用51单片机举例)

第一步:先配置定时器1,100us产生一次中断

void Timer1_Init(void)		//100微秒@11.0592MHz
{
	TMOD &= 0x0F;			//设置定时器模式
	TMOD |= 0x20;			//设置定时器模式
	TL1 = 0xA4;				//设置定时初始值
	TH1 = 0xA4;				//设置定时重载值
	TF1 = 0;				//清除TF1标志
	TR1 = 1;				//定时器1开始计时
	ET1 = 1;				//使能定时器1中断
	EA = 1;
}

第二步:在定时器1的中断服务函数中加加计数值,当计数值小于比较值,电机驱动,反之电机停转。

unsigned char Counter,Compare;	//计数值和比较值,用于输出PWM

//定时器1中断服务函数
void Timer1_Isr(void) interrupt 3
{
	Counter++;
	Counter%=100;	//计数值变化范围限制在0~10
	Compare = 100;
	if(Counter<Compare)
	{
		ENA=1;		//输出1
		ENB=1;		//输出1

	}
	else				//计数值大于比较值
	{
		ENA=0;		//输出0
		ENB=0;		//输出0


	}
}

这样,我们就实现了电机控速的功能。

此时PWM的频率为100Hz,当然电机的频率低于1k可能会有噪声。

3.案例二:呼吸灯(这里用stm32f1+hal库举例)

	  for(int i=0;i<100;i++){
		  __HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,i);
		  HAL_Delay(20);
	  }
	  for(int i=99;i>0;i--){
		  __HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,i);
		  HAL_Delay(20);
	  }

上面是核心代码,本质就是维持一个比较值一段时间,如何在增加比较值,使得占空比呈现一个逐渐增大的趋势,LED灯越来越亮

像上图这样。

完成这样一个呼吸灯的周期是20*100+20*100= 4000ms=4s(这个不是PWM的周期)

图中的纵轴为占空比,横轴为时间

如图可以计算,PWM周期为10000Hz

计算流程如上

4.案例三:呼吸灯优化--正弦函数

#include <math.h> // 需要包含数学库以使用 sinf 函数

// ... (可能需要包含HAL库GPIO头文件,如 "gpio.h")
// ... (假设 ucLed 数组 和 led_disp 函数已在别处定义)
extern uint8_t ucLed[6];
extern void led_disp(uint8_t *ucLed);

/**
 * @brief LED 显示处理函数 - 呼吸灯效果 (在主循环中周期性调用)
 */
void led_proc(void)
{
    // 呼吸灯相关变量 (使用 static 确保它们在函数调用之间保持值)
    static uint32_t breathCounter = 0;      // 呼吸效果的内部计时器,模拟时间流逝
    static uint8_t pwmCounter = 0;          // 软件PWM的内部计数器,用于生成PWM波形
    static uint8_t brightness = 0;          // 当前计算出的LED亮度值 (0-pwmMax)
    static const uint16_t breathPeriod = 2000; // 定义一个完整的呼吸周期时长 (单位:毫秒或调用次数,取决于调用频率)
    static const uint8_t pwmMax = 10;       // 软件PWM周期的最大计数值 (决定PWM精度和频率)

    // 更新呼吸计时器:每次调用函数时加1,达到周期后归零
    // 这个计数器相当于呼吸效果的时间轴
    breathCounter = (breathCounter + 1) % breathPeriod;

    // 核心:计算当前时刻的亮度值
    // 使用正弦函数 (sinf) 来模拟平滑的亮度变化
    // (2.0f * 3.14159f * breathCounter) / breathPeriod 将 breathCounter 映射到 0 到 2π 的弧度范围
    // sinf(...) 的结果在 -1.0 到 1.0 之间
    // (sinf(...) + 1.0f) 将范围变为 0.0 到 2.0
    // * pwmMax / 2.0f 将范围缩放到 0 到 pwmMax,即我们期望的亮度范围
    brightness = (uint8_t)((sinf((2.0f * 3.14159f * breathCounter) / breathPeriod) + 1.0f) * pwmMax / 2.0f);

    // 更新软件PWM计数器:每次调用函数时加1,达到 pwmMax 后归零
    // 这个计数器用于在 pwmMax 的周期内比较亮度,决定当前时刻LED是亮还是灭
    pwmCounter = (pwmCounter + 1) % pwmMax;

    // 软件PWM逻辑:
    // 如果 pwmCounter 小于当前的亮度值 brightness,则LED应该亮 (ucLed[0] = 1)
    // 否则,LED应该灭 (ucLed[0] = 0)
    // 效果:brightness 越大,LED在一个PWM周期内亮的时间越长,看起来就越亮
    // 当 brightness 为 0 时,pwmCounter 永远不小于 0,LED 始终灭
    // 当 brightness 为 pwmMax 时,pwmCounter 始终小于 pwmMax,LED 始终亮
    ucLed[0] = (pwmCounter < brightness) ? 1 : 0; // 控制第一个LED (ucLed[0])

    // 调用之前定义的 led_disp 函数,将计算好的 ucLed 状态更新到实际的GPIO引脚
    led_disp(ucLed); // 注意:led_disp内部最好也有优化,避免状态不变时重复写GPIO
}
                

易知:PWM的周期为10ms,呼吸灯的周期是2s

pmwMax是一个时间窗口,pwmCounter是pwm的计数值,也就是把一个周期的PWM分成几块,如pwmCounter为10,则把一个周期的PWM分成十个时间片

优点是波形平滑,和案例二线性的比较的话,pwmMax越大,越精细,波形更平滑,当然不能一直大下去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值