定时器
定时器是嵌入式系统中最基本的功能之一,它除了可以实现定时器功能外,还能够实现延时、PWM输出、波形发生器、舵机控制、节拍器、周期唤醒、自动数据采集等功能。在MicroPython中,很多函数的功能也依赖定时器。
1.获取Timer类里面的方法:
>>> help(pyb.Timer) object <class 'Timer'> is of type type init -- <function> deinit -- <function> channel -- <function> counter -- <function> source_freq -- <function> freq -- <function> prescaler -- <function> period -- <function> callback -- <function> UP -- 0 DOWN -- 16 CENTER -- 32 PWM -- 0 PWM_INVERTED -- 1 OC_TIMING -- 2 OC_ACTIVE -- 3 OC_INACTIVE -- 4 OC_TOGGLE -- 5 OC_FORCED_ACTIVE -- 6 OC_FORCED_INACTIVE -- 7 IC -- 8 ENC_A -- 9 ENC_B -- 10 ENC_AB -- 11 HIGH -- 0 LOW -- 2 RISING -- 0 FALLING -- 2 BOTH -- 10 BRK_OFF -- 0 BRK_LOW -- 1 BRK_HIGH -- 2
定时器的使用方法是先导入Timer模块,然后定义定时器,设置定时器ID、频率、回调函数等参数。如:
>>> from pyb import Timer >>> tim = Timer(1,freq=100) #使用定时器1,频率100hz >>> tim = Timer(4,freq=200,callback=print('yes')) #使用定时器4,频率200hz,并设置回调函数 yes >>>
插入知识点:板载LED(3)和LED(4)分别是橙色的灯和蓝色的灯,它们两个都可以进行亮度调节,其它两个(LED(1)、LED(2))没有调节亮度功能,区别就在于LED(3)和LED(4)使用了定时器实现PWM调节亮度。LED(3)使用的是定时器2,LED(4)使用的是定时器3,所以在使用这两个灯的亮度调节功能时不可以再使用这两个定时器了,不然程序就会和预想的格格不入。
LED_YELLOW -- Pin(Pin.cpu.A15, mode=Pin.ALT, af=Pin.AF1_TIM2)
LED_BLUE -- Pin(Pin.cpu.B4, mode=Pin.ALT, af=Pin.AF2_TIM3)
Timer(n)
定义Timer,n=1~14。
class Timer - 控制内部定时器
定时器可用于各种任务。目前,只实现了最简单的情况:定期调用函数。
每个计时器都包含一个以特定速率计数的计数器。计数的速率是外设时钟频率(Hz)除以定时器预分频器。当计数器到达计时器周期时,它会触发一个事件,计数器将重置为零。通过使用回调方法,timer事件可以调用Python函数。
定时器的变化率 = 外设时钟频率(Hz) / 定时器预分频器
1.实例:使用固定频率(也就是定时时间)来翻转LED的状态
>>> time = pyb.Timer(4) #创建使用定时器4 >>> time.init(freq=2) #定时频率2Hz,也就是0.5秒计满 >>> time.callback(lambda t:pyb.LED(1).toggle())
freq参数就是用户自己想设定的时间,但是是以赫兹来表示:
>>> time = pyb.Timer(4) >>> time.init(freq=2) #定时2hz,也就是0.5s >>> time.callback(lambda t:pyb.LED(1).toggle()) >>> time.init(freq=10) #定时10hz,也就是0.1s >>> time.init(freq=0.1) #定时0.1hz,也就是10s
2.实例:定时器计满时,执行回调函数,回调函数的形参就是我们要实现功能的函数或者匿名函数。
>>> def tick(info): #该函数是用于定时器的回调函数的形参,所有用于回调函数的用户函数(def tick(info):)都必须要填一个定位参数 ... print('yes') ... ... ... >>> tim Timer(4, freq=0, prescaler=31249, period=26879, mode=UP, div=1) >>> tim.callback(tick) #回调函数的形参是用户函数或者匿名函数都必须有一个位置参数才可以正常工作(位置参数可能就是一个实例),而且调用时不用些括号 >>> yes yes yes yes
验证上面我们测试出来的又必须要的位置参数到底是什么个东西:
>>> def tick(t): ... print(t) ... ... ... >>> tim = pyb.Timer(4,freq=0.1) >>> tim.callback(tick) >>> Timer(4, freq=0, prescaler=31249, period=26879, mode=UP, div=1) Timer(4, freq=0, prescaler=31249, period=26879, mode=UP, div=1) Timer(4, freq=0, prescaler=31249, period=26879, mode=UP, div=1) Timer(4, freq=0, prescaler=31249, period=26879, mode=UP, div=1)
上面的代码中,我们建立了一个tick函数,该函数是用于定时器中的回调函数的功能函数,我们上面测试时发现回调函数里面的形参必须是带一个形参的函数而且赋给回调函数时还必须是直接是函数名不能带括号。而我们定义的tick函数里面的功能是打印tick(t)里面的t值,很明显t必须是被赋值才有打印值,实验得出是这个值为:Timer(4, freq=0, prescaler=31249, period=26879, mode=UP, div=1)。这个值正是Timer类构造的一个tim实例对象,所以用于回调函数的形参是一个功能函数或者匿名的功能函数,而这个功能函数里面的形参是用来接受实例的形参。
结论:回调函数的功能类似于中断函数,就是某个条件满足时才调用,比如定时器、计数器、中断......。回调函数的形参就是来存放和调用当条件满足时要执行的功能函数(包括匿名函数),而我们定义的功能函数还必须包含一个形参,这个参数是用来导入触发该事件的对象,比如定时器4这个对象,触发条件是定时器4计时到设定的时间时。
其它例子:
tim = pyb.Timer(4, freq=100) # freq in Hz tim = pyb.Timer(4, prescaler=0, period=99) tim.counter() # 获取计数器(也可以设置) tim.prescaler(2) # 设置预分频器(也可以获取) tim.period(199) # 设置周期(也可以获取) tim.callback(lambda t: ...) # 为更新中断设置回调(t=tim实例) tim.callback(None) # clear callback
注意: 定时器(2)和定时器(3)用于PWM分别设置LED(3)和LED(4)的强度。但是,如果相关LED的强度设置为1到254之间的值,则这些定时器仅配置为PWM。如果不使用LED的强度特征,则这些定时器可以免费用于通用目的。类似地,Timer(5)控制伺服驱动器,Timer(6)用于定时ADC / DAC读/写。建议在程序中使用其他定时器。
构造函数:pyb.Timer(id,...)
构造给定id 的新计时器对象。如果给出了附加参数,则计时器由初始化init(...)
。 id
可以是1到14。
Timer类里面的方法
Timer.
init
(*,freq,prescaler,period )
初始化定时器。初始化必须是频率(以Hz为单位)或预分频和周期:
>>> tim = pyb.Timer(1) #构造Timer类的一个对象 >>> tim Timer(1) >>> tim.init(freq=100) #设置定时器1为100赫兹触发(1/100=0.01s) >>> tim Timer(1, freq=100, prescaler=124, period=13439, mode=UP, div=1, deadtime=0, brk=BRK_OFF) >>> tim.init(prescaler=83,period=999) #设置预分频和周期 >>> tim Timer(1, freq=2000, prescaler=83, period=999, mode=UP, div=1, deadtime=0, brk=BRK_OFF)
freq
- 指定计时器的周期性频率。您也可以将此视为计时器经历一个完整周期的频率。prescaler
[0-0xffff] - 指定要加载到 定时器的预分频器寄存器(PSC)中的值。该定时器时钟源是由(分割),以在所述定时器时钟到达。定时器2-7和12-14的时钟源为84 MHz(pyb.freq()[2] * 2),定时器1和8-11的时钟源为168 MHz(pyb.freq()[ 3] * 2)。prescaler + 1
period
定时器 s 1,3,4 和6-15的[0-0xffff] 。定时器 s 2和5的[0-0x3fffffff] 。指定要加载到定时器的AutoReload寄存器(ARR)中的值。这确定了计时器的周期(即计数器周期时)。定时器计数器将在 定时器时钟周期后翻转。period + 1
mode
可以是以下之一:div
可以是1,2或4中的一个。将定时器时钟分频以确定数字滤波器使用的采样时钟。
Timer.UP
- 将计时器配置为从0到ARR(默认)Timer.DOWN
- 将计时器配置为从ARR计数到0。Timer.CENTER
- 将计时器配置为从0到ARR计数,然后再降低到0。callback
- 根据Timer .callback()deadtime
- 指定此时的互补信道上的转换之间的“死”或非活动时间量(两个信道都将处于非活动状态)。deadtime
可以是0到1008之间的整数,具有以下限制:0-128步长为1. 128-256步长为2,256-512步长为8,512-1008步长为16.deadtime
措施刻度source_freq
除以div
时钟滴答。deadtime
仅适用于计时器 1和8。
您必须指定freq或者指定period和prescaler。
Timer.
deinit
()
取消初始化计时器。
禁用回调(以及关联的irq)。
禁用任何通道回调(以及相关的irq)。停止计时器,并禁用计时器外围设备。
Timer.
callback
(fun)
设置定时器触发时要调用的函数。 fun
传递1个参数,定时器对象。如果fun
是,None
那么将禁用回调。
Timer.
channel
(channel, mode, ...)
如果仅传递通道号,则返回先前初始化的通道对象(或者None
如果没有先前通道)。
否则,初始化并返回Timer Channel对象。
每个通道都可以配置为执行pwm,输出比较或输入捕获。所有通道共享相同的基础计时器,这意味着它们共享相同的计时器时钟。
关键字参数:
mode
可以是以下之一:callback
- 根据Timer Channel.callback()
Timer.PWM
- 在PWM模式下配置定时器(高电平有效)。Timer.PWM_INVERTED
- 在PWM模式下配置定时器(低电平有效)。Timer.OC_TIMING
- 表示没有引脚被驱动。Timer.OC_ACTIVE
- 发生比较匹配时,引脚将被激活(激活由极性决定)Timer.OC_INACTIVE
- 发生比较匹配时,引脚将变为无效。Timer.OC_TOGGLE
- 发生比较匹配时,引脚将被切换。Timer.OC_FORCED_ACTIVE
- 引脚被强制激活(忽略比较匹配)。Timer.OC_FORCED_INACTIVE
- 引脚被强制激活(忽略比较匹配)。Timer.IC
- 在输入捕捉模式下配置定时器。Timer.ENC_A
- 在编码器模式下配置定时器。计数器仅在CH1更改时更改。Timer.ENC_B
- 在编码器模式下配置定时器。计数器仅在CH2更改时更改。Timer.ENC_AB
- 在编码器模式下配置定时器。当CH1或CH2发生变化时,计数器会发生变化。pin
无(默认)或Pin对象。如果指定(而非None),这将导致为该定时器通道配置指示引脚的备用功能。如果引脚不支持此定时器通道的任何备用功能,则会引发错误。
详细资料官方地址:http://docs.micropython.org/en/latest/library/pyb.Timer.html?highlight=timer
定时器的通道:
>>> timer = pyb.Timer(1,freq=1000) >>> timer = pyb.Timer(2,freq=1000) >>> ch1 = timer.channel(1,pyb.Timer.PWM,pin=pyb.Pin.board.X1) #通道1 >>> ch1 = timer.channel(1,pyb.Timer.PWM,pin=pyb.Pin.board.X2) >>> ch1 = timer.channel(2,pyb.Timer.PWM,pin=pyb.Pin.board.X1) >>> ch1 = timer.channel(2,pyb.Timer.PWM,pin=pyb.Pin.board.X2) >>> ch1 = timer.channel(2,pyb.Timer.PWM,pin=pyb.Pin.board.X5)#通道2时输出引脚不对就报错 Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: Pin(A4) doesn't have an af for Timer(2) >>> ch1 = timer.channel(4,pyb.Timer.PWM,pin=pyb.Pin.board.X1) #通道4 >>> ch1 = timer.channel(4,pyb.Timer.PWM,pin=pyb.Pin.board.X5) #通道4对应输出引脚不对就报错 Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: Pin(A4) doesn't have an af for Timer(2) >>> ch1 = timer.channel(5,pyb.Timer.PWM,pin=pyb.Pin.board.X1) #定时器2的通道只有四个 Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid channel (5)
通道channel结论:每个定时器都有相应数量的通道,比如定时器2就有四个通道(1~4),而每个通道都对应着具体的引脚。
PWM例子:
timer = pyb.Timer(2, freq=1000) ch2 = timer.channel(2, pyb.Timer.PWM, pin=pyb.Pin.board.X2, pulse_width=8000) ch3 = timer.channel(3, pyb.Timer.PWM, pin=pyb.Pin.board.X3, pulse_width=16000)
每个定时器的通道个数图片:
Timer.
counter
([value])
-
获取或设置计时器计数器。
Timer.
freq
([value])-
获取或设置定时器的频率(如果设置,则更改预分频器和周期)。
Timer.
period
([value])-
获取或设置计时器的周期。
Timer.
prescaler
([value])-
获取或设置计时器的预分频器。
Timer.
source_freq
()-
获取计时器源的频率。
定时器的通道号分别都对应着芯片上固定的引脚,具体引脚需查询手册:在手册第66页附近,比如PA0引脚对应的有TIM2_CH1也就是定时器1的通道2,TIM5_ch1,同时也对应着定时器5的通道1,以此类推。
一个定时器可以对应多个芯片引脚,也就是通道,一个定时器比如有四个通道那么这四个通道就共用一个定时器,我们可以用其来输出PWM,我们每个通道只要改变空占比就可以实现4路PWM了。(这4个通道共用一个定时器,比如四个通道输出四部PWM,其频率是一样的,但是占比可以单独修改.)
class Timer Channel - 设置定时器的通道
定时器通道用于使用定时器生成/捕获信号。
Timer Channel对象是使用Timer.channel()方法创建的。
Methods
channel.
capture
([value])-
获取或设置与频道关联的捕获值。capture,compare和pulse_width是同一函数的所有别名。capture是通道处于输入捕获模式时使用的逻辑名称。
channel.
compare
([value])-
获取或设置与频道关联的比较值。capture,compare和pulse_width是同一函数的所有别名。compare是通道处于输出比较模式时使用的逻辑名称。
自定义PWM驱舵机:
>>> from pyb import Pin,Timer >>> tim = Timer(5,freq=50) >>> ch1 = tim.channel(1,Timer.PWM,pin=Pin('X1'),pulse_width=8000) #pulse_width可以省略,注意定时器不要用重了 >>> ch1.pulse_width_percent(3) >>> ch1.pulse_width_percent(12)
板载舵机注意事项:
1 创建pyb.Servo(id)时。id取值1~4。
2 由于每个舵机生产批次和厂家不同,因此会存在差别。在未针对特定的舵机校准前,使用angle()方法指定旋转角度都会存在偏差。而且指定极限角度如-90°或90°时,舵机会发出咔哒声,说明指定的角度实际对应的参数值已经超出舵机的旋转的范围。时间长了会损坏舵机,请立即指定角度0,或上一次正常的角度范围。
3 为校准电机,达到精确指定旋转角度,可使用sv.pulse_width(width)方法,通过指定width的值,找到,舵机旋转最小的width值和最大的width值。并再调用sv.calibration(pulse_min, pulse_max, pulse_centre)方法,用前面测出的width值校准此通道以适应这个舵机。这样调用angle()方法时才能准确指定舵机的旋转角度。
4 舵机的旋转,无论你在使用angle()方法时指没指定时间值,都需要时间才能达到指定的角度。
5 uPyBoard板虽然有4个舵机接口,但在单独使用USB供电的情况下,也需要注意舵机的数量不要接太多。太多或舵机带动大负载时所需电流很大,有可能会烧毁USB接口。
舵机控制资料链接:
https://blog.csdn.net/spritez/article/details/84843431
https://blog.csdn.net/weixin_41805734/article/details/89790559
控制舵机的PWM波是一种方波,频率为50Hz,周期即其倒数:20ms,在每个周期里面,高电平占0.5ms到2.5ms之间。0.5ms代表的是0度,2.5ms代表的是180度,其他的度数可以按比例换算。
50Hz也就是20ms内的0.5-2.5换成百分比形式就是2.5%-12.5%。7.5即为中间。只有此值内才可以控制舵机转动。
from pyb import Pin, Timer i = 7.5 # 20ms内的0.5-2.5换成百分比形式就是2.5%-12.5%。7.5即为中间。 sw = pyb.Switch() # 用板子上的按键控制 tim = Timer(5, freq=50) # 选择定时器5,频率50hz ch1 = tim.channel(1, Timer.PWM, pin=Pin.cpu.A0) # 选择通道1和定时器5对应引脚A0 while True: sw_state = sw() if sw_state: i += 0.01 pyb.udelay(500) # 延时500us,减点速 if i > 12.0: i = 12.0 else: i -= 0.01 pyb.udelay(500) #延时是让其能有时间缓冲一下,程序周期是短的,而舵机转动时间是长的 if i < 3.0: i = 3.0 ch1.pulse_width_percent(i) # 控制占空比,20ms内0.5-2.5ms的百分比(不给满)
载用户按键按下舵机正转,松手就复位至-80到-90°之间。
用定时器来对GPIO口的PWM调节:我们使用的是板载蓝灯或者橙色灯,因为在板载上的LED灯里面就它两个有定时器的通道口,所有说只要这个引脚有定时器通道功能就可以设置其PWM,我觉得如果没有定时器通道时我们还可以自己模拟PWM,只是这样子就麻烦些。
使用定时器通道来实现PWM功能:
>>> tim_B4 = Timer(3,freq=100) >>> ch1_B4 = tim_B4.channel(1,Timer.PWM,pin=Pin('B4')) #对应蓝灯的定时器是3,通道号是1 >>> ch1_B4.pulse_width_percent() 0.0 >>> ch1_B4.pulse_width_percent(1) #亮度调节 >>> ch1_B4.pulse_width_percent(2) >>> ch1_B4.pulse_width_percent(100) #最亮 >>> ch1_B4.pulse_width_percent(50) >>> ch1_B4.pulse_width_percent(20) >>> ch1_B4.pulse_width_percent(10) >>> ch1_B4.pulse_width_percent(1)
模拟脉冲;
time = 0 tim = Timer(5,freq=10) def i_pwm(t): global time time += 1 if time >= 10: time = 0 tim.callback(i_pwm) def drive(pwm): if time < pwm:pyb.LED(1).on() else:pyb.LED(1).off() while True: drive(3) pyb.delay(1)
呼吸灯的另一种实现方式:
PWM
在大部分微控制器上,PWM其实是定时器的一种工作模式。定时器可以控制多个通道,分布控制不同的 GPIO 输出可变频率和占空比的方波。同一个定时器下的不同PWM通道,频率都是相同的,但是可以分别设置不同的占空比。
PWM功能需要使用Timer和Pin两个模块,首先定义Timer并设置定时器的基本工作参数,然后指定Timer的通道,并设定PWM模式及关联的Pin,最后设置输出脉冲宽度或者脉冲宽度百分比(占空比)。
下面例子演示了使用PWM控制PYB V10上LED3和LED4,通过改变占空比和频率,就可以改变LED亮度或者闪烁频率。