Arduino-maestro control

#include <Math.h> // For math functions

#include <PololuMaestro.h>

MiniMaestro maestro(Serial1);

// Define the analog input pin for the trigger signal

const int triggerPin = A0; // Use an analog pin for voltage signals

volatile bool triggerDetected = false; // Flag to indicate trigger detection

int currentPrecalculatedIndex = 0; // Keep track of the current precalculated position

//a few definitions 

struct MotorPosition {
  double Motor1;
  double Motor2;
  double Motor3;
};

MotorPosition motorPositions;
MotorPosition PrecalculatedPositions[200]; // Declare PrecalculatedPositions as a global array
double AlphaBeta[][2] = {
  {-10.72, -9},
  {-7.86, -9.07},
  {-4.76, -8.79},
  {-1.9, -8.8},
  {0.97, -8.95},
  {3.97, -9.18},
}

void InitializePrecalculatedPositions() {
  int index = 0;
  int numEntries = sizeof(AlphaBeta) / (sizeof(AlphaBeta[0]) * 2);

  for (int i = 0; i < numEntries; i++) {
    double alpha = AlphaBeta[i][0];
    double beta = AlphaBeta[i][1];

    // Calculate and store motor positions
    calculateMotorPositionFromDegree(alpha, beta, maximumValue, minimumValue);
    PrecalculatedPositions[index] = motorPositions;
    index++;
  }
}


void setup() {
  // Initialize Serial communication with the Maestro controller
  Serial1.begin(9600);

  // Set up the external trigger pin for interrupt on rising edge
  pinMode(triggerPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(triggerPin), handleTrigger, RISING);
  InitializePrecalculatedPositions();

}

void loop()
{
  if (triggerDetected) {
    if (currentPrecalculatedIndex < sizeof(PrecalculatedPositions) / sizeof(PrecalculatedPositions[0])) {
      // Send the calculated positions to the Maestro controller one by one
      MotorPosition position = PrecalculatedPositions[currentPrecalculatedIndex];

      // Convert angle to microseconds using your conversion function
      int targetMicros1 = angleToMicroseconds(position.Motor1, nullPositionMotor1);
      int targetMicros2 = angleToMicroseconds(position.Motor2, nullPositionMotor2);
      int targetMicros3 = angleToMicroseconds(position.Motor3, nullPositionMotor3);

      // Set the target positions using the maestro.setTarget function
      maestro.setTarget(channelMotor1, targetMicros1);
      maestro.setTarget(channelMotor2, targetMicros2);
      maestro.setTarget(channelMotor3, targetMicros3);

      currentPrecalculatedIndex++;
    }

    // Reset the trigger detection flag
    triggerDetected = false;
  }

  // Your other code can continue here
}

int angleToMicroseconds(double angle, double nullPosition)
{
  // Convert the angle to microseconds using your custom conversion logic
  double oneDegreeInMicroSec = 8.33; // Modify this value as needed
  int targetMicros = nullPosition + (angle * oneDegreeInMicroSec);
  return targetMicros;
}

Above is the code that I wrote in arduino, to control the servo motors. Each time there’s a trigger recieved, it has to keep moving through the precalculated positions.

I have connected the tx of arduino to rx of maestro. How should my c# code be for maestro to run?

