Data logger using a ISR to sample ADC

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
Jim.S
Posts: 84
Joined: Fri Dec 18, 2015 7:36 pm

Data logger using a ISR to sample ADC

Post by Jim.S » Fri Dec 18, 2015 8:48 pm

I'm aiming to write a simple data logger to sample analogue data using a timer interrupt service routine (ISR) then, in the main part of the code, convert this to a string and write it to the SD card. I appreciate the restrictions on what I can do in the ISR (ie. I can only work with integers, cant allocate memory) and the need for a semaphore/mutex to protect the data in the transfer. But my questions (for now!) are

1) Has anyone written anything like this that I can copy/modify
2) if not, is there an existing model for a suitable circular buffer that I can copy (before I write something from scratch). The favoured Pythonic approach seems to be the use of a deque but I assume this isn’t available for micropython yet.
3) The micropythonic approach for communication between the ISR and the main programme seems to be the bytearray. I'm a bit confused on how these work and am unsure if I can use them for easily communicating a series of 12 bit integers from the ADC (O'k the probably transfer as 2 bytes, but is this handled automatically some how?)

[Generally I thought that the documentation of the interrupt handling is pretty good, But I will admit that I have only skimmed it , and being old-school will have to print it out on paper and study it properly)

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

Re: Data logger using a ISR to sample ADC

Post by dhylands » Fri Dec 18, 2015 9:45 pm

For multibyte data, I'd be inclined to user array.array and setup each entry to be a 2-byte int.

My normal approach would be to declare a size. Then have a put index that the ISR uses to write entries into the preallocated array, and have a get index the the main loop uses to retrieve elements from the array.

The get and put pointer should go from zero to size -1, and if they're equal, it means that there is nothing in the array.

The way I think of it is that data[put_index] points to the next empty space to put data, and data[get_index] points to the first valid entry.

You can calculate the next put_index by doing something like next_put_index = (put_index + 1) % size

If next_put_index == get_index then your array is full and you should toss the sample
else you can store your sample in array[put_index] and set put_index = next_put_index.

Jim.S
Posts: 84
Joined: Fri Dec 18, 2015 7:36 pm

Re: Data logger using a ISR to sample ADC

Post by Jim.S » Mon Dec 21, 2015 10:11 pm

Thanks. That approach seems to work. (First time I have used and understood modulo arithmetic). Not sure it is fully debugged but good enough for now.

Code: Select all

import pyb
import micropython
import array
import mutex

mutex = mutex.Mutex()

class CircularBuffer:
    def __init__(self,typ,size):
        #typ - array.array type code
        #'h' signed short int
        #'H' unsighed short int
        #'i' signed int
        #'I' unsigned int
        #'l' signed long
        #'L' unsigned long
        buffer_0=[0 for i in range(size)]
        self.size=size
        self.buffer=array.array(typ,buffer_0)
        self.put_index=0
        self.get_index=0
    def put(self,value):
        #insert value into buffer
        self.buffer[self.put_index]=value
        # note the modulo arithmetic 
        self.put_index=(self.put_index+1)%self.size
    def get(self):
        #test value
        if (self.put_index!=self.get_index):
            #read value from buffer
            value=self.buffer[self.get_index]
            self.get_index=(self.get_index+1)%self.size
        else:
            #return None if all the valid values have been got.
            value=None
        return value

class BufferTest(object):
    def __init__(self):
        self.voltage=pyb.ADC(pyb.Pin.board.X1)
	self.value=int(0)
	self.buff=CircularBuffer('I',10)
	tim = pyb.Timer(4)
        tim.init(freq=1)# 1Hz
        tim.callback(self.buffer_test_cb)     
    def buffer_test_cb(self, tim):
	self.value=self.voltage.read()
        if mutex.test():
            self.buff.put(self.value)
            mutex.release()
	
micropython.alloc_emergency_exception_buf(100)
q=BufferTest()
print('circular buffer test code')
while True:
    pyb.delay(800)
    with mutex:
        x=q.buff.get()
    if x!=None:
        print(x*3.3/4096)

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

Re: Data logger using a ISR to sample ADC

Post by pythoncoder » Tue Dec 22, 2015 6:33 am

With a circular buffer a mutex should be unnecessary. This is because the put_index is altered only by the ISR and the get_index is altered only by the main loop: you never encounter a race condition where both are trying to alter the same variable. This is a key advantage of circular buffers.

Your buffer put() code should check for the case where the buffer is full and discard the data. Given that empty is defined as the case where put and get indices are identical, full is defined as the case where the put pointer is one less than the get pointer (modulo length). In other words the buffer's capacity is one less than its length.

You're on the right lines, though ;)
Peter Hinch
Index to my micropython libraries.

Post Reply