Neo 550s and Spark Max - Follow Mode Delay?

Hi all!

I have this weird issue that I would like to get your opinion before I bring it to Rev officially…

So yesterday, Jan. 29th, we tested our triple neo 550 shooter. I tested to make sure that there’s no mechanical issues with gearbox and all the motors were turning in the same direction.

Then, I set Device ID 1 Neo 550 as my master for the triple 550s gearbox. All the SparkMAX was configured with 30A current limit.
I started to take a step-response by applying voltage and increasing it over time. And at right around 6V, one of our members said “It’s smoking!”, so I stopped the testing, and disabled the robot. Right after that, a big cloud of smoke came from Neo 550s, Device 1, the master device.

I did all the math and ensure everything looks good, but it smoked at 6V which didn’t make sense to me and didn’t match with my math at all.

Today, Jan 30th, after replacing the dead motor, I plugged everything back together and started to explore the reasonings.

This graph below is time vs voltage(orange line), and time vs current draw. Purple is the master, and green and blue are the followers. In this case, I set Device 1 as the master, thus purple is Device 1.

This graph below is the same graph, the only exception is that Device 2 is now the master device(purple).

And this graph below is the same except I now have 3 devices independent, without a master device; driving 3 motors in sync.

As you see in the top 2 graphs, there’s a significant delay between the master device and follower devices. In the worst case, there’s about 1 second of delay. Without master device and running 3 motors in sync, things look roughly consistent and stable.

During this test, the firmware version was 1.5.1, and I did reflash them but made no difference.

This led me to believe that we killed our first motor because it was the master device, and taking significantly more loads than other 2 motors. It’s hard to believe that a motor can die with 30A current limiting, but the combination of defect motor and significantly more load on the motor could’ve killed the motor easily.

I would appreciate any input, and comments. I would like to contact Rev eventually, but wanted to get more opinions beforehand.

Thanks.

Can you share your code? We would need to see code to see how you were setting configurations on your master and slave controllers. My first instinct is to say it could be something to do with your control frame setting on your slave motor controllers.

Also, as a sidenote: mods, is it possible to get some type of warning/suggestion when people make posts in a programming-related subforum, similar to how it warns you to search for similar posts, to remind people to share their code? I feel like every other programming help thread starts off without code being shared.

1 Like

It’s nothing complicated…
Here’s the code without the master device. Adding a couple lines to add master, and change the master devices etc etc.

package com.team2898.engine.controls.characterization;

import java.util.function.Supplier
import com.revrobotics.CANSparkMax
import com.revrobotics.CANSparkMax.IdleMode
import com.team2898.engine.logic.LoopManager
import com.team2898.engine.motion.controller.SparkMAXWrapper
import edu.wpi.first.networktables.NetworkTableInstance
import edu.wpi.first.wpilibj.*
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard
import java.io.File
import java.lang.Math.PI

class MotorGroup : TimedRobot() {

    private val ENCODER_EPR = 500

    val master: CANSparkMax = SparkMAXWrapper(2, "Shooter Master")
    val slave0: CANSparkMax = SparkMAXWrapper(1, "Shooter Slave 1")
    val slave1: CANSparkMax = SparkMAXWrapper(3, "Shooter Slave 2")

    lateinit var encoderPosition: Supplier<Double>
    lateinit var encoderRate: Supplier<Double>

    val autoSpeedEntry = NetworkTableInstance.getDefault().getEntry("/robot/autospeed")
    val telemetryEntry = NetworkTableInstance.getDefault().getEntry("/robot/telemetry")

    var priorAutospeed = 0.0
    val numberArray = arrayOfNulls<Number>(6)

    val sb = StringBuilder()

    override fun robotInit() {
        master.apply {
            inverted = true
            idleMode = IdleMode.kCoast
            setSmartCurrentLimit(30)
        }

        slave0.apply {
            inverted = true
            idleMode = IdleMode.kCoast
            setSmartCurrentLimit(30)
        }

        slave1.apply {
            inverted = true
            idleMode = IdleMode.kCoast
            setSmartCurrentLimit(30)
        }

        val encoder = Encoder(8, 9)
        encoder.distancePerPulse = 2 * PI / ENCODER_EPR // rad/s


        encoderPosition = Supplier { encoder.distance }
        encoderRate = Supplier { encoder.rate }

        encoder.reset()

        NetworkTableInstance.getDefault().setUpdateRate(0.010)
        LoopManager.onDisable()
    }

