RafaelFalcaro Posted August 30, 2023 Posted August 30, 2023 Hello, I'm trying to use PowerPMAC as a high frequency pulse generator through PFM. I'm using a custom servo loop, where I monitor the positioning error of my motor and calculate what the PFM output frequency should be so that enough pulses are generated for it to reach the desired position in the next servo cycle, saturating at 5 MHz. Here is the code: double custom_pfm(struct MotorData *Mptr) { // Auxiliar variables double servo_out, servo_period, pfm_clk_freq, pos_error; unsigned int pfm_clock_div; volatile GateArray3 *Gate3; // If closed loop is enabled if (Mptr->ClosedLoop == 1) { // Calculates position error relative to JogPos pos_error = ((Mptr->JogPos) - (Mptr->HomePos)) - ((Mptr->ActPos) - (Mptr->HomePos)); // Calculates PFM clock frequency Gate3 = GetGate3MemPtr(0); pfm_clock_div = ((Gate3->HardwareClockCtrl) >> 16) & 0b1111; pfm_clk_freq = 100e6 / pow(2, pfm_clock_div); // Calculates Servo Period servo_period = (pshm->ServoPeriod) / 1000; // Calculates Servo Out servo_out = ((pos_error / servo_period) * 65536) / pfm_clk_freq; // Saturates at 5 MHz if (servo_out > 3276.8) { return 3276.8; } else if (servo_out < -3276.8) { return -3276.8; } // Return Servo Out return servo_out; } // Return 0 if closed loop is not enabled return 0.0; } The implementation is working relatively well, but this type of overshoot happens, which I would like to solve: The EncTable used by the motor is configured like this: Gate3[0].Chan[0].TimerMode = 3 EncTable[11].Type = 1 EncTable[11].pEnc = PowerBrick[0].Chan[0].TimerA.a EncTable[11].index1 = 0 EncTable[11].index2 = 0 EncTable[11].index3 = 0 EncTable[11].MaxDelta = 0 EncTable[11].ScaleFactor = 1/256 Why does overshoot happen? I imagined that by calculating exactly the frequency needed to generate the pulses, the behavior of the position curve would be a ramp that stops exactly at the setpoint... Quote
leandro.martins Posted August 31, 2023 Posted August 31, 2023 Hello Rafael, Are you getting the expected value in pfm_clock_div? It's not clear to me why you are masking with 0b1111, instead of getting the first 4 bits (0xF) after the right-shift getting the bits 16-19 of HardwareClockCtrl. Quote
RafaelFalcaro Posted August 31, 2023 Author Posted August 31, 2023 Hello Leandro, Yes, the pfm_clock_div value is correct. Making the mask with 0b1111 or 0xF don't give the same result? Anyway, my PfmClockDiv is zero (I'm using 100MHz clock on the PFM), so I did the test below and the overshoot keeps happening. // Calculates PFM clock frequency Gate3 = GetGate3MemPtr(0); pfm_clock_div = 0; pfm_clk_freq = 100e6 / pow(2, pfm_clock_div); Quote
RafaelFalcaro Posted August 31, 2023 Author Posted August 31, 2023 Continuing the tests here, I'm coming to the conclusion that it won't be possible to do what I need, because of the fact that the return of the servo loop has an effect on the PFM immediately at the end of the calculation, and not at the instant of the next Servo interruption. Because of this detail, the calculation that takes ServoPeriod into account is not true, as ServoOut can be updated at any time within the ServoPeriod interval. And I also believe that it is not possible to measure what that instant was for each Motor, only for the entire Servo cycle (Sys.ServoTime), as this would be possible to deduct such time from the ServoPeriod and adjust the ServoOut. Is there any register that measures this time between the start of the Servo Interrupt and the end of the loop calculation of motor n? Or even, a register that configures the controller to only use the new return value of the servo loop in the next Servo Interrupt? Quote
leandro.martins Posted September 1, 2023 Posted September 1, 2023 (edited) 22 hours ago, RafaelFalcaro said: Yes, the pfm_clock_div value is correct. Making the mask with 0b1111 or 0xF don't give the same result? You're right, I've misread as 0x0b1111. 15 hours ago, RafaelFalcaro said: Or even, a register that configures the controller to only use the new return value of the servo loop in the next Servo Interrupt? Maybe you can do this using a static variable. I've never tried to use in a Real-time routine on powerpmac, so, I'm not sure if work. If you want to try, it would be something like this: double custom_pfm(struct MotorData *Mptr) { // Auxiliar variables double servo_out, servo_period, pfm_clk_freq, pos_error; static double servo_out_nxt = 0; unsigned int pfm_clock_div; volatile GateArray3 *Gate3; // If closed loop is enabled if (Mptr->ClosedLoop == 1) { servo_out = servo_out_nxt; // Calculates position error relative to JogPos pos_error = ((Mptr->JogPos) - (Mptr->HomePos)) - ((Mptr->ActPos) - (Mptr->HomePos)); // Calculates PFM clock frequency Gate3 = GetGate3MemPtr(0); pfm_clock_div = ((Gate3->HardwareClockCtrl) >> 16) & 0b1111; pfm_clk_freq = 100e6 / pow(2, pfm_clock_div); // Calculates Servo Period servo_period = (pshm->ServoPeriod) / 1000; // Calculates Servo Out servo_out_nxt = ((pos_error / servo_period) * 65536) / pfm_clk_freq; // Saturates at 5 MHz if (servo_out_nxt > 3276.8) { return 3276.8; } else if (servo_out_nxt < -3276.8) { return -3276.8; } // Return Servo Out return servo_out; } else { servo_out_nxt = 0; } // Return 0 if closed loop is not enabled return 0.0; } Edited September 1, 2023 by leandro.martins Quote
RafaelFalcaro Posted September 1, 2023 Author Posted September 1, 2023 Thanks for the suggestion, Leandro! But it didn't work... the oscillation of the graph below happens when I execute a move. Also, the 1-cycle delay that the snippet of code you suggested doesn't change the fact that the return from the Servo loop has an effect on the PFM at any instant of the Servo Period, which in my opinion is the big problem. Looking at the user manual, in the "Phase and Servo Hardware and Software Synchronization" section, I understand that the solution is to parameterize the motor that uses the PFM so that the PowerPMAC commutes it (PhaseCtrl > 0), but I could not define the required registers for this to work fine. Quote
leandro.martins Posted September 2, 2023 Posted September 2, 2023 (edited) Since you want a high-frequency pulse generator, it might help to use PhaseCtrl = 8. In that way, I suppose that with your servo routine being executed at the phase interruption, obtaining the same effect as a motor being commuted (without closing the current loop). If I'm right in supposing that, you will need to : Consider the Sys.PhaseOverServoTime in your servo routine. Set your feedback properly to this mode by configuring the Motor[x].pPhaseEnc/PhaseEncLeftShift/PhaseEncRightShift I hope that helps. Edited September 4, 2023 by leandro.martins Quote
MoMo Posted September 28, 2023 Posted September 28, 2023 If you want a very stable pulse frequency output, please use the open loop command such as #1out10 Since PMAC controls the pulse frequency through the PID algorithm to control the number of pulses, there will be overshoot and frequency jitter. If you want to reduce overshoot, you can adjust Motor[x].Servo.Kvfb=0;Motor[x].Servo.Ki=0 Adjust Motor[x].Servo.Kp with Motor[x].Servo.Kvff=0, then adjust Motor[x].Servo.Kvff precisely The precise velocity and number of pulses are impossible to obtain at the same time unless you use FPGA hardware circuitry Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.