Meadow F7 V2 Simultaneous Multiple PWM issue

Dear Team,

I have the Meadow F7 V2 Micro OS 1.5.0 board and I am using the Meadow.Foundation.Motors.Stepper.GpioStepper library to run multiple stepper motors (6 nos.) (Pul, Dir) and using multiple synchronized tasks to run each Stepper.GoTo function simultaneously but the execution behavior is very strange, it never allow more than one stepper to run at same time.

My code is below:


using Meadow;
using Meadow.Devices;
using Meadow.Foundation.Motors;
using Meadow.Hardware;
using Meadow.Peripherals;
using Meadow.Peripherals.Motors;
using System.Collections.Generic;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Http;
using Meadow.Foundation.Motors.Stepper;
using Meadow.Units;

namespace MeadowApp
{
    public class MeadowApp : App<F7FeatherV2>
    {
        //<!=SNIP=>
        private StepDirStepper stepper_1;
        private StepDirStepper stepper_2;
        private StepDirStepper stepper_3;
        private StepDirStepper stepper_4;
        private StepDirStepper stepper_5;
        private StepDirStepper stepper_6;

        private RotationDirection direction;



        public override Task Initialize()
        {



            //   st.MinimumStartupDwellMicroseconds = 100;


            StepDirStepper st_1 = new StepDirStepper(
                Device.Pins.D15.CreateDigitalOutputPort(true),
                Device.Pins.D14.CreateDigitalOutputPort(true),
                stepsPerRevolution: 400);
            st_1.LinearAccelerationConstant = 200;


            stepper_1 = st_1;


            StepDirStepper st_2 = new StepDirStepper(
                Device.Pins.D13.CreateDigitalOutputPort(true),
                Device.Pins.D12.CreateDigitalOutputPort(true),
                stepsPerRevolution: 400);
            st_2.LinearAccelerationConstant = 200;


            stepper_2 = st_2;


            StepDirStepper st_3 = new StepDirStepper(
                Device.Pins.D11.CreateDigitalOutputPort(true),
                Device.Pins.D10.CreateDigitalOutputPort(true),
                stepsPerRevolution: 400);
            st_3.LinearAccelerationConstant = 200;


            stepper_3 = st_3;


            StepDirStepper st_4 = new StepDirStepper(
                Device.Pins.D09.CreateDigitalOutputPort(true),
                Device.Pins.D08.CreateDigitalOutputPort(true),
                stepsPerRevolution: 400);
            st_4.LinearAccelerationConstant = 200;


            stepper_4 = st_4;



            StepDirStepper st_5 = new StepDirStepper(
                Device.Pins.D07.CreateDigitalOutputPort(true),
                Device.Pins.D06.CreateDigitalOutputPort(true),
                stepsPerRevolution: 400);
            st_5.LinearAccelerationConstant = 200;


            stepper_5 = st_5;


            StepDirStepper st_6 = new StepDirStepper(
                Device.Pins.D05.CreateDigitalOutputPort(true),
                Device.Pins.D04.CreateDigitalOutputPort(true),
                stepsPerRevolution: 400);
            st_6.LinearAccelerationConstant = 200;


            stepper_6 = st_6;


            return base.Initialize();
        }

        public override async Task Run()
        {


            direction = RotationDirection.Clockwise;

            //while (true)
            //{
                Resolver.Log.Info($"{direction}");



                await ExecuteMultipleTasksAsync();

                 

                Thread.Sleep(1000);

                direction = direction switch
                {
                    RotationDirection.CounterClockwise => RotationDirection.Clockwise,
                    _ => RotationDirection.CounterClockwise
                };
            //}
        }

        //<!=SNOP=>

        public async Task ExecuteMultipleTasksAsync()
        {
            var tasks = new List<Task>
    {
        StepperAsync(stepper_1),
        StepperAsync(stepper_2),
        StepperAsync(stepper_3),
        StepperAsync(stepper_4),
        StepperAsync(stepper_5),
        StepperAsync(stepper_6),
    };

            try
            {
                await Task.WhenAll(tasks);
            }
            catch (AggregateException ex)
            {
                foreach (var innerEx in ex.InnerExceptions)
                {
                    // Handle each inner exception
                }
            }
        }

        public async Task<bool> StepperAsync(StepDirStepper St)
        {
            try
            {
                await St.GoTo(new Angle(5, Angle.UnitType.Revolutions), new AngularVelocity(5, AngularVelocity.UnitType.RevolutionsPerSecond));


                return true;
            }
            catch (HttpRequestException ex)
            {
                // Handle the exception
                return false;
            }
        }


        


    }
}

So there are a few challenges here that I’ll describe below, but the net effect is that the F7 micro platform can’t do what you’re asking of it from managed code.

As some background, the F7 is a single-core microcontroller. It does have PWMs, but those PWMs are designed for continuous output, not outputting a specified, exact number of pulses.

The way the Pul/Dir driver works is that it calculates the number of pulses required for the move, and then does those pulses in a loop. For timing > 1ms between pulses it pauses using Sleep (or maybe await Task.Delay), both of which are 1ms resolution - that number is important. So if you’re pulsing at > 1ms per pulse then there will be an opportunity for the driver to pause and allow other things to run.

If the pulsing requires <1ms between each pulse, the way the driver currently works is it calculates and then does a tight loop calling a nop function to eat processor time. It’s inefficient, but likely not much better than the native OS does. In your case, you’re driving each motor at 400 pulses per rotation at a rate of 5RPM, which requires 2000 pulses per second (500us per pulse).

Using a motor drive that isn’t pul/dir, something that is dedicated to controlling the pulses itself, and has the controller command it via serial/ethernet would be an alternate way to achieve this. There are a lot of motor drives supporting a wide variety of protocols that might provide some options.

If you’re like to discuss the problem and potential solutions in more depth, you can get more real-time communication with us over on Slack.
https://slack.wildernesslabs.co/