Trying to use timers to avoid a blocking loop

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
User avatar
UltraBob
Posts: 43
Joined: Mon Jul 28, 2014 1:18 pm
Location: Zushi, Japan
Contact:

Trying to use timers to avoid a blocking loop

Post by UltraBob » Sun Aug 10, 2014 3:29 pm

I'm trying to use a timer to update an lcd regularly with information from a temperature sensor, and to therefore better understand how to work with the board.

Here is part of my code:

Code: Select all

tempPin = pyb.ADC(pyb.Pin.board.X11)
def getTemp(pin):
    temps = []
    for i in range(100):
        tempReading = pin.read()
        tempVolts = tempReading * 3.3 / 4096.0 
        tempC = (tempVolts - 0.5) * 100.0
        temps.append(tempC)
    return sum(temps) / float(len(temps))
    
def displayTemp(pin):
    lcd.move_to(0,0)
    lcd.clear()
    lcd.putstr("Temp: "+str("{0:.1f}".format(getTemp(pin)))+" deg C")
    pyb.LED(4).toggle()

timer = pyb.Timer(14)
timer.init(freq=2)
timer.callback(lambda t: displayTemp(tempPin))
Comments on the quality of the code are welcome as well, but anyone who has worked with timer callbacks on the micropython board will probably immediately know that this is a MemoryError related question.

