Page 1 of 2

Trying to use timers to avoid a blocking loop

Posted: Sun Aug 10, 2014 3:29 pm
by UltraBob
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.

Re: Trying to use timers to avoid a blocking loop

Posted: Sun Aug 10, 2014 4:08 pm
by Turbinenreiter
* 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.

Re: Trying to use timers to avoid a blocking loop

Posted: Mon Aug 11, 2014 6:43 am
by dhylands
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)

Re: Trying to use timers to avoid a blocking loop

Posted: Tue Aug 12, 2014 8:07 am
by pythoncoder
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

Re: Trying to use timers to avoid a blocking loop

Posted: Tue Aug 12, 2014 8:10 am
by fma
Very interesting! Thanks for sharing.

Re: Trying to use timers to avoid a blocking loop

Posted: Tue Aug 12, 2014 8:10 am
by UltraBob
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!

Re: Trying to use timers to avoid a blocking loop

Posted: Tue Aug 12, 2014 8:16 am
by nelfata
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.

Re: Trying to use timers to avoid a blocking loop

Posted: Tue Aug 12, 2014 2:42 pm
by dhylands
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.

Re: Trying to use timers to avoid a blocking loop

Posted: Wed Aug 13, 2014 12:37 pm
by pythoncoder
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

Re: Trying to use timers to avoid a blocking loop

Posted: Wed Aug 13, 2014 2:50 pm
by dhylands
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.