0

I'm wiring my Raspberry pi to a USdigital encoder , especifically this model "H6BM-1000-500-IE-S-H" , it has 1000 signals per revolution , I've followed the tutorial on

http://raspi.tv/2013/how-to-use-interrupts-with-python-on-the-raspberry-pi-and-rpi-gpio-part-3

It works , however if I spin it fast (with my hand not attached to a motor), I miss some interrupts ,The encoder outputs a ~5v signal , so I made the a circuit to lower it to ~3v Resistors used to lower 5v to 3v3 for interrupt

The question here is , are the missing interrupts due to software (good RPi.GPIO alternatives?), or probably hardware (I was thinking switching the circuit to a voltage regulator IC ) and if so , which is the best solution for interrupts thanks in advance

here is my code

import RPi.GPIO as GPIO  
import time
GPIO.setmode(GPIO.BCM)

class Signal(object):
    """docstring for Encoder"""
    def __init__(self, pin):
        self.pin = pin
        self.count = 0
        GPIO.add_event_detect(self.pin, GPIO.RISING, callback=interrupt, bouncetime=1)

    def interrupt(self,pin):
        self.count += 1
        print "Interrupts detected = %s"% self.count


def main():
    a = Signal(21)
    while 1:
        time.sleep(1)


if __name__ == '__main__':
     main()

EDIT :

I've tried joan's solution , and alone works perfectly , but when I introduce python code it stops working as good , and y then have the same problem as before , should I use a wrapper from C to python and write my whole class there ?? if so which is the fastest and better way to do it ?

heres my modified code if it helps anyone , it is the interrupt handling for the encoder , the idea is to make a velocity PID afterwards thanks in advance

class Signal(object):
    """docstring for Interrupt"""
    def __init__(self, pinA , pinB):
        self.pinA      = pinA
        self.pinB      = pinB
        self.gpio      = pigpio.pi()
        self.perimetro = 40*m.pi
        self.cuenta    = 0
        self.estado    = 0
        self.tiempo_actual = 0 
        self.tiempo_anterior = time.time()
        self.velocidad = 0

        #Definicion de funciones que correran al interrumpirse en los pines
        self.interrupcionA  = self.gpio.callback(self.pinA, edge = pigpio.FALLING_EDGE ,func = self.guia)
        self.interrupcionB = self.gpio.callback(self.pinB, edge = pigpio.EITHER_EDGE , func = self.referencia)


    def guia(self,a,b,c):
        self.tiempo_actual = time.time()
        if self.estado:
            self.cuenta += 1

        if not self.estado:
            self.cuenta -= 1

        tiempo = self.tiempo_actual-self.tiempo_anterior
        self.tiempo_anterior  = self.tiempo_actual
        self.velocidad = (self.perimetro/1000)/tiempo
        print "cuenta en %s" % self.cuenta    

    def referencia(self,a,b,c):
        if int(self.gpio.read(self.pinB)):
            self.estado = 1

        if not int(self.gpio.read(self.pinB)):
            self.estado = 0                         

def main():

    encoder = Signal(21,20)
    try:
        while 1:
            time.sleep(1)       
    except KeyboardInterrupt:
        self.gpio.clear_bank_1(bin(2**20+2**21))

if __name__ == '__main__':
    main() 

2 Answers 2

3

Python may not be the best choice if you have high or sustained data rates. You would be much better off using C.

Try the following Python. It should capture all the interrupts although if you have sustained high interrupt rates it may take time for them all to be processed.

#!/usr/bin/env python

import time

import pigpio # http://abyz.me.uk/rpi/pigpio/python.html

pi = pigpio.pi()
if not pi.connected:
   exit(0)

cb = pi.callback(21)

while True:
   print(cb.tally())
   cb.reset_tally()
   time.sleep(1)
2
  • 1
    Interrupts are not saved. There is no catchup. in processing. If another interrupt occurs before you finish processing one, then it is lost. Nov 25, 2019 at 23:46
  • @JackCreasey according to the docs in the URL provided, there is catchup: "The callbacks will get all level changes but there will be a latency". This is a crucial feature, so it would great to get absolute confirmation. Jan 11, 2021 at 10:08
1

You can handle high speed interrupts on the R'Pi using Python easily if you make some configuration changes:

  1. Constrain operation to cpu 0,1,2 for the 'Pi
  2. Never do prints in the interrupt routines
  3. Set syscheckinterval to a large value to reduce overhead
  4. When you start your Python app, remap to cpu 3 and set the priority to realtime (-20)

