1、使用脉冲占空比拟合不同波形的方式称为 PWM(脉冲宽度调制)控制技术——通过 对一系列脉冲的宽度进行调制,来等效地获得所需要波形(含形状和幅值)。PWM 控制 的基本原理为:冲量相等而开头不同的窄脉冲加在具有惯性的环节上时,其效果基本 相同。其中冲量指窄脉冲的面积;效果相同指环节输出响应波形基本相同。 例如:可以用一系列等幅不用一系列等幅不等宽的脉冲来代替一个正弦半波,见图
要改变等效输出正弦波幅值,按同一比例改变各脉冲宽度即可。 若把拟合的波形改成呼吸特性曲线,即可得到控制呼吸灯使用的 PWM 波形,要生成 拟合的 PWM波形,通常使用计算法和调制法,本文中使用计算法:根据拟合波形的频率、幅值和半周期脉冲数,准确计算 PWM 波各脉冲宽度和
间隔,据此控制开关器件的通断,就可得到所需 PWM 波形。在下边编程实现中会详细说明。
2、要改变PWM输出波形的宽度,就要改变比较寄存器 CCRx 的值,想要输出不通宽度来拟合正弦波,则需要CCRx的值呈现如下图的变化趋势,即要生成一张CCRx的数值表,按周期变化将表中元素的值赋给CCRx。
本文所使用硬件为野火的stm32f103指南者开发板,LED使用PB5引脚控制的红色LED
利用野火提供的呼吸灯例程说明
资料
提取码:i2u1
bsp_breathing.h 文件
定义了三组LED的宏,通过修改代码中的 #define LIGHT_COLOR RED_LIGHT
语句,可以切换使用红、绿、蓝三种颜色的呼吸灯。 在每组宏定义中,定义了定时器编号
、定时器时钟使能
、红灯中PB5引脚重映射操作
、GPIO 端口
和引脚号
、通道对应的比较寄存器名
以及中断通道
和中断服务函数名
。 定时器的比较寄存器 CCRx 在控制呼吸灯的单个周期内需要切换为 PWM表中不同的数值,所以需要利用定时器中断。
bsp_breathing.c 文件
野火的库封装度比较高,所以使用的都是宏定义名,在上面的硬件配置中启用不同的宏,便会对应不同的管脚。其中由于红灯使用的引脚需要用到第二功能,本代码 使用宏 BRE_GPIO_REMAP_FUN ()进行了该引脚的功能重定义操作
PWM表则是一个周期内比较寄存器CCRx的变化值,即脉冲宽度的变化值。
bsp_breathing.c 文件
此表用以下python代码生成
#! python3
#coding=utf-8
"""
Python版本:3.x
外部库:matplotlib1.5.3、numpy1.11.2
运行结果:
命令行中会打印计算得的各点数据,
在当前目录下会生成py_index_wave.c文件,包含上述数据,
并且会弹出描绘曲线的对话框。
"""
import matplotlib.pyplot as plt
import numpy as np
import math
#修改本变量可以更改点数,如16、32、64等
POINT_NUM = 110
#指数曲线最大为2的MAX_POWER次方
MAX_POWER = 10
# POINT_NUM 个点
x1 = np.linspace(0,MAX_POWER,POINT_NUM/2)
#f = 2^(x)
up =[]
for i in x1:
temp = round(2**i)
#得到升序列
up.append( temp )
x2 = np.linspace(MAX_POWER,2*MAX_POWER,POINT_NUM/2)
#f = 2^(2*MAX_POWER-x)
down=[]
for i in x2:
temp = round(2**(MAX_POWER*2-i))
#得到降序列
down.append( temp )
line = list(x1)+list(x2)
val = list(up)+list(down)
print(line)
print("*"*80)
print(list(map(int,val)))
#写入序列到文件
with open("py_index_Wave.c",'w',encoding= 'gb2312') as f:
print(list(map(int,val)),file= f)
#绘图
plt.plot(line,val,"-o")
plt.show()
该 python脚本生成 PWM表数据的原理,实质是按照如下函数曲线进行采样:
若 0<= x <=10:
y = 2^x
若 10< x <=20:
y = 2^(20−)
所以PWM表的范围为0~1024
bsp_breathing.c 文件
本配置主体与全彩 LED 灯实验中的类似,代码中初始化了控制 RGB 灯用的定时器, 它被配置为向上计数,PWM 通道输出也被配置成当计数器 CNT
的值小于
输出比较寄存器CCRx
的值时,PWM通道输出低电平
,点亮LED灯。在函数的最后还使能了定时器中断,每当定时器的一个计数周期完成时,产生中断,配合中断服务函数,即可切换 CCRx 比较寄存器的值。
代码中的 TIM_Period
和 TIM_Prescaler
是关键配置。 其中 TIMPeriod 被配置为(1024-1),它控制定时器的定时周期,定时器的计数寄存器 CNT从 0开始,每个时钟会对计数器加 1,计数至 1023时完成一次计数,产生中断,也 就是说一共 1024 个计数周期,与 PWM 表元素中的最大值相同。若定时器的输出比较寄存器CCRx被赋值为PWM表中的元素,即可改变输出对应占空比的PWM波,控制LED灯, 如: 若CCRx=1,那么在CNT
根据PWM 表更新 CCRx 的值,即可输出占空比呈呼吸特性曲线变化的 PWM波形,达到呼吸灯的效果。 最终,拟合曲线的周期由 TIMPeriod
、PWM
表的点数、TIM_Prescaler
以及下面中断服务函数的 period_cnt
比较值共同决定,本工程需要调整这些参数使得拟合曲线的周期约为 3 秒,从而达到较平缓的呼吸效果。
stm32f10x_it.c 文件
在中断服务函数中,包含两个静态变量 period_cnt
和 pwm_index
。 其中 pwm_index 比较容易理解,它用于指示当前要使用 PWM 表中的哪个元素,从而 在BRE_TIMx->BRE_CCRx = indexWave[pwm_index];
语句中可以给 CCRx赋予正确的数值,而且当 PWM表中的数据都使用一遍时,pwm_index
将重新指向 PWM表的开头,开始下一次呼吸循环。 在本例的单次呼吸循环中,每个PWM表元素都会使用10次,代码中利用period_cnt 变量指示当前使用的次数,当 period_cnt> period_class 时(即 period_cnt>10 时),pwm_index 才会指向下一个元素。每个 PWM 表元素使用多次,主要是为了在 TIMPeriod、PWM 表的 点数、TIM_Prescaler 都固定的情况下,通过调整每个元素的重复次数可以调整整个拟合波 形的周期。如把代码中的比较值 period_class 改为 100,每个 PWM 表遍历一次的时间就变 为原来配置的 10 倍,其拟合的呼吸周期也就相应地改变了。
period_class
在bsp_breathing.c
文件中定义
TIMPeriod、PWM 表的点数、TIM_Prescaler 以及 period_cnt 都会影响到 拟合曲线的周期,而在实际应用中又有如下要求:
周期计算公式如下:
STM32系统时钟默认频率和周期:
f_pclk = 72000000
t_pclk = 1/f_pclk
定时器 update事件周期,即定时器中断周期:
t_timer = t_pclk * TIMER_TIM_Prescaler * TIMER_TIM_Period
每个 PWM点的时间:
T_Point = t_timer * PERIOD_CLASS
最终,遍历 PWM表的周期,即拟合曲线的周期:
T_PWM = T_Point * POINT_NUM
本例周期计算
PWM点数: POINT_NUM = 110
周期倍数: PERIOD_CLASS = 10
定时器定时周期: TIMER_TIM_Period = 1024
定时器分频: TIMER_TIM_Prescaler = 200
代入公式,计算得 T_PWM = 3.128 秒
#! python3
#coding=utf-8
"""
Python版本:3.x
计算不同配置下呼吸灯的周期
运行结果:
打印出当前配置中一个呼吸周期的时间
"""
#PWM点数
POINT_NUM = 110
#周期倍数
PERIOD_CLASS = 10
#幅值等级
AMPLITUDE_CLASS = 1
#定时器定时周期
TIMER_TIM_Period = 2**10
#定时器分频
TIMER_TIM_Prescaler = 200
#STM32系统时钟频率和周期
f_pclk = 72000000
t_pclk = 1/f_pclk
#定时器update事件周期
t_timer = t_pclk*TIMER_TIM_Prescaler*TIMER_TIM_Period
#每个PWM点的时间
T_Point = t_timer * PERIOD_CLASS * AMPLITUDE_CLASS
#整个呼吸周期
T_Up_Down_Cycle = T_Point * POINT_NUM
print ("呼吸周期:",T_Up_Down_Cycle)
内容参考自《零死角玩转STM32—F103指南者》