Hello, I been using Yukon BigMotor with PWM to drive a bot, its working well. Decided to see if I could get it to work with PIO reading the docs on getting started with BigMotor and using PIO with MotorCuster. I copied an example of multiple_motors.py and bodged it in a way that seemed to be in accordance to the docs on using the PIO instead of PMW (docs a bit sparse on this) but after a trying all sorts of stuff the motors just will not turn. :-( . If anyone can suggest what to do with the bodged code example I enclose it may well save a brick wall from being bashed by my bonce.
import math
from pimoroni_yukon import Yukon
from pimoroni_yukon import SLOT6 as LEFT_SLOT
from pimoroni_yukon import SLOT1 as RIGHT_SLOT
from pimoroni_yukon.modules import BigMotorModule
from pimoroni_yukon.timing import ticks_ms, ticks_add
from motor import MotorCluster
# Constants
GEAR_RATIO = 74.83
ENCODER_CPR = 48
MOTOR_CPR = GEAR_RATIO * ENCODER_CPR
SPEED = 0.005
UPDATES = 50
SPEED_EXTENT = 1.0
WAVE_SCALE = 1.0
# Variables
# Create a new Yukon object
yukon = Yukon()
# A list to store BigMotorModule objects created later
modules = []
# The offset used to animate the motors
phase_offset = 0
# Function to get a motor speed from its index
def speed_from_index(index, offset=0.0):
phase = (((index * WAVE_SCALE) / BigMotorModule.NUM_MOTORS) + offset) * math.pi * 2
speed = math.sin(phase) * SPEED_EXTENT
return speed
# Generator to get the next PIO and State Machine numbers
def pio_and_sm_generator():
pio = 0
sm = 0
while True:
yield (pio, sm)
sm += 1
# Wrap the SM and increment the PIO
if sm > 3:
sm -= 4
pio += 1
# An instance of the generator
pio_and_sm = pio_and_sm_generator()
# Create a BigMotorModule object, with details of the encoder
# PIO motor control - init_motor = False
try:
# Find out which slots of Yukon have BigMotorModule attached
for slot in yukon.find_slots_with(BigMotorModule):
pio, sm = next(pio_and_sm)
module = BigMotorModule(encoder_pio=pio,
encoder_sm=sm,
counts_per_rev = MOTOR_CPR,
init_motor = False)
yukon.register_with_slot(module, slot)
modules.append(module)
# Record the number of motors that will be driven
NUM_MOTORS = len(modules) * BigMotorModule.NUM_MOTORS
print(f"Up to {NUM_MOTORS} motors available")
yukon.verify_and_initialise()
yukon.enable_main_output()
# use MotorCluster to create motors for the modules in the modules list
pio, sm = next(pio_and_sm)
motors = MotorCluster(pio, sm, pins=[module.motor_pins for module in modules])
motors.enable_all()
# Record the start time of the program loop
current_time = ticks_ms()
# Loop until the BOOT/USER button is pressed
while not yukon.is_boot_pressed():
# Read all the encoders and give all the motors a speed
current_motor = 0
for module in modules:
capture = module.encoder.capture() # Capture the state of the encoder
print(f"RPS{current_motor} = {capture.revolutions_per_second}", end=", ")
#speed
#speed = speed_from_index(current_motor, phase_offset)
#motors.all_to_speed(speed)
motors.speed(0, 0.25)
motors.speed(1, 0.5)
current_motor += 1
print()
# Advance the current time by a number of seconds
current_time = ticks_add(current_time, int(1000 / UPDATES))
# Monitor sensors until the current time is reached, recording the min, max, and average for each
# This approach accounts for the updates taking a non-zero amount of time to complete
yukon.monitor_until_ms(current_time)
finally:
# Put the board back into a safe state, regardless of how the program may have ended
yukon.reset()
I am curious why you are wanting to use PIO to drive the motors rather than standard PWM? Perhaps you could give a bit more details about your project? How many motors are you driving? do you need encoders? Is there a reason you want to keep PWM free?
Also, could you show the output you get when running you program?
Without knowing answers to those questions, I suspect the reason you are having issues is not so much an issue with your code, but rather a PIO limitation itself. There are only 2 PIO units (with 4 state machines each) that have a limited amount of program memory. Both MotorCluster and Encoder are not exactly light PIO programs so its possible that its ran out of memory.
Also, although the multiple_motors.py example is useful, the dynamic searching for modules using find_slots_with is unnecessary once you know which slots you intend to use for your project. I would recommend checking out the rover example for something that actually runs on hardware yukon/examples/showcase/rover/main.py at main · pimoroni/yukon · GitHub
Hi Chris, thanks for you reply.
The use of PIO to drive the motors was just a test. Some months ago I had an initial test of Yukon on a bot using PWM with the BigMotor boards but then got diverted. Getting back to it last week, and finding I had forgotten how all the code works due to my abysmal lack of doc’s and comments in my code, I decided to produce some small example snippets to illustrate driving the motors, getting the bot to turn accurately, communicate via UART etc to refer to if required. I also saw that there was an updated firmware for the Yukon, so I installed that and started off with a PWM example to drive the motors and then thought to add a simple PIO example.
When it did not work I resorted to copying a provided example, but amending the code to use PIO which I then posted for assistance. A quick amendment to the code I posted to include reporting free memory at points before the loop starts, and again in the finally statement before the Yukon reset, shows the following output:
MicroPython 17d8234-dirty on 2024-09-27; Pimoroni Yukon with RP2040
Type “help()” for more information.
%Run -c $EDITOR_CONTENT
( a file and memory report before running the program)
MPY: soft reboot
File System Size 15,728,640 - Free Space 15,351,808
gc.mem_free : 223120
%Run -c $EDITOR_CONTENT
(running the posted program)
%Run -c $EDITOR_CONTENT
MPY: soft reboot
Running Yukon 1.0.2, MicroPython 17d8234-dirty on 2024-09-27
Checking output voltage …
Finding slots with ‘Big Motor + Encoder’ module
[Slot1] Found ‘Big Motor + Encoder’ module
[Slot2] No 'Big Motor + Encoder module [Slot3] No 'Big Motor + Encoder module
[Slot4] No 'Big Motor + Encoder module [Slot5] No 'Big Motor + Encoder module
[Slot6] Found ‘Big Motor + Encoder’ module
Up to 2 motors available
Checking output voltage …
Verifying modules
[Slot1] ‘Big Motor + Encoder’ module detected and registered.
[Slot2] Module slot is empty.
[Slot3] Module slot is empty.
[Slot4] Module slot is empty.
[Slot5] Module slot is empty.
[Slot6] ‘Big Motor + Encoder’ module detected and registered.
Initialising modules
[Slot1 ‘Big Motor + Encoder’] Initialising … done
[Slot6 ‘Big Motor + Encoder’] Initialising … done
Checking input voltage …
Enabling output …
Output enabled gc.mem_free : 152848 gc.mem_free : 146912
RPS0 = 0.0, RPS1 = 0.0,
RPS0 = 0.0, RPS1 = 0.0,
RPS0 = 0.0, RPS1 = 0.0,
RPS0 = 0.0, RPS1 = 0.0,
RPS0 = 0.0, RPS1 = 0.0,
RPS0 = 0.0, RPS1 = 0.0,
RPS0 = 0.0, RPS1 = 0.0,
Note: more of the above repeated lines deleted and
when a Ctrl C to end the program we get:
gc.mem_free : 126672
Output disabled
I also tried a very simple program to run one motor with init_encoder = False and used MotorCluster to use PIO just for the motor. But again the motor did not turn and using the yukon.monitor_until_ms(current_time) in a similar way in the code I pasted did not print out anything amiss.
As said the use of PIO was just to provide an example in case it came in useful for a future use and I’m happy with using PWM. But it would be nice to know if PIO can be used as thus far I’ve not succeeded.
Thanks for the information and program output. Looks like you’re using two Big Motor modules. That should be entirely possible with PIO for both the motor and its encoder.
Hmm. That should certainly work. Could you try running the all_motors_no_encoders.py I linked to before? If that works but your simple program did not then there may be something missing from your program.
However, if that example does not work then it could be that MotorCluster got broken in the new firmware. A test would be to downgrade to the old firmware and see if it starts working again (Be sure to back up your Yukon’s files first, if there’s anything important on it).
And indeed, it should be possible. Assuming the tests above prove fruitful, I would suggest changing the program to specifically try to register BigMotor modules in slots 1 and 6 (rather than use find_slots_with). This way you can explicitly assign the encoders to PIO 0, then assign the MotorCluster to PIO 1. Having both on the same PIO will not work due to program space.
My error appears to be I was following the MotorCluster docs which I guess are emphasised for the motor board rather than the Yukon board. I was enabling the motors with the motor cluster object i.e. motors.enable_all() and when I changed to enabling the BigMotor objects in a modules list i.e. for module in modules: module.enable() all was well. (due warning I may well have changed other things too in my latest quick hack at my code which I did without too much of a structured approach)
Thanks for the other tips and PIO is working well. I was sort of under the impression that using PIO, with the statemachines working away without much CPU involvment would be an advantage, but from your comments perhaps PWM is the best way to drive the motors. Anyway its all working any which way :)
Glad to hear you got it working. I’ll admit I completely forgot about enabling motors and modules.
If anything, PIO will actually be a disadvantage over PWM, as with our MotorCluster implementation there are interrupts that are triggered that the CPU needs to regularly handle, whereas the PWM hardware will happily output a signal without CPU involvement indefinitely.
I have seen alternative implementations for PIO PWM though, that use a state machine per pin. Those would have no CPU involvement.