6

I have just finished my first ever Raspberry Pi project (a hamster wheel counter that uploads to Thingspeak) following an online project I found that included the code.

(https://www.hackster.io/8bitsandabyte/project-floofball-an-iot-hamster-wheel-fec29c)

Everything seems to work well, with the exception of I am getting huge overcounts if the magnet is either passed very slowly over the reed switch, or if the wheel stops fully when the magnet is near the reed switch. In the latter event, the counter will tick up at a rate of 244 counts a minute, only stopping when / if the magnet is rotated away from the switch.

From searching online, I believe this type of issue is know as "bounce" and that there are ways to strip out this type of erroneous reading via either code or hardware changes to "debounce".

Would anyone be able to help advise me the best way to do this? I have added the code below here for reference.

I don't actually have a wiring schematic as I had to more or less work that out from the code as there wasn't once included in the project itself that I could find.

Any help or advice anyone could provide is greatly appreciated!

PJ

P.S. I have ordered a Hall effect sensor as well but it will not arrive for a few weeks, would that help with this issue at all?

# Import libraries
from __future__ import print_function
import time
import datetime
import schedule
import paho.mqtt.publish as publish
import RPi.GPIO as io
io.setmode(io.BCM)

print("This is a hamster tracker")

#Setup the pins of the Raspberry
# Pin for the wheel
wheelpin = 17
# Setup wheel pin
io.setup(wheelpin, io.IN, pull_up_down=io.PUD_UP)

# Pins for the LCD Screen
# Register Select
lcdRs = 7
# Enable or Strobe
lcdE = 8
# Data pins
lcdData4 = 25
lcdData5 = 24
lcdData6 = 23
lcdData7 = 18
# Setup LCD Pins
io.setup(lcdE, io.OUT)
io.setup(lcdRs, io.OUT)
io.setup(lcdData4, io.OUT)
io.setup(lcdData5, io.OUT)
io.setup(lcdData6, io.OUT)
io.setup(lcdData7, io.OUT)

#LCD Constants
# Characters per line
lcdWidth = 16
lcdChr = True
lcdCmd = False
# LCD Ram address 1st line
lcdLine1 = 0x80
# LCD Ram address 2nd line
lcdLine2 = 0xC0
# Timing
lcdPulse = 0.0005
lcdDelay = 0.0005

# Circumference of wheel
# 28.5cm diameter * PI = 89.54 cm = 0.8954 m = 0.0008954 km
wheelsize = 0.0008954

# Number of wheel rotations
rotations = 0

# Distance covered in hamsterwheel
distance = 0

# Daily distance
# Distance covered in hamsterwheel to be displayed on LCD screen
# Reset to 0 every day at 00:00
dailyDistance = 0

# Speed
speed = 0

# Set the starttime to now
starttime = datetime.datetime.now()

#  ThingSpeak Channel Settings

# The ThingSpeak Channel ID
# Replace this with your Channel ID
channelID = "123456"

# The Write API Key for the channel
# Replace this with your Write API key
apiKey = "ABCDEF123456GHIJK"

#  MQTT Connection Methods
# Set useUnsecuredTCP to True to use the default MQTT port of 1883
# This type of unsecured MQTT connection uses the least amount of system resources.
useUnsecuredTCP = False

# Set useUnsecuredWebSockets to True to use MQTT over an unsecured websocket on port 80.
# Try this if port 1883 is blocked on your network.
useUnsecuredWebsockets = False

# Set useSSLWebsockets to True to use MQTT over a secure websocket on port 443.
# This type of connection will use slightly more system resources, but the connection
# will be secured by SSL.
useSSLWebsockets = True

# The Hostname of the ThinSpeak MQTT service
mqttHost = "mqtt.thingspeak.com"

# Set up the connection parameters based on the connection type
if useUnsecuredTCP:
    tTransport = "tcp"
    tPort = 1883
    tTLS = None

if useUnsecuredWebsockets:
    tTransport = "websockets"
    tPort = 80
    tTLS = None

if useSSLWebsockets:
    import ssl
    tTransport = "websockets"
    tTLS = {'ca_certs':"/etc/ssl/certs/ca-certificates.crt",'tls_version':ssl.PROTOCOL_TLSv1}
    tPort = 443

# Create the topic string
topic = "channels/" + channelID + "/publish/" + apiKey



def lcdByte(bits, mode):
    # Send byte to data pins
    # bits = data
    # mode = True  for character
    #        False for command

    io.output(lcdRs, mode)  # RS

    # High bits
    io.output(lcdData4, False)
    io.output(lcdData5, False)
    io.output(lcdData6, False)
    io.output(lcdData7, False)
    if bits & 0x10 == 0x10:
        io.output(lcdData4, True)
    if bits & 0x20 == 0x20:
        io.output(lcdData5, True)
    if bits & 0x40 == 0x40:
        io.output(lcdData6, True)
    if bits & 0x80 == 0x80:
        io.output(lcdData7, True)

    # Toggle 'Enable' pin
    lcdToggleEnable()

    # Low bits
    io.output(lcdData4, False)
    io.output(lcdData5, False)
    io.output(lcdData6, False)
    io.output(lcdData7, False)
    if bits & 0x01 == 0x01:
        io.output(lcdData4, True)
    if bits & 0x02 == 0x02:
        io.output(lcdData5, True)
    if bits & 0x04 == 0x04:
        io.output(lcdData6, True)
    if bits & 0x08 == 0x08:
        io.output(lcdData7, True)

    # Toggle 'Enable' pin
    lcdToggleEnable()


def lcdToggleEnable():
    # Toggle enable
    time.sleep(lcdDelay)
    io.output(lcdE, True)
    time.sleep(lcdPulse)
    io.output(lcdE, False)
    time.sleep(lcdDelay)


# Initialise LCD Display
lcdByte(0x33,lcdCmd) # 110011 Initialise
lcdByte(0x32,lcdCmd) # 110010 Initialise
lcdByte(0x06,lcdCmd) # 000110 Cursor move direction
lcdByte(0x0C,lcdCmd) # 001100 Display On,Cursor Off, Blink Off
lcdByte(0x28,lcdCmd) # 101000 Data length, number of lines, font size
lcdByte(0x01,lcdCmd) # 000001 Clear display
time.sleep(lcdDelay)

def lcdShowMessage(message, line):
    # Send string to lcd display

    message = message.ljust(lcdWidth, " ")

    lcdByte(line, lcdCmd)

    for i in range(lcdWidth):
        lcdByte(ord(message[i]), lcdChr)

# Reset the wheel measurements
def resetValues():
    global distance
    global speed
    global rotations
    print ('Before reset', distance, 'km', speed, 'km/h', rotations, 'rotations')
    distance = 0
    speed = 0
    rotations = 0
    print ('After reset', distance, 'km', speed, 'km/h', rotations, 'rotations')

def resetDailyValues():
    global dailyDistance
    dailyDistance = 0

# Send IoT message to Thingspeak
def sendMessage():
    print("Hello, I'm a delivery to Thingspeak")
    print ('Minute Update', distance, 'km', speed, 'km/h', rotations, 'rotations')
    # build the payload string
    tPayload = "field1=" + str(rotations) + "&field2=" + str(distance) + "&field3=" + str(speed)
    resetValues()
    # attempt to publish this data to the topic
    try:
        publish.single(topic, payload=tPayload, hostname=mqttHost, port=tPort, tls=tTLS, transport=tTransport)


    except:
        print("There was an error while publishing the data.")


# Send a message every minute
schedule.every(2).minutes.do(sendMessage)
# Reset the daily values at midnight
schedule.every().day.at("00:00").do(resetDailyValues)


# Function to calculate the current speed of the hamsterwheel
def calculateSpeed(spintime):
    seconds = spintime.days * 24 * 60 * 60 + spintime.seconds + spintime.microseconds / 1000000.
    currentSpeed = wheelsize / (seconds/60./60.)
    return currentSpeed

# While the script runs
while True:
        # Check the pending scheduled tasks
        schedule.run_pending()
        # Show the daily covered distance on the first line of the LCD
        lcdShowMessage(str(round(dailyDistance, 4))+"Km", lcdLine1)
        # Show the current speed on the second line of the LCD
        lcdShowMessage(str(round(speed, 1))+ "Km/h", lcdLine2)

        # When the magnet passes the magnet reed switch, one rotation has happened
        if (io.input(wheelpin) == 0):
            # The end of the rotation is now
            endtime = datetime.datetime.now()
            # The time spent spinning was the endtime - starttime
            spintime = endtime - starttime
            # New starttime is the endtime
            starttime = endtime
            # Calculating the speed based on the spintime
            speed = calculateSpeed(spintime)
            # Calculating the distance covered
            distance = distance + wheelsize
            dailyDistance = dailyDistance + wheelsize
            # Calculating the amount of rotations
            rotations += 1
            # Print to console and sleep
            print (round(distance, 4), 'km', round(speed, 3), 'km/h', rotations, 'rotations')
            time.sleep(0.1)
1
  • 1
    upvote for integrating a hamster into your project ... lol – jsotola Dec 15 '19 at 1:18
1

A proper bounce filter would help as you are likely suffering from the mechanical contact bounce of the reed switch.

However the Hall effect sensor will not have this particular problem. So you could just ignore the problem for now as it will be fixed by the use of the Hall effect sensor.

If you want a bounce filter you could add one by writing additional software or look at hardware filters (using a capacitor and a resistor).

A bounce filter ignores short edges. In your case you would have to write code to measure the length in time of each edge and discard those shorter than possible for your system.

RPi.GPIO has a "bounce" parameter. It does not implement a bounce filter. It simply ignores level changes after the first until a "bounce" period has elapsed. It would not help with your stated problem.

3
  • thanks for this Joan. So do you mean to say when I get the hall sensor, even if the magnet stops on top of the sensor I won't get these maxed out readings of 244 / min? – punkypj Dec 14 '19 at 14:52
  • That's right, the Hall effect sensor won't give multiple pulses if it is stopped over a magnet. It will work just fine with your software. – joan Dec 14 '19 at 16:21
  • brilliant, thanks for the heads up! will definitely be swapping it out when they arrive then :) – punkypj Dec 14 '19 at 17:45

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.