Add IR, encoder, motor, display

This commit is contained in:
Konstantin Lampalzer 2021-08-22 18:37:20 +02:00
parent 530ddd8be7
commit 63b8f868c9
6 changed files with 248 additions and 247 deletions

39
compLib/Display.py Normal file
View file

@ -0,0 +1,39 @@
import string
from compLib.LogstashLogging import logstash_logger
from compLib.Spi import Spi, Register
LINE_COUNT = 4
CHARS_PER_LINE = 16
class Display(object):
"""Access the different IR Sensors of the robot
"""
@staticmethod
def write(line: int, text: string):
if len(text) > CHARS_PER_LINE:
logstash_logger.error(f"Too many characters specified!")
return
if line <= 0 or line > LINE_COUNT:
raise IndexError("Invalid line number specified")
to_write = [0] * CHARS_PER_LINE
for i in range(len(text)):
to_write[i] = ord(text[i])
if line == 1:
Spi.write(Register.DISPLAY_LINE_1_C0, CHARS_PER_LINE, to_write)
elif line == 2:
Spi.write(Register.DISPLAY_LINE_2_C0, CHARS_PER_LINE, to_write)
elif line == 3:
Spi.write(Register.DISPLAY_LINE_3_C0, CHARS_PER_LINE, to_write)
elif line == 4:
Spi.write(Register.DISPLAY_LINE_4_C0, CHARS_PER_LINE, to_write)
@staticmethod
def clear():
for i in range(1, LINE_COUNT + 1):
Display.write(i, "")

37
compLib/Encoder.py Normal file
View file

@ -0,0 +1,37 @@
import atexit
from enum import Enum
from compLib.LogstashLogging import Logging
from compLib.Spi import Spi, Register
MOTOR_COUNT = 4
class Encoder(object):
"""Class used to read the encoders
"""
@staticmethod
def read(port: int) -> int:
"""Read encoder from a specified port
:param port: Port, which the motor is connected to. 1-4 allowed
:raises: IndexError
:return: Current encoder position
"""
if port <= 0 or port > MOTOR_COUNT:
raise IndexError("Invalid encoder port specified!")
if port == 1:
return Spi.read(Register.MOTOR_1_POS_B3, 4)
elif port == 2:
return Spi.read(Register.MOTOR_2_POS_B3, 4)
elif port == 3:
return Spi.read(Register.MOTOR_3_POS_B3, 4)
elif port == 4:
return Spi.read(Register.MOTOR_4_POS_B3, 4)
@staticmethod
def reset(port: int):
# TODO implement registers and me
pass

View file