    override fun disabledInit() {
        File("/home/lvuser/logs/test.csv").writeText(sb.toString())
        println("Robot disabled")
        master.set(0.0)
    }

    override fun disabledPeriodic() {}

    override fun robotPeriodic() {
        SmartDashboard.putNumber("encoder_pos", encoderPosition.get())
        SmartDashboard.putNumber("encoder_rate", encoderRate.get())
        SmartDashboard.putNumber("Master Current", master.outputCurrent)
        SmartDashboard.putNumber("Master Voltage", master.busVoltage)
    }

    override fun teleopInit() {
        println("Robot in operator control mode")
    }

    override fun teleopPeriodic() {
        listOf(master, slave0, slave1).forEach { it.apply {

        }}
    }

    override fun autonomousInit() {
        sb.append("t, master v, master a, slave 0 v, slave 0 a, slave 1 v, slave 1 a\n")
        println("Robot in autonomous mode")
    }

    override fun autonomousPeriodic() {
        sb.append("${Timer.getFPGATimestamp()}, ${master.appliedOutput * 12.0}, ${master.outputCurrent}," +
                "${slave0.appliedOutput * 12.0}, ${slave0.outputCurrent}," +
                "${slave1.appliedOutput * 12.0}, ${slave1.outputCurrent}\n")

        val now = Timer.getFPGATimestamp()

        val position = encoderPosition.get()
        val rate = encoderRate.get()

        val battery = RobotController.getBatteryVoltage()

        val motorVolts = master.busVoltage * master.appliedOutput

        val autospeed = autoSpeedEntry.getDouble(0.0)
        priorAutospeed = autospeed

        listOf(master, slave0, slave1).forEach { it.apply {
            set(autospeed)
        }}

        numberArray[0] = now
        numberArray[1] = battery
        numberArray[2] = autospeed
        numberArray[3] = motorVolts
        numberArray[4] = position
        numberArray[5] = rate

        telemetryEntry.setNumberArray(numberArray)
    }
}

I don’t see anything immediately obvious in here, but I do notice you’re using a Spark MAX wrapper class. Can you share what’s in there? Is there any default configuration you do?

Also, when you set the slaves to follow, do you literally just call follow in this file, or is there something special in your wrapper? I’m asking because my team uses a SparkMaxFactory (and equivalents for other motor controllers) to generate preconfigured Spark MAXes, and usually if there’s something weird happening with our controllers its because there’s some setting we have by default in there which is causing problems.

Also, as a general practice, I would highly recommend resetting motor controllers to default on initialization, and for Spark MAXes, running burnFlash() once you configure your parameters. This should help resolve issues with repurposing motor controllers with different configs, and not losing configs if you brown out.

There’s nothing fancy in my wrapper. It sets it to brushless mode, and set the nominal voltage to 12V. it also has default setting to send applied voltage and current to networktable – but that has nothing to to with controller itself.

And yes, all I needed was step response of this shooter. Nothing fancy here, when I call follow, I just call default follow method in CANSparkMax class.

See http://www.revrobotics.com/sparkmax-users-manual/#section-3-3-2-1

There’s two possibilities I can think of that would cause a discrepancy like what you see in your graphs:

  1. The Spark MAXes are actually following the motor controller with a delay. This could be because the master, for whatever reason, is not updating its periodic status frame 0 with enough frequency. You can manually set that for the master to see if that helps.

  2. There isn’t actually a delay in the setting of the motor power, but rather just a delay in the receiving of data back to the roboRIO from the slave controllers. In this case, you want to check the periodic status frames of the slave motor controllers.

Also, I assume that while you’re not plotting it, you see the same delay in the applied output column of your CSV?

Also, just to confirm: your wrapper does nothing different between a master and slave config for the Spark MAX?

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.