Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
440 changes: 72 additions & 368 deletions .github/copilot-instructions.md

Large diffs are not rendered by default.

64 changes: 6 additions & 58 deletions EEPROM/hexdrive.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,6 @@
_MAX_SERVO_RANGE = 1400 # 1400us either side of centre (VERY WIDE)
_SERVO_MAX_TRIM = 1000 # 1000us either side of centre for trimming the centre position

# Stepper Motor Constants
_STEPPER_NUM_PHASES = 8 # Number of phases in the stepping sequence (this includes half steps)
_STEPPER_SEQUENCE = (
(1, 0, 1, 0),
(0, 0, 1, 0),
(0, 1, 1, 0),
(0, 1, 0, 0),
(0, 1, 0, 1),
(0, 0, 0, 1),
(1, 0, 0, 1),
(1, 0, 0, 0),
)

# EEPROM Constants
_EEPROM_ADDR = 0x50 # I2C address of the EEPROM on the HexDrive and HexSense Hexpansion
_EEPROM_NUM_ADDRESS_BYTES = 2 # Number of bytes used for the memory address when reading from the EEPROM (e.g. 2 for 16-bit addressing)
Expand All @@ -57,21 +44,19 @@

class HexDriveType:
"""Represents a sub-type of HexDrive Hexpansion module."""
__slots__ = ("pid", "name", "motors", "servos", "steppers")
__slots__ = ("pid", "name", "motors", "servos")

def __init__(self, pid_byte: int, motors: int = 0, servos: int = 0, steppers: int = 0, name: str = "Unknown"):
def __init__(self, pid_byte: int, motors: int = 0, servos: int = 0, name: str = "Unknown"):
self.pid: int = pid_byte # Product ID byte read from the EEPROM to identify the type of HexDrive
self.name: str = name # A friendly name for the type of HexDrive
self.motors: int = motors # Number of motor channels supported by this type of HexDrive (0, 1 or 2)
self.servos: int = servos # Number of servo channels supported by this type of HexDrive (0, 2 or 4)
self.steppers: int = steppers # Number of stepper motors supported by this type of HexDrive (0 or 1)

_HEXDRIVE_TYPES = (
HexDriveType(0xCA, motors=2, name="2 Motor"),
HexDriveType(0xCB, motors=2, servos=4, steppers=1),
HexDriveType(0xCB, motors=2, servos=4),
HexDriveType(0xCC, servos=4, name="4 Servo"),
HexDriveType(0xCD, motors=1, servos=2, name="1 Mot 2 Srvo"),
HexDriveType(0xCE, steppers=1, name="1 Stepper"),
)
Comment thread
Robotmad marked this conversation as resolved.

class HexDriveApp(app.App): # pylint: disable=no-member
Expand All @@ -89,7 +74,6 @@ def __init__(self, config: HexpansionConfig | None = None):
self.PWMOutput: list[PWM | None] = [None] * _MAX_NUM_CHANNELS
self._freq: list[int] = [0] * _MAX_NUM_CHANNELS
self._motor_output: list[int] = [0] * _MAX_NUM_MOTORS
self._stepper: bool = False
if config is None:
print("D:No Config")
return
Expand Down Expand Up @@ -169,7 +153,7 @@ async def _handle_stop_app(self, event):

def background_update(self, delta: int):
""" This is called from the main loop of the BadgeOS to allow the app to do any background processing it needs to do. """
if (self.config is None) or not (self._pwm_setup or self._stepper):
if (self.config is None) or not self._pwm_setup:
# if we are not properly initialised then do not attempt to do anything
return
# Check keep alive period and turn off PWM outputs if exceeded
Expand All @@ -189,8 +173,6 @@ def background_update(self, delta: int):
except Exception as e: # pylint: disable=broad-except
print(self._pwm_log_string(channel) + f"Off failed {e}")
self.PWMOutput[channel] = None # Tidy Up
elif self._stepper:
self.motor_release()
# we keep retriggering in case anything else has corrupted the PWM outputs


Expand All @@ -201,7 +183,7 @@ def get_version(self) -> int:

