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)
Data logger using a ISR to sample ADC
Re: Data logger using a ISR to sample ADC
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.
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.
Re: Data logger using a ISR to sample ADC
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)
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Data logger using a ISR to sample ADC
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
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.
Index to my micropython libraries.