Skip to content

Commit

Permalink
Readme for Big Motor, and class changes
Browse files Browse the repository at this point in the history
  • Loading branch information
ZodiusInfuser committed Oct 24, 2023
1 parent 221d715 commit 3a87b7b
Show file tree
Hide file tree
Showing 6 changed files with 256 additions and 69 deletions.
52 changes: 0 additions & 52 deletions docs/big_motor.md

This file was deleted.

214 changes: 214 additions & 0 deletions docs/modules/big_motor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
### Big Motor Module - Library Reference <!-- omit in toc -->

This is the library reference for the [Big Motor + Encoder Module for Yukon](https://pimoroni.com/yukon).

- [Getting Started](#getting-started)
- [Initialising the Module](#initialising-the-module)
- [Using the Module](#using-the-module)
- [Enabling its Driver](#enabling-its-driver)
- [Accessing the Motor](#accessing-the-motor)
- [More than 4 Big Motors](#more-than-4-big-motors)
- [Accessing the Encoder](#accessing-the-encoder)
- [Direct GPIO Access](#direct-gpio-access)
- [Onboard Sensors](#onboard-sensors)
- [References](#references)
- [Constants](#constants)
- [Variables](#variables)
- [Methods](#methods)


## Getting Started

To start using a Big Motor + Encoder Module, you first need to import the class from `pimoroni_yukon.modules`.

```python
from pimoroni_yukon.modules import BigMotorModule
```

Then create an instance of `BigMotorModule`, giving it the frequency to drive the motor at, which PIO and State Machine (SM) to use for the encoder, and how many counts the encoder will produce per revolution of the motor shaft.

```python
# Constants
FREQUENCY = 25000
ENCODER_PIO = 0
ENCODER_SM = 0
COUNTS_PER_REV = 12

module = BigMotorModule(FREQUENCY,
ENCODER_PIO,
ENCODER_SM,
COUNTS_PER_REV)
```

:warning: **Be sure to choose a PIO and State Machine that does not conflict with any others you have already set up.**


## Initialising the Module

As with all Yukon modules, `BigMotorModule` must be initialised before it can be used. This is achieved by first registering the module with the `Yukon` class, with the slot it is attached to.

```python
from pimoroni_yukon import SLOT1 as SLOT

# Import and set up Yukon and BigMotorModule instances

yukon.register_with_slot(module, SLOT)
```

Then `Yukon` can verify and initialise its modules.

```python
yukon.verify_and_initialise()
```

This checks each slot on the board to see if the modules expected by your program are physically attached to the board. Only if they match will the `BigMotorModule` be initialised, giving it access to the GPIO of the slot it is attached to.

Power can now be provided to all modules, by calling.

```python
yukon.enable_main_output()
```

## Using the Module

### Enabling its Driver

With the `BigMotorModule` powered, its motor driver can be enabled or disabled by calling `.enable()` or `.disable()`. The state can also be queried by calling `.is_enabled()`.

### Accessing the Motor

The `BigMotorModule` class makes use of the [Motor Library](https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/modules/motor/README.md).

By default a single Motor object is created and made accessible through the `.motor` variable.

For example, to move the motor at half its speed, the following line of code can be run:

```python
module.motor.speed(0.5)
```

Up to four modules, for a total of 4 big motors, can be used in this way, provided their PWM pins do not conflict. Refer to the Yukon board pinout for the slots you are using.


#### More than 4 Big Motors

To drive more than 4 big motors, or use slots that would normally have conflicting PWMs, a [MotorCluster](https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/modules/motor/README.md#motorcluster) object should to be used.

During creation of the `BigMotorModule`, instruct it to *not* create the motor object, by providing it with the `init_motor=False` parameter.

```python
module = BigMotorModule(init_motor=False)
```

This makes the `.motor` variable inaccessible, and instead makes a `.motor_pins` tuple available. These pins can then be passed to a MotorCluster.

```python
# Constants
CLUSTER_PIO = 0
CLUSTER_SM = 0

motors = MotorCluster(CLUSTER_PIO, CLUSTER_SM, pins=module.motor_pins)
```

:warning: **Be sure to choose a PIO and State Machine that does not conflict with any others you have already set up.**

If you have multiple Big Motor + Encoder Modules you wish to use with a single Motor Cluster, then a technique called list comprehension can be used to combine all the `.motor_pins` together.

```python
pins = [module.motor_pins for module in modules]
```


### Accessing the Encoder

The `BigMotorModule` class makes use of the [Encoder Library](https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/modules/encoder/README.md).

By default a single Encoder object is created and made accessible through the `.encoder` variable.

For example, to read the current angle of the encoder, the following line of code can be run:

```python
angle = module.encoder.degrees()
```

#### Direct GPIO Access

If your project does not need an encoder, then the pins on Big Motor + Encoder Module can be made available as GPIO. During creation of the `BigMotorModule`, instruct it to *not* create the encoder object, by providing it with the `init_encoder=False` parameter. Other encoder specific parameters, such as the PIO and SM, can also be omitted.

```python
module = BigMotorModule(init_encoder=False)
```

This makes the `.encoder` variable inaccessible, and instead makes an `.encoder_pins` tuple available. These pins can then be passed to a MotorCluster.


### Onboard Sensors

The Big Motor + Encoder module's motor driver features a current output, letting its draw be monitored. This can be read by calling `.read_current()`.

There is also an onboard thermistor, letting its temperature be monitored. This can be read by calling `.read_temperature()`.

Additionally, the fault state of the motor driver can be read by calling `.read_fault()`. This will be `False` during normal operation, but will switch to `True` under various conditions. For details of these conditions, check the [DRV8706H datasheet](https://www.ti.com/lit/ds/symlink/drv8706-q1.pdf).


## References

### Constants

```python
NAME = "Big Motor + Encoder"
NUM_MOTORS = 1
DEFAULT_FREQUENCY = 25000
DEFAULT_COUNTS_PER_REV = MMME_CPR # 12
TEMPERATURE_THRESHOLD = 50.0
CURRENT_THRESHOLD = 25.0
SHUNT_RESISTOR = 0.001
GAIN = 80
```

### Variables

```python
# If init_motor was True
motor: Motor

# If init_motor was False
motor_pins: tuple[Pin, Pin]

# If init_encoder was True
encoder: Encoder

# If init_encoder was False
encoder_pins: tuple[Pin, Pin]
```

### Methods

```python
# Address Checking
@staticmethod
is_module(adc1_level: int, adc2_level: int, slow1: bool, slow2: bool, slow3: bool) -> bool

# Initialisation
BigMotorModule(frequency: float=DEFAULT_FREQUENCY,
encoder_pio: int=0, encoder_sm: int=0, counts_per_rev: float=DEFAULT_COUNTS_PER_REV,
init_motor: bool=True, init_encoder: bool=True)
initialise(slot: SLOT, adc1_func: Callable, adc2_func: Callable) -> None
reset() -> None

# Power Control
enable() -> None
disable() -> None
is_enabled() -> bool

# Sensing
read_fault() -> bool
read_current(samples: int=1) -> float
read_temperature(samples: int=1) -> float

# Monitoring
monitor() -> None
get_readings() -> OrderedDict
process_readings() -> None
clear_readings() -> None
```
2 changes: 2 additions & 0 deletions docs/modules/led_strip.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ module = LEDStripModule(STRIP_TYPE,
BRIGHTNESS)
```

:warning: **Be sure to choose a PIO and State Machine that does not conflict with any others you have already set up.**

### Strip Types

Both WS2812's (aka NeoPixels) and APA102's (aka DotStars) are supported by the `LEDStripModule`.
Expand Down
2 changes: 2 additions & 0 deletions docs/modules/quad_servo_direct.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ CLUSTER_SM = 0
servos = ServoCluster(CLUSTER_PIO, CLUSTER_SM, pins=module.servo_pins)
```

:warning: **Be sure to choose a PIO and State Machine that does not conflict with any others you have already set up.**

If you have multiple Quad Servo Modules you wish to use with a single Servo Cluster, then a technique called nested list comprehension can be used to combine all the `.servo_pins` together.

```python
Expand Down
2 changes: 2 additions & 0 deletions docs/modules/quad_servo_reg.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ CLUSTER_SM = 0
servos = ServoCluster(CLUSTER_PIO, CLUSTER_SM, pins=module.servo_pins)
```

:warning: **Be sure to choose a PIO and State Machine that does not conflict with any others you have already set up.**

If you have multiple Quad Servo Modules you wish to use with a single Servo Cluster, then a technique called nested list comprehension can be used to combine all the `.servo_pins` together.

```python
Expand Down
53 changes: 36 additions & 17 deletions lib/pimoroni_yukon/modules/big_motor.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,39 +28,58 @@ class BigMotorModule(YukonModule):
def is_module(adc1_level, adc2_level, slow1, slow2, slow3):
return adc1_level == ADC_LOW and slow1 is IO_LOW and slow3 is IO_HIGH

def __init__(self, frequency=DEFAULT_FREQUENCY, counts_per_rev=None):
def __init__(self, frequency=DEFAULT_FREQUENCY,
encoder_pio=0, encoder_sm=0, counts_per_rev=DEFAULT_COUNTS_PER_REV,
init_motor=True, init_encoder=True):
super().__init__()

if init_encoder:
if encoder_pio < 0 or encoder_pio > 1:
raise ValueError("encoder_pio out of range. Expected 0 or 1")

if encoder_sm < 0 or encoder_sm > 3:
raise ValueError("encoder_sm out of range. Expected 0 to 3")

self.__frequency = frequency
self.__encoder_pio = encoder_pio
self.__encoder_sm = encoder_sm
self.__counts_per_rev = counts_per_rev

self.__init_motor = init_motor
self.__init_encoder = init_encoder

def initialise(self, slot, adc1_func, adc2_func):
try:
# Create pwm objects
self.__pwm_p = slot.FAST4
self.__pwm_n = slot.FAST3
except ValueError as e:
if slot.ID <= 2 or slot.ID >= 5:
conflicting_slot = (((slot.ID - 1) + 4) % 8) + 1
raise type(e)(f"PWM channel(s) already in use. Check that the module in Slot{conflicting_slot} does not share the same PWM channel(s)") from None
raise type(e)("PWM channel(s) already in use. Check that a module in another slot does not share the same PWM channel(s)") from None

# Create motor object
self.motor = Motor((self.__pwm_p, self.__pwm_n), freq=self.__frequency)
# Store the pwm pins
pwm_p = slot.FAST4
pwm_n = slot.FAST3

if self.__init_motor:
# Create motor object
self.motor = Motor((pwm_p, pwm_n), freq=self.__frequency)
else:
self.motor_pins = (pwm_p, pwm_n)

# Create motor control pin objects
self.__motor_en = slot.SLOW3
self.__motor_nfault = slot.SLOW2

if self.__counts_per_rev is not None:
# Store the encoder pins
enc_a = slot.FAST1
enc_b = slot.FAST2

if self.__init_encoder:
# Create rotary encoder object
self.encoder = Encoder(0, 2, (slot.FAST1, slot.FAST2), counts_per_rev=self.__counts_per_rev, count_microsteps=True)
self.encoder = Encoder(self.__encoder_pio, self.__encoder_sm, (enc_a, enc_b), counts_per_rev=self.__counts_per_rev, count_microsteps=True)
else:
self.encoder_pins = (enc_a, enc_b)

# Pass the slot and adc functions up to the parent now that module specific initialisation has finished
super().initialise(slot, adc1_func, adc2_func)

def reset(self):
self.motor.disable()
self.motor.decay_mode(SLOW_DECAY)
if self.__init_motor:
self.motor.disable()
self.motor.decay_mode(SLOW_DECAY)

self.__motor_nfault.init(Pin.IN)
self.__motor_en.init(Pin.OUT, value=False)
Expand Down

0 comments on commit 3a87b7b

Please sign in to comment.