2

Working to create a data acquisition system for a custom off-road vehicle. Using Raspberry Pi and a custom tachometer (tested and confirmed working) to measure RPM. Using interrupts in the following code to get RPM value.

def get_rpm():                                         
    GPIO.wait_for_edge(17, GPIO.FALLING)
    start = time.time()
    GPIO.wait_for_edge(17, GPIO.FALLING)
    end = time.time()
    duration = end - start
    rpm = (1/duration)*60
    return rpm

This code only works if the engine is running and producing a spark. If there is no spark, the code sits waiting for that edge and does not proceed. When calling get_rpm(), if the code is waiting for an edge, this causes other processes to hang.

My intended workaround for this is to get the state of the engine in another process. I think it will work best in two parts.

Part 1, running (looped) in a separate thread:

GPIO.wait_for_edge(17, GPIO.RISING)
last = time.time

Part 2, running called as a function as needed:

def get_state():
    while time.time - last < .5:
        engine_state = true
    else:
        engine_state = false
    return engine_state

With Part 1 saving last to memory accessible to Part 2, Part 2 will determine whether or not the car is running based on the last time the spark plug sparked. Using engine_state as a comparator, the data acquisition system will get and store the RPM value from get_rpm() only when engine_state is true.

How can I implement Part 1 in such a way that I can use the last variable in Part 2? last will be changing very, very quickly. I don't want to store it to a text file on the Raspberry Pi's SD card every time last is updated. I want to store last in RAM.

Thanks so much!

Iron Fist
  • 10,739
  • 2
  • 18
  • 34
  • `last` is already in RAM, I think all you need is to pass it as a variable to `get_state(last)`.. – Iron Fist Apr 05 '16 at 22:47
  • `time.time` should be `time.time()` I think – jDo Apr 05 '16 at 22:53
  • Are you setting up the pin configuration somewhere else or not at all? Couldn't you use interrupt callbacks instead? They're threaded by nature and won't block. I don't think you have to wait for the edge. Interesting project btw. :) – jDo Apr 05 '16 at 22:53
  • @jDo Yes, pins are all configured somewhere else. Didn't include that in the question because it is already established working. Could you provide a quick example of an interrupt callback implementation? I'm very new to Python and I'm very appreciative of your help! – Bobothetwit Apr 05 '16 at 23:03
  • @IronFist Thank you for reformatting my code snippets. Could you please provide an example of what you mean by "pass it as a variable"? I'm new to Python and not familiar with all the nomenclature. Thanks so much! – Bobothetwit Apr 05 '16 at 23:05
  • @Bobothetwit Sure sure... I just need to be sure how the hardware works and how you interpret it. Does the tachometer create a rising signal that indicates a spark? What does the falling edge indicate? – jDo Apr 05 '16 at 23:10
  • @jDo the tachometer outputs a square wave, with both a rising edge and a falling edge. Either a rising edge or a falling edge can be used to indicate a spark, as each spark will produce both. – Bobothetwit Apr 05 '16 at 23:18

1 Answers1

1

This is just for inspiration. I don't have my Pis at hand so this is written blindly from memory.

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN)
# the line below is important. It'll fire on both rising and falling edges
# and it's non-blocking
GPIO.add_event_detect(17, GPIO.BOTH, callback=callback_func)

length_of_last_high = 0
length_of_last_low = 0

last_rise = 0
last_fall = 0
last_callback = 0

def callback_func(pin):
    # all of these global variables aren't needed 
    # but I left them in for demo purposes
    global last_rise
    global last_fall
    global last_callback
    global length_of_last_high
    global length_of_last_low
    last_callback = time.time()
    if GPIO.input(17):
        print "Pin 17 is rising!"
        length_of_last_high = last_callback - last_rise
        last_rise = last_callback
    else:
        print "Pin 17 is falling!"
        length_of_last_low = last_callback - last_fall 
        last_fall = last_callback


# last passed as parameter (the preferred solution if it works).
# You test it - I can't at the moment
def get_state(last=last_rise):
    engine_state = False
    if time.time() - last < .5:
        engine_state = True
    return engine_state

# last as global variable. 
# This works too but global is bad practice if it can be avoided     
# (namespace littering)
def get_state2():
    global last_rise
    engine_state = False
    if time.time() - last_rise < .5:
        engine_state = True
    return engine_state


def main():
    while True:
        print "Not blocking! This loop could sleep forever. It wouldn't affect the readings"
        time.sleep(1)
        print get_state()

"""
GPIO.cleanup()
"""
jDo
  • 3,962
  • 1
  • 11
  • 30
  • This is a really great example of how to use threaded callbacks, and is helpful for sure. One thing I'm stuck on is how to accurately get RPM using this method. The time our signal spends high is not equal to the time it spends low. The time between sparks (low to high) is greater than the time a spark takes to go from high to low. The duty cycle might be something like 20%. (https://en.wikipedia.org/wiki/Duty_cycle#/media/File:PWM_duty_cycle_with_label.gif) Because of this, we need to find the time between two rising edges or between two falling edges to calculate RPM. We can't use (cont.) – Bobothetwit Apr 07 '16 at 01:02
  • We can't use the time between a rising edge and a falling edge to calculate RPM. Using a threaded callback, the callback runs each time the falling (or rising) edge is detected. This is incompatible with our current method of determining RPM. Wait_for_edge on pin 17 can't be used in conjunction with the add_event_detect in the same code file. Any ideas here? @jDo The get_state functionality works GREAT, but we can't use it at the same time in the same code as get_RPM the way it is currently implemented. – Bobothetwit Apr 07 '16 at 01:04
  • @Bobothetwit I didn't forget your question btw. I've just been reading up on it, e.g. [here](http://forums.hybridz.org/topic/14725-pulsewidth-vs-dutycycle-vg30et-ecu/), and doing a few tests using threads and randomness to emulate the tachometer input. Do you know roughly what the RPM should be when the duty cycle is 20%? (it would tell me if my tests are way off or actually producing something useful). – jDo Apr 08 '16 at 09:43
  • @Bobothetwit I think I understand that you *"(...) need to find the time between two rising edges or between two falling edges to calculate RPM."*. If you save the time at rise, the time at fall and then substract one from the other, you'd get the pulse width; this is *not* what you want, right? Instead, you want to measure what in sinusoidal maths might be the wave length: From this rise (ignoring the fall) --> _|‾‾|___|‾ <-- to this rise (or the opposite using falling edges). Right? Perhaps, what I called `length_of_last_high` is actually `secs_since_last_high` and might be what you're after – jDo Apr 08 '16 at 09:55