This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

DRV8301-69M-KIT: InstaSpin FOC Stability Problem

Part Number: DRV8301-69M-KIT
Other Parts Discussed in Thread: MOTORWARE

I have a small surface-mount permanent magnet motor mounted to a test stand.  I'm driving it with a slightly modified version of Lab 11 from the InstaSPIN labs.  I removed the speed loop, so I'm directly commanding the phase currents.  At any speed, if I exceed a particular amount of current (20Apk), the controller seems to get lost.  The phase currents become dramatically non-sinusoidal and the motor emits significant noise.  If I reduce the current command, the motor recovers and runs smoothly.  My command is always 100% q-axis current.  I'm commanding the d-axis current to be zero.

I can't tell whether this is the FAST estimator losing its lock on the angle, causing the current regulators to apply the wrong voltages, or if the regulators are falling apart and that's causing the estimator to get lost.  But, not matter what I do with the PI gains, the problem remains.

I posted this previously, and the resulting thread below took a deep dive into any potential issues with the hardware, including some alleged issues with the 8301 itself.  That all came up short, and I'm convinced that the demo kit is just fine and should be able to run this motor at the requested current.  That thread has more or less died because I accidentally marked it solved.  If there ARE any hardware suggestions, I'd like to keep them in that thread just for organization reasons.  Here's that thread, just for reference.

e2e.ti.com/.../869797

I'd like to explore what might be causing the estimator to lose its lock.  I have some new information that wasn't available when I posted previously.

First, I added some extra code that lets me intercept the FAST estimator and run the motor in open-loop mode at a fixed frequency.  The bench load is a DC machine connected to a variable resistor bank, so at a fixed frequency, if I don't vary the load resistance, the motor torque should be constant.  That also means the q-axis current should be constant.  If I run the motor up to 20A with the estimator intact, I can observe the phase voltage (and the commanded d- and q-axis voltages).  When I intercept the estimator with that same voltage, everything stays the same.  However, from here I am able to adjust the phase voltage amplitude until the phase current is at a minimum.  Basically, I can guess-and-check until we're at the maximum torque per amp operating point, which also means that we weren't there before.  This indicates that the phase current applied to the motor has a d-axis component to it.

Second, as I approach the 20Apk ceiling, the estimated angle starts to "wobble".  What I mean is that there's an oscillation on top of the triangular wave (I can extract the data directly from the 28069 and plot it).  This oscillation is better, but still present, when I intercept the estimator and run the motor in open-loop (the FAST estimator still runs in the background; I just don't use the angle to drive the phase voltages).

Third, I added a position sensor to the test stand.  It's a quadrature encoder with an index pulse.  I calibrated the index pulse to the back EMF, so I know the rotor position leads the index pulse by 196° electrical.  When I run the motor with the full control system and spit the angle out on a DAC port, the estimated angle is advanced from the true rotor position.  This isn't just happening at large currents...It happens even at Iq_ref = 0.1pu (5A).  With the measured phase angle advanced, the current controller will supply some demagnetizing current.  This makes sense, because the torque I get per amp, even at low speeds and low currents, is less than what I expect coming from the torque constant.

I've checked the motor parameters and measured them twice, but they're OK.  Adjusting the inductance hasn't helped much.

Right now I'm working on a couple things.  First, I'm using the open-loop operation to try and find where the machine starts to saturate, as I'm sure that's not helping.  Second, I'm going to plot the angle deviation vs. current level.  Finally, I have a library that lets me adjust the flux constant during operation, but I haven't gotten time to use that.  Maybe a combination of this AND the inductance adjustment will help, and I'm sure it will be necessary if the motor is saturating.  But, even at low currents the estimated angle doesn't seem to sync with the measured angle.

I'm happy to run any suggested tests.  I can generate plots of just about anything, either on an oscilloscope or internal to the DSP.  Any help is much appreciated.

Thanks,