I am aware that interrupts are not allowed to allocate memory, and I'm wondering if there is any way to make what I'm trying to do here work. I tried predefining all the variables (all float to 0.0, all strings to "" and all lists to [] so that there would be no memory allocation for variables, but that didn't help. Your wisdom appreciated!

P.S. the lambda is there in the callback, because without it, the code seemed to only ever be called once, even if it was code that didn't cause a memory error, so bonus point for helping me understand that.

Turbinenreiter
Posts: 288
Joined: Sun May 04, 2014 8:54 am

Re: Trying to use timers to avoid a blocking loop

Post by Turbinenreiter » Sun Aug 10, 2014 4:08 pm

* do you really need the float() in the return of getTemp?

* try to print() what you want to return. does that work? I guess callbacks can't return stuff.

* maybe try to merge those two functions into one. maybe callbacks can't call other functions.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Trying to use timers to avoid a blocking loop

Post by dhylands » Mon Aug 11, 2014 6:43 am

Any attempt to allocate an object from within the timer callback will cause a memory error.

The statement

Code: Select all

x = 0.5 
will allocate memory for a float, which can't be done from inside an ISR.

I wrote up something using threads from http://forum.micropython.org/viewtopic. ... rator#p208

The timer interrupt just sets a flag, and the DisplayThread uses the flag to know when to update the display. I also just used a print rather than an lcd output, but that shouldn't make any difference.

Code: Select all

import pyb

def getTemp(pin):
    temps = []
    for i in range(100):
        tempReading = pin.read()
        tempVolts = tempReading * 3.3 / 4096.0
        tempC = (tempVolts - 0.5) * 100.0
        temps.append(tempC)
    return sum(temps) / float(len(temps))

def displayTemp(pin):
    print("Temp: "+str("{0:.1f}".format(getTemp(pin)))+" deg C")
    pyb.LED(4).toggle()

timerExpired = False
tempPin = pyb.ADC(pyb.Pin.board.X11)

def timerCallback(tim):
    global timerExpired
    timerExpired = True

def DisplayTask():
    global timerExpired
    timer = pyb.Timer(14)
    timer.init(freq=2)
    timer.callback(timerCallback)
    while True:
        if timerExpired:
            timerExpired = False
            displayTemp(tempPin)
        yield None

TaskQueue = [ DisplayTask() ]

while True:
    # main loop here
    for task in TaskQueue:
        next(task)

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Trying to use timers to avoid a blocking loop

Post by pythoncoder » Tue Aug 12, 2014 8:07 am

A good way to simulate concurrency is to use "weightless threads". There are numerous articles on the web about this, the one which got me started was
http://www.ibm.com/developerworks/library/l-pythrd/
The basic idea is to make each thread into a generator. The code uses the yield statement at points where you want control to be passed to another thread. A simple scheduler runs an infinite loop running the next command on each thread in turn. Here is the simplest example I can think of (tested on a PC under Python3)

Code: Select all

def thread1():
    for x in range(1,20,2):
        yield x # odd numbers

def thread2():
    for x in range(0,20,2):
        yield x # even numbers

th1 = thread1() # create generator instances
th2 = thread2()

while True:
    try:
        print(next(th1))
        print(next(th2))
    except StopIteration:
        print("Done")
        break
When run it issues interleaved odd and even numbers, demonstrating how control alternates between the two threads. In a simple embedded application the threads would run forever and there would be no need to catch the StopIteration exception.

Most of my embedded projects involve threading. In essence one thread might read values from hardware, another might display results, and a third would respond to user interactions. When running under an OS I normally use the threading library, but I suspect this is too heavyweight to be ported to MicroPython. I'm writing a scheduler for MicroPython using the above technique but with additional functionality which I may make available once it's fully implemented and documented. But for simple applications like yours I'd suggest basing your code on my example above.

Regards, Pete
Peter Hinch
Index to my micropython libraries.

fma
Posts: 164
Joined: Wed Jan 01, 2014 5:38 pm
Location: France

Re: Trying to use timers to avoid a blocking loop

Post by fma » Tue Aug 12, 2014 8:10 am

Very interesting! Thanks for sharing.
Frédéric

User avatar
UltraBob
Posts: 43
Joined: Mon Jul 28, 2014 1:18 pm
Location: Zushi, Japan
Contact:

Re: Trying to use timers to avoid a blocking loop

Post by UltraBob » Tue Aug 12, 2014 8:10 am

These are all really helpful and enlightening. I'm trying to get a week ahead on coursework for an online class I'm taking to accommodate vacation next week. If I can get that done, the next thing on my list is to dive into these and explore them thoroughly.

Thanks!

nelfata
Posts: 74
Joined: Wed Apr 30, 2014 10:50 pm

Re: Trying to use timers to avoid a blocking loop

Post by nelfata » Tue Aug 12, 2014 8:16 am

Hi,
I am in the process of integrating ChibiOS with MP.
I think that could be useful to build a threading library based on this RTOS.
I am still having problems with incompatible definitions and build, between the two. The idea is to have MP run on top of this RTOS in one thread and take advangtage of the features and drivers ChibiOS.
So it is taking much longer to integrate the two.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Trying to use timers to avoid a blocking loop

Post by dhylands » Tue Aug 12, 2014 2:42 pm

pythoncoder wrote:Most of my embedded projects involve threading. In essence one thread might read values from hardware, another might display results, and a third would respond to user interactions. When running under an OS I normally use the threading library, but I suspect this is too heavyweight to be ported to MicroPython. I'm writing a scheduler for MicroPython using the above technique but with additional functionality which I may make available once it's fully implemented and documented. But for simple applications like yours I'd suggest basing your code on my example above.

Regards, Pete
I'm definitely interested in this. I'd be happy to help out with testing/coding.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Trying to use timers to avoid a blocking loop

Post by pythoncoder » Wed Aug 13, 2014 12:37 pm

Thanks for the offer. It may be a couple of weeks before I have anything fit for public consumption, but in essence I have the following capabilities subject to more documentation and testing.

Threads submit execution to other threads by yielding either a number of microseconds as a nominal delay or a "Waitfor" object. The latter will cause the scheduler to suspend execution of the thread for a time period, until the arrival of an interrupt, or until a passed polling function returns nonzero. The scheduler despatches a thread according to a set of priorities: events take priority over delays, otherwise the most overdue thread gets executed. It passes information back to the thread namely the return value of any polling function, number of interrupts which have occurred, and lateness of its scheduling. It uses a free running microsecond timer for timing (with overflow handled properly).
I've created the following utility classes which employ the scheduler to run a background thread: a driver for two line LCD modules based on the Hitachi HD44780 controller chip and a debounced switch class using polling.
Using cooperative multithreading delays measured in microseconds are clearly optimistic: depending on the other threads running you're unlikely to see delays much shorter than a mS or so and - in my testing - upto 20mS on occasion. But it seems to work OK for the kind of application I have in mind: interfacing to sensors, pushbuttons and cheap LCD displays. Threads yielding small numbers of uS provides a crude method of prioritising one thread over another and of requesting execution ASAP.

I have no experience of posting code for public use and testing but I assume the way ahead will be to learn to use github.

Regards, Pete
Peter Hinch
Index to my micropython libraries.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Trying to use timers to avoid a blocking loop

Post by dhylands » Wed Aug 13, 2014 2:50 pm

Just some random ramblings based on my own experience.

The way a RTOS (real-time OS) works is that the highest priority thread runs until it blocks, or is preempted by an even higher priority thread. Threads of equal priority run in a round-robin fashion determined by the time slice.

If you specified a timout of zero using what you described, that would give you the round robin. I wouldn't use timeouts to specify priority. I would just specify it explicitly.

There are a couple of common ways to implement priority.

Threads are normally in 2 states, ready-to-run, or waiting (for an event, timeout, etc).

Threads that are ready-to-run can be kept in what's called a priority queue (ordered based on priority) or more commonly, you have a separate queue for each priority. When a thread yields it just goes to the back of the queue and the scheduler picks the new "highest priority" thread to run.

Often times, the queue is actually just a double linked list of threads. And a thread is always on exactly one queue (if it's blocked, then it would be on a blocked queue). Using the doubly linked list gives O(1) time to change queues and requires no memory allocations.

The features I'd be looking for would be semaphores, events, and timed things. I typically use semaphores for resource management (if I have a buffer with 20 spots in it, I'd have a semaphore with a count of 20).

And yes - github is a good way of sharing.

Post Reply