@ -1,157 +1,40 @@
import pigpio
from compLib.ADC import ADC
from compLib.LogstashLogging import Logging
from compLib.Spi import Spi, Register
import spidev
import threading
import time
TOP_LEFT_CHANNEL = 0
TOP_RIGHT_CHANNEL = 1
TOP_IR_MIN_VOLTAGE = 0.0
TOP_IR_MAX_VOLTAGE = 3.3
BOTTOM_LEFT_PIN = 14
BOTTOM_MIDDLE_PIN = 15
BOTTOM_RIGHT_PIN = 23
BOTTOM_LEFT_CHANNEL = 2
BOTTOM_MIDDLE_CHANNEL = 1
BOTTOM_RIGHT_CHANNEL = 0
adc = ADC()
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1000000
# GPIO.setmode(GPIO.BCM)
GPIO = pigpio.pi()
GPIO.set_mode(BOTTOM_LEFT_PIN, pigpio.INPUT)
GPIO.set_mode(BOTTOM_MIDDLE_PIN, pigpio.INPUT)
GPIO.set_mode(BOTTOM_RIGHT_PIN, pigpio.INPUT)
states = {
str(BOTTOM_LEFT_PIN): GPIO.read(BOTTOM_LEFT_PIN),
str(BOTTOM_MIDDLE_PIN): GPIO.read(BOTTOM_MIDDLE_PIN),
str(BOTTOM_RIGHT_PIN): GPIO.read(BOTTOM_RIGHT_PIN)}
analog_states = {
str(BOTTOM_LEFT_CHANNEL): 0,
str(BOTTOM_MIDDLE_CHANNEL): 0,
str(BOTTOM_RIGHT_CHANNEL): 0}
def gpio_callback(gpio, level, tick):
states[str(gpio)] = level
GPIO.callback(BOTTOM_LEFT_PIN, pigpio.EITHER_EDGE, gpio_callback)
GPIO.callback(BOTTOM_MIDDLE_PIN, pigpio.EITHER_EDGE, gpio_callback)
GPIO.callback(BOTTOM_RIGHT_PIN, pigpio.EITHER_EDGE, gpio_callback)
def read_analog_channel(channel):
spi.writebytes([channel << 3, 0])
time.sleep(0.0033) # 300HZ
bytes = spi.readbytes(2)
return (bytes[0] * 256 + bytes[1]) >> 2
def sensor_thread():
while True:
analog_states[str(BOTTOM_LEFT_CHANNEL)] = read_analog_channel(BOTTOM_LEFT_CHANNEL)
analog_states[str(BOTTOM_MIDDLE_CHANNEL)] = read_analog_channel(BOTTOM_MIDDLE_CHANNEL)
analog_states[str(BOTTOM_RIGHT_CHANNEL)] = read_analog_channel(BOTTOM_RIGHT_CHANNEL)
analog_thread = threading.Thread(target=sensor_thread, daemon=True)
analog_thread.start()
time.sleep(0.25)
SENSOR_COUNT = 5
class IRSensor(object):
"""Access the different IR Sensors at top / bottom of the robot
"""Access the different IR Sensors of the robot
"""
@staticmethod
def top_left_percent() -> int:
"""Get top left infrared sensor percentage
def read(sensor: int) -> int:
if sensor <= 0 or sensor > SENSOR_COUNT:
raise IndexError("Invalid sensor specified!")
:return: Percentage between 0 and 100
:rtype: int
"""
voltage = adc.read(TOP_LEFT_CHANNEL)
return int((voltage - TOP_IR_MIN_VOLTAGE) / TOP_IR_MAX_VOLTAGE * 100)
if sensor == 1:
return Spi.read(Register.IR_1_H, 2)
elif sensor == 2:
return Spi.read(Register.IR_2_H, 2)
elif sensor == 3:
return Spi.read(Register.IR_3_H, 2)
elif sensor == 4:
return Spi.read(Register.IR_4_H, 2)
return 0
@staticmethod
def top_right_percent() -> int:
"""Get top right infrared sensor percentage
:return: Percentage between 0 and 100
:rtype: int
"""
voltage = adc.read(TOP_RIGHT_CHANNEL)
return int((voltage - TOP_IR_MIN_VOLTAGE) / TOP_IR_MAX_VOLTAGE * 100)
@staticmethod
def bottom_left() -> bool:
"""Get bottom left infrared sensor status
:return: True, if sensor detects IR signals
:rtype: bool
"""
return states[str(BOTTOM_LEFT_PIN)]
# return GPIO.read(BOTTOM_LEFT_PIN)
@staticmethod
def bottom_middle() -> bool:
"""Get bottom middle infrared sensor status
:return: True, if sensor detects IR signals
:rtype: bool
"""
return states[str(BOTTOM_MIDDLE_PIN)]
# return GPIO.read(BOTTOM_MIDDLE_PIN)
@staticmethod
def bottom_right() -> bool:
"""Get bottom right infrared sensor status
:return: True, if sensor detects IR signals
:rtype: bool
"""
return states[str(BOTTOM_RIGHT_PIN)]
# return GPIO.read(BOTTOM_RIGHT_PIN)
@staticmethod
def bottom_left_analog() -> int:
"""Get bottom left infrared sensor value: ranges from 0 to 1023
:return: 10-bit brightness value
:rtype: int
"""
return analog_states[str(BOTTOM_LEFT_CHANNEL)]
@staticmethod
def bottom_middle_analog() -> int:
"""Get bottom middle infrared sensor value: ranges from 0 to 1023
:return: 10-bit brightness value
:rtype: int
"""
return analog_states[str(BOTTOM_MIDDLE_CHANNEL)]
@staticmethod
def bottom_right_analog() -> int:
"""Get bottom right infrared sensor value: ranges from 0 to 1023
:return: 10-bit brightness value
:rtype: int
"""
return analog_states[str(BOTTOM_RIGHT_CHANNEL)]
def set(sensor: int, on: bool):
if sensor <= 0 or sensor > SENSOR_COUNT:
raise IndexError("Invalid sensor specified!")
if sensor == 1:
Spi.write(Register.IR_1_LED, on)
elif sensor == 2:
Spi.write(Register.IR_2_LED, on)
elif sensor == 3:
Spi.write(Register.IR_3_LED, on)
elif sensor == 4:
Spi.write(Register.IR_4_LED, on)

View file

@ -1,81 +0,0 @@
from compLib.IRSensor import IRSensor
import time
ir = IRSensor()
MIN_VALUE = 0.0
MAX_VALUE = 0.0
MOVING_AVERAGE_SIZE = 30.0
def approximate_rolling_average(avg, value):
avg -= avg / MOVING_AVERAGE_SIZE
avg += value / MOVING_AVERAGE_SIZE
return avg
class IRWrapper(object):
"""Wrapper around the IRSensor to enable calibration of the sensor
"""
@staticmethod
def calibrate():
"""Calibrate the black and white values of the IRSensors
This is done by putting the bot on black line with the middle sensor
Afterwards, all sensors are read for 2 seconds and filtered.
The minimum value is used for white, maximum is black.
"""
global MIN_VALUE
global MAX_VALUE
left_avg = ir.bottom_left_analog()
middle_avg = ir.bottom_middle_analog()
right_avg = ir.bottom_right_analog()
start_time = time.time()
while time.time() - start_time < 2:
left_avg = approximate_rolling_average(left_avg, ir.bottom_left_analog())
middle_avg = approximate_rolling_average(middle_avg, ir.bottom_middle_analog())
right_avg = approximate_rolling_average(right_avg, ir.bottom_right_analog())
time.sleep(0.01)
MIN_VALUE = min([left_avg, middle_avg, right_avg])
MAX_VALUE = max([left_avg, middle_avg, right_avg])
@staticmethod
def adjust_for_calibration(raw_value: float) -> float:
"""Adjust a raw sensor value to 0 to 1000
:return: Value between 0 and 1000, White and Black
:rtype: float
"""
x = (raw_value - MIN_VALUE) * 1000.0 / (MAX_VALUE - MIN_VALUE)
return max(min(1000.0, x), 0.0)
@staticmethod
def bottom_left_calibrated() -> float:
"""Returns calibrated value of the bottom left analog sensor
:return: Value between 0 and 1000, White and Black
:rtype: float
"""
return IRWrapper.adjust_for_calibration(ir.bottom_left_analog())
@staticmethod
def bottom_middle_calibrated() -> float:
"""Returns calibrated value of the bottom middle analog sensor
:return: Value between 0 and 1000, White and Black
:rtype: float
"""
return IRWrapper.adjust_for_calibration(ir.bottom_middle_analog())
@staticmethod
def bottom_right_calibrated() -> float:
"""Returns calibrated value of the bottom right analog sensor
:return: Value between 0 and 1000, White and Black
:rtype: float
"""
return IRWrapper.adjust_for_calibration(ir.bottom_right_analog())

View file

@ -1,49 +1,76 @@
import atexit
from enum import Enum
from compLib.LogstashLogging import Logging
from compLib.Spi import Spi, Register
MOTOR_COUNT = 4
MAX_MOTOR_SPEED = 4095.0
MAX_MOTOR_SPEED = 65535
MOTOR_PERCENTAGE_MULT = MAX_MOTOR_SPEED / 100.0
class MotorMode(Enum):
COAST = 0,
FORWARD = 1,
BACKWARD = 2,
BREAK = 3
class Motor(object):
"""Class used to control the motors
"""
@staticmethod
def pwm(port: int, pwm: int, mode: MotorMode):
"""Set specified motor to a specific pwm value and motor mode
:param port: Port, which the motor is connected to. 1-4 allowed
:param pwm: Raw PWM value which the motor should be set to. From 0 to 2^16 - 1
:param mode: Motor mode. See enum MotorMode for more info
:raises: IndexError
"""
if port <= 0 or port > MOTOR_COUNT:
raise IndexError("Invalid Motor port specified!")
if port == 1:
Spi.write(Register.MOTOR_1_PWM_H, 2, pwm)
Spi.write(Register.MOTOR_1_CTRL, 1, mode)
elif port == 2:
Spi.write(Register.MOTOR_2_PWM_H, 2, pwm)
Spi.write(Register.MOTOR_2_CTRL, 1, mode)
elif port == 3:
Spi.write(Register.MOTOR_3_PWM_H, 2, pwm)
Spi.write(Register.MOTOR_3_CTRL, 1, mode)
elif port == 4:
Spi.write(Register.MOTOR_4_PWM_H, 2, pwm)
Spi.write(Register.MOTOR_4_CTRL, 1, mode)
@staticmethod
def power(port: int, percent: float):
"""Set specified motor to percentage power
:param port: Port, which the motor is connected to. 0-3, 0 -> top left, 3 -> top right
:param port: Port, which the motor is connected to. 1-4
:param percent: Percentage of max speed. between -100 and 100
:raises: IndexError
"""
Logging.get_logger().debug(f"Motor.power {port} {percent}")
if port < 0 or port >= MOTOR_COUNT:
if port <= 0 or port > MOTOR_COUNT:
raise IndexError("Invalid Motor port specified!")
if percent < -100 or percent > 100:
raise IndexError("Invalid Motor speed specified! Speed is between -100 and 100 percent!")
forward = True
mode = MotorMode.COAST
if percent < 0:
percent = abs(percent)
forward = False
mode = MotorMode.BACKWARD
elif percent > 0:
mode = MotorMode.FORWARD
# bottom left motor is inverted - REEEEEEEEEEEE
if port == 1:
forward = not forward
pwm = percent * MOTOR_PERCENTAGE_MULT
adjusted_speed = int(min(max(0.0, percent), 100.0) * MOTOR_PERCENTAGE_MULT)
if forward:
pwm.setMotorPwm(port * 2, adjusted_speed)
pwm.setMotorPwm(port * 2 + 1, 0)
else:
pwm.setMotorPwm(port * 2, 0)
pwm.setMotorPwm(port * 2 + 1, adjusted_speed)
Motor.pwm(port, int(pwm), mode)
@staticmethod
def all_off():
@ -52,8 +79,17 @@ class Motor(object):
"""
Logging.get_logger().debug(f"Motor.all_off")
for i in range(0, MOTOR_COUNT):
Motor.power(i, 0)
for i in range(1, MOTOR_COUNT + 1):
Motor.active_break(i)
@staticmethod
def active_break(port: int):
"""
Actively break with a specific motor
:param port: Port, which the motor is connected to. 1-4
"""
Motor.pwm(port, 0, MotorMode.BREAK)
atexit.register(Motor.all_off)

