Performing operations on non-initialized Timer leads to crashes/lockups
After pressing RESET button and typing:
Micro Python v1.3.10 on 2015-02-13; PYBv1.0 with STM32F405RG
Type "help()" for more information.
>>> n = 3
>>> def countdown(timer):
... global n
... if n <= 0:
... print("Launch!")
... timer.deinit()
... else:
... print(n)
... n -= 1
...
>>> tim = pyb.Timer(4, freq=1, callback=countdown)
At this moment REPL hangs and I need to press RESET to recover. :cry:
Probably related issue:
Micro Python v1.3.10 on 2015-02-13; PYBv1.0 with STM32F405RG
Type "help()" for more information.
>>> n = 3
>>> def countdown(timer):
... global n
... if n <= 0:
... print("Launch!")
... timer.deinit()
... else:
... print(n)
... n -= 1
...
>>> tim = pyb.Timer(4)
>>> tim.callback(countdown)
>>> tim.init(freq=1)
>>> # Nothing happens :|
Nevertheless it does work if I continue and provide both kwargs:
>>> tim.init(freq=1, callback=countdown)
3
>>> 2
1
Launch!
However, if I continue with:
>>> tim.deinit()
>>> tim.callback(countdown)
>>> Launch!
I did not expect after issuing deinit() to have triggered callbacks without explicitly calling init with freq kwarg. :confused:
And after issuing a soft reset
PYB: sync filesystems
PYB: soft reboot
Micro Python v1.3.10 on 2015-02-13; PYBv1.0 with STM32F405RG
Type "help()" for more information.
>>> tim = pyb.Timer(4, callback=print("I did not set freq option"))
I did not set freq option
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: must specify either freq, or prescaler and period
>>> tim = pyb.Timer(4)
>>> tim.callback(print("I did not set freq option"))
I did not set freq option
>>>
It certainly comes as a surprise that callback is still kicking without explicit freq
RFC: defining machine.Timer
This issue is to discuss the API of the machine.Timer class. There are currently 3 different version of Timer which don't have much in common:
esp8266:
# id must be -1, period is in milliseconds, mode is PERIODIC or ONE_SHOT
Timer(id, *, period, mode, callback) # constructor
Timer.init(*, period, mode, callback) # reinitialise all parameters
Timer.deinit() # stop the timer
cc3200/wipy:
# mode is PERIODIC, ONE_SHOT or PWM
Timer(id, mode, *, width=16) # constructor
Timer.init(mode, *, width=16) # reinitialise
Timer.deinit() # stop
# each timer has one or more channels
# each timer channel is associated with a specific IO pin (eg for PWM)
Timer.channel(*, freq, period, polarity, duty_cycle) # create channel assoc with TImer
TimerChannel.freq([freq]) # get/set
TimerChannel.period([period]) # get/set
TimerChannel.duty_cycle([duty]) # get/set
TimerChannel.irq(...) # get or set up the IRQ callback
stmhal/pyboard (actually pyb.Timer):
# freq is in Hz (can be float <1), prescaler/period are raw values, mode is UP/DOWN/CENTER
Timer(id, *, freq, prescaler, period, mode, div, callback, deadtime) # constructor
Timer.init(*, freq, prescaler, period, mode, div, callback, deadtime) # reinit
Timer.counter([value]) # get/set the raw timer counter
Timer.source_freq() # get source frequency that clocks this timer
Timer.freq([value]) # get/set
Timer.prescaler([value]) # get/set
Timer.period([value]) # get/set
Timer.callback(callable) # get/set callback (pass None to disable)
# each timer has multiple chanels (usually 4)
# pin is the pin to associate with the channel (eg for PWM)
Timer.channel(mode, *, callback, pin, pulse_width, pulse_width_percent, compare, polarity) # create
TimerChannel.pulse_width([value]) # get/set
TimerChannel.pulse_width_percent([value]) # get/set
TimerChannel.capture([value]) # get/set
TimerChannel.compare([value]) # get/set
TimerChannel.callback(callable) # get/set callback
To compare current usage, for a simple periodic timer at 10Hz one would do:
# esp8266
t = Timer(-1)
t.init(period=100, mode=Timer.PERIODIC, callback=lambda t:print(t))
# stmhal/pyboard
Timer(1, freq=10, callback=lambda t:print(t))
For a one-shot after 2 seconds:
# esp8266
t = Timer(-1)
t.init(period=2000, mode=Timer.ONE_SHOT, callback=lambda t:print(t))
# stmhal/pyboard
def cb(t):
print(t)
t.deinit() # or t.callback(None)
Timer(1, freq=0.5, callback=cb)
At the very least I'd suggest that all ports have the same interface to create periodic and one-shot timers, as well as the same way to configure the timer irq/callback. Configuration of the irq should follow the samy signature as machine.Pin.irq(); see also #2297.
I also feel that a timer should have start() and stop() methods, so that one can create an initialise a timer at one point in the code, then start it precisely when needed. This requires definining that the first callback happens one period after start() is called.