def get_status(self) -> bool:
""" Get the current status of the app - True if the app is running and able to respond to commands, False if not. """
return (self._pwm_setup or self._stepper)
return (self._pwm_setup)


def set_logging(self, state: bool):
Expand Down Expand Up @@ -344,7 +326,6 @@ def set_servoposition(self, channel: int | None = None, position: int | None = N
return False

self._outputs_energised = True
self._stepper = False
self._time_since_last_update = 0
return True

Expand Down Expand Up @@ -416,37 +397,6 @@ def set_pwm(self, duty_cycles: tuple[int, ...]) -> bool:
return True


### Stepper Motor Support

# --------------------------------------------------
# Public methods for controlling stepper motors.
# ---------------------------------------------------

def motor_step(self, phase: int) -> int | None:
""" Step the motor to a specific phase in the stepping sequence. Returns None if failed (e.g. invalid phase or not configured for stepper),
otherwise returns the phase that was set. The phase is a value from 0 to _STEPPER_NUM_PHASES-1 which corresponds to the
stepping sequence defined in _STEPPER_SEQUENCE."""
if phase >= _STEPPER_NUM_PHASES or self._hexdrive_type.steppers == 0:
return None
if not self._stepper:
# not currently configured for stepper motor - configure
self._pwm_deinit()
self._stepper = True
for channel, value in enumerate(_STEPPER_SEQUENCE[phase]):
self.config.pin[channel].value(value)
self._outputs_energised = True
self._time_since_last_update = 0
return phase


def motor_release(self):
""" Release the motor by setting all outputs to low. This will stop the motor and allow it to be turned by hand. """
for channel in range(4 if self._stepper else (self._hexdrive_type.motors * 2)):
self.config.pin[channel].value(0)
self._outputs_energised = False
self._time_since_last_update = 0


# --------------------------------------------------
# Private methods for internal use only.
# --------------------------------------------------
Expand Down Expand Up @@ -477,9 +427,7 @@ def _pwm_init(self) -> bool:
# ignore the remaining channels - we will switch them on when needed
pass
if 0 < self._freq[channel]:
if self._set_pwmoutput(channel, 0):
self._stepper = False
else:
if not self._set_pwmoutput(channel, 0):
return False
self._pwm_setup = True
return self._pwm_setup
Expand Down
15 changes: 4 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# BadgeBot app

Companion app for the HexDrive hexpansion. Supports 2 brushed DC motors, 4 RC servos, 1 motor + 2 servos, or a single two-phase stepper. Features Logo-style motor programming, PID line following with automatic gain tuning, I²C sensor testing, servo/stepper test modes, and persistent settings management.
Companion app for the HexDrive hexpansion. Supports 2 brushed DC motors, 4 RC servos, 1 motor + 2 servos. Features Logo-style motor programming, PID line following with automatic gain tuning, I²C sensor testing, servo test mode, and persistent settings management.

This guide is current for BadgeBot version 1.5

Expand All @@ -14,10 +14,9 @@ If your HexDrive software (stored on the EEPROM on the hexpansion) is not the la
- 2 Motor
- 4 Servo
- 1 Motor and 2 Servos
- Stepper
- Unknown

The board can drive 2 brushed DC motors, 4 RC servos, 1 DC motor and 2 servos or a single two phase Stepper Motor.
The board can drive 2 brushed DC motors, 4 RC servos, 1 DC motor and 2 servos.
Once you have selected the desired 'flavour' - please confirm by pressing the "C" (confirm) button.

There must be a HexDrive board plugged in and running the latest software to use the BadgeBot app. If this is not the case then you will see a warning that you need a HexDrive with a reference to this repo.
Expand All @@ -27,7 +26,6 @@ There must be a HexDrive board plugged in and running the latest software to use
The main menu presents the following options:
- **Line Follower** – PID-controlled line following using a HexSense with QTRX reflectance sensors
- **Motor Moves** – Logo/turtle-style motor programming (record UP/DOWN/LEFT/RIGHT sequences, then execute)
- **Stepper Test** – Test and control a single stepper motor (position and speed modes)
- **Servo Test** – Test up to 4 RC servos (position, trim, and scanning modes)
- **PID Auto Tune** – Automatic PID gain tuning using relay feedback (Åström-Hägglund method)
- **Settings** – Adjust configurable parameters (see below)
Expand Down Expand Up @@ -62,7 +60,6 @@ The main menu includes a sub-menu of Settings which can be adjusted.
|------------------|-------------------------------------------|----------------|--------|--------|
| brightness | LED brightness | 1.0 | 0.1 | 1.0 |
| logging | Enable or disable logging | False | False | True |
| step_max_pos | Maximum stepper position | 3100 | 0 | 65535 |

The PID gains are best set by using the "PID Auto Tune" menu option. Place the robot on a line and press C to start the tuning process. The auto-tuner uses relay feedback (Åström-Hägglund method) to determine the ultimate gain and period of oscillation, then calculates PID gains using Ziegler-Nichols tuning rules. The tuning process includes a quality score (0-100%) indicating how consistent the oscillation data was. Results are automatically saved to settings.

Expand All @@ -74,7 +71,7 @@ When running from badge power the current available is limited - the best way to

The maximum allowed servo range is VERY WIDE - most Servos will not be able to cope with this, so you probably want to reduce the ```servo_range``` setting to suit your servos.

Each Servo or Motor driver requires a PWM signals to control it, so a single HexDrive takes up four PWM resources on the ESP32. As there are 8 such resources, the 'flavour' of your HexDrives will determine how many you can run simultaneously as long as you don't have any other hexpansions or applications using PWM resources. Two '4 Servo', 'Stepper' or 'Unknown' flavour HexDrives will use up all the available PWM channels, whereas you can run up to 4 HexDrives in '2 Motor' flavour. (While each motor driver does actually require two PWM signals we have been able to reduce this to one by swapping it between the active signal when the motor direction changes.)
Each Servo or Motor driver requires a PWM signals to control it, so a single HexDrive takes up four PWM resources on the ESP32. As there are 8 such resources, the 'flavour' of your HexDrives will determine how many you can run simultaneously as long as you don't have any other hexpansions or applications using PWM resources. Two '4 Servo' or 'Unknown' flavour HexDrives will use up all the available PWM channels, whereas you can run up to 4 HexDrives in '2 Motor' flavour. (While each motor driver does actually require two PWM signals we have been able to reduce this to one by swapping it between the active signal when the motor direction changes.)

If you unplug a HexDrive the PWM resources will be released immediately so you can move them around the badge easily.

Expand All @@ -93,7 +90,6 @@ This repo contains lots of files that you don't need on your badge to use a HexD
+ motor_controller.mpy
+ motor_moves.mpy
+ servo_test.mpy
+ stepper_test.mpy
+ settings_mgr.mpy
+ line_follow.mpy
+ autotune.mpy
Expand Down Expand Up @@ -164,11 +160,8 @@ You can use one motor and 1 or 2 servos simultaneously.
### Frequency
You can adjust the PWM frequency, default 20000Hz for motors and 50Hz for servos by calling the ```set_freq()``` function.

### Stepper Motor
You can control a single 2 phase stepper motor using ```motor_step()``` specifying which of the 8 possible phases to output in the range 0 to 7. There are 8 possible values as half stepping is supported. To use only full steps specify phase values of 0, 2, 4 and 6. Information on the pros and cons of using full or half stepping can be found online and what is right for you will depend on your motor and the application. The motor can be released (so that it is not taking power to hold it in a fixed position) using ```motor_release()```.

#### Keep Alive
To protect against most badge/software crashes causing the motors or servos to run out of control there is a keep alive mechanism which means that if you do not make a call to the ```set_pwm```, ```set_motors```, ```motor_step``` or ```set_servoposition``` functions the motors/servos will be turned off after 1000mS (default - which can be changed with a call to ```set_keep_alive()```).
To protect against most badge/software crashes causing the motors or servos to run out of control there is a keep alive mechanism which means that if you do not make a call to the ```set_pwm```, ```set_motors``` or ```set_servoposition``` functions the motors/servos will be turned off after 1000mS (default - which can be changed with a call to ```set_keep_alive()```).

### Developers setup
This is to help develop the BadgeBot application using the Badge simulator.
Expand Down
Loading
Loading