public void InitializeSerialPort()
        {
            mySerialPort = new SerialPort("COM10");

            mySerialPort.BaudRate = 9600;
            mySerialPort.Parity = Parity.None;
            mySerialPort.StopBits = StopBits.One;
            mySerialPort.DataBits = 8;
            mySerialPort.Handshake = Handshake.None;
            mySerialPort.RtsEnable = true;

            try
            {
                mySerialPort.Open();
                MessageBox.Show("Serial port opened successfully.");
          }
            catch (UnauthorizedAccessException ex)
            {
                MessageBox.Show("Unauthorized access to the serial port. Check if the port is already in use by another application.");
            }
            catch (IOException ex)
            {
                MessageBox.Show("An I/O error occurred while opening the serial port.");
            }
            catch (ArgumentException ex)
            {
                MessageBox.Show("Invalid serial port settings or name.");
            }
        }

        public void CloseSerialPort()
        {
            if (mySerialPort != null && mySerialPort.IsOpen)
            {
                mySerialPort.Close();
                MessageBox.Show("Serial port closed successfully.");
            }
        }


        public virtual void OnTriggerReceived()
        {
            if (servoController.IsConnected)
            {
                // Check if the event has subscribers (is not null)
                if (TriggerReceived != null)
                {
                    TriggerReceived?.Invoke(this, EventArgs.Empty);
                    
                    //MessageBox.Show("Trigger received ontriggerrecieve()!");
                }
            }
        }

        public void AttachDataReceivedHandler()
        {
            if (servoMotorHandler != null && mySerialPort != null && mySerialPort.IsOpen)
            //if (servoController.IsConnected)
            {
                mySerialPort.DataReceived += new SerialDataReceivedEventHandler(Maestro_TriggerReceived);
            }
            else
            {
                // Log an error or add debugging information
                mainWindow.Log("servoMotorHandler or mySerialPort is null.");
            }
        }


        private void Maestro_TriggerReceived(object sender, SerialDataReceivedEventArgs e)
        {
            mainWindow.allowTrigger = true;
            SerialPort sp = (SerialPort)sender;
       while (sp.BytesToRead >= 12)
            {
                byte[] buffer = new byte[12];
                sp.Read(buffer, 0, 12);

                double motorPosition1 = BitConverter.ToDouble(buffer, 0);
                double motorPosition2 = BitConverter.ToDouble(buffer, 8);
                double motorPosition3 = BitConverter.ToDouble(buffer, 16);

                // Set the motor positions based on the received values
                servoMotorHandler.SetMotorPositionsAndSpeed(motorPosition1, motorPosition2, motorPosition3, servoMotorHandler.speed);

                // Update the UI with the received positions
                mainWindow.BeginInvoke(new Action(() =>
                {
                    mainWindow.PositionTextBox1.Text = motorPosition1.ToString();
                    mainWindow.PositionTextBox2.Text = motorPosition2.ToString();
                    mainWindow.PositionTextBox3.Text = motorPosition3.ToString();
                }));
            }

            OnTriggerReceived();
}

Does it work like this?

Hello.

It is not clear to me what you are trying to do or what you are having trouble with. Could you explain how you want your system to work and what it is doing instead?

Brandon

Hello

I would like to control the motors with arduino, so I have used the set target command. However how should I program the maestro to actually move the motors when its recieving this command from arduino through tx-rx communication?

I have tried two things:

  1. Program the arduino to calculate the positions and send the positions to maestro, so that the c# code recieves these positions and sets target in the c# code. Previoulsy I tried sending a random byte to maestro and it worked. However sending these positions are giving system protocol error, so probably maestro cannot recieve positions.

  2. Program the arduino to calculate the position and set the target in arduino itself. However I am not sure how to recieve this command in the maestro. Thats exactly what I tried to program above but its not working. Or does it not need an additional c# code for arduino trying to control the motors?

It sounds like all you want to do is set the target values for the Maestro channels from your Arduino. If that is the case, you should not need any C# code and the Maestro does not need to be connected to the PC through USB at all (although, connecting it to the Maestro Control Center is a useful way to see what’s going on). Additionally, the Maestro does not need a special script to do this. Sending a set target command from the Arduino to the Maestro’s UART interface should directly set the target on the specified channel if you have it configured properly.

Could you post a copy of your Maestro’s settings file as well as pictures of your setup that show all of your connections? You can save a copy of your Maestro settings file from the “File” drop-down menu of the Maestro Control Center while the controller is connected. Could you clarify what Arduino you’re using? Also, it looks like the Arduino code you posted has a few problems such as undefined variables; could you post an updated copy of the actual Arduino code you are running?

Brandon

1 Like

I figured out that my mistake was not to have converted the values given to set target from microsceconds to degrees, I had multiply it by 4.

void SetTarget(byte channel, double angle, double nullPosition) {
  // Ensure that angle is within the specified range
  angle = max(min_angle, min(max_angle, angle));
  unsigned int targetInUs = DegreeToMicroSeconds(angle, nullPosition);
  targetInUs *= 4;
  maestro.setTarget(channel, targetInUs);
}

It started working as soon as i powered it up. Thank you :+1:

1 Like