Brushed Motor Control?

Hello!

I have an application that requires control of one brushless motor and one brushed motor. I’ve already got an Odrive V3.6 driving the brushless motor, but I see that brushed motors don’t appear to be part of the software. I’m familiar with embedded C/C++ programming and github pull requests.

Would anyone be able to generally advise me on what would need to change? Commutation is easy, but I suppose auto-identification may need to change.

If someone could point me in the right direction, I could probably get this added to the code.

Thanks!

Euhm, what motor are you trying to drive?

Cheers

This one:

And why would you do this if you have an ODrive?

Cheers

Theoretically it is possible to drive a DC motor with an ODrive, because the output state essentially consists of three half bridges. But it seems kind of overkill to use an ODrive for this as you won’t need the special commutation techniques

Yes, that is what I would also say, why not use the ODrive how it is supposed to be used? I mean you pay for it.

Cheers

At its core similar switching concepts are used to control the two different types of motors. You would have to rip out all of the field oriented control parts of the code for one of the two axes. I haven’t mucked around in the firmware too much, but I would probably look at creating a whole new motor object because that is where the majority of the FOC logic lies.

Also, your brushed motor would hook up to only two of the three phases. There is an assumption in the firmware that the sum of the current across the three phases balances out. You would need to be careful to make sure the measure_phase_resistance code is updated to reflect how you have your brushed motor hooked up. One advantage of only using two of the three phases is if you hooked the motor up between phases B and C then you could setup logic to detect short circuits to ground in your motor because the magnitude of current in phase B should always be the opposite of the magnitude of current in phase C. If that isn’t the case then you are losing current somewhere (i.e. a short circuit).

Thanks everyone:

To answer why… We need to control one brushless motor already. I have experience with the VESC and the Odrive, but I like the Odrive more. The brushless motor is the robot wheel drive motor aka the traction motor. The brushed motor is for steering.

So I need to control two motors. The Odrive is perfectly capable (electrically) of driving a brushed motor (i’ve designed several of my own brushless motor controllers).

We were going to use a separate brushed motor controller, but I realized it simplifies our overall system to use a single motor controller architecture. Also our robot is large and driving two traction motors with the Odrive would mean long encoder and phase wires, which is undesirable. I’m comfortable with software development on embedded systems so I’m not afraid to try something unsupported - thats what makes Open Source so valuable!

Thanks @jalbob for the heads up on the measure phase resistance section. Yes I would make a new motor type. The FOC part is easy as commutation on a brushed motor is fixed of course, so it’s more about where all I need to touch the code. If I proceed with this I will try to set up a fork on my github so others can see what I am doing, and hopefully it could lead to a pull request. Even though the Odrive is slightly more controller than I need for this one motor, its low cost, open attitude, and its ability to drive our brushless traction motor all make it attractive here.

I’m taking a look at this code today, and one thing that is not clear to me is the relationship between desired voltage and modulation timing. In a three phase motor, I see that enqueue_modulation_timings accepts parameters mod_alpha and mod_beta, which get passed in to the SVM function to compute modulation timings.

On a brushed motor, working in vector space is not necessary. I’d like to have a very simple function that accepts voltage setpoint and direction and then sets the timings.

Even enqueue_voltage_timings is confusing to me because it uses the terms v_alpha and v_beta. Any tips on how timing relates to phase voltage would be appreciated!

A simple way to hack it might be to connect to phase A and phase B, leave phase C disconnected. Then send voltage commands along v_alpha only, leaving v_beta = 0.

Of course the proper way would be to dispense with the SVM all together. In this case, you may wish to use phase B and phase C (leaving phase A disconnected), since as mentioned this may have current measurement redundancy you could use (not required).

To do the proper way, you need to send your own modulation timings. You may have seen in enqueue_modulation_timings the following:

    next_timings_[0] = (uint16_t)(tA * (float)TIM_1_8_PERIOD_CLOCKS);
    next_timings_[1] = (uint16_t)(tB * (float)TIM_1_8_PERIOD_CLOCKS);
    next_timings_[2] = (uint16_t)(tC * (float)TIM_1_8_PERIOD_CLOCKS);

