Motor2040: Target RPM

Hi,
I have a motor2040 quad motor board from pimoroni
Connected to it is a micromotor with an 11:1 gearbox, and a MME encoder

I can read from the encoder fine, and drive the motor at arbitrary speeds
What I am struggling so far to acheive is to drive the motor at a defined target RPM

Can anyone give me any pointers? It feels like this is possible…but I haven’t managed it yet!

I have based the below on the velocity example script, to try and set a specific “revolutions per second” but so far it only outputs:

RPS TARGET = 10, RPS ACTUAL = -25.02543…

The actual never approaches the target, which makes me think I must have got something fairly fundamental wrong here!

import gc
import time
from motor import Motor, motor2040
from encoder import Encoder, MMME_CPR
from pimoroni import Button, PID

"""
Attempting to drive a connected 1:11 ratio motor ar a defined RPM 
Press "Boot" to exit the program.
"""

MOTOR_PINS = motor2040.MOTOR_A          # The pins of the motor being profiled
ENCODER_PINS = motor2040.ENCODER_A      # The pins of the encoder attached to the profiled motor
GEAR_RATIO = 11                         # The gear ratio of the motor
COUNTS_PER_REV = MMME_CPR * GEAR_RATIO  # The counts per revolution of the motor's output shaft

DIRECTION = 1                 # The direction to spin the motor in. NORMAL_DIR (0), REVERSED_DIR (1)

UPDATE_LIMIT = 100                      # How many times to update the motor per second
UPDATE_RATE = 1 / UPDATE_LIMIT
PRINT_DIVIDER = 4                       # How many of the updates should be printed (i.e. 2 would be every other update)

# PID values
RPS_KP = 30.0                           # RPS proportional (P) gain
RPS_KI = 0.0                            # RPS integral (I) gain
RPS_KD = 0.4                            # RPS derivative (D) gain

# Free up hardware resources ahead of creating a new Encoder
gc.collect()

# Create a motor
m = Motor(MOTOR_PINS, direction=DIRECTION)

# Create an encoder, using PIO 0 and State Machine 0
enc = Encoder(0, 0, ENCODER_PINS, direction=DIRECTION, counts_per_rev=COUNTS_PER_REV, count_microsteps=True)

# Create the user button
user_sw = Button(motor2040.USER_SW)

# Create PID object for velocity control
rps_pid = PID(RPS_KP, RPS_KI, RPS_KD, UPDATE_RATE)

# Enable the motor to get started
m.enable()


update = 0
# Set the initial value and create a random end value between the extents
target_rps = 1 #rps
rps_pid.setpoint =  target_rps

# Continually move the motor until the user button is pressed
while not user_sw.raw():

    # Capture the state of the encoder
    capture = enc.capture()
    
    # Calculate the acceleration to apply to the motor to move it closer to the velocity setpoint
    accel = rps_pid.calculate(capture.revolutions_per_second)

    #m.speed(m.speed() + (accel * UPDATE_RATE))
    m.speed(0.1)
    
    # Print out the current motor values and their setpoints, but only on every multiple
    if update == 0:
        print("RPM ACTUAL =", capture.revolutions_per_minute, end=", ")
        print("RPS TARGET =", rps_pid.setpoint, end=", ")
        print("RPS ACTUAL =", capture.revolutions_per_second, end=", ")
        print("RPS TARGET =", rps_pid.setpoint, end=", ")
        print("ACCEL VAL =", accel, end="\n")
 
    update += 1     # Move along in time

    # Have we reached the end of this sequence
    if update >= UPDATE_LIMIT:
        update = 0  # Reset the counter

    time.sleep(UPDATE_RATE)

# Disable the motor
m.disable()


Shows how to seek to a specific speed - but so far I haven’t got my head around how to map that speed value to an RPM.

Interestingly, whilst I can change the direction of the motor, whichever direction it is moving in, capture.revolutions_per_second reads a negative value - which may be causing the PID issues?

Hi @dbrb2,

What I think is happening here is that your motors (being 11:1 rather than 50:1) have a different number of gears between the motor shaft (that the encoder connects to) and the output shaft than what the example was written for, causing the direction to be reversed. As such your encoder will be counting down when it should be counting up, which is why you’re seeing -25 as your actual RPS.