Matt

  • Matt Meier said:
    First, I added some extra code that lets me intercept the FAST estimator and run the motor in open-loop mode at a fixed frequency. 

    Have you verified interrupt decimation rates for your added code?

    5.1.5 Decimation Rates
    Decimation rates allow the user to configure each loop rate to meet their code execution requirements. It is recommended to use the default decimation rates as a starting point. The user must verify real-time scheduling is met, verifying that a single interrupt period allows execution of all software in the ISR. This can be done by simply toggling (2) GPIO pins, one at the start and the other at the end of the ISR, and observing on an oscilloscope. If real-time scheduling is not met then InstaSPIN performance is not predictable. Real-time scheduling is required for consistent InstaSPIN performance.


    Below are the default decimation rates:
    // Defines the number of pwm clock ticks per isr clock tick
    // Note: Valid values are 1, 2 or 3 only
    #define USER_NUM_PWM_TICKS_PER_ISR_TICK (1)
    // Defines the number of isr ticks (hardware) per controller clock tick (software)
    #define USER_NUM_ISR_TICKS_PER_CTRL_TICK (1)
    // Defines the number of controller clock ticks per estimator clock tick
    #define USER_NUM_CTRL_TICKS_PER_EST_TICK (1)
    // Defines the number of controller clock ticks per current controller clock tick
    #define USER_NUM_CTRL_TICKS_PER_CURRENT_TICK (1)
    // Defines the number of controller clock ticks per speed controller clock tick
    #define USER_NUM_CTRL_TICKS_PER_SPEED_TICK (10)

    // Defines the number of controller clock ticks per trajectory clock tick

    #define USER_NUM_CTRL_TICKS_PER_TRAJ_TICK (10)

  • I'm running one ISR execution per PWM period, at 30kHz.  The estimator and the current regulators run every time.  The controller doesn't run.

    The added code doesn't affect the performance of the system, or its ability to execute in the allotted time.

    Matt

  • The point of decimation relates directly to motor speed causing skew in the SOC to ISR timing from adding any code into the timing loops. Text suggests one must verify timing via either above method for added code brought into the closed loop timings of the FAST estimator. Default is 1 tick @20kHz could be why at higher PWM 30Khz the estimator falls apart. Tweaking the ticks is simple - what you have to loose.

    Matt Meier said:
    The estimator and the current regulators run every time.

    Yet the tick rates in SOC can skew from PWM triggers if you added any code into the loop. Adjusting the tick rate, chapter 9 page 364.

    There are two main reasons why a tick rate from the interrupt to the controller might be higher than 1:
    • The first reason is to reduce the CPU usage.
    • The second one is to allow a higher PWM frequency and reduce the tick rate so that InstaSPIN can still be executed at higher frequencies. For example, if the PWM frequency is 50 kHz, if no hardware decimation is used (which will be discussed later in this document) the end of conversion ISR is at the same rate, 50 kHz. There needs to be enough time in 1/50 kHz = 20 μs to execute all the functions. If the functions within the ISR take 30 μs, then: Execution time > ISR Period → 30 μs > 20 μs This will lead to interrupt overrun, causing ADC samples to be overwritten, and control timing will also be affected. In cases where the interrupt is shorter than the execution time, it is safe to use different ISR to CTRL tick rates if the following guidelines are taken into consideration.
    Verify there is enough time in the interrupt to execute InstaSPIN. This is because when executing InstaSPIN in the interrupt service routine there has to be enough time to avoid conversion overrun. For example, if an ISR hasn't been serviced, and a second one comes in, the first one was completely lost, and the timing is affected. A good example is shown in Figure 9-7, when the ISR has enough time so that InstaSPIN completes execution with no ISR overrun

  • The default is actually 45kHz PWM, divided to 15kHz for the ISR.  This problem was there when I ran the default values.  I increased the ISR rate so I could get better resolution in the PI controller, and that helped significantly with a different problem.  I could either use 30kHz or 60kHz for the PWM frequency.  I picked 30kHz because it's more than fast enough to get clean currents.

    At 30kHz there is enough time to execute InstaSPIN and my added code.  There's no interrupt overrun; I verified this experimentally.  The system also runs fine at low currents.  If there was an overrun the system wouldn't work at any operating point.

    This problem existed in the original implementation as-is from Ti.  I began to modify the software because the original out-of-the-box software wasn't working (and I did not expect it to work flawlessly without some adjustment).  I added additional code to help track down the problem.  It came after the problem.

    Matt

  • Matt Meier said:
    The default is actually 45kHz PWM, divided to 15kHz for the ISR

    That is totally different from what the InstaSpin Rev.H user guide shows x69 kit (90Mhz) part of the same discussion as x49c (100Mhz). SDK user.h shows 4-5 different PWM frequency but stops at 20Khz. 

    //! \brief Defines the Pulse Width Modulation (PWM) frequency, kHz
    //!
    //#define USER_PWM_FREQ_kHz          ((float32_t)(5.0))      //5KHz PWM frequency
    //#define USER_PWM_FREQ_kHz          ((float32_t)(10.0))     //10KHz PWM frequency
    //#define USER_PWM_FREQ_kHz          ((float32_t)(12.0))     //12KHz PWM frequency
    //#define USER_PWM_FREQ_kHz          ((float32_t)(15.0))     //15KHz PWM frequency
    #define USER_PWM_FREQ_kHz          ((float32_t)(20.0))       //20KHz PWM frequency
    

  • All I can tell you is what the default values are in the user.h file for the demo kit I'm using, and what I changed them to.  There's not an overrun, and the problem is neither a function of loop frequency nor PWM frequency.

    Here's the evidence that this is not an overrun problem.  I set this I/O pin high at the beginning of the main ISR and then set it low at the end of the ISR.  For this plot, I forced every possible code section to run in its entirety, so that we can see the absolute maximum required time for the ISR.  As you can see, the complete ISR (which never runs in its entirety because there are branches) requires ~27us.  In case it comes up, I've also verified that the main asynchronous loop also continues to run.

    Matt

  • I have some new interesting information.  I collected enough data today to demonstrate the following things.

    1.  The current regulators are doing a good job applying current 90° in advance of the estimated angle.  I spit out a 180° square wave on a digital I/O pin and log this on an oscilloscope along with the phase current.  Assuming the estimated angle is correct, the current is where it should be.

    2.  The estimated angle is inaccurate, it leads the real rotor angle, and it changes with the amount of applied current.  I have a position sensor tied to my oscilloscope, and I can trigger on it.  Keeping the rotor at 2000rpm, as I increase gIdq_ref_pu[1] from 0.1 (5A peak) to 0.425 (21.25A), the error in the estimated angle increases.  It also doesn't start out very well (it's originally in advance of the q-axis by almost 30°).  A similar thing happens at 3000rpm, but it doesn't appear to be speed-dependent.  I'll complete this test tomorrow.  I tried changing the value for motor inductance and also the flux constant, both together and separately, but that doesn't provide much help.  However, if I increase the inductance value too much from the reported value, the current starts to have a low-frequency (2-3Hz) oscillation that I can cure by adjusting the regulator gains.

    3.  When I take over control and run the motor in open-loop mode, I can get significantly more torque per amp than what the controller provides at any operating point.  This also makes sense, considering the controller is applying a significant amount of demagnetizing current.

    Matt

  • The 3krpm test shows a similar trend across the current command range.  I also verified the following during the open-loop test.

    1.  The current angle when we have minimum phase current is aligned 90° ahead of the rotor (it's on the q-axis, in-phase with the back EMF).

    2.  The torque-per-amp at this operating point is what is predicted by the flux constant.

    3.  The estimator angle still leads the rotor, and the angle is approximately the same as it was when the machine was running in closed-loop using the estimator angle.

    The first two are obviously to be expected, but it's nice when the measurements all match up with each other and with the theoretical prediction.  I like to know the test was valid.

  • Matt Meier said:
    I tried changing the value for motor inductance and also the flux constant, both together and separately, but that doesn't provide much help. 

    Interesting point and did you check to see table 8.1 patch was applied (2SPRUHJ1H–January 2013–Revised June 2019) update? Find it odd they show the entire patch if it existed in older control suites.

    8.1.1 softwareUpdate1p6() - Function is Required in User Code
    The function softwareUpdate1p6() is a work-around for a bug in InstaSPIN-FOC v1.6 to correct how inductance is converted from Henries to per unit value when using the inductance from user.h. This function needs to be called whenever motor parameters are loaded from user.h when using InstaSPINFOC v1.6.
    The following fixes are in this patch:
    • Added a maximum per inductance value. Thus, we wanted the per unit inductance values scaled with respect to this maximum value. This would impact the Q format of the inductance value as well.

    • Set the current controller gain values (Id/Iq current controllers) based on these new per unit inductance values.

    Below is the source code to the patch, it is used in every InstaSPIN-FOC v1.6 and InstaSPIN-MOTION lab example.

  • I can't apply that function directly because it calls constants for all its calculations and I wanted to observe the estimator as I changed those on the fly, but yes.

  • Did you have any waveforms of the motor phase current to show the issue? What's the peak phase current of the motor when you set the maximum torque current? And what's the USER_ADC_FULL_SCALE_CURRENT_A, USER_MOTOR_MAX_CURRENT, and USER_MAX_VS_MAG_PU in your project? 

    Please monitor the gMotorVars.Vs in CCS watch window and check if it is close to USER_MAX_VS_MAG_PU.

  • By now I have tons of plots, and I'm happy to share them or take more data.  I can process it however you like.  I'm not sure I understand what you mean about the peak phase current when I set the maximum torque current.  Do you mean the peak phase current at the max torque-per-amp operating point?  It varies based on the torque.  The torque constant is ~7.8mNm per amp, and at the optimal operating point we do get approximately that same torque per amp.  It does begin to fall off above 20Apk, just slightly, because the machine is starting to saturate a little bit.

    USER_ADC_FULL_SCALE_CURRENT_A = 82.5.  It's a DRV8301-69M-KIT, so we just left this one alone.

    USER_IQ_FULL_SCALE_CURRENT_A = 50

    USER_MOTOR_MAX_CURRENT = 40

    USER_MAX_VS_MAG_PU = 0.5

    I attached a series of waveforms below that show the measured currents as they degrade.  Plot 1 is at gIdq_ref_pu[1] = 0.425, Plot 2 is 0.45, and Plot 3 is 0.475.  For the first plot, the output |Vs| is ~0.068.  It starts to oscillate significantly for the higher currents.

  • I should add that I also have plots of the estimator angle, measured phase current in the C2000, and gVdq_out_pu for these tests (although not at exactly the same time as the oscilloscope plots).

  • More information.

    I increased the cutoff frequency in the voltage measurement circuit because, despite my reported test in the previous thread, I wasn't comfortable running at an electrical frequency so close to the filter's cutoff frequency.  I had 10,000pF capacitors in the right package for the demo kit so I just used those, putting the cutoff frequency at 3356Hz (instead of the 335.6Hz as the kit was built).  I'm using 30kHz PWM frequency, so the feedback ripple still isn't a problem.

    Then I re-ran the test where I swept the current reference from 0.15pu (7.5A) to 0.4pu (20A) and watched the deviation in the applied current angle.  It's about the same.  The estimator still falls apart at about the same current, too.  This confirms solidly that the feedback filter isn't drastically affecting the system, at least at 2000rpm.

    I also have played around with applying a fixed offset to the estimated angle before feeding it into the PI controllers.  At first glance, this does seem to work...I can get the current to align with the motor's back EMF.  It's a promising kludge, but that's a lot of data I need to collect to generate the lookup table for angle offsets across the operating range.  Besides, it's not a very satisfying solution.

    Matt

  • I implemented that library that lets me change the flux constant while the motor is running, and that didn't do much, either.  I also have tried just changing the values in user.h and recompiling the software but the results are about the same, so I'm pretty sure the live adjustment isn't flawed.

    Changing the motor parameters (inductance and flux constant) DOES change things a little, but it doesn't come close to solving the angle error problem.  It does not help at all with the thing where the controller completely fails.

    To recap, I think we have two problems.  I'm not sure whether they're related.  The first problem is that I can't ask for much more than 0.4pu (20A peak) q-axis current without the controller completely falling apart.  This may be related to the stator steel saturating...it looks like there is some mild saturation around this operating point.  This shouldn't cause the system to completely get lost, and I would expect that we should be able to adjust the motor model to compensate for this.  However, adjusting the permeability-dependent motor variables doesn't make any difference here.  I think I would accept the explanation that the Ti estimator simply can't handle even mild saturation.  I'd just like someone to confirm that before we give up trying.

    While investigating this, I've come across a second problem.  It looks like the estimator's angle is in advance of the rotor by 30° or more, even at low currents, well before the stator steel starts to saturate.  This one we can't tolerate.  I think I have a kludge that will work for now, but it's tedious and clunky and it doesn't fix the problem for the next project.

    Matt

  • Yanming, you requested my user.h in the previous thread.  I attached it at the bottom.  I also have attached a plot of the applied current phase angle (measured against a position sensor) vs. magnitude.

    6131.user.h

  • Matt Meier said:
    Here's the evidence that this is not an overrun problem.

    Hi Matt,

    Yet how can we be sure if the PWM drive signal is not included in the capture to indicate FAST is not being skewed at that high current load? The point of decimation timing mainISR GPIO's is prove ROM calls stay synchronous with EOC interrupt triggers, CPU interrupt timing. My capture showed GPIO goes high about mid point of each PWM 50µs period and ends 10µs before the falling edged of each 50µs period. The GPIO total high on time is 32µs remains very consistent at any speed. Any frequency above 20Khz may require adding few more ticks mainISR timing according to text. Have you tried 2-3 ticks yet?

    I noticed your above posted current captures appear lot like today's several attempts LAB13 field weakening (FW). Rotor actually slowed down when it should speed up according to SDK Pg.114 Fig.77 XY graph of Bfw loop (id/iq) below Bmtpw (torque/A) loop. It seems the current likes to jump upward almost double when FW switch is enabled real time. I wonder if that is what happens when current jumps since both switch modes do exist in ROM.

  • Gl said:

    Hi Matt,

    Yet how can we be sure if the PWM drive signal is not included in the capture to indicate FAST is not being skewed at that high current load? The point of decimation timing mainISR GPIO's is prove ROM calls stay synchronous with EOC interrupt triggers, CPU interrupt timing. My capture showed GPIO goes high about mid point of each PWM 50µs period and ends 10µs before the falling edged of each 50µs period. The GPIO total high on time is 32µs remains very consistent at any speed. Any frequency above 20Khz may require adding few more ticks mainISR timing according to text. Have you tried 2-3 ticks yet?

    Yes.  I've used multiple PWM frequencies and ISR decimations, including the defaults.  The two problems remain, and are not qualitatively any different.

    The plot shows that our present system is OK because the total ISR time for our controller, even with the extra code, is 26us.  That includes the FAST estimator, which is called from the ISR.  There's plenty of time to fit that into a 30kHz ISR loop.  I don't really know what you mean by FAST being skewed by the current load.  But, I do know the timing for reading the shunt current is important.  Regardless, the code from the start of the ISR through the ADC reading is completely unchanged.

    Gl said:

    I noticed your above posted current captures appear lot like today's several attempts LAB13 field weakening (FW). Rotor actually slowed down when it should speed up according to SDK Pg.114 Fig.77 XY graph of Bfw loop (id/iq) below Bmtpw (torque/A) loop. It seems the current likes to jump upward almost double when FW switch is enabled real time. I wonder if that is what happens when current jumps since both switch modes do exist in ROM.

    I really don't know, but depending on your field weakening limits you might actually require double the total phase current to get the speed you're requesting.  How does that relate to this thread in particular?

  • Matt Meier said:
      I don't really know what you mean by FAST being skewed by the current load. 

    Point of decimation is to probe one or several PWM drives with GPIO tick capture to show InstaSpin timing is not skewed outside each PWM cycle especially at high currents. That can not be determined by the GPIO tick capture alone as posted. The InstaSpin SPRUHJ1H–January 2013–Revised June 2019 states the default tick times for 20Khz modulation. One has to follow the procedure in the manual to determine SW decimation times or to add more ticks.

    Matt Meier said:
    I really don't know, but depending on your field weakening limits you might actually require double the total phase current to get the speed you're requesting. 

    That would seem logical perhaps an incorrect conclusion. According to lab 13 guide negative id current is injected into the air gap to reduce torque as to increase rotor speed. Oddly the lower FW loop graph indicates reduced Id should occur but does not. What occurred was DC Bus voltage drooped as current shot upward near peak of two series supplies. Note the red loop is FW and green is MTPA maximum torque per amp, same on your x69 ROM. The flash/ram switch method may differ but is there non the less.

        

  • Matt Meier said:
    Changing the motor parameters (inductance and flux constant) DOES change things a little, but it doesn't come close to solving the angle error problem.  It does not help at all with the thing where the controller completely fails

    Have you added online RS calculations into your SW to account for wire resistance changes under such high phase currents? That seems to be a point made via x49 kit lab is02/is10, reason for RS online offset recalibration calls. Text off hand infers the stator inductance changes with temperature rise. If you put a thermal probe on the stator does it rapidly rise during this current harmonic you posted above?

  • We have also fiddled with Rs, but no positive results.  20A isn't much in these windings, so the motor doesn't get warm when we run it on the bench.  We don't need to use Rs_online because the resistance will be predictable in our end application.

    The inductance (and the permeability) won't be changing much.  Besides that the motor doesn't get warm, we're talking about only a few percent even if the motor reaches 100°C.  Besides, we've played with those values and didn't see any effect.

  • Gl said:

    Point of decimation is to probe one or several PWM drives with GPIO tick capture to show InstaSpin timing is not skewed outside each PWM cycle especially at high currents. That can not be determined by the GPIO tick capture alone as posted. The InstaSpin SPRUHJ1H–January 2013–Revised June 2019 states the default tick times for 20Khz modulation. One has to follow the procedure in the manual to determine SW decimation times or to add more ticks.

    The purpose of decimation is to allow the ISR and all the required tasks to execute before the ISR is called again.   If the main ISR takes longer than the PWM period then the system won't work.  Another consideration is that the main background loop also needs to operate because it includes some important, although non-time-critical, tasks.  I checked this, too.  The main loop has ample time to continue executing between ISRs.  We actually DID have an overrun problem on an AVR we developed off of this chip, but that was an extreme case.  I think I understand what you mean by "skew" now.  It's not a problem, and it's not related to the amount of current in the motor.

    We aren't field-weakening, at least not on purpose.  We're manually controlling the motor in MTPA operation, by commanding Id = 0.  The controller is regulating Id to zero internally, but we can measure the quadrature currents and Id is not zero, which means the angle is wrong.  The plot of the estimator angle vs real rotor angle verifies this.

  • Hi Matt,

    1. Please monitor the gMotorVars.Vs in the watch window, check if its value is close to USER_MAX_VS_MAG_PU.  The below code should be in updateGlobalVariables(), if no, please add it.

    // calculate vector Vs in per units
    gMotorVars.Vs = _IQsqrt(_IQmpy(gMotorVars.Vd, gMotorVars.Vd) + _IQmpy(gMotorVars.Vq, gMotorVars.Vq));

    2. You need to calibrate the offset of the encoder first and remove the offset in angle calculation, and then compare the angle from the encoder to the angle from the estimator. The error should be very small in any state.

    3. Increase USER_MOTOR_RES_EST_CURRENT, USER_MOTOR_IND_EST_CURRENT and USER_MOTOR_FLUX_EST_FREQ_Hz. And use lab02c to identify the motor again. It seems like the motor parameters are not real identification values.

    4. It seems like you are using 30kHz ISR and current control bandwidth, the ISR code execution time is very close to the ISR period. You may try 40kHz PWM frequency and set the USER_NUM_PWM_TICKS_PER_ISR_TICK to 2. The 20kHz may be enough for controlling the motor.  And don't need to change the hardware if there is no current sampling saturation, please check the peak-peak phase current of the motor.

    5. Could you please post the main project file and show you how to set the motor current? What's reference torque current, speed, and load if there is a stability problem? How to add the loading on the motor? Did the adding load over the rated torque of the motor?

  • Yanming Luo said:

    Hi Matt,

    1. Please monitor the gMotorVars.Vs in the watch window, check if its value is close to USER_MAX_VS_MAG_PU.  The below code should be in updateGlobalVariables(), if no, please add it.

    // calculate vector Vs in per units
    gMotorVars.Vs = _IQsqrt(_IQmpy(gMotorVars.Vd, gMotorVars.Vd) + _IQmpy(gMotorVars.Vq, gMotorVars.Vq));

    We did, when I took those current traces on the 13th.  The value I for those plots was ~0.068.  It's nowhere close to USER_MAX_VS_MAG_PU.

    Yanming Luo said:

    2. You need to calibrate the offset of the encoder first and remove the offset in angle calculation, and then compare the angle from the encoder to the angle from the estimator. The error should be very small in any state.

    {/quote]

    We did.  We calibrate the sensor by driving the machine as an unloaded generator and integrating the voltage to get the motor flux.  From there we know the location of the d and q axes.  We could also just use the back EMF zero crossings, but we took the extra step so we could match the zero crossings to the flux waveform.  The estimator angle is converted to a square wave.  This square wave matches perfectly with the applied current, so I know the PI controller is properly regulating the d- and q-axis currents.  However, the error between the estimator angle and the encoder angle is not small.  That said, I'm adding a short post after this one that casts doubt on my above post about changing the voltage feedback filters.

    3. Increase USER_MOTOR_RES_EST_CURRENT, USER_MOTOR_IND_EST_CURRENT and USER_MOTOR_FLUX_EST_FREQ_Hz. And use lab02c to identify the motor again. It seems like the motor parameters are not real identification values.

    Motor ID fails for this motor; I'm not sure why, but we never really bothered with it.  We measured the motor parameters directly.  They match the measurements from the customer, and they also are close to the values from finite element analysis.  If we have to do a motor ID then we can try to figure out why it fails.

    Yanming Luo said:

    4. It seems like you are using 30kHz ISR and current control bandwidth, the ISR code execution time is very close to the ISR period. You may try 40kHz PWM frequency and set the USER_NUM_PWM_TICKS_PER_ISR_TICK to 2. The 20kHz may be enough for controlling the motor.  And don't need to change the hardware if there is no current sampling saturation, please check the peak-peak phase current of the motor.

    After Gl returned to the decimation topic, I tried several combinations so I could put that idea to rest.  Among them were the MotorWare defaults (45kHz PWM and 15kHz main ISR) and the combination you suggested.  We also used 20kHz PWM with a 20kHz ISR, and 20kHz PWM with a 10kHz ISR, and 60kHz PWM with a 30kHz ISR.

    The peak-to-peak phase current is 40A (20Apk) right before the system starts to fail.  That's right around 50% of the full scale measurement capacity of this demo kit.

    I can change the voltage feedback filter back if you like.

    Yanming Luo said:

    5. Could you please post the main project file and show you how to set the motor current? What's reference torque current, speed, and load if there is a stability problem? How to add the loading on the motor? Did the adding load over the rated torque of the motor?

    USER_MAX_VS_MAG_PU

    I attached the main.c that we're using.  All the failures I've reported happen with this program.  The only difference is that I can't run this one open-loop and it doesn't have the internal plotting added.  The only difference between this file and proj_lab11.c is that I removed the speed loop.

    We set the motor current directly in the debugger window.  The reference current and speed are listed with the current plots in my post from the 13th.

    The load was ~130mNm or so.  It is hard to tell after the oscillations start, but if you want then I can add a digital filter to average the torque meter readout.  It is supplied by a DC generator connected to a variable resistor bank.  I can vary the load torque as needed.

    There is no torque rating for this motor yet.  I can't run it well enough with this kit to do a thermal test, so we don't know what its limits are.  The motor was designed for 150mNm.

    Matt

  • Matt Meier said:

    More information.

    I increased the cutoff frequency in the voltage measurement circuit because, despite my reported test in the previous thread, I wasn't comfortable running at an electrical frequency so close to the filter's cutoff frequency.  I had 10,000pF capacitors in the right package for the demo kit so I just used those, putting the cutoff frequency at 3356Hz (instead of the 335.6Hz as the kit was built).  I'm using 30kHz PWM frequency, so the feedback ripple still isn't a problem.

    Then I re-ran the test where I swept the current reference from 0.15pu (7.5A) to 0.4pu (20A) and watched the deviation in the applied current angle.  It's about the same.  The estimator still falls apart at about the same current, too.  This confirms solidly that the feedback filter isn't drastically affecting the system, at least at 2000rpm.

    This may not be 100% accurate.  This morning I found that the position sensor had moved.  I think we may have adjusted the torque stand alignment before running this test, and may have bumped the sensor.  I'm going to redo this trial to see if the angle error is any better.  Regardless, the estimator still falls apart at ~20Apk as usual, and that's not related to the sensor in any way.

  • Hi Matt,

    1. I don't think the ~0.068 is the correct value for gMotorVars.Vs. Please help to verify it again.

    2. You might tune the identification variables as I mentioned above, try to achieve the motor parameters by identification. It should be useful for running the motor on this kit.

    3. You don't need to change the voltage sensing circuit. Recommend the cut-off frequency of the voltage sampling filter is between 300~1000Hz. The high-frequency motor can use a higher cut-off frequency. 

    4. Could you please post the main.c again since I can't find it in this thread?

    5. Can you try to add a lower loading on the motor at the same speed if possible? To look at what happen.

  • 1.  It's correct.  It also makes sense if you construct the phasor diagram:

    The bus voltage is set to 28V because previously, when we were initially having trouble at 6500rpm, we wanted to remove the impact of the phase voltage hitting the modulation limit.  The phase voltage at this operating point is only 2.3V.  The bus voltage might have been a little high when I ran that test because it's unregulated, so it changes based on time of day.

    I've reduced and increased the bus voltage and seen no effect unless I reduce it so far that we hit the modulation limits.

    2.  I'll work on it after I re-do my test from 3 and change the hardware back.

    3.  I'll change it back after re-doing the phase vs. current plot with the new filter.

    4.  Sorry.  I'm not sure what happened.  I tried again.

    5.  I have.  The currents are clean and steady, but they are not in the correct position.  Take a look at my plot earlier in this thread with the current request vs. phase angle, from Feb. 18.  The torque does change with phase current like you would expect, apart from the phasing problem.

    4162.main.c
    // --COPYRIGHT--,BSD
    // Copyright (c) 2015, Texas Instruments Incorporated
    // All rights reserved.
    //
    // Redistribution and use in source and binary forms, with or without
    // modification, are permitted provided that the following conditions
    // are met:
    //
    // *  Redistributions of source code must retain the above copyright
    //    notice, this list of conditions and the following disclaimer.
    //
    // *  Redistributions in binary form must reproduce the above copyright
    //    notice, this list of conditions and the following disclaimer in the
    //    documentation and/or other materials provided with the distribution.
    //
    // *  Neither the name of Texas Instruments Incorporated nor the names of
    //    its contributors may be used to endorse or promote products derived
    //    from this software without specific prior written permission.
    //
    // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
    // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
    // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
    // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
    // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
    // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    // --/COPYRIGHT
    //! \file   solutions/instaspin_foc/src/proj_lab011.c
    //! \brief A Simplified Example without Controller Module
    //!
    //! (C) Copyright 2015, Texas Instruments, Inc.
    
    //! \defgroup PROJ_LAB11 PROJ_LAB11
    //@{
    
    //! \defgroup PROJ_LAB11_OVERVIEW Project Overview
    //!
    //! A Simplified Example without Controller Module
    //! 
    //!
    
    // **************************************************************************
    // the includes
    
    // system includes
    #include <math.h>
    #include "headers/main.h"
    
    #ifdef FLASH
    #pragma CODE_SECTION(mainISR,"ramfuncs");
    #endif
    
    // Include header files used in the main function
    
    // **************************************************************************
    // the defines
    
    // **************************************************************************
    // the globals
    
    CLARKE_Handle   clarkeHandle_I;  //!< the handle for the current Clarke
                                     //!< transform
    CLARKE_Obj      clarke_I;        //!< the current Clarke transform object
    
    CLARKE_Handle   clarkeHandle_V;  //!< the handle for the voltage Clarke
                                     //!< transform
    CLARKE_Obj      clarke_V;        //!< the voltage Clarke transform object
    
    EST_Handle      estHandle;       //!< the handle for the estimator
    
    PID_Obj         pid[3];          //!< three objects for PID controllers
                                     //!< 0 - Speed, 1 - Id, 2 - Iq
    PID_Handle      pidHandle[3];    //!< three handles for PID controllers
                                     //!< 0 - Speed, 1 - Id, 2 - Iq
    uint16_t        pidCntSpeed;     //!< count variable to decimate the execution
                                     //!< of the speed PID controller
    
    IPARK_Handle    iparkHandle;     //!< the handle for the inverse Park
                                     //!< transform
    IPARK_Obj       ipark;           //!< the inverse Park transform object
    
    SVGEN_Handle    svgenHandle;     //!< the handle for the space vector generator
    SVGEN_Obj       svgen;           //!< the space vector generator object
    
    #ifdef CSM_ENABLE
    #pragma DATA_SECTION(halHandle,"rom_accessed_data");
    #endif
    HAL_Handle      halHandle;       //!< the handle for the hardware abstraction
                                     //!< layer (HAL)
    
    HAL_PwmData_t   gPwmData = {_IQ(0.0),_IQ(0.0),_IQ(0.0)};  //!< contains the
                                     //!< pwm values for each phase.
                                     //!< -1.0 is 0%, 1.0 is 100%
    
    HAL_AdcData_t   gAdcData;        //!< contains three current values, three
                                     //!< voltage values and one DC buss value
    
    MATH_vec3       gOffsets_I_pu = {_IQ(0.0),_IQ(0.0),_IQ(0.0)};  //!< contains
                                     //!< the offsets for the current feedback
    
    MATH_vec3       gOffsets_V_pu = {_IQ(0.0),_IQ(0.0),_IQ(0.0)};  //!< contains
                                     //!< the offsets for the voltage feedback
    
    MATH_vec2       gIdq_ref_pu = {_IQ(0.0),_IQ(0.0)};  //!< contains the Id and
                                     //!< Iq references
    
    MATH_vec2       gVdq_out_pu = {_IQ(0.0),_IQ(0.0)};  //!< contains the output
                                     //!< Vd and Vq from the current controllers
    
    MATH_vec2       gIdq_pu = {_IQ(0.0),_IQ(0.0)};  //!< contains the Id and Iq
                                     //!< measured values
    
    #ifdef CSM_ENABLE
    #pragma DATA_SECTION(gUserParams,"rom_accessed_data");
    #endif
    USER_Params     gUserParams;
    
    volatile MOTOR_Vars_t gMotorVars = MOTOR_Vars_INIT;   //!< the global motor
                                     //!< variables that are defined in main.h and
                                     //!< used for display in the debugger's watch
                                     //!< window
    
    #ifdef FLASH
    // Used for running BackGround in flash, and ISR in RAM
    extern uint16_t *RamfuncsLoadStart, *RamfuncsLoadEnd, *RamfuncsRunStart;
    
    #ifdef CSM_ENABLE
    extern uint16_t *econst_start, *econst_end, *econst_ram_load;
    extern uint16_t *switch_start, *switch_end, *switch_ram_load;
    #endif
    
    #endif
    
    #ifdef DRV8301_SPI
    // Watch window interface to the 8301 SPI
    DRV_SPI_8301_Vars_t gDrvSpi8301Vars;
    #endif
    
    #ifdef DRV8305_SPI
    // Watch window interface to the 8305 SPI
    DRV_SPI_8305_Vars_t gDrvSpi8305Vars;
    #endif
    
    _iq gFlux_pu_to_Wb_sf;
    
    _iq gFlux_pu_to_VpHz_sf;
    
    _iq gTorque_Ls_Id_Iq_pu_to_Nm_sf;
    
    _iq gTorque_Flux_Iq_pu_to_Nm_sf;
    
    _iq gSpeed_krpm_to_pu_sf = _IQ((float_t)USER_MOTOR_NUM_POLE_PAIRS * 1000.0
                / (USER_IQ_FULL_SCALE_FREQ_Hz * 60.0));
    
    _iq gSpeed_hz_to_krpm_sf = _IQ(60.0 / (float_t)USER_MOTOR_NUM_POLE_PAIRS
                / 1000.0);
    
    // **************************************************************************
    // the functions
    void main(void)
    {
        // IMPORTANT NOTE: If you are not familiar with MotorWare coding guidelines
        // please refer to the following document:
        // C:/ti/motorware/motorware_1_01_00_1x/docs/motorware_coding_standards.pdf
    
        // Only used if running from FLASH
        // Note that the variable FLASH is defined by the project
    
        #ifdef FLASH
        // Copy time critical code and Flash setup code to RAM
        // The RamfuncsLoadStart, RamfuncsLoadEnd, and RamfuncsRunStart
        // symbols are created by the linker. Refer to the linker files.
        memCopy((uint16_t *)&RamfuncsLoadStart,(uint16_t *)&RamfuncsLoadEnd,
                (uint16_t *)&RamfuncsRunStart);
    
        #ifdef CSM_ENABLE
    	  //copy .econst to unsecure RAM
    	  if(*econst_end - *econst_start)
    		{
    		  memCopy((uint16_t *)&econst_start,(uint16_t *)&econst_end,(uint16_t *)&econst_ram_load);
    		}
    
    	  //copy .switch ot unsecure RAM
    	  if(*switch_end - *switch_start)
    		{
    		  memCopy((uint16_t *)&switch_start,(uint16_t *)&switch_end,(uint16_t *)&switch_ram_load);
    		}
        #endif
      	#endif
    
        // initialize the Hardware Abstraction Layer  (HAL)
        // halHandle will be used throughout the code to interface with the HAL
        // (set parameters, get and set functions, etc) halHandle is required since
        // this is how all objects are interfaced, and it allows interface with
        // multiple objects by simply passing a different handle. The use of
        // handles is explained in this document:
        // C:/ti/motorware/motorware_1_01_00_1x/docs/motorware_coding_standards.pdf
        halHandle = HAL_init(&hal,sizeof(hal));
    
        // check for errors in user parameters
        USER_checkForErrors(&gUserParams);
    
        // store user parameter error in global variable
        gMotorVars.UserErrorCode = USER_getErrorCode(&gUserParams);
    
        // do not allow code execution if there is a user parameter error. If there
        // is an error, the code will be stuck in this forever loop
        if(gMotorVars.UserErrorCode != USER_ErrorCode_NoError)
        {
            for(;;)
            {
                gMotorVars.Flag_enableSys = false;
            }
        }
    
        // initialize the Clarke modules
        // Clarke handle initialization for current signals
        clarkeHandle_I = CLARKE_init(&clarke_I,sizeof(clarke_I));
        // Clarke handle initialization for voltage signals
        clarkeHandle_V = CLARKE_init(&clarke_V,sizeof(clarke_V));
    
        // initialize the estimator
        estHandle = EST_init((void *)USER_EST_HANDLE_ADDRESS,0x200);
    
        // initialize the user parameters
        // This function initializes all values of structure gUserParams with
        // values defined in user.h. The values in gUserParams will be then used by
        // the hardware abstraction layer (HAL) to configure peripherals such as
        // PWM, ADC, interrupts, etc.
        USER_setParams(&gUserParams);
    
        // set the hardware abstraction layer parameters
        // This function initializes all peripherals through a Hardware Abstraction
        // Layer (HAL). It uses all values stored in gUserParams.
        HAL_setParams(halHandle,&gUserParams);
    
        #ifdef FAST_ROM_V1p6
        {
            // These function calls are used to initialize the estimator with ROM
            // function calls. It needs the specific address where the controller
            // object is declared by the ROM code.
            CTRL_Handle ctrlHandle = CTRL_init((void *)USER_CTRL_HANDLE_ADDRESS
                                ,0x200);
            CTRL_Obj *obj = (CTRL_Obj *)ctrlHandle;
    
            // this sets the estimator handle (part of the controller object) to
            // the same value initialized above by the EST_init() function call.
            // This is done so the next function implemented in ROM, can
            // successfully initialize the estimator as part of the controller
            // object.
            obj->estHandle = estHandle;
    
            // initialize the estimator through the controller. These three
            // function calls are needed for the F2806xF/M implementation of
            // InstaSPIN.
            CTRL_setParams(ctrlHandle,&gUserParams);
            CTRL_setUserMotorParams(ctrlHandle);
            CTRL_setupEstIdleState(ctrlHandle);
        }
        #else
        {
            // initialize the estimator. These two function calls are needed for
            // the F2802xF implementation of InstaSPIN using the estimator handle
            // initialized by EST_init(), these two function calls configure the
            // estimator, and they set the estimator in a proper state prior to
            // spinning a motor.
            EST_setEstParams(estHandle,&gUserParams);
            EST_setupEstIdleState(estHandle);
        }
        #endif
    
        // disable Rs recalculation
        // **NOTE: changing the input parameter from 'false' to 'true' will cause
        // **      run time issues. Lab11 does not support Rs Online function
        //
        EST_setFlag_enableRsRecalc(estHandle,false);
    
        // set the number of current sensors
        setupClarke_I(clarkeHandle_I,USER_NUM_CURRENT_SENSORS);
    
        // set the number of voltage sensors
        setupClarke_V(clarkeHandle_V,USER_NUM_VOLTAGE_SENSORS);
    
        // set the pre-determined current and voltage feeback offset values
        gOffsets_I_pu.value[0] = _IQ(I_A_offset);
        gOffsets_I_pu.value[1] = _IQ(I_B_offset);
        gOffsets_I_pu.value[2] = _IQ(I_C_offset);
        gOffsets_V_pu.value[0] = _IQ(V_A_offset);
        gOffsets_V_pu.value[1] = _IQ(V_B_offset);
        gOffsets_V_pu.value[2] = _IQ(V_C_offset);
    
        // initialize the PID controllers
        {
            // This equation defines the relationship between per unit current and
            // real-world current. The resulting value in per units (pu) is then
            // used to configure the controllers
            _iq maxCurrent_pu = _IQ(USER_MOTOR_MAX_CURRENT
                        / USER_IQ_FULL_SCALE_CURRENT_A);
    
            // This equation uses the scaled maximum voltage vector, which is
            // already in per units, hence there is no need to include the #define
            // for USER_IQ_FULL_SCALE_VOLTAGE_V
            _iq maxVoltage_pu = _IQ(USER_MAX_VS_MAG_PU * USER_VD_SF);
    
            float_t fullScaleCurrent = USER_IQ_FULL_SCALE_CURRENT_A;
            float_t fullScaleVoltage = USER_IQ_FULL_SCALE_VOLTAGE_V;
            float_t IsrPeriod_sec = 1.0 / USER_ISR_FREQ_Hz;
            float_t Ls_d = USER_MOTOR_Ls_d;
            float_t Ls_q = USER_MOTOR_Ls_q;
            float_t Rs = USER_MOTOR_Rs;
    
            // This lab assumes that motor parameters are known, and it does not
            // perform motor ID, so the R/L parameters are known and defined in
            // user.h
            float_t RoverLs_d = Rs / Ls_d;
            float_t RoverLs_q = Rs / Ls_q;
    
            // For the current controller, Kp = Ls*bandwidth(rad/sec)  But in order
            // to be used, it must be converted to per unit values by multiplying
            // by fullScaleCurrent and then dividing by fullScaleVoltage.  From the
            // statement below, we see that the bandwidth in rad/sec is equal to
            // 0.25/IsrPeriod_sec, which is equal to USER_ISR_FREQ_HZ/4. This means
            // that by setting Kp as described below, the bandwidth in Hz is
            // USER_ISR_FREQ_HZ/(8*pi).
            _iq Kp_Id = _IQ((0.25 * Ls_d * fullScaleCurrent) / (IsrPeriod_sec
                        * fullScaleVoltage));
    
            // In order to achieve pole/zero cancellation (which reduces the
            // closed-loop transfer function from a second-order system to a
            // first-order system), Ki must equal Rs/Ls.  Since the output of the
            // Ki gain stage is integrated by a DIGITAL integrator, the integrator
            // input must be scaled by 1/IsrPeriod_sec.  That's just the way
            // digital integrators work.  But, since IsrPeriod_sec is a constant,
            // we can save an additional multiplication operation by lumping this
            // term with the Ki value.
            _iq Ki_Id = _IQ(RoverLs_d * IsrPeriod_sec);
    
            // Now do the same thing for Kp for the q-axis current controller.
            // If the motor is not an IPM motor, Ld and Lq are the same, which
            // means that Kp_Iq = Kp_Id
            _iq Kp_Iq = _IQ((0.25 * Ls_q * fullScaleCurrent) / (IsrPeriod_sec
                        * fullScaleVoltage));
    
            // Do the same thing for Ki for the q-axis current controller.  If the
            // motor is not an IPM motor, Ld and Lq are the same, which means that
            // Ki_Iq = Ki_Id.
            _iq Ki_Iq = _IQ(RoverLs_q * IsrPeriod_sec);
    
            // There are three PI controllers; one speed controller and two current
            // controllers.  Each PI controller has two coefficients; Kp and Ki.
            // So you have a total of six coefficients that must be defined.
            // This is for the speed controller
            pidHandle[0] = PID_init(&pid[0],sizeof(pid[0]));
            // This is for the Id current controller
            pidHandle[1] = PID_init(&pid[1],sizeof(pid[1]));
            // This is for the Iq current controller
            pidHandle[2] = PID_init(&pid[2],sizeof(pid[2]));
    
            // The following instructions load the parameters for the speed PI
            // controller.
            PID_setGains(pidHandle[0],_IQ(1.0),_IQ(0.01),_IQ(0.0));
    
            // The current limit is performed by the limits placed on the speed PI
            // controller output.  In the following statement, the speed
            // controller's largest negative current is set to -maxCurrent_pu, and
            // the largest positive current is set to maxCurrent_pu.
            PID_setMinMax(pidHandle[0],-maxCurrent_pu,maxCurrent_pu);
            PID_setUi(pidHandle[0],_IQ(0.0));  // Set the initial condition value
                                               // for the integrator output to 0
    
            pidCntSpeed = 0;  // Set the counter for decimating the speed
                              // controller to 0
    
            // The following instructions load the parameters for the d-axis
            // current controller.
            // P term = Kp_Id, I term = Ki_Id, D term = 0
            PID_setGains(pidHandle[1],_IQ(0.3), _IQ(0.003), _IQ(0.0));//_IQ(0.7), _IQ(0.003), _IQ(0.0));//Kp_Id,Ki_Id,_IQ(0.0));//
    
            // Largest negative voltage = -maxVoltage_pu, largest positive
            // voltage = maxVoltage_pu
            PID_setMinMax(pidHandle[1],-maxVoltage_pu,maxVoltage_pu);
    
            // Set the initial condition value for the integrator output to 0
            PID_setUi(pidHandle[1],_IQ(0.0));
    
            // The following instructions load the parameters for the q-axis
            // current controller.
            // P term = Kp_Iq, I term = Ki_Iq, D term = 0
            PID_setGains(pidHandle[2],_IQ(0.3), _IQ(0.003), _IQ(0.0));//Kp_Iq,Ki_Iq,_IQ(0.0));//_IQ(0.3), _IQ(0.003), _IQ(0.0));//
    
            // The largest negative voltage = 0 and the largest positive
            // voltage = 0.  But these limits are updated every single ISR before
            // actually executing the Iq controller. The limits depend on how much
            // voltage is left over after the Id controller executes. So having an
            // initial value of 0 does not affect Iq current controller execution.
            PID_setMinMax(pidHandle[2],_IQ(0.0),_IQ(0.0));
    
            // Set the initial condition value for the integrator output to 0
            PID_setUi(pidHandle[2],_IQ(0.0));
        }
    
        // initialize the speed reference in kilo RPM where base speed is
        // USER_IQ_FULL_SCALE_FREQ_Hz.
        // Set 10 Hz electrical frequency as initial value, so the kRPM value would
        // be: 10 * 60 / motor pole pairs / 1000.
        gMotorVars.SpeedRef_krpm = _IQmpy(_IQ(10.0),gSpeed_hz_to_krpm_sf);
    
        // initialize the inverse Park module
        iparkHandle = IPARK_init(&ipark,sizeof(ipark));
    
        // initialize the space vector generator module
        svgenHandle = SVGEN_init(&svgen,sizeof(svgen));
    
        // setup faults
        HAL_setupFaults(halHandle);
    
        // initialize the interrupt vector table
        HAL_initIntVectorTable(halHandle);
    
        // enable the ADC interrupts
        HAL_enableAdcInts(halHandle);
    
        // enable global interrupts
        HAL_enableGlobalInts(halHandle);
    
        // enable debug interrupts
        HAL_enableDebugInt(halHandle);
    
        // disable the PWM
        HAL_disablePwm(halHandle);
    
        // compute scaling factors for flux and torque calculations
        gFlux_pu_to_Wb_sf = USER_computeFlux_pu_to_Wb_sf();
        gFlux_pu_to_VpHz_sf = USER_computeFlux_pu_to_VpHz_sf();
        gTorque_Ls_Id_Iq_pu_to_Nm_sf = USER_computeTorque_Ls_Id_Iq_pu_to_Nm_sf();
        gTorque_Flux_Iq_pu_to_Nm_sf = USER_computeTorque_Flux_Iq_pu_to_Nm_sf();
    
        // enable the system by default
        gMotorVars.Flag_enableSys = true;
    
        #ifdef DRV8301_SPI
            // turn on the DRV8301 if present
            HAL_enableDrv(halHandle);
            // initialize the DRV8301 interface
            HAL_setupDrvSpi(halHandle,&gDrvSpi8301Vars);
        #endif
    
        #ifdef DRV8305_SPI
            // turn on the DRV8305 if present
            HAL_enableDrv(halHandle);
            // initialize the DRV8305 interface
            HAL_setupDrvSpi(halHandle,&gDrvSpi8305Vars);
        #endif
    
        // Begin the background loop
        for(;;)
        {
            // Waiting for enable system flag to be set
            while(!(gMotorVars.Flag_enableSys));
    
            // loop while the enable system flag is true
            while(gMotorVars.Flag_enableSys)
            {
                // If Flag_enableSys is set AND Flag_Run_Identify is set THEN
                // enable PWMs and set the speed reference
                if(gMotorVars.Flag_Run_Identify)
                {
                    // update estimator state
                    EST_updateState(estHandle,0);
    
                    #ifdef FAST_ROM_V1p6
                        // call this function to fix 1p6. This is only used for
                        // F2806xF/M implementation of InstaSPIN (version 1.6 of
                        // ROM), since the inductance calculation is not done
                        // correctly in ROM, so this function fixes that ROM bug.
                        softwareUpdate1p6(estHandle);
                    #endif
    
                    // enable the PWM
                    HAL_enablePwm(halHandle);
                }
                else  // Flag_enableSys is set AND Flag_Run_Identify is not set
                {
                    // set estimator to Idle
                    EST_setIdle(estHandle);
    
                    // disable the PWM
                    HAL_disablePwm(halHandle);
    
                    // clear integrator outputs
                    PID_setUi(pidHandle[0],_IQ(0.0));
                    PID_setUi(pidHandle[1],_IQ(0.0));
                    PID_setUi(pidHandle[2],_IQ(0.0));
    
                    // clear Id and Iq references
                    gIdq_ref_pu.value[0] = _IQ(0.0);
                    gIdq_ref_pu.value[1] = _IQ(0.0);
                }
    
                // update the global variables
                updateGlobalVariables(estHandle);
    
                // enable/disable the forced angle
                EST_setFlag_enableForceAngle(estHandle,
                        gMotorVars.Flag_enableForceAngle);
    
                // set target speed
                gMotorVars.SpeedRef_pu = _IQmpy(gMotorVars.SpeedRef_krpm,
                        gSpeed_krpm_to_pu_sf);
    
                #ifdef DRV8301_SPI
                    HAL_writeDrvData(halHandle,&gDrvSpi8301Vars);
    
                    HAL_readDrvData(halHandle,&gDrvSpi8301Vars);
                #endif
                #ifdef DRV8305_SPI
                    HAL_writeDrvData(halHandle,&gDrvSpi8305Vars);
    
                    HAL_readDrvData(halHandle,&gDrvSpi8305Vars);
                #endif
    
            } // end of while(gFlag_enableSys) loop
    
            // disable the PWM
            HAL_disablePwm(halHandle);
    
            gMotorVars.Flag_Run_Identify = false;
        } // end of for(;;) loop
    } // end of main() function
    
    
    //! \brief     The main ISR that implements the motor control.
    interrupt void mainISR(void)
    {
        // Declaration of local variables
        _iq angle_pu = _IQ(0.0);
        _iq speed_pu = _IQ(0.0);
        _iq oneOverDcBus;
        MATH_vec2 Iab_pu;
        MATH_vec2 Vab_pu;
        MATH_vec2 phasor;
    
        // acknowledge the ADC interrupt
        HAL_acqAdcInt(halHandle,ADC_IntNumber_1);
    
        // convert the ADC data
        HAL_readAdcDataWithOffsets(halHandle,&gAdcData);
    
        // remove offsets
        gAdcData.I.value[0] = gAdcData.I.value[0] - gOffsets_I_pu.value[0];
        gAdcData.I.value[1] = gAdcData.I.value[1] - gOffsets_I_pu.value[1];
        gAdcData.I.value[2] = gAdcData.I.value[2] - gOffsets_I_pu.value[2];
        gAdcData.V.value[0] = gAdcData.V.value[0] - gOffsets_V_pu.value[0];
        gAdcData.V.value[1] = gAdcData.V.value[1] - gOffsets_V_pu.value[1];
        gAdcData.V.value[2] = gAdcData.V.value[2] - gOffsets_V_pu.value[2];
    
        // run Clarke transform on current.  Three values are passed, two values
        // are returned.
        CLARKE_run(clarkeHandle_I,&gAdcData.I,&Iab_pu);
    
        // run Clarke transform on voltage.  Three values are passed, two values
        // are returned.
        CLARKE_run(clarkeHandle_V,&gAdcData.V,&Vab_pu);
    
        // run the estimator
        // The speed reference is needed so that the proper sign of the forced
        // angle is calculated. When the estimator does not do motor ID as in this
        // lab, only the sign of the speed reference is used
        EST_run(estHandle,&Iab_pu,&Vab_pu,gAdcData.dcBus,gMotorVars.SpeedRef_pu);
    
        // generate the motor electrical angle
        angle_pu = EST_getAngle_pu(estHandle);
        speed_pu = EST_getFm_pu(estHandle);
    
        // get Idq from estimator to avoid sin and cos, and a Park transform,
        // which saves CPU cycles
        EST_getIdq_pu(estHandle,&gIdq_pu);
    
        // run the appropriate controller
        if(gMotorVars.Flag_Run_Identify)
        {
            // Declaration of local variables.
            _iq refValue;
            _iq fbackValue;
            _iq outMax_pu;
    
            // when appropriate, run the PID speed controller
            // This mechanism provides the decimation for the speed loop.
    /*        if(pidCntSpeed >= USER_NUM_CTRL_TICKS_PER_SPEED_TICK)
            {
                // Reset the Speed PID execution counter.
                pidCntSpeed = 0;
    
                // The next instruction executes the PI speed controller and places
                // its output in Idq_ref_pu.value[1], which is the input reference
                // value for the q-axis current controller.
                PID_run_spd(pidHandle[0],gMotorVars.SpeedRef_pu,speed_pu,
                        &(gIdq_ref_pu.value[1]));
            }
            else
            {
                // increment counter
                pidCntSpeed++;
            }*/
    
            // Get the reference value for the d-axis current controller.
            refValue = gIdq_ref_pu.value[0];
    
            // Get the actual value of Id
            fbackValue = gIdq_pu.value[0];
    
            // The next instruction executes the PI current controller for the
            // d axis and places its output in Vdq_pu.value[0], which is the
            // control voltage along the d-axis (Vd)
            PID_run(pidHandle[1],refValue,fbackValue,&(gVdq_out_pu.value[0]));
    
            // get the Iq reference value
            refValue = gIdq_ref_pu.value[1];
    
            // get the actual value of Iq
            fbackValue = gIdq_pu.value[1];
    
            // The voltage limits on the output of the q-axis current controller
            // are dynamic, and are dependent on the output voltage from the d-axis
            // current controller.  In other words, the d-axis current controller
            // gets first dibs on the available voltage, and the q-axis current
            // controller gets what's left over.  That is why the d-axis current
            // controller executes first. The next instruction calculates the
            // maximum limits for this voltage as:
            // Vq_min_max = +/- sqrt(Vbus^2 - Vd^2)
            outMax_pu = _IQsqrt(_IQ(USER_MAX_VS_MAG_PU * USER_MAX_VS_MAG_PU)
                    - _IQmpy(gVdq_out_pu.value[0],gVdq_out_pu.value[0]));
    
            // Set the limits to +/- outMax_pu
            PID_setMinMax(pidHandle[2],-outMax_pu,outMax_pu);
    
            // The next instruction executes the PI current controller for the
            // q axis and places its output in Vdq_pu.value[1], which is the
            // control voltage vector along the q-axis (Vq)
            PID_run(pidHandle[2],refValue,fbackValue,&(gVdq_out_pu.value[1]));
    
            // The voltage vector is now calculated and ready to be applied to the
            // motor in the form of three PWM signals.  However, even though the
            // voltages may be supplied to the PWM module now, they won't be
            // applied to the motor until the next PWM cycle. By this point, the
            // motor will have moved away from the angle that the voltage vector
            // was calculated for, by an amount which is proportional to the
            // sampling frequency and the speed of the motor.  For steady-state
            // speeds, we can calculate this angle delay and compensate for it.
            angle_pu = angleDelayComp(speed_pu,angle_pu);
    
            // compute the sine and cosine phasor values which are part of the inverse
            // Park transform calculations. Once these values are computed,
            // they are copied into the IPARK module, which then uses them to
            // transform the voltages from DQ to Alpha/Beta reference frames.
            phasor.value[0] = _IQcosPU(angle_pu);
            phasor.value[1] = _IQsinPU(angle_pu);
    
            // set the phasor in the inverse Park transform
            IPARK_setPhasor(iparkHandle,&phasor);
    
            // Run the inverse Park module.  This converts the voltage vector from
            // synchronous frame values to stationary frame values.
            IPARK_run(iparkHandle,&gVdq_out_pu,&Vab_pu);
    
            // These 3 statements compensate for variations in the DC bus by adjusting the
            // PWM duty cycle. The goal is to achieve the same volt-second product
            // regardless of the DC bus value.  To do this, we must divide the desired voltage
            // values by the DC bus value.  Or...it is easier to multiply by 1/(DC bus value).
            oneOverDcBus = EST_getOneOverDcBus_pu(estHandle);
            Vab_pu.value[0] = _IQmpy(Vab_pu.value[0],oneOverDcBus);
            Vab_pu.value[1] = _IQmpy(Vab_pu.value[1],oneOverDcBus);
    
            // Now run the space vector generator (SVGEN) module.
            // There is no need to do an inverse CLARKE transform, as this is
            // handled in the SVGEN_run function.
            SVGEN_run(svgenHandle,&Vab_pu,&(gPwmData.Tabc));
        }
        else  // gMotorVars.Flag_Run_Identify = 0
        {
            // disable the PWM
            HAL_disablePwm(halHandle);
    
            // Set the PWMs to 50% duty cycle
            gPwmData.Tabc.value[0] = _IQ(0.0);
            gPwmData.Tabc.value[1] = _IQ(0.0);
            gPwmData.Tabc.value[2] = _IQ(0.0);
        }
    
        // write to the PWM compare registers, and then we are done!
        HAL_writePwmData(halHandle,&gPwmData);
    
        return;
    } // end of mainISR() function
    
    
    //! \brief  The angleDelayComp function compensates for the delay introduced
    //! \brief  from the time when the system inputs are sampled to when the PWM
    //! \brief  voltages are applied to the motor windings.
    _iq angleDelayComp(const _iq fm_pu,const _iq angleUncomp_pu)
    {
        _iq angleDelta_pu = _IQmpy(fm_pu,_IQ(USER_IQ_FULL_SCALE_FREQ_Hz
                    / (USER_PWM_FREQ_kHz*1000.0)));
        _iq angleCompFactor = _IQ(1.0 + (float_t)USER_NUM_PWM_TICKS_PER_ISR_TICK
                    * 0.5);
        _iq angleDeltaComp_pu = _IQmpy(angleDelta_pu,angleCompFactor);
        uint32_t angleMask = ((uint32_t)0xFFFFFFFF >> (32 - GLOBAL_Q));
        _iq angleComp_pu;
        _iq angleTmp_pu;
    
        // increment the angle
        angleTmp_pu = angleUncomp_pu + angleDeltaComp_pu;
    
        // mask the angle for wrap around
        // note: must account for the sign of the angle
        angleComp_pu = _IQabs(angleTmp_pu) & angleMask;
    
        // account for sign
        if(angleTmp_pu < _IQ(0.0))
        {
            angleComp_pu = -angleComp_pu;
        }
    
        return(angleComp_pu);
    } // end of angleDelayComp() function
    
    
    //! \brief  Call this function to fix 1p6. This is only used for F2806xF/M
    //! \brief  implementation of InstaSPIN (version 1.6 of ROM) since the
    //! \brief  inductance calculation is not done correctly in ROM, so this
    //! \brief  function fixes that ROM bug.
    void softwareUpdate1p6(EST_Handle handle)
    {
        float_t fullScaleInductance = USER_IQ_FULL_SCALE_VOLTAGE_V
                        / (USER_IQ_FULL_SCALE_CURRENT_A
                        * USER_VOLTAGE_FILTER_POLE_rps);
        float_t Ls_coarse_max = _IQ30toF(EST_getLs_coarse_max_pu(handle));
        int_least8_t lShift = ceil(log(USER_MOTOR_Ls_d / (Ls_coarse_max
                             * fullScaleInductance)) / log(2.0));
        uint_least8_t Ls_qFmt = 30 - lShift;
        float_t L_max = fullScaleInductance * pow(2.0,lShift);
        _iq Ls_d_pu = _IQ30(USER_MOTOR_Ls_d / L_max);
        _iq Ls_q_pu = _IQ30(USER_MOTOR_Ls_q / L_max);
    
        // store the results
        EST_setLs_d_pu(handle,Ls_d_pu);
        EST_setLs_q_pu(handle,Ls_q_pu);
        EST_setLs_qFmt(handle,Ls_qFmt);
    
        return;
    } // end of softwareUpdate1p6() function
    
    
    //! \brief     Setup the Clarke transform for either 2 or 3 sensors.
    //! \param[in] handle             The clarke (CLARKE) handle
    //! \param[in] numCurrentSensors  The number of current sensors
    void setupClarke_I(CLARKE_Handle handle,const uint_least8_t numCurrentSensors)
    {
        _iq alpha_sf,beta_sf;
    
        // initialize the Clarke transform module for current
        if(numCurrentSensors == 3)
        {
            alpha_sf = _IQ(MATH_ONE_OVER_THREE);
            beta_sf = _IQ(MATH_ONE_OVER_SQRT_THREE);
        }
        else if(numCurrentSensors == 2)
        {
            alpha_sf = _IQ(1.0);
            beta_sf = _IQ(MATH_ONE_OVER_SQRT_THREE);
        }
        else
        {
            alpha_sf = _IQ(0.0);
            beta_sf = _IQ(0.0);
        }
    
        // set the parameters
        CLARKE_setScaleFactors(handle,alpha_sf,beta_sf);
        CLARKE_setNumSensors(handle,numCurrentSensors);
    
        return;
    } // end of setupClarke_I() function
    
    
    //! \brief     Setup the Clarke transform for either 2 or 3 sensors.
    //! \param[in] handle             The clarke (CLARKE) handle
    //! \param[in] numVoltageSensors  The number of voltage sensors
    void setupClarke_V(CLARKE_Handle handle,const uint_least8_t numVoltageSensors)
    {
        _iq alpha_sf,beta_sf;
    
        // initialize the Clarke transform module for voltage
        if(numVoltageSensors == 3)
        {
            alpha_sf = _IQ(MATH_ONE_OVER_THREE);
            beta_sf = _IQ(MATH_ONE_OVER_SQRT_THREE);
        }
        else
        {
            alpha_sf = _IQ(0.0);
            beta_sf = _IQ(0.0);
        }
    
        // In other words, the only acceptable number of voltage sensors is three.
        // set the parameters
        CLARKE_setScaleFactors(handle,alpha_sf,beta_sf);
        CLARKE_setNumSensors(handle,numVoltageSensors);
    
        return;
    } // end of setupClarke_V() function
    
    
    //! \brief     Update the global variables (gMotorVars).
    //! \param[in] handle  The estimator (EST) handle
    void updateGlobalVariables(EST_Handle handle)
    {
        // get the speed estimate
        gMotorVars.Speed_krpm = EST_getSpeed_krpm(handle);
    
        // get the torque estimate
        {
            _iq Flux_pu = EST_getFlux_pu(handle);
            _iq Id_pu = PID_getFbackValue(pidHandle[1]);
            _iq Iq_pu = PID_getFbackValue(pidHandle[2]);
            _iq Ld_minus_Lq_pu = _IQ30toIQ(EST_getLs_d_pu(handle)
                        - EST_getLs_q_pu(handle));
    
            // Reactance Torque
            _iq Torque_Flux_Iq_Nm = _IQmpy(_IQmpy(Flux_pu,Iq_pu),
                        gTorque_Flux_Iq_pu_to_Nm_sf);
    
            // Reluctance Torque
            _iq Torque_Ls_Id_Iq_Nm = _IQmpy(_IQmpy(_IQmpy(Ld_minus_Lq_pu,Id_pu),
                        Iq_pu),gTorque_Ls_Id_Iq_pu_to_Nm_sf);
    
            // Total torque is sum of reactance torque and reluctance torque
            _iq Torque_Nm = Torque_Flux_Iq_Nm + Torque_Ls_Id_Iq_Nm;
    
            gMotorVars.Torque_Nm = Torque_Nm;
        }
    
        // get the magnetizing current
        gMotorVars.MagnCurr_A = EST_getIdRated(handle);
    
        // get the rotor resistance
        gMotorVars.Rr_Ohm = EST_getRr_Ohm(handle);
    
        // get the stator resistance
        gMotorVars.Rs_Ohm = EST_getRs_Ohm(handle);
    
        // get the stator inductance in the direct coordinate direction
        gMotorVars.Lsd_H = EST_getLs_d_H(handle);
    
        // get the stator inductance in the quadrature coordinate direction
        gMotorVars.Lsq_H = EST_getLs_q_H(handle);
    
        // get the flux in V/Hz in floating point
        gMotorVars.Flux_VpHz = EST_getFlux_VpHz(handle);
    
        // get the flux in Wb in fixed point
        gMotorVars.Flux_Wb = _IQmpy(EST_getFlux_pu(handle),gFlux_pu_to_Wb_sf);
    
        // get the estimator state
        gMotorVars.EstState = EST_getState(handle);
    
        // Get the DC buss voltage
        gMotorVars.VdcBus_kV = _IQmpy(gAdcData.dcBus,
                _IQ(USER_IQ_FULL_SCALE_VOLTAGE_V / 1000.0));
    
        // read Vd and Vq vectors per units
        gMotorVars.Vd = gVdq_out_pu.value[0];
        gMotorVars.Vq = gVdq_out_pu.value[1];
    
        // calculate vector Vs in per units: (Vs = sqrt(Vd^2 + Vq^2))
        gMotorVars.Vs = _IQsqrt(_IQmpy(gMotorVars.Vd,gMotorVars.Vd)
                + _IQmpy(gMotorVars.Vq,gMotorVars.Vq));
    
        // read Id and Iq vectors in amps
        gMotorVars.Id_A = _IQmpy(gIdq_pu.value[0],
                _IQ(USER_IQ_FULL_SCALE_CURRENT_A));
        gMotorVars.Iq_A = _IQmpy(gIdq_pu.value[1],
                _IQ(USER_IQ_FULL_SCALE_CURRENT_A));
    
        // calculate vector Is in amps:  (Is_A = sqrt(Id_A^2 + Iq_A^2))
        gMotorVars.Is_A = _IQsqrt(_IQmpy(gMotorVars.Id_A,gMotorVars.Id_A)
                + _IQmpy(gMotorVars.Iq_A,gMotorVars.Iq_A));
    
        return;
    } // end of updateGlobalVariables() function
    
    //@} //defgroup
    // end of file
    
    
    

  • After re-running the test, I'm about 95% convinced that the demo kit's cutoff frequency is a major contributor to the problem.  When I properly calibrated the sensor and ran the test, I got maybe a 5°-10° error between the rotor angle and the estimator angle.  The torque per amp I got matched the machine's torque constant, AND if I took over and jumped to open loop control I couldn't do significantly better than what the controller had been applying.  Moreover, when I put the old filter back on, the angle error jumped back to 25° or more, increasing with current.

    It makes logical sense.  I don't know how the estimator is adjusting for the measurement error, but at 167Hz (our electrical frequency) the phase voltage is attenuated by 13.3dB (compared to 13.1dB for just the resistive dividers), but the phase shift is 26.5°.  That's a lot to accommodate post-measurement.

    I'd like to thoroughly document this issue, so I'm going to disassemble the test stand to calibrate the torque meter and then run both tests over again.

    Besides fixing the angle error, the instability wasn't as bad.  By massaging the PI controllers I was able to push the current up to 30A, well into the saturation region for the motor.  The angle was predictably inaccurate, but that's not surprising because the motor parameters were all incorrect.

    I'll put everything together when I'm finished retaking the data.  I have to get some work done on another project before tomorrow morning, so if I don't finish tonight then I'll get this all summarized tomorrow.

    Matt

  • Matt Meier said:
    I got maybe a 5°-10° error between the rotor angle and the estimator angle. 

    What generator triggers the ADC sample window SOC for Id/Vd? What if you change INT 1st priority (Id) to be EOC#1 and EMF (Vd) to be EOC#0 and or round robin others? Would the rotation angle then have precedence over current angle interrupt in ROM calls? I wonder if the ROM calculated sample values are static variable assignments.   

  • The three phase voltages, three phase currents, and DC bus voltage analog measurements are all triggered by the EPWM interrupt (the main ISR).  They're all serviced every PWM loop, as far as I understand; it's running the Ti software from Lab 11 almost verbatim.  Id and Iq have to be calculated, so I'm not sure what you mean by changing the Id interrupt priority.  I also don't know what you mean by the ROM calculated sample values.

  • I'm talking about the ADC sample EOC interrupt priority order for voltage/current associated PWM trigger times. Interesting how inductive current lags voltage as link covers. That directly relates to ADC sample order time relationships of data sent to Clarke/Park transforms to maintain the correct phase each Uout sector. Does the PWM duty cycle hit 100% modulation when it all falls apart or does it remain under modulated? The labs HW configuration are basic construct and my need tweaking to achieve the finer granularity you seek?

    https://www.nde-ed.org/EducationResources/CommunityCollege/EddyCurrents/Physics/circuitsphase.htm

    .

  • The analog measurements happen every cycle.  They are all complete when the estimator is run because the main ISR is only indirectly triggered by the EPWM module.  In reality, the PWM trigger starts the ADC conversions and when the ADCs are complete then they trigger the main ISR.  The main ISR completes well before the next conversion begins, so you can think of the conversions as a snapshot of the state of the analog signals when the ISR begins.  Having the ISR trigger chain set up this way is a nice feature of this system; in many MCUs the ADCs have to be initiated by software or an external trigger.  Regardless, the method is usually similar.  You read the analog signals, then you do math, then you apply the PWM signals.

    I'll grant that the ADCs don't happen in parallel, so in theory there is a lag between when the first one happens and the final one is complete.  However, this lag is very VERY small compared to the machine being controlled.  It takes less than 2us for the conversions in this system to complete.  

    At 2000rpm, 2us is only 0.12°, so you can see that the delay from one conversion to the next is insignificant.

    The PWM duty cycle never hits 100% ever because it's limited to 95% in the software (remember...aside from the speed loop being removed, the base software is intact).  However, it also never comes close to the limit.  At full current at 2000rpm we're at less than 10% of the duty cycle limit.

    Besides that, the 5°-10° error is totally reasonable in my opinion.  I would consider that "solved" because I haven't even tried to tune the things we have access to.  We haven't fine-tuned the motor parameters or the PI controllers, and at the higher currents we know the motor is saturating and we haven't accounted for that yet.

    I'm almost finished collecting the two data sets.  I'll post them shortly.

    Matt

  • I got sidetracked.  I found that the rotor was slipping with respect to the position sensor, which is mounted to the torque meter and not the machine itself, so some of the previous results aren't completely accurate.  Conceptually, the problems we saw were all real, but for the final write-up I wanted to make sure we had everything as accurate as possible so I installed a new sleeve and mount for the rotor so that it wouldn't continue to drift over time.  For this data, I calibrated the position sensor both before and after the each so that I knew nothing had changed.

    Here's a summary of what I've come across, including notes on what mattered and what didn't, going back to the previous thread.  I'm not going to list all the things we tried that were unrelated to the estimator itself, but there were many.

    1.  When we started, the motor operation was unstable at the desired operating point (6500rpm and 125mNm).  To remove some variables, we increased the DC bus voltage and changed the motor speed, but the stability problem remained if we tried to get much more than 20Apk.

    2.  To investigate the stability problem, we added some software that allowed us to intercept the controller (both the estimator and PI controllers) and run the motor in open-loop mode.  We found that this enabled smooth operation well over 20Apk, but we also discovered that running the program in open-loop mode allowed us to generate significantly more torque per amp than we got with the InstaSPIN system intact.  This indicated to us that the current angle was not 90°.

    2.  We added a position sensor to our system and ran the machine again, and saw that the current phase angle at max torque per amp was ~90°, but the current angle was advanced by 20°-30° with the estimator engaged.

    3.  We used a GPIO port to report the estimated phase angle, and we saw that the PI controllers were applying current almost exactly 90° ahead of the estimated angle, which was itself leading the rotor by 20-30°.  Our observation that the rotor angle was getting worse with increased current was likely from the gradual slipping of the rotor.  If I correct for the shift between the rotor and sensor over time (which we can do two different ways) then we get a curve generally similar to the one below.

    4.  We tried running the system at 3000rpm and the angle error was a little bit worse.

    5.  We changed the voltage feedback filter and re-ran the sweep of estimator angle vs. current.  It led the rotor's q-axis significantly and continued to get worse.  This, I think, is where the rotor really started slipping on the dyno at high torque levels.

    5.  In trying to determine why the estimator had such a drastic error, we tried adjusting the motor parameters.  We have a library from a previous project that lets us adjust the motor's flux constant on the fly, in addition to the inductance and resistance.  By adjusting these we were able to improve the angle but with too much adjustment the operation got noisy and somewhat unstable.  We were able to get about 10° worth of improvement from the best combination.

    6.  I found that the position sensor had slipped vs. the rotor, so I recalibrated everything and re-ran the sweep of current angle vs. applied current.  I calibrated the angle before and after the test, and found that it had drifted ~10°.  So, I took the stand apart and re-mounted the rotor with new bushings and a bigger set screw.

    7.  I re-started the test and the current angle was better, and the rotor was not shifting.  After exceeding 20Apk, the oscillation returned but was markedly improved...I wonder whether the rotor was also somewhat eccentric in the previous tests.  I was able to tune the PI controller to remove the instability and push the current by another 50% (well into hard saturation for the stator) without it becoming too unstable.

    8.  I re-ran the test up to 25Apk (0.5pu) and the results were good.

    9.  I took the motor up to the 6500rpm/125mNm target and the instability returned, which I was able to remove this time by adjusting the PI controller.

    10.  I replaced the filter to the nominal values and re-ran the Iq sweep at 2000rpm, calibrating the position sensor before and after to make sure there was no drift.

    Here's a plot for the rotor angle vs. commanded current with both filter capacitors.

    In the default case (the blue curve), I adjusted the motor parameters to get the best operation I could.  I'm sure massaging the parameters will further reduce the errors in the red curve.  The drop-off to the right is because we aren't adjusting the motor parameters for saturation, and the estimator obviously can't track the rotor without the correct motor parameters.

    If there are tests that Ti wants me to run then I'm open to it...I know from the documentation that the estimator must be compensating for the filter frequency (there's a section that suggests that the filter frequency can be as low as the motor's fundamental frequency), but I don't really know how so I can't track this one down any further without some help.  Regardless, I'm happy I can ship the demo kit off to the people doing the fluid tests on the pump motor.

    Many thanks to Gl and Yanming for the time and effort you put in.

    Thanks again,

    Matt