Timer callback, "lambda t:" and memory allocation failure

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
Aris
Posts: 14
Joined: Wed Feb 25, 2015 1:00 pm

Timer callback, "lambda t:" and memory allocation failure

Post by Aris » Wed Feb 25, 2015 2:18 pm

Hi all,

First of all I want to apologize for my English since I'm from Spain and also this is my first post in a forum.

I'm trying to read a value from an ADC pin (e.g. X19) and write the same value in a DAC pin (eg X5). To do this I created a timer of 1Hz with a trigger as fallows:

Code: Select all

timer = pyb.Timer(1)
timer.init(freq=1)
timer.callback(readAndWrite())
def readAndWrite():
    value = pyb.ADC.read()
    value = int(value/4095)*255
    pyb.DAC.write(value)
    pyb.LED(1).toggle()
The pyb.LED(1).toggle() is there only to make sure it works, but I realised it didn't. It only calls that function once in the beggining, while the timer keeps counting. Then I tried with the next code in order to make it simpler:

Code: Select all

timer.callback(readAndWrite())

def readAndWrite():
    pyb.LED(1).toggle()
It kept doing the same. It worked just once which means the led toggles just once. Then I tried using the piece of code "lambda t:" inside the callback and it worked properly, the led1 blinked. So I applied this and also I wrote the complete readAndWrite with the whole code but it generates an error message: MemoryError: memory allocation failed, heap is locked.

blmorris
Posts: 348
Joined: Fri May 02, 2014 3:43 pm
Location: Massachusetts, USA

Re: Timer callback, "lambda t:" and memory allocation failure

Post by blmorris » Thu Feb 26, 2015 2:44 pm

I have an application which uses the following code to read a potentiometer connected to an ADC pin and write the (scaled) value to the DAC:

Code: Select all

tim = pyb.Timer(1)
tim.init(freq = 20)
dac = pyb.DAC(1)
pot = pyb.ADC('B0')
tim.callback(lambda t: dac.write(int(pot.read() >> 4)))
Since callbacks are not (yet) allowed to allocate memory, you need to be careful about how you write them - as you are finding out ;)
Some ways to get around this are to pre-allocate your objects - for example, initializing 'dac = pyb.DAC(1)' and 'pot = pyb.ADC('B0')'
Also avoid assigning intermediate values - in your case, the variable 'value'
Finally, your code 'value = int(value/4095)*255' will always almost return '0' because value is =< 4095; also this division will allocate a float, which isn't allowed. The bit-shift scaling in my example does work.

-Bryan
Last edited by blmorris on Thu Feb 26, 2015 2:50 pm, edited 1 time in total.

blmorris
Posts: 348
Joined: Fri May 02, 2014 3:43 pm
Location: Massachusetts, USA

Re: Timer callback, "lambda t:" and memory allocation failure

Post by blmorris » Thu Feb 26, 2015 2:50 pm

Also, before getting discouraged about using callbacks to manage complex tasks, note that there have been some discussions here on the forum regarding the use of coroutines to implement cooperative multi-tasking - in particular @pythoncoder has published code to do this, (which I haven't yet used, but intend to get around to studying it very soon…)
-Bryan

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

Re: Timer callback, "lambda t:" and memory allocation failure

Post by dhylands » Thu Feb 26, 2015 7:34 pm

Your code has a number of issues, which I'll address. I got the following to work:

Code: Select all

import pyb

import micropython
micropython.alloc_emergency_exception_buf(100)

adc = pyb.ADC(pyb.Pin('X1'))
dac = pyb.DAC(1)

def readAndWrite(timer):
    value = adc.read()
    value = value // 4095 * 255
    dac.write(value)
    pyb.LED(1).toggle()

timer = pyb.Timer(1)
timer.init(freq=1)
timer.callback(readAndWrite)
1 - Anytime you're writing code which uses callbacks, I recommend that you add the lines:

Code: Select all

import micropython
micropython.alloc_emergency_exception_buf(100)
2 - You weren't instantiating the ADC and DAC objects correctly. Before writing an IRQ you should probably verify that your code runs outside of a callback. For example, just call readAndWrite() from the REPL several times in a row and verify it works.

3 - You timer callback was missing the timer argument. Timer callbacks have to have a single argument which is the timer associated with the IRQ.

4 - You were using value / 4095 which needs to allocate a floating point number. Allocating floats isn't allowed inside an irq. If you use // instead, then it will do integer division.

5 - You need to have the definition of readAndWrite before you try to use it (so I had to reorder things to get it work).

6 - You called timer.callback(readAndWrite()) which passes what invoking the readAndWrite function. This is why your LED flashed once. The readAdnWrite() portion calls the function. There is no return value, so this means that the function returns None. This then passes None to the timer.callback routine which tells it to disable callbacks. What you wanted to do was: timer.callback(readAndWrite) Note there there are no parens after the readAndWrite. This passes the function to timer.callback, whereas adding the parens passes in the result of calling readAndWrite. A subtle, but very important difference.

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

Re: Timer callback, "lambda t:" and memory allocation failure

Post by dhylands » Thu Feb 26, 2015 7:37 pm

I see Bryan addressed the divide as well. I didn't even consider the < 4095 case. You can reorder your operations to do:

Code: Select all

value = value * 255 // 4095
will perform without a bitshift.

The bitshift is probably more efficient, but slightly less obvious to figure out what's happening.

Aris
Posts: 14
Joined: Wed Feb 25, 2015 1:00 pm

Re: Timer callback, "lambda t:" and memory allocation failure

Post by Aris » Sat Feb 28, 2015 4:26 pm

Thank you very much Dave and Bryan.

Now it's working properly.

robertjensen
Posts: 5
Joined: Tue Feb 24, 2015 12:21 am
Location: Maryland, USA

Re: Timer callback, "lambda t:" and memory allocation failure

Post by robertjensen » Tue Mar 03, 2015 1:31 am

There will be differences between value>>4 and value*255/4095.

The former will map the interval 4080 through 4095 to 255, 4064 through 4079 to 254, etc. An even 16-to-1 mapping of each of the 256 intervals.

The multiply-divide approach will sometimes map 17 input values to one output value, sometime map 16 input values to one output value, and will map only the value of 4095 to 255. Those 17-to-1 mappings occur periodically.

So, if you want the most uniform mapping, I'd go with the bit-shift method.

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

Re: Timer callback, "lambda t:" and memory allocation failure

Post by dhylands » Tue Mar 03, 2015 3:24 am

Right you are - I never really thought about it before.

Using value * 256 // 4096 should give exactly the same results as >> 4

Post Reply