1

I am working on a project in which I need high time-resolution data. I will be measuring data from an IR break beam and an SPI device. My SPI device functions at 1ksps while I need readings from 100ksps from the GPIO pin for my IR beam.

I plan on having a script continually monitor a button allowing me to control when data is saved and when it is discarded. How can I make my GPIO pin reads not wait on my SPI request.

Pseudo-code below:

# main()
    # Repeat
        # If button for > 0.5 second

            # Save Data
            # Repeat
                # If !button for > 0.5 second
                    #break

                # start_time = time.time()

                # record time
                # record SPI
                # record IR GPIO
                # record button GPIO

                # sleep_time = 1/1E5 - (time.time() - start_time)
                if (sleep_time < 0):
                    sleep_time = 0

                # Sleep sleep_time

Key questions:

  • How can I make my GPIO reads not wait on my slow SPI reads
  • I assume I need additional threads for this, how can I control these threads (to stop recording data when the button is un-pressed)

Edit: The most useful resource I found was this presentation which gave a nice overview of the tools python has to achieve concurrent processing and their pros and cons. http://www.slideshare.net/dabeaz/an-introduction-to-python-concurrency

I decided to go with multiprocessing as suggested below. This approach was conceptually simple, did not run the risk of my SPI calls blocking due to CPU utilization (as is a risk in threaded approaches), and allowed me to issue a stop command and return data to my main process with pipes.

  • You need to clarify what you are trying to achieve. I don't think your proposed solution will work. However I'm hesitant to make any suggestions without a better understanding of your project. – joan Jan 15 '17 at 14:01
1

Using Python's concurrent.futures module you can have it dispatch work to a thread pool then have it execute code when complete without blocking main.

There are a lot of horrible things in threading though. It is hard.

Also, noticed this which offers an alternative implementation using asyncio (native python 3.4+) which you should definately check out.

I've sketched a really poor implementation of this below which has a number of short comings and has some 'fill in the blank' sections for your actual GPIO / SPI code and the saving data sections. But it's runnable (unless i mangled the formatting). If you need this to run constantly so you get many more IR readings than SPI ones then you'll have to do something a bit more complicated, probably with queues, so the worker keeps running jobs rather than just doing it once per cycle. I couldn't quite tell if thats the intention though.

from concurrent import futures
from time import sleep, time
import multiprocessing

# number of threads in pool goes here, it'll queue work if you have jobs > workers
tpe = futures.ThreadPoolExecutor(max_workers=multiprocessing.cpu_count()) 

def do_spi_work():
    sleep(1.1)
    return "done SPI work!"

def do_ir_work():
    sleep(0.3)
    return "done IR work!"

def handle(result):
    # you'd put your saving routine in here note this is still the
    # worker thread - files, sqlite, mdb might not be happy with multi threaded access
    status = result.result()
    print("i just got a callback that said {}".format(status))

def read_button():
    # do your button read code here, something like..
    # result = GPIO.input(4)
    result = True
    return result

def check_break(count):
    #some condition
    return count > 5

count = 0 # this is just for check_break, this code only loops 5 times

# 'main' starts here
while(True):    
    if read_button():
        start_time = time() #do your timing stuff here as its fast

        #these won't block
        tpe.submit(do_spi_work).add_done_callback(handle)
        tpe.submit(do_ir_work).add_done_callback(handle)

        # if you need both threads to talk to each other then its more fun...
        # [insert horror here]

        sleep_time = 1000 * (time() - start_time) #then your sleeping code to block here.
        sleep(sleep_time) # blocks

        # and something to check if you want to quit this loop you'd make check_break less terrible.
        # you might want to wrap this in a try/catch to clean up GPIO when the program is killed too.
        if check_break(count):
            break

        count += 1
    else:
        sleep(1) # some sort of back off to prevent it looping too fast

If you just want to push the SPI read to churn in the background while blocking on each (fast) GPIO read in the main loop you can probably do this without the futures just use normal Python threading stuff whilst looping the SPI read and save routine in the background. You will need some mechanism to allow the thread to end. My example uses a global state variable for brevity.

from threading import Thread
from time import time, sleep

def spi_worker():
    global state
    while (not state):
        result = read_spi()
        save_spi(result)

def read_spi():
    sleep(0.5)
    return "spi result"

def save_spi(result):
    print('SPI: i just saved {}'.format(result))

def read_gpio():
    sleep(0.1)
    return "GPIO result"

def save_gpio(result):
    print("GPIO: i just saved {}".format(result))

state = False

def check():
    global state
    return state

t = Thread(target=spi_worker)
t.start()

count = 0
while (not check()):
    start_time = time()
    #do work on main
    result = read_gpio()
    save_gpio(result)

    #nap
    sleep_time = (time() - start_time)
    sleep(sleep_time)

    if (count > 50):
        state = False
        break;
    count += 1

Again, this is probably riddled with threading issues but should give you an idea of how it could be done.

  • Thanks, you gave me a starting point and lots of insight. Doing more reading. – nate Jan 15 '17 at 21:49
  • Glad it helped, once you get into it you should be able to tear my example code to pieces for all its shortcomings but I think it'll solve your problem. – tobyd Jan 15 '17 at 22:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.