This will allow you to get quite good response out to 5+kHz interrupt rates with no delays.

Here's some sample code:

#! /usr/bin/python2

##Interupt driven x1, x2 up/down encoder counter
##Jack Creasey

from RPi import GPIO
import os
import sys

##*********************************
#Define pin usage for encoder
#**********************************
phase_a = 21   
phase_a_dash= 16
phase_b = 20

##*********************************
#Define pin usage for PWM 
#**********************************
pwm_out = 12                #connect pin 12 to pin 24 to create a pwm timer interrupt
pwm_in = 24

#**********************************
#Setup GPIO
#**********************************

GPIO.setwarnings(False)
GPIO.cleanup()
GPIO.setmode(GPIO.BCM)
GPIO.setup(phase_a, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(phase_a_dash, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(phase_b, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(pwm_in, GPIO.IN, pull_up_down=GPIO.PUD_UP)

GPIO.setup(pwm_out, GPIO.OUT)  # Set GPIO pin 12 to output mode.
pwm = GPIO.PWM(pwm_out, 200)   # Initialize PWM pin and frequency
pwm.start(5)                   # Start PWM with 5% duty cycle


#**********************************
#Global variables
#**********************************
global counter
global lastcounter
global pwmticks
global lastpwmticks



def setup():

    #Find out our pid so we can remap to cpu 3
    mypid=os.getpid()
    myppid=os.getppid()

    print("My pid is "), mypid

    print('Setting cpu affinity to cpu 3')
    select_cpu="sudo taskset  -cp 3 "
    print('Setting priority to -20')
    set_priority="sudo renice -n -20 -p "

    #Call out to the os to remap the cpu and set priority high
    ret=os.system(set_priority+str(mypid))
    ret=os.system(select_cpu+str(mypid))

    sys.setcheckinterval(1000); ##no Python threading so just let Python run for a long time




def my_callback(channel):       #x1 sensing

    global counter

    if GPIO.input(phase_b):
            counter += 1
    else:
            counter -= 1

def my_callback1(channel):      #x2 sensing

    global counter

    if GPIO.input(phase_b):
            counter -= 1
    else:
            counter += 1

def my_callback2(channel):      #PWM interrupt 

    global pwmticks

    pwmticks += 1


setup()
counter = 0
lastcounter=counter

pwmticks=0
lastpwmticks=pwmticks

GPIO.add_event_detect(phase_a, GPIO.FALLING  , callback=my_callback)        #x1
GPIO.add_event_detect(phase_a_dash, GPIO.RISING  , callback=my_callback1)   #x2
GPIO.add_event_detect(pwm_in, GPIO.RISING  , callback=my_callback2)



try:

    while True:                             #busy work for Python main loop to do
        if counter != lastcounter:
            lastcounter=counter
            sys.stdout.write("\r" + str(counter) + "   \r")
            sys.stdout.flush()

        if pwmticks >= (lastpwmticks + 1000):
            lastpwmticks=pwmticks
            sys.stdout.write("\n Ticks= " + str(lastpwmticks) + "   \n")
            sys.stdout.flush()

except KeyboardInterrupt:
      print("Ctl C pressed - ending program") 
9
  • The code I posted will perform far better than your optimised code.
    – joan
    Nov 26, 2019 at 7:50
  • @joan But I won't lose any interrupts. Both speed of interrupts and the scheduling that goes on under Linux will result in dropped interrupts. I've used the code form above for x1, x2, x4 and x8 variants and it works well. Nov 26, 2019 at 14:34
  • @joan Obviously I will eventually lose interrupts if they occur too fast. I'm using an R'Pi 4 and can handle Quadrature out to 5kHz into a global counter (I have not gone further). You can of course make the callback routines simple C to reduce the overhead. You can obviously optimize even further by eliminating an scheduling on cpu 3, but I have not had to go that far for my needs. Nov 26, 2019 at 16:01
  • I just tried with pigpio on a Pi4B and abyz.me.uk/rpi/pigpio/examples.html#Python_rotary_encoder_py can handle 30k detents per second (a detent being each GPIO completing a square wave). The limit is my test software, it could probably go faster.
    – joan
    Nov 26, 2019 at 16:27
  • @joan ....nice ....but I think you'd agree that my code could run at least as fast as yours, and probably much faster. I've tested pigpio and found no advantage over GPIO for this particular application. I'd love to see your code profiled (running under normal scheduling) , as I'm sure you must have timing variances. If I get a chance in the next day or so I'll hook up to a generator and test my implementation to it's limit Nov 26, 2019 at 16:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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