Here tA, tB and tC is a number from 0.0f to 1.0f that describes “when this phase goes high”. So lets say we forget about phase A (you can hardcode it to 0.5f), and focus on the other two. Say you want to drive max power one way, then you set tB = 0.0f, tC = 1.0f, which will raise phase B high very early in the cycle, and C very late: meaning phase B is high for all of it, and C low for all of it (they are low when they are not high). Conversely, if you send C high super early, and B high very late, it will be C that is high a lot, and B low, so you get max power the other way.

To give just a little voltage along +B, -C, you would set tB=0.45, tC=0.55. This means both are low for a long while, which produces no voltage over the motor, then B goes high first while C is staying low for a bit, then C goes high as well so both sit high and then again there is no voltage.

To recap

  • tB = 0.0f, tC = 1.0f - full bus voltage positive into B, negative into C
  • tB = 1.0f, tC = 0.0f - full bus voltage negative into B, positive into C
  • tB = 0.5f, tC = 0.5f - both phases go up and down together, giving 0 net voltage over motor.
  • You should have tB + tC = 1.0, this means the 0-voltage-over-motor time is shared between high and low sides equally.

Since the modulation timings is just numbers 0.0 to 1.0, to get real volts you need to take your voltage setpoint and divide by your bus voltage. Then map it according to the rules above.

You can even do closed loop current control. Here you would need to bypass the clarke and park transforms in the top of FOC_Current, the calculation of Ialpha, Ibeta, Id, Iq. Instead just have Imotor calculated by 0.5f * (I_B - I_C), essentially averaging the two, but one is negated since it is going out of the ODrive when the other is going in.

Hope that helps, thanks for volunteering for this!

1 Like

Fantastic, thank you Oskar! Yes I would prefer to use current control so I will try that method. But the explanation of the timings is very helpful! I don’t know if I would have figured that out on my own, but with that explained I know I’ll get something working. I’ll update when I make progress. :gear:

A post was split to a new topic: Induction Motor control?

@Eelco Sounds like a great idea! I’ve been wondering about induction motors for robotics for a long time. Also if I’m driving a brushed motor it would be great to be able to use the third leg of the bridge to drive a relay.

But great news:

I got brushed motor support working! It’s still pretty hacky and I accidentally committed extra changes due to some extra spaces that got thrown in. (EDIT: It’s cleaned up now and I’ve replaced the commit link.)
See the commit in my branch here:

Note that right now I’ve really hacked the way I do control. I tell it to do current control, but then take that value as a ratio from -1.0 to 1.0 and use that to drive from minimum to maximum timing. It works, but it doesn’t feel like the right way to go. (EDIT: I replaced that code, so this isn’t true anymore. I now take the “current” to mean a voltage setpoint in volts.) I just wanted to see if it will work, but I will have to think about the interface. Maybe we should add “set_voltage_setpoint” to the command protocol?

@madcowswe Do you have any comments on this code and what I could do to move towards a pull request? Also, I didn’t get current control working as I was a little confused by something. You said

just have Imotor calculated by 0.5f * (I_B - I_C)

Only I didn’t see an Imotor term in the FOC_current function.

Lastly, I am interested in using an encoder with this brushed motor. I suppose I should read that code myself before asking questions, but any thoughts on how to integrate that would be helpful.

Thanks! Really happy Odrive has been working so well for me. :slight_smile:

Thats awesome. I hadnt looked much at the odrive source before, but it looks pleasantly comprehensible, and I am impressed that you got this to work with such a modest number of lines changed. That does bode well for induction motors; though it would be great if someone with experience in the electronics hardware side of things could reflect on just how silly a notion it is.

Yeah! I’d love to see more work done here.

Incidentally, I’ve erased my old messy commit and have made a clean commit with just the necessary changes here:

Note that this now uses the “current” parameter to mean voltage (rather than a ratio of vbus), to better match how something like this was done elsewhere in the code.

Velocity control and position control seem to work with a small change to allow brushed motors to indicate encoder ready without doing offset measurement (which is not needed here).

1 Like

On another note; what about 3 phase motors with DC exited rotors? Given constant excitation current, these are almost identical to a BLDC, as far as the 3-phase circuitry is concerned, I think? That said, making the most of the controllable rotor excitation is kindof the point of such motors… but if there is an unused outputpin on the odrive controller to control the rotor circuitry, again the rest is should be a software problem.