I have a class for a DC-motor. It has attributes that can e.g. change the set rotational speed.
In order to control the actual speed I would like to run a thread that guards the acceleration. So if a user sets a new speed the thread will slowly increase the actual speed to match the set speed.
My approach:
import time
import threading
import RPi.GPIO as GPIO
class motorController:
#constructors
def __init__(self,maxPower,powerPin,directionPin,direction=True):
self.maxPower = abs(maxPower)
self.power = 0 #initially off
self.setPower = 0
#set pins motors are connected to
self.powerPin = powerPin
self.directionPin = directionPin
self.direction = direction
#initialize PWM
GPIO.setmode(GPIO.BCM)
#initialize powerPin
GPIO.setup(self.powerPin, GPIO.OUT) # set pin to output
self.powerPwm = GPIO.PWM(self.powerPin, 100) # Initialize PWM on pwmPin 100Hz frequency, max is 8kHz, min is 10Hz
self.powerPwm.start(self.power) # Start PWM with 0% duty cycle
#initialize directionPin
GPIO.setup(self.directionPin, GPIO.OUT,initial=self.direction) # set pin to output
#initialize controll deamon
self.__lastCall = time.time()
self.setPointIsNotReached = threading.Condition() #currently unused
self.deamonTimeLock = threading.Lock()
self.controllThread = threading.Thread(target=self.__powerControllerDeamon())
self.controllThread.setDaemon(True)
self.controllThread.start()
def setTargetPower(self,setPower):
setPower = min(setPower,self.maxPower)
self.setPower = max(setPower,-self.maxPower)
with self.deamonTimeLock:
self.__lastDeamonCall = t
def __setPower(self,power):
#limit power to maxPower
self.power = min(abs(power),self.maxPower)
self.powerPwm.ChangeDutyCycle(self.power)
#set rotation direction
if power < 0:
self.direction = False
else:
self.direction = True
GPIO.output(self.directionPin, self.direction) #set to 3.3V if direction is true, or 0V if direction is false
def __powerControllerDeamon(self):
#private method that controls the power of the motor
t = time.time()
dt = t-self.__lastCall
if self.power > self.setPower:
m = -50 #ramp from 100 to 0 in minimal 2 sec
else:
m = 20 # ramp from 0 to 100 in minimal 5sec
newPower = self.power + m * dt
#detect if power is reched through change of sign
dP = self.setPower - self.power
dP2 = self.setPower - newPower
if dP*dP2 <= 0: #change of sign or one is zero
newPower = self.setPower #set point reached
else:
with self.setPointIsNotReached
self.setPointIsNotReached.notify()#set point NOT reached
print(newPower)
self.__setPower(newPower)
with self.deamonTimeLock:
self.__lastDeamonCall = t
and
from motorController import motorController as mC
import time
try:
m1 = mC(100,18,17)
m1.setTargetPower(100)
time.sleep(10) #should have powered up by now
except BaseException as e:
import RPi.GPIO as GPIO
GPIO.cleanup()
raise(e)
In this example, the thread function is called once and then never again.
I'm new to threading and python. My questions:
Can I create a thread from a constructor and have it running until the main program ends (that's why i set deamon true)?
If the answer is yes, what's my mistake?
Your __powerControllerDeamon method should contain a loop that runs until it reaches the desired speed or until it is told to stop.
Since it doesn't contain a loop, it will run once and exit.
Note that this loop should also contain a small amount of sleep, to prevent it from hogging the CPU.
Related
I am having problems with a module that I am trying to import in python.
It is being run through a RPi 4b, driving 4 motors through 2 L298N h bridges.
When I run this code (FourMotorClassWithTurn) there is no problem and the motors perform as expected.
import RPi.GPIO as GPIO
from time import sleep
GPIO.setmode(GPIO.BCM)#setup the pin out to be board numbers not BCM
GPIO.setwarnings(False)#stops set warnings from showing
class Motor():
def __init__(self, EnFL, In1FL, In2FL, EnFR, In3FR, In4FR,
EnBR, In1BR, In2BR, EnBL, In3BL, In4BL):
self.EnFL = EnFL
self.In1FL = In1FL
self.In2FL = In2FL
#Front left motor
self.EnFR = EnFR
self.In3FR = In3FR
self.In4FR = In4FR
#Front right motor
self.EnBR = EnBR
self.In1BR = In1BR
self.In2BR = In2BR
#Back right motor
self.EnBL = EnBL
self.In3BL = In3BL
self.In4BL = In4BL
#Back left motor
GPIO.setup(self.EnFL,GPIO.OUT)
GPIO.setup(self.In1FL,GPIO.OUT)
GPIO.setup(self.In2FL,GPIO.OUT)
GPIO.setup(self.EnFR,GPIO.OUT)
GPIO.setup(self.In3FR,GPIO.OUT)
GPIO.setup(self.In4FR,GPIO.OUT)
GPIO.setup(self.EnBR,GPIO.OUT)
GPIO.setup(self.In1BR,GPIO.OUT)
GPIO.setup(self.In2BR,GPIO.OUT)
GPIO.setup(self.EnBL,GPIO.OUT)
GPIO.setup(self.In3BL,GPIO.OUT)
GPIO.setup(self.In4BL,GPIO.OUT)
self.pwmFL = GPIO.PWM(self.EnFL,100);
self.pwmFL.start(0);
self.pwmFR = GPIO.PWM(self.EnFR,100);
self.pwmFR.start(0);
self.pwmBR = GPIO.PWM(self.EnBR,100);
self.pwmBR.start(0);
self.pwmBL = GPIO.PWM(self.EnBL,100);
self.pwmBL.start(0);
#-------------------------------------------------------------------------
def move(self, speed=0.5, turn=0, time=0):
"""
Sets the motor to move
Parameters
----------
speed : TYPE, float
DESCRIPTION. Sets the speed of the motor 0 - 1. The default is 0.
turn : TYPE, float
DESCRIPTION. Sets the turn direction and rate of the
platform (-1) - 1. Full left = -1, full right = 1 The default is 0.
time : TYPE, float
DESCRIPTION. Sets the run time of the motor in seconds.
The default is 0.
Returns
-------
None.
"""
speed *= 100 #converts speed in PWM duty cycle value
turn *= 100 #converts turn in PWM duty cycle value
leftSpeed = speed + turn #adjusts the motor speed on each side for
#differential steering
rightSpeed = speed - turn
if leftSpeed>100: leftSpeed=100
elif leftSpeed<-100: leftSpeed=-100
if rightSpeed>100: rightSpeed=100
elif rightSpeed<-100: rightSpeed=-100 #PWM can only accept a maximum
#of 100 so values need limiting
self.pwmFL.ChangeDutyCycle(abs(leftSpeed))
self.pwmFR.ChangeDutyCycle(abs(rightSpeed))
self.pwmBR.ChangeDutyCycle(abs(rightSpeed))
self.pwmBL.ChangeDutyCycle(abs(leftSpeed))
#PWm can only accept positive values
if leftSpeed>0:
GPIO.output(self.In1FL, GPIO.LOW)
GPIO.output(self.In2FL, GPIO.HIGH)
GPIO.output(self.In3BL, GPIO.LOW)
GPIO.output(self.In4BL, GPIO.HIGH)
#left motor forward = In1_LOW & In3_LOW
# In2_HIGH & IN4_HIGH
else:
GPIO.output(self.In1FL, GPIO.HIGH)
GPIO.output(self.In2FL, GPIO.LOW)
GPIO.output(self.In3BL, GPIO.HIGH)
GPIO.output(self.In4BL, GPIO.LOW)
#left motor backwards = In1_HIGH & In3_HIGH
# In2_LOW & IN4_LOW
if rightSpeed>0:
GPIO.output(self.In3FR, GPIO.LOW)
GPIO.output(self.In4FR, GPIO.HIGH)
GPIO.output(self.In1BR, GPIO.LOW)
GPIO.output(self.In2BR, GPIO.HIGH)
#right motor forward = In1_LOW & In3_LOW
# In2_HIGH & IN4_HIGH
else:
GPIO.output(self.In3FR, GPIO.HIGH)
GPIO.output(self.In4FR, GPIO.LOW)
GPIO.output(self.In1BR, GPIO.HIGH)
GPIO.output(self.In2BR, GPIO.LOW)
#right motor backwards = In1_HIGH & In3_HIGH
# In2_LOW & IN4_LOW
print(f'Right speed is {rightSpeed}.')
print(f'Left speed is {leftSpeed}.')
sleep(time)
#-------------------------------------------------------------------------
def stop(self, time):
"""
Stops the motor
Parameters
----------
t : TYPE, optional
DESCRIPTION. Sets the stop time of the motor. The default is 0.
Returns
-------
None.
"""
self.pwmFL.ChangeDutyCycle(0)
self.pwmFR.ChangeDutyCycle(0)
self.pwmBR.ChangeDutyCycle(0)
self.pwmBL.ChangeDutyCycle(0)
sleep(time)
#----End of class--------------------------------------------------------------
def main():
"""
Test script if motor class is run in main
Returns
-------
None.
"""
motor1 = Motor(13,3,4,19,22,27,21,20,16,14,15,18)
motor1.stop(2)
motor1.move(0.4,-0.6,2)
GPIO.cleanup()
#-----------------------------------------------------------------------------
if __name__ == '__main__':
main()
However when I try to import the class into another module
from FourMotorClassWithTurn import Motor
import RPi.GPIO as GPIO
from time import sleep
GPIO.setmode(GPIO.BCM)#setup the pin out to be board numbers not BCM
GPIO.setwarnings(False)#stops set warnings from showing
motor1 = Motor(13,3,4,19,22,27,21,20,16,14,15,18)
motor1.stop(2)
motor1.move(0.4,-0.6,2)
GPIO.cleanup()
The motors will run once correctly but when I try to run it a second time I get a PWM fault
motor1 = Motor(13,3,4,19,22,27,21,20,16,14,15,18)
line 58, in init
self.pwmFL = GPIO.PWM(self.EnFL,100);
RuntimeError: A PWM object already exists for this GPIO channel
Anyone have an idea how to solve this?
I have a problem with the Jsn-sr04t water proof ultrasonic sensor, knowing that the connection in the raspberry pi zero W1 is well made becouse it is activated but the readings of this are totally wrong, the pins were entered correctly but this one seems to have a data entry error. I tried several programs in python that were used on this same sensor model
PD: I'm aware that this works, because I tested it on an arduino mega and the sensor worked correctly
import RPi.GPIO as GPIO
import os
import time
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO_TRIGGER = 10
GPIO_ECHO = 8
TRIGGER_TIME = 0.00001
MAX_TIME = 0.004 # max time waiting for response in case something is missed
GPIO.setup(GPIO_TRIGGER, GPIO.OUT) # Trigger
GPIO.setup(GPIO_ECHO, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Echo
GPIO.output(GPIO_TRIGGER, False)
def measure():
# Pulse the trigger/echo line to initiate a measurement
GPIO.output(GPIO_TRIGGER, True)
time.sleep(TRIGGER_TIME)
GPIO.output(GPIO_TRIGGER, False)
# ensure start time is set in case of very quick return
start = time.time()
timeout = start + MAX_TIME
while GPIO.input(GPIO_ECHO) == 0 and start <= timeout:
start = time.time()
if(start > timeout):
return -1
stop = time.time()
timeout = stop + MAX_TIME
# Wait for end of echo response
while GPIO.input(GPIO_ECHO) == 1 and stop <= timeout:
stop = time.time()
if(stop <= timeout):
elapsed = stop-start
distance = float(elapsed * 34300)/2.0
else:
return -1
return distance
if __name__ == '__main__':
try:
while True:
distance = measure()
if(distance > -1):
print("Measured Distance = %.1f cm" % distance)
else:
print("#")
time.sleep(0.5)
except KeyboardInterrupt:
print("Measurement stopped by User")
GPIO.cleanup()
constantly OUTPUT: "#"
Seems like an issue with the GPIO settings or a timing issue as the same setup works with an Arduino.
Try with higher trigger on time (maybe 20us)
After elapsed calculation, print the elapsed time on the screen and check the actual value
If it still doesn't work, then you can also checkout the alternate output options like serial UART, PWM, etc. You can get more details here -
https://tutorials.probots.co.in/communicating-with-a-waterproof-ultrasonic-sensor-aj-sr04m-jsn-sr04t/
I have a attached a rotary encoder to a DC motor shaft in hopes of creating a python script on a PI4 in which one could set a desired angle, motor will move CW at a 10 - duty cycle, and motor will stop and hold position on desired angle that once set angle is read back into code from rotary encoder (# 800 pulses per revolution - reading out 0.45 deg increments).
The PI controller will ideally then hold the set angle (set in code but later GUI) and will not move regardless of outside force on the shaft.
Lastly i am using if statments after my PI control signal 'PI' into a duty cycle ouput thus controlling the speed of the motor as it reaches the set point (either slowing down or speeding up tp get to the set point) with error PI polarity governing directional output....if this could be done better i'd appreciate any suggestions
I am only getting 1 error i cant figure out and cant find a solution online...but i feel i am close to getting this code right. Any criticism is welcome. The error is regarding the "PI" output of the Pi equation:
"Value of type "int" is not indexable"
from RPi import GPIO
import time
import threading
#Encoder GPIO Pins
clk = 15 # GRN/YLW Z+
dt = 11 # BLU/GRN Z-
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
GPIO.setup(clk, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(dt, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
#Motor GPIO Pins
PWMPin = 18 # PWM Pin connected to ENA.
Motor1 = 16 # Connected to Input 1.
Motor2 = 18 # Connected to Input 2.
GPIO.setup(PWMPin, GPIO.OUT) # We have set our pin mode to output
GPIO.setup(Motor1, GPIO.OUT)
GPIO.setup(Motor2, GPIO.OUT)
GPIO.output(Motor1, GPIO.LOW)# When program start then all Pins will be LOW.
GPIO.output(Motor2, GPIO.LOW)
def MotorClockwise():
GPIO.output(Motor1, GPIO.LOW) # Motor will move in clockwise direction.
GPIO.output(Motor2, GPIO.HIGH)
def MotorAntiClockwise():
GPIO.output(Motor1, GPIO.HIGH) # Motor will move in anti-clockwise direction.
GPIO.output(Motor2, GPIO.LOW)
#Sim Values
duty_c = 10 #duty Cycle
PwmValue = GPIO.PWM(PWMPin, 10000) # We have set our PWM frequency to 5000.
PwmValue.start(duty_c) # That's the maximum value 100 %.
error = 0
Set_point = 1.8
encoder_counter = 0
encoder_angle = 0
Kp = 2
Ki = 1
PI = 0
#Sim Parameters
Ts = .1 #sampling time
Tstop = 200 #end simulation time
N = int(Tstop/Ts)#simulation length
def ReadEncoder():
global encoder_counter
global encoder_angle
clkLastState = GPIO.input(clk)
while True:
clkState = GPIO.input(clk)
dtState = GPIO.input(dt)
encoder_angle = float(encoder_counter/800)*360
if clkState != clkLastState:
if dtState != clkState:
encoder_counter += 1
else:
encoder_counter -= 1
#print("encoder_counter", encoder_counter )
print("encoder_angle", encoder_angle )
time.sleep(1)
clkState = clkLastState
# some delay needed in order to prevent too much CPU load,
# however this will limit the max rotation speed detected
time.sleep(.001)
def PI_function():
global PI
global duty_c
global error
global Kp
global Ki
global encoder_angle
for k in range(N+1):
error = int(Set_point) - encoder_angle
print (f"{error} Error Detected")
PI = PI[k-1] + Kp*(error[k] - error[k-1]) + (Kp/Ki)*error[k] #PI equation
print (f"{PI} PI value")
if (PI > 0):
MotorClockwise()
else:
MotorAntiClockwise()
if ((PI > duty_c) & (duty_c <100)):
duty_c += 1
if ((PI < duty_c) & (duty_c > 10)):
duty_c -= 1
return()
def main_function():
if (N != 0):
encoder_thread = threading.Thread( target=ReadEncoder)
encoder_thread.start()
encoder_thread.join()
PI_thread = threading.Thread( target=PI_function)
PI_thread.start()
PI_thread.join()
PwmValue.ChangeDutyCycle(duty_c)
else:
PwmValue.ChangeDutyCycle(0)
print(f"Motor Stopped & Holding at {encoder_angle} Degrees")
I have a python program I wrote that moves my solar panel with the sun throughout the day. It works fine, but about twice a week I get RecursionError: maximum recursion depth exceeded
I'm pretty new to python and I've looked up Recursion Errors, it says when a function calls itself too many times, I don't see where that is happening in this program.
from datetime import date
from datetime import datetime, timezone, timedelta
import astral
from astral import Astral
import time
import pytz
# import RPi.GPIO as GPIO
# Global Variables
ast = Astral()
city_Name = 'Cleveland'
local_City = ast[city_Name]
# sun_Position = local_City.sun(local=True)
reset_Solar = True
# Retrieves and returns current time
def get_Current_Time():
eastern = pytz.timezone('America/New_York')
curr_Time = datetime.now(eastern)
return curr_Time
def main_Function():
global local_City
sun_Position = local_City.sun(local=True)
current_Time = get_Current_Time()
solar_Sunrise = sun_Position.get('sunrise')
solar_Noon = sun_Position.get('noon')
solar_Sunset = sun_Position.get('sunset')
calc_Sunrise_Noon = solar_Noon - solar_Sunrise
total_Seconds = calc_Sunrise_Noon.seconds
calc_Hours, remainder = divmod(total_Seconds, 3600)
calc_Minutes, calc_Seconds = divmod(remainder, 60)
time_To_Adjust = total_Seconds / 24
print (current_Time)
print (solar_Sunrise)
if current_Time >= solar_Sunrise and current_Time < solar_Sunset:
solar_Adjust_Active(time_To_Adjust)
elif reset_Solar == True:
reset_Solar_Panel()
else:
solar_Adjust_Deactive()
def solar_Adjust_Active(time_To_Adjust):
while time_To_Adjust > 0:
current_Time = get_Current_Time()
time_To_Adjust = time_To_Adjust - 1
time.sleep(1)
print (current_Time)
print (time_To_Adjust)
daylight_Adjustment()
def solar_Adjust_Deactive():
global local_City
curr_Time = get_Current_Time()
calc_Tomorrow = curr_Time.date() + timedelta(days=1)
sun_Position_Tomorrow = local_City.sun(local=True, date = calc_Tomorrow)
solar_Sunrise_Tomorrow = sun_Position_Tomorrow.get('sunrise')
time_Till_Sunrise = solar_Sunrise_Tomorrow - curr_Time
sunrise_Total_Seconds = time_Till_Sunrise.seconds
calc_Sunrise_Hours, remainder = divmod(sunrise_Total_Seconds, 3600)
calc_Sunrise_Minutes, calc_Sunrise_Seconds = divmod(remainder, 60)
while sunrise_Total_Seconds > 0:
sunrise_Total_Seconds = sunrise_Total_Seconds - 1
time.sleep(1)
# print ('Seconds till Sunrise', sunrise_Total_Seconds)
print (solar_Sunrise_Tomorrow)
main_Function()
def daylight_Adjustment():
global reset_Solar
# Adustment to Solar Panel
#GPIO.setmode(GPIO.BCM)
# init pin numbers
#pin_Open = [6]
# set mode default state is 'low'
#GPIO.setup(pin_Open, GPIO.OUT)
# Activate Open Relay to High (High turns Relay on)
#GPIO.output(pin_Open, GPIO.HIGH) # Activate Open relay
# Start Timer for duration actuator will be activated
timer = 0
while timer < 1:
time.sleep(1)
timer = timer + 1
print ('Panal adjusted')
# set Open relay back to low (Turns Relay off)
#GPIO.output(pin_Open, GPIO.LOW)
# Reset GPIO settings
#GPIO.cleanup()
reset_Solar = True
main_Function()
def reset_Solar_Panel():
global reset_Solar
print ('Setting panel back to original position')
# Adustment to Solar Panel
# GPIO.setmode(GPIO.BCM)
# init pin numbers
# pin_Open = [XX]
# set mode default state is 'low'
# GPIO.setup(pin_Open, GPIO.OUT)
# Activate Open Relay to High (High turns Relay on)
# GPIO.output(pin_Open, GPIO.HIGH) # Activate Open relay
# Start Timer for duration actuator will be activated
timer = 0
while timer <= 48:
time.sleep(1)
timer = timer + 1
# set Open relay back to low (Turns Relay off)
# GPIO.output(pin_Open, GPIO.LOW)
# Reset GPIO settings
# GPIO.cleanup()
reset_Solar = False
main_Function()
main_Function()
You have (at least one) a loop in your code:
def main_Function():
-> solar_Adjust_Active(time_To_Adjust)
-> daylight_Adjustment()
-> main_Function() -> .... # lets run that for a couple of days ...
Normally you have a main loop (f.e. while True:) and call/return from functions into your main loop without recursing:
Pseudocode:
def resetToStart():
move panels to sunrise position
return
def movePanel():
get_currnt_optimal_position_for_datetime
move_to_that_position
return
resetToStart() # start at a well known position
while True:
if NightTime: # sun is down
resetToStart()
sleep till daytime
elif DayTime: # sun is up
movePanel()
sleep some till adjustment needed
I'm working on automation of a vision based machine driven by stepper motor. Structure of my program is as followed.
import ImageProcessing
import Mechanices
import cv2
def rotate_motor(steps,direction):
global g_motor_ploc
DIR = 20 # Direction GPIO Pin
STEP = 21 # Step GPIO Pin
delay = .00055
GPIO.setmode(GPIO.BCM)
GPIO.setup(DIR, GPIO.OUT)
GPIO.setup(STEP, GPIO.OUT)
GPIO.output(DIR, direction)
for x in range(steps):
GPIO.output(STEP, GPIO.HIGH)
sleep(delay)
GPIO.output(STEP, GPIO.LOW)
sleep(delay)
GPIO.cleanup()
def findImage():
####################################
< Very Fast Image Processing Code >
####################################
return position_of_object
def calculate_steps(position):
####################################
< Very Fast Steps and Direction Calculation Code >
####################################
return steps_required,direction_of_rotation
def main():
while True:
position=findImage()
steps, direction = calculate_steps
rotate_motor(steps,directions) # Very Slow Process
if __name__ == "__main__":
main()
Despite very fast processing of images, whole program has to wait for rotate_motor to complete before processing next frame. In mean time if object moves backward it fails to see the change unless previously calculated position is achieved.
I'm looking forward to run rotate_motor in a separate thread and update the number_of_steps and direction required for motion. So that even if previously calculated position is not achieved, it should know how many more steps are required or change of direction is required.
So I modified the program as followed:
import multiprocessing as mp
import cv2
(r, g_w) = mp.Pipe(True)
g_motor_ploc = 0
def rotate_motor(steps,direction):
global g_motor_ploc
DIR = 20 # Direction GPIO Pin
STEP = 21 # Step GPIO Pin
delay = .00055
GPIO.setmode(GPIO.BCM)
GPIO.setup(DIR, GPIO.OUT)
GPIO.setup(STEP, GPIO.OUT)
GPIO.output(DIR, direction)
for x in range(steps):
GPIO.output(STEP, GPIO.HIGH)
sleep(delay)
GPIO.output(STEP, GPIO.LOW)
sleep(delay)
if direction==1:
g_motor_ploc -= 1
else:
g_motor_ploc += 1
GPIO.cleanup()
def findImage():
####################################
< Very Fast Image Processing Code >
####################################
return position_of_object
def calculate_steps(position):
####################################
< Very Fast Steps and Direction Calculation Code >
####################################
return steps_required,direction_of_rotation ## (Clockwise, Counter Clockwise)
def linear_motion(r):
global g_motor_ploc
while True:
loc = r.recv()
steps = loc - g_motor_ploc
if steps < 0:
direction = 1
else:
direction = 0
rotate_motor(abs(steps),direction)
def main():
while True:
position=findImage()
steps = calculate_steps
g_w.send(steps)
if __name__ == "__main__":
motor_proc = mp.Process(target=linear_motion, args=(r,))
motor_proc.daemon = True
motor_proc.start()
main()
Using the mp.Pipe() it interrupts the linear_motion() as soon as new position is send to the pipe. However this process if so fast that linear_motion never reach to the rotate_motor part.
Any suggestions, I'm confused.