To fix this, invert the direction of ONLY the Motor or Encoder. E.g. have the motor be NORMAL_DIR, and the encoder be REVERSED_DIR (or visa versa).

The PID will then be able to behave correctly, though you may need to tweak the values for your exact motor setup.

Hope that helps.

Thanks!

I had noticed that whatever the direction of the motor, the RPS still seemed to stay negative

In the below, I have:

# Create a motor
m = Motor(MOTOR_PINS, direction=0)

# Create an encoder, using PIO 0 and State Machine 0
enc = Encoder(0, 0, ENCODER_PINS, direction=1, counts_per_rev=COUNTS_PER_REV, count_microsteps=True)

The result of that unfortunately seems to be that the motor now jumps around all over the place, sometimes even changing direction.

I wonder if that is because my PID values are wrong and it is overshooting in various directions…I’ve tried various values though so far without success.

I know from experimentation that driving the motor at a static m.speed(0.08) gives me ~1rps
However, I so far can’t persuafe the PID loop to hone in on that

import gc
import time
from motor import Motor, motor2040
from encoder import Encoder, MMME_CPR
from pimoroni import Button, PID

"""
Attempting to drive a connected 1:11 ratio motor at a defined RPM 
Press "Boot" to exit the program.
"""

MOTOR_PINS = motor2040.MOTOR_A          # The pins of the motor being profiled
ENCODER_PINS = motor2040.ENCODER_A      # The pins of the encoder attached to the profiled motor
GEAR_RATIO = 11                         # The gear ratio of the motor
COUNTS_PER_REV = MMME_CPR * GEAR_RATIO  # The counts per revolution of the motor's output shaft

DIRECTION = 1                 # The direction to spin the motor in. NORMAL_DIR (0), REVERSED_DIR (1)

UPDATE_LIMIT = 100                      # How many times to update the motor per second
UPDATE_RATE = 1 / UPDATE_LIMIT
PRINT_DIVIDER = 4                       # How many of the updates should be printed (i.e. 2 would be every other update)

# PID values
RPS_KP = 30.0                           # RPS proportional (P) gain
RPS_KI = 0.0                            # RPS integral (I) gain
RPS_KD = 0.4                            # RPS derivative (D) gain

# Free up hardware resources ahead of creating a new Encoder
gc.collect()

# Create a motor
m = Motor(MOTOR_PINS, direction=0)

# Create an encoder, using PIO 0 and State Machine 0
enc = Encoder(0, 0, ENCODER_PINS, direction=1, counts_per_rev=COUNTS_PER_REV, count_microsteps=True)

# Create the user button
user_sw = Button(motor2040.USER_SW)

# Create PID object for velocity control
rps_pid = PID(RPS_KP, RPS_KI, RPS_KD, UPDATE_RATE)

# Enable the motor to get started
m.enable()


update = 0
# Set the initial value and create a random end value between the extents
target_rps = 1 #rps
rps_pid.setpoint =  target_rps

# Continually move the motor until the user button is pressed
while not user_sw.raw():

    # Capture the state of the encoder
    capture = enc.capture()
    
    # Calculate the acceleration to apply to the motor to move it closer to the velocity setpoint
    accel = rps_pid.calculate(capture.revolutions_per_second)
    m.speed(m.speed() + (accel * UPDATE_RATE))
    
    # Print out the current motor values and their setpoints, but only on every multiple
    if update == 0:
        print("RPM ACTUAL =", capture.revolutions_per_minute, end=", ")
        print("RPS TARGET =", rps_pid.setpoint, end=", ")
        print("RPS ACTUAL =", capture.revolutions_per_second, end=", ")
        print("RPS TARGET =", rps_pid.setpoint, end=", ")
        print("ACCEL VAL =", accel, end="\n")
 
    update += 1     # Move along in time

    # Have we reached the end of this sequence
    if update >= UPDATE_LIMIT:
        update = 0  # Reset the counter

    time.sleep(UPDATE_RATE)

# Disable the motor
m.disable()

Ah!
PID tuning

PID values

RPS_KP = 5 # RPS proportional (P) gain
RPS_KI = 0.5 # RPS integral (I) gain
RPS_KD = 0.0 # RPS derivative (D) gain

Seems to give me something reasonable:


RPM ACTUAL = 1013.289, RPS TARGET = 16.66667, RPS ACTUAL = 16.88815, RPS TARGET = 16.66667, ACCEL VAL = -1.038949
RPM ACTUAL = 996.6506, RPS TARGET = 16.66667, RPS ACTUAL = 16.61084, RPS TARGET = 16.66667, ACCEL VAL = 0.3432188
RPM ACTUAL = 1003.57, RPS TARGET = 16.66667, RPS ACTUAL = 16.72616, RPS TARGET = 16.66667, ACCEL VAL = -0.2399376
RPM ACTUAL = 1002.538, RPS TARGET = 16.66667, RPS ACTUAL = 16.70897, RPS TARGET = 16.66667, ACCEL VAL = -0.1597673
RPM ACTUAL = 987.2763, RPS TARGET = 16.66667, RPS ACTUAL = 16.45461, RPS TARGET = 16.66667, ACCEL VAL = 1.107361

Ok. With one motor and encoder the PID control seemed to work fine.

With 4, so far, the values are all over the place. Not sure why yet…

import gc
import time
from motor import Motor, motor2040
from encoder import Encoder, MMME_CPR
from pimoroni import Button, PID

"""
Attempting to drive a connected 1:11 ratio motor ar a defined RPM 
Press "Boot" to exit the program.
"""
GEAR_RATIO = 11                         # The gear ratio of the motor
COUNTS_PER_REV = MMME_CPR * GEAR_RATIO  # The counts per revolution of the motor's output shaft

UPDATE_LIMIT = 100                      # How many times to update the motor per second
UPDATE_RATE = 1 / UPDATE_LIMIT

# PID values
RPM_KP = 1                           # RPM proportional (P) gain
RPM_KI = 0.0                         # RPM integral (I) gain
RPM_KD = 0.0                         # RPM derivative (D) gain

WHEEL_DIAM=100
WHEEL_CIRC=3.14*WHEEL_DIAM/1000

#Create a list of motors
MOTOR_PINS = [motor2040.MOTOR_A, motor2040.MOTOR_B, motor2040.MOTOR_C, motor2040.MOTOR_D]
motors = [Motor(pins, direction=0) for pins in MOTOR_PINS]
   
# Create a list of encoders
ENCODER_PINS = [motor2040.ENCODER_A, motor2040.ENCODER_B, motor2040.ENCODER_C, motor2040.ENCODER_D]
ENCODER_NAMES = ["A", "B", "C", "D"]
NUM_ENCODERS = len(ENCODER_PINS)
enc = [Encoder(0, i, ENCODER_PINS[i], counts_per_rev=COUNTS_PER_REV, count_microsteps=True, direction=1) for i in range(NUM_ENCODERS)]
    
# Create the user button
user_sw = Button(motor2040.USER_SW)

# Create PID object for RPS control for each motor


pid_list = [PID(RPM_KP, RPM_KI, RPM_KD, UPDATE_RATE), PID(RPM_KP, RPM_KI, RPM_KD, UPDATE_RATE), PID(RPM_KP, RPM_KI, RPM_KD, UPDATE_RATE), PID(RPM_KP, RPM_KI, RPM_KD, UPDATE_RATE)]

# Enable all motors 
motors[0].enable()
motors[1].enable()
motors[2].enable()
motors[3].enable()


update = 0
# Set the initial value and create a random end value between the extents
target_rpmA=1000
target_rpmB=500
target_rpmC=100
target_rpmD=50

pid_list[0].setpoint =  target_rpmA
pid_list[1].setpoint =  target_rpmB
pid_list[2].setpoint =  target_rpmC
pid_list[3].setpoint =  target_rpmD

# Continually move the motor until the user button is pressed
while not user_sw.raw():
    # Capture the state of the encoders    
    captures=[enc[0].capture(), enc[1].capture(), enc[2].capture(), enc[3].capture()]

    # Calculate the acceleration to apply to the motor to move it closer to the velocity setpoint
    accels = [pid_list[0].calculate(captures[0].revolutions_per_minute), pid_list[1].calculate(captures[1].revolutions_per_minute), pid_list[2].calculate(captures[2].revolutions_per_minute), pid_list[3].calculate(captures[3].revolutions_per_minute) ]
              
    motors[0].speed(motors[0].speed() + (accels[0] * UPDATE_RATE))
    motors[1].speed(motors[1].speed() + (accels[1] * UPDATE_RATE))
    motors[2].speed(motors[2].speed() + (accels[2] * UPDATE_RATE))
    motors[3].speed(motors[3].speed() + (accels[3] * UPDATE_RATE))
        
    # Print out the current motor values and their setpoints, but only on every multiple
    if update == 0:  
        print("RPM A TARGET =", target_rpmA, end=", ")        
        print("RPM A ACTUAL =", captures[0].revolutions_per_minute, end="\n")
        
        print("RPM B TARGET =", target_rpmB, end=", ")  
        print("RPM B ACTUAL =", captures[1].revolutions_per_minute, end="\n")

        print("RPM C TARGET =", target_rpmC, end=", ")  
        print("RPM C ACTUAL =", captures[2].revolutions_per_minute, end="\n")
        
        print("RPM D TARGET =", target_rpmD, end=", ")  
        print("RPM D ACTUAL =", captures[3].revolutions_per_minute, end="\n\n")
                            
    update += 1     # Move along in time

    # Have we reached the end of this sequence
    if update >= UPDATE_LIMIT:
         update = 0  # Reset the counter

    time.sleep(UPDATE_RATE)

RPM A TARGET = 1000, RPM A ACTUAL = 996.8942
RPM B TARGET = 500, RPM B ACTUAL = 493.674
RPM C TARGET = 100, RPM C ACTUAL = 221.9359
RPM D TARGET = 50, RPM D ACTUAL = 0.0

(and these values vary wildly)
Is it something to do with running 4 caprue and PID

Glad you have been able to make progress.

Hmm, not really as I have a demo in our office that reliably works with 4 motors and PID (position control admittedly but still).

There’s a velocity example that works with 4 motors, the quad_velocity_sequence.py, which I think you’ve already used judging by your code?

One questionable thing in your code are these lines:

    # Capture the state of the encoders    
    captures=[enc[0].capture(), enc[1].capture(), enc[2].capture(), enc[3].capture()]

    # Calculate the acceleration to apply to the motor to move it closer to the velocity setpoint
    accels = [pid_list[0].calculate(captures[0].revolutions_per_minute), pid_list[1].calculate(captures[1].revolutions_per_minute), pid_list[2].calculate(captures[2].revolutions_per_minute), pid_list[3].calculate(captures[3].revolutions_per_minute) ]          

In that you are creating two fresh lists every update, rather than populating previous lists with values. It would be better to have the code structured more like this:

    # Capture the state of all the encoders
    for i in range(motor2040.NUM_MOTORS):
        captures[i] = encoders[i].capture()

    for i in range(motor2040.NUM_MOTORS):
        # Calculate the acceleration to apply to the motor to move it closer to the velocity setpoint
        accel = vel_pids[i].calculate(captures[i].revolutions_per_second)

        # Accelerate or decelerate the motor
        motors[i].speed(motors[i].speed() + (accel * UPDATE_RATE))

Whether that will make your code behave better I’m not sure. You could try playing around with the update rate to see if higher or lower sampling is needed. Also it’s worth sanity checking your wiring in case there’s a loose connection on motors C and D.

Ah thanks yes, good spot.
I don’t think it is the motor wiring - I have tried swapping motor A and motor D, and the behaviour of the motors is the same

You were right about the list though - that was definitely an error

I’m still seeing a lot of fluctuation in the reported RPM:

import gc
import time
from motor import Motor, motor2040
from encoder import Encoder, MMME_CPR
from pimoroni import Button, PID

"""
Attempting to drive a connected 1:11 ratio motor ar a defined RPM 
Press "Boot" to exit the program.
"""
GEAR_RATIO = 11                         # The gear ratio of the motor
COUNTS_PER_REV = MMME_CPR * GEAR_RATIO  # The counts per revolution of the motor's output shaft

UPDATE_LIMIT = 500                      # How many times to update the motor per second
UPDATE_RATE = 1 / UPDATE_LIMIT

# PID values
RPM_KP = 5                           # RPM proportional (P) gain
RPM_KI = 0.0                         # RPM integral (I) gain
RPM_KD = 0.0                         # RPM derivative (D) gain

WHEEL_DIAM=100
WHEEL_CIRC=3.14*WHEEL_DIAM/1000

#Create a list of motors
MOTOR_PINS = [motor2040.MOTOR_A, motor2040.MOTOR_B, motor2040.MOTOR_C, motor2040.MOTOR_D]
motors = [Motor(pins, direction=0) for pins in MOTOR_PINS]
   
# Create a list of encoders
ENCODER_PINS = [motor2040.ENCODER_A, motor2040.ENCODER_B, motor2040.ENCODER_C, motor2040.ENCODER_D]
ENCODER_NAMES = ["A", "B", "C", "D"]
NUM_ENCODERS = len(ENCODER_PINS)
encoders = [Encoder(0, i, ENCODER_PINS[i], counts_per_rev=COUNTS_PER_REV, count_microsteps=True, direction=1) for i in range(NUM_ENCODERS)]
    
# Create the user button
user_sw = Button(motor2040.USER_SW)

# Create PID object for RPS control for each motor
pid_list = [PID(RPM_KP, RPM_KI, RPM_KD, UPDATE_RATE) for i in range(motor2040.NUM_MOTORS)]

# Enable all motors 
motors[0].enable()
motors[1].enable()
motors[2].enable()
motors[3].enable()


update = 0

target_rpmA=1000
target_rpmB=500
target_rpmC=100
target_rpmD=50

pid_list[0].setpoint =  target_rpmA
pid_list[1].setpoint =  target_rpmB
pid_list[2].setpoint =  target_rpmC
pid_list[3].setpoint =  target_rpmD

captures = [ encoders[0].capture(), encoders[1].capture(), encoders[2].capture(), encoders[3].capture() ]

# Continually move the motor until the user button is pressed
while not user_sw.raw():
    
    for i in range(motor2040.NUM_MOTORS):
        captures[i] = encoders[i].capture()
   
    for i in range(motor2040.NUM_MOTORS):
        accel = pid_list[i].calculate(captures[i].revolutions_per_minute)
        motors[i].speed(motors[i].speed() + (accel * UPDATE_RATE))
        
    # Print out the current motor values and their setpoints, but only on every multiple
    if update == 0:  
        print("RPM A TARGET =", target_rpmA, end=", ")        
        print("RPM A ACTUAL =", captures[0].revolutions_per_minute, end="\n")
        
        print("RPM B TARGET =", target_rpmB, end=", ")  
        print("RPM B ACTUAL =", captures[1].revolutions_per_minute, end="\n")

        print("RPM C TARGET =", target_rpmC, end=", ")  
        print("RPM C ACTUAL =", captures[2].revolutions_per_minute, end="\n")
        
        print("RPM D TARGET =", target_rpmD, end=", ")  
        print("RPM D ACTUAL =", captures[3].revolutions_per_minute, end="\n\n")
                            
    update += 1     # Move along in time

    # Have we reached the end of this sequence
    if update >= UPDATE_LIMIT:
         update = 0  # Reset the counter

    time.sleep(UPDATE_RATE)


Ahah!

I have changed the target value for the PID control loop to use revolutions per second rather than revolutions per minute

So far, it looks like that has helped a lot…and for some reason sending RPM into the PID loop was causing mayhem.

Could it be down to the RPM occsionally reporting as zero - I think maybe this is after a counter overflow, so giving a spurious shove to the PID loop…?

RPM A TARGET = 1000, RPM A ACTUAL = 1002.254
RPM B TARGET = 500, RPM B ACTUAL = 492.8777
RPM C TARGET = 100, RPM C ACTUAL = 107.9178
RPM D TARGET = 50, RPM D ACTUAL = 54.37745

RPM A TARGET = 1000, RPM A ACTUAL = 1009.943
RPM B TARGET = 500, RPM B ACTUAL = 501.4881
RPM C TARGET = 100, RPM C ACTUAL = 102.6575
RPM D TARGET = 50, RPM D ACTUAL = 54.56335

RPM A TARGET = 1000, RPM A ACTUAL = 989.6017
RPM B TARGET = 500, RPM B ACTUAL = 501.2668
RPM C TARGET = 100, RPM C ACTUAL = 98.76871
RPM D TARGET = 50, RPM D ACTUAL = 48.53434

RPM A TARGET = 1000, RPM A ACTUAL = 1005.013
RPM B TARGET = 500, RPM B ACTUAL = 501.2558
RPM C TARGET = 100, RPM C ACTUAL = 86.09076
RPM D TARGET = 50, RPM D ACTUAL = 57.13471