View file

@ -2,7 +2,7 @@ import spidev
from threading import Thread, Lock
from enum import Enum
# from LogstashLogging import logstash_logger
from LogstashLogging import logstash_logger
SPI_BUS = 1
SPI_DEVICE = 2
@ -29,8 +29,7 @@ class Spi(object):
rx_buffer = spi.xfer([0] * 32)
if rx_buffer[1] != write_reg:
# logstash_logger.error(f"SPI error during write to register {tx_buffer[0]}!")
print(f"SPI error during write to register {write_reg}!")
logstash_logger.error(f"SPI error during write to register {tx_buffer[0]}!")
return rx_buffer
@ -59,4 +58,92 @@ class Spi(object):
pos += 1
rx_buf = Spi.transfer(tx_buf)
return int.from_bytes(rx_buf[2:2 + length], byteorder='big', signed=False)
return int.from_bytes(rx_buf[2:2 + length], byteorder='big', signed=False)
@staticmethod
def write_array(reg: int, length: int, values: list[int]):
tx_buf = [0] * SPI_BUFFER_SIZE
tx_buf[0] = 1
tx_buf[1] = reg
tx_buf[2] = length
pos = 3
for i in values:
tx_buf[pos] = i
pos += 1
rx_buf = Spi.transfer(tx_buf)
return rx_buf
class Register(Enum):
IDENTIFICATION_MODEL_ID = 1,
IDENTIFICATION_MODEL_REV_MAJOR = 2,
IDENTIFICATION_MODEL_REV_MINOR = 3,
IDENTIFICATION_MODEL_REV_PATCH = 4,
# Motor encoder positions
MOTOR_1_POS_B3 = 10,
MOTOR_1_POS_B2 = 11,
MOTOR_1_POS_B1 = 12,
MOTOR_1_POS_B0 = 13,
MOTOR_2_POS_B3 = 14,
MOTOR_2_POS_B2 = 15,
MOTOR_2_POS_B1 = 16,
MOTOR_2_POS_B0 = 17,
MOTOR_3_POS_B3 = 18,
MOTOR_3_POS_B2 = 19,
MOTOR_3_POS_B1 = 20,
MOTOR_3_POS_B0 = 21,
MOTOR_4_POS_B3 = 22,
MOTOR_4_POS_B2 = 23,
MOTOR_4_POS_B1 = 24,
MOTOR_4_POS_B0 = 25,
# Motor pwm speed
MOTOR_1_PWM_H = 26,
MOTOR_1_PWM_L = 27,
MOTOR_1_CTRL = 28,
MOTOR_2_PWM_H = 29,
MOTOR_2_PWM_L = 30,
MOTOR_2_CTRL = 31,
MOTOR_3_PWM_H = 32,
MOTOR_3_PWM_L = 33,
MOTOR_3_CTRL = 34,
MOTOR_4_PWM_H = 35,
MOTOR_4_PWM_L = 36,
MOTOR_4_CTRL = 37,
# Servo goal position
SERVO_1_PWM_H = 38,
SERVO_1_PWM_L = 39,
SERVO_2_PWM_H = 40,
SERVO_2_PWM_L = 41,
SERVO_3_PWM_H = 42,
SERVO_3_PWM_L = 43,
SERVO_4_PWM_H = 44,
SERVO_4_PWM_L = 45,
# IR Sensor value
IR_1_H = 46,
IR_1_L = 47,
IR_2_H = 48,
IR_2_L = 49,
IR_3_H = 50,
IR_3_L = 51,
IR_4_H = 52,
IR_4_L = 53,
IR_5_H = 54,
IR_5_L = 55,
IR_1_LED = 56,
IR_2_LED = 57,
IR_3_LED = 58,
IR_4_LED = 59,
IR_5_LED = 60,
# Display registers
DISPLAY_LINE_1_C0 = 63,
DISPLAY_LINE_2_C0 = 79,
DISPLAY_LINE_3_C0 = 95,
DISPLAY_LINE_4_C0 = 111