RPM A TARGET = 1000, RPM A ACTUAL = 992.284
RPM B TARGET = 500, RPM B ACTUAL = 501.0421
RPM C TARGET = 100, RPM C ACTUAL = 98.88646
RPM D TARGET = 50, RPM D ACTUAL = 75.16328

RPM A TARGET = 1000, RPM A ACTUAL = 993.9642
RPM B TARGET = 500, RPM B ACTUAL = 508.8044
RPM C TARGET = 100, RPM C ACTUAL = 100.1184
RPM D TARGET = 50, RPM D ACTUAL = 46.71703
import gc
import time
from motor import Motor, motor2040
from encoder import Encoder, MMME_CPR
from pimoroni import Button, PID

"""
Attempting to drive a connected 1:11 ratio motor ar a defined RPM 
Press "Boot" to exit the program.
"""
GEAR_RATIO = 11                         # The gear ratio of the motor
COUNTS_PER_REV = MMME_CPR * GEAR_RATIO  # The counts per revolution of the motor's output shaft

UPDATE_LIMIT = 100                      # How many times to update the motor per second
UPDATE_RATE = 1 / UPDATE_LIMIT

# PID values
RPM_KP = 2.0                           # RPM proportional (P) gain
RPM_KI = 0.0                         # RPM integral (I) gain
RPM_KD = 0.0                         # RPM derivative (D) gain

#Create a list of motors
MOTOR_PINS = [motor2040.MOTOR_A, motor2040.MOTOR_B, motor2040.MOTOR_C, motor2040.MOTOR_D]
motors = [Motor(pins, direction=0) for pins in MOTOR_PINS]
   
# Create a list of encoders
ENCODER_PINS = [motor2040.ENCODER_A, motor2040.ENCODER_B, motor2040.ENCODER_C, motor2040.ENCODER_D]
ENCODER_NAMES = ["A", "B", "C", "D"]
NUM_ENCODERS = len(ENCODER_PINS)
encoders = [Encoder(0, i, ENCODER_PINS[i], counts_per_rev=COUNTS_PER_REV, count_microsteps=True, direction=1) for i in range(NUM_ENCODERS)]
    
# Create the user button
user_sw = Button(motor2040.USER_SW)

# Create PID object for RPS control for each motor
pid_list = [PID(RPM_KP, RPM_KI, RPM_KD, UPDATE_RATE) for i in range(motor2040.NUM_MOTORS)]

# Enable all motors 
motors[0].enable()
motors[1].enable()
motors[2].enable()
motors[3].enable()


update = 0

target_rpmA=1000
target_rpmB=500
target_rpmC=100
target_rpmD=50

pid_list[0].setpoint =  target_rpmA/60
pid_list[1].setpoint =  target_rpmB/60
pid_list[2].setpoint =  target_rpmC/60
pid_list[3].setpoint =  target_rpmD/60

captures = [ encoders[0].capture(), encoders[1].capture(), encoders[2].capture(), encoders[3].capture() ]

# Continually move the motor until the user button is pressed
while not user_sw.raw():
    
    for i in range(motor2040.NUM_MOTORS):
        captures[i] = encoders[i].capture()
   
    for i in range(motor2040.NUM_MOTORS):
        accel = pid_list[i].calculate(captures[i].revolutions_per_second)
        motors[i].speed(motors[i].speed() + (accel * UPDATE_RATE))
        
    # Print out the current motor values and their setpoints, but only on every multiple
    if update == 0:  
        print("RPM A TARGET =", target_rpmA, end=", ")        
        print("RPM A ACTUAL =", captures[0].revolutions_per_minute, end="\n")
        
        print("RPM B TARGET =", target_rpmB, end=", ")  
        print("RPM B ACTUAL =", captures[1].revolutions_per_minute, end="\n")

        print("RPM C TARGET =", target_rpmC, end=", ")  
        print("RPM C ACTUAL =", captures[2].revolutions_per_minute, end="\n")
        
        print("RPM D TARGET =", target_rpmD, end=", ")  
        print("RPM D ACTUAL =", captures[3].revolutions_per_minute, end="\n\n")
                            
    update += 1     # Move along in time

    # Have we reached the end of this sequence
    if update >= UPDATE_LIMIT:
         update = 0  # Reset the counter

    time.sleep(UPDATE_RATE)