Feat(#1): DSP, Reconstruction Filter and Tmc init optimisation#167
Feat(#1): DSP, Reconstruction Filter and Tmc init optimisation#167manoukianv wants to merge 21 commits into
Conversation
7a6ae9e to
e4df0e7
Compare
0399851 to
daadedd
Compare
cbf24ca to
30e1006
Compare
| float32_t spline_y[4] = {0}; // Buffer value | ||
| float32_t spline_y2[4] = {0}; // Buffer for Spline Natural | ||
| float32_t spline_scratch[8] = {0}; // Buffer for Spline Natural | ||
| arm_spline_instance_f32 spline_instance; // Instance pour CMSIS-DSP |
| float spline_x[4] = {0}; // Buffer time(en us) | ||
| float spline_y[4] = {0}; // Buffer value | ||
| #endif | ||
| bool isSplineReady = false; // Le tampon est-il plein ? |
|
|
||
|
|
||
| /** | ||
| * @brief Contient l'état et les tampons pour |
|
|
||
| CommandStatus command(const ParsedCommand& cmd,std::vector<CommandReply>& replies); | ||
| /** | ||
| * @brief Met à jour les tampons d'interpolation pour un effet non-conditionnel. |
There was a problem hiding this comment.
Rewrite the following comments in english
| // 3. Apply the reduction to the main torque. | ||
| // We must only reduce the magnitude of the torque, not invert it. | ||
| reductionAmount = clip(reductionAmount, 0.0f, fabsf((float)torque)); | ||
| if(torque > 0) { |
There was a problem hiding this comment.
Maybe make one common implementation and only switch the actual PID functions to dsp. The code is mostly identical but structured slightly differently making it harder to maintain/read
There was a problem hiding this comment.
I'm sorry I'm not sure to understand:
Does the idea is to make generic call and make functions that include the ifdef on dsp in those function? I can, I think you prefer see the real impact.
| @@ -103,81 +107,100 @@ | |||
| */ | |||
| void EffectsCalculator::calculateEffects(std::vector<std::unique_ptr<Axis>> &axes) | |||
There was a problem hiding this comment.
Explain why the order was changed and what has been changed in this function compared to the previous implementation.
| int32_t force = 0; | ||
| int axisCount = axes.size(); | ||
| int32_t forces[MAX_AXIS] = {0}; | ||
| int64_t forces[MAX_AXIS] = {0}; |
There was a problem hiding this comment.
Why is this 64b now if the output forces are 16b? A 32b variable should be enough to prevent overflows when calculating in 16b.
There was a problem hiding this comment.
This is an issue, my bad
| rampupFactor = ( 1 + sin(phaseRad ) ) / 2; // sin value is -1..1 range, we translate it to 0..2 and we scale it by 2 | ||
|
|
||
| #ifdef USE_DSP_FUNCTIONS | ||
| float phaseRad = PI * ((fabsf (speed) / speedRampupCeil) - 0.5f);// we start to compute the normalized angle (speed / normalizedSpeed@5%) and translate it of -1/2PI to translate sin on 1/2 periode |
There was a problem hiding this comment.
Code duplication of the phaseRad depending on dsp function definition. Just use M_PI and cast to float if required. It is the same constant. Same as above in the other PI/M_PI variations.
There was a problem hiding this comment.
Yes I just prefer to use the dsp PI value instead of the constant to have the max precision that the dsp offers.
Not mandatory, but as PI is provided, I'm using it.
As you prefer
| break; | ||
| } | ||
|
|
||
| case FFB_EFFECT_RAMP: |
There was a problem hiding this comment.
Ramp doesn't have a magnitude but still calls the interpolation?
There was a problem hiding this comment.
you right, I add a selector to exclude hard computing for ramp.
| void Flash_Dump(std::vector<std::tuple<uint16_t,uint16_t>> *result,bool includeAll = false); | ||
| bool Flash_Format(); | ||
|
|
||
| #ifdef COGGING_TABLE_FLASH_START_ADDRESS |
There was a problem hiding this comment.
Actual implementation not in this PR. Seems to be in #169
There was a problem hiding this comment.
You right I failed the split, to much code to d split, at the beginning, I have only 1 commit.
| void EffectsCalculator::updateEffectReconstruction(FFB_Effect* effect, float new_magnitude, float new_offset, bool is_periodic) | ||
| { | ||
| // Update target variables (for NONE mode and fallback) | ||
| effect->magnitude = (int16_t)new_magnitude; |
There was a problem hiding this comment.
why is this changing the effect magnitude again? Shouldn't it be set by the game?
There was a problem hiding this comment.
It think the misunderstanding come from wrong code in wrong branch during the split again, I try to make it simpler but split is not really good.
This method is called by the ffb process to store magnitude and offset on effect compatible with the reconstruction filter. When we process the ffb from hid we need to do 2 things:
- Store the magnitude and offset as previous in the code
- Store the magnitude and offset in the "temporal structure" to be able to compute the slicing value.
The code you point is the storing of the value I move here, but we can also let in the ffb. I regroup all the process on magnitude and offset here.
Give me your preference.
There was a problem hiding this comment.
You are absolutely right. The misunderstanding definitely came from me trying to regroup everything during the split, but it violates the separation of concerns.
HidFFB should be the one responsible for setting the base state ( effect->magnitude and effect->offset ) since it decodes the USB HID reports from the game. EffectsCalculator should only handle the DSP and filter logic. By mixing them, it obscured the flow of data.
I have reverted this: HidFFB now assigns the magnitude and offset directly just like it did before, and then it calls EffectsCalculator::updateEffectReconstruction(...) purely to push the new samples into the temporal structure for the reconstruction filter.
|
|
||
| effect_p.magnitude = data->magnitude; | ||
| //effect_p.magnitude = data->magnitude; | ||
| effects_calc->updateEffectReconstruction(&effect_p, (float)data->magnitude, 0.0f, false); |
There was a problem hiding this comment.
Does this additional call break other FFB sources like SerialFFB.cpp?
There was a problem hiding this comment.
I don't see the SerialFFB 😥
So I don't apply reconstruction filter on them, so yes I make a break :-/
| if(!overridesCondition){ | ||
| float phaseX = M_PI*2.0 * (effect->directionX/36000.0f); | ||
| #ifdef USE_DSP_FUNCTIONS | ||
| float phaseX = PI*2.0f * (effect->directionX/36000.0f); |
There was a problem hiding this comment.
I'm homogeneous, I used th PI of TMC to get max accuracy. I can change all of them
| float a0, a1, a2, b1, b2; | ||
| float Fc, Q, peakGain; | ||
|
|
||
| #ifdef USE_DSP_FUNCTIONS |
There was a problem hiding this comment.
Couldn't we just simplify this by using a struct/union for both implementations if the coefficients are the same and correct the sign in the non dsp implementation? also simplifies the tmc section and makes everything more readable.
There was a problem hiding this comment.
I'm asking to Gemini, I'm not familiar with that concept.
This is not an union it's an exclusion.
| ParsedCommand newCmd = cmd; | ||
| newCmd.target = handler; | ||
| if(newCmd.target == nullptr || !(cmd.target->isValidCommandId(cmd.cmdId, CMDFLAG_STR_ONLY))){ | ||
| if(newCmd.target == nullptr || !(newCmd.target->isValidCommandId(cmd.cmdId, CMDFLAG_STR_ONLY))){ |
There was a problem hiding this comment.
Why was this changed? Might be a fix but i also don't see how it would change the behaviour currently if cmd and newCmd are identical at this point and all handlers have the same type.
Just curious
There was a problem hiding this comment.
Both are identical because there is not update at this point of the code (since the init), but is strange to compare old/new. If we update new before the of, there will be a bug, it's to prevent it.
There was a problem hiding this comment.
The order was changed to prevent redundant zero-torque commands and potential FFB glitches. Previously, axis->setEffectTorque(0) was called at the beginning of every calculation cycle, only to be overwritten a few microseconds later at the end of the function with the actual calculated force. This caused double calls to the motor driver per cycle. Now, setEffectTorque(0) is only called if the effects system is inactive ( !isActive() ). If it is active, the torque is updated only once at the end with the final calculated value.
| } | ||
| TMC4671Biquad(const TMC4671Biquad_t bq) : params(bq){} | ||
| TMC4671Biquad(const Biquad& bq,bool enable = true){ | ||
| #ifdef USE_DSP_FUNCTIONS |
There was a problem hiding this comment.
Same comment as for the filters. Could just use the same coefficient struct for the filter implementation instead of duplicating everything here too
There was a problem hiding this comment.
Ok understand, it's more clear now.
This is not the same case, but I can tweak that
There was a problem hiding this comment.
In a previous commit ( refactor: simplify Biquad filter implementations and TMC init ), we cleaned up the duplicated #ifdef USE_DSP_FUNCTIONS logic by centralizing the coefficient sign inversion directly inside the Biquad class ( calcBiquad ). This ensures the Biquad class now natively outputs the coefficients in the correct format for both DSP and non-DSP modes, which allowed us to remove the duplicated logic here in TMC4671.h .
Regarding sharing the exact same struct: the Biquad software filter uses float coefficients, while the TMC4671 hardware controller requires int32_t values scaled by 1 << 29 (Q29 format) for its registers. Because of this necessary hardware conversion step, we cannot share the exact same struct type between them, but the duplicate logic has been successfully removed.
| * | ||
| * Created on: 23.01.2020 | ||
| * Author: Yannick | ||
| * Author: Yannick, Vincent |
| #endif | ||
| } | ||
|
|
||
| int16_t TMC4671::getVelocityControllerTorque(){ |
There was a problem hiding this comment.
Where is this used? Doesn't seem to be used in the anticogging either...
There was a problem hiding this comment.
No I removed them in the anticogging too.
As you keep in the external encoder the code on set Ephi for velocity and position, I'mm asking if you want to keep it, or if we clean all.
| Mcu.Pin85=VP_TIM8_VS_no_output1 | ||
| Mcu.Pin86=VP_TIM9_VS_ClockSourceINT | ||
| Mcu.Pin87=VP_TIM13_VS_ClockSourceINT | ||
| Mcu.Pin88=VP_STMicroelectronics.X-CUBE-ALGOBUILD_VS_DSPOoLibraryJjLibrary_1.4.0_1.4.0 |
- Change forces array from int64_t to int32_t to avoid unnecessary 64-bit math overhead. - Deduplicate sine phase calculation using MATH_PI and MATH_SIN macros. - Restore magnitude/offset assignment in HidFFB instead of EffectsCalculator to maintain separation of concerns.
- Prevents breaking SerialFFB and other sources that don't push samples to the reconstruction filter.
- Use MATH_PI, MATH_SIN, and MATH_COS macros to remove duplicate ifdef blocks for direction calculations. - Fix Z-axis calculation missing DSP optimizations.
manoukianv
left a comment
There was a problem hiding this comment.
Thanks for the review! I've addressed all the comments and pushed the fixes. Let me know if everything looks good to you now.
| float a0, a1, a2, b1, b2; | ||
| float Fc, Q, peakGain; | ||
|
|
||
| #ifdef USE_DSP_FUNCTIONS |
There was a problem hiding this comment.
I'm asking to Gemini, I'm not familiar with that concept.
This is not an union it's an exclusion.
| void Flash_Dump(std::vector<std::tuple<uint16_t,uint16_t>> *result,bool includeAll = false); | ||
| bool Flash_Format(); | ||
|
|
||
| #ifdef COGGING_TABLE_FLASH_START_ADDRESS |
There was a problem hiding this comment.
You right I failed the split, to much code to d split, at the beginning, I have only 1 commit.
| // 3. Apply the reduction to the main torque. | ||
| // We must only reduce the magnitude of the torque, not invert it. | ||
| reductionAmount = clip(reductionAmount, 0.0f, fabsf((float)torque)); | ||
| if(torque > 0) { |
There was a problem hiding this comment.
I'm sorry I'm not sure to understand:
Does the idea is to make generic call and make functions that include the ifdef on dsp in those function? I can, I think you prefer see the real impact.
| int32_t force = 0; | ||
| int axisCount = axes.size(); | ||
| int32_t forces[MAX_AXIS] = {0}; | ||
| int64_t forces[MAX_AXIS] = {0}; |
There was a problem hiding this comment.
This is an issue, my bad
| void EffectsCalculator::updateEffectReconstruction(FFB_Effect* effect, float new_magnitude, float new_offset, bool is_periodic) | ||
| { | ||
| // Update target variables (for NONE mode and fallback) | ||
| effect->magnitude = (int16_t)new_magnitude; |
There was a problem hiding this comment.
It think the misunderstanding come from wrong code in wrong branch during the split again, I try to make it simpler but split is not really good.
This method is called by the ffb process to store magnitude and offset on effect compatible with the reconstruction filter. When we process the ffb from hid we need to do 2 things:
- Store the magnitude and offset as previous in the code
- Store the magnitude and offset in the "temporal structure" to be able to compute the slicing value.
The code you point is the storing of the value I move here, but we can also let in the ffb. I regroup all the process on magnitude and offset here.
Give me your preference.
| #endif | ||
| } | ||
|
|
||
| int16_t TMC4671::getVelocityControllerTorque(){ |
There was a problem hiding this comment.
No I removed them in the anticogging too.
As you keep in the external encoder the code on set Ephi for velocity and position, I'mm asking if you want to keep it, or if we clean all.
| void EffectsCalculator::updateEffectReconstruction(FFB_Effect* effect, float new_magnitude, float new_offset, bool is_periodic) | ||
| { | ||
| // Update target variables (for NONE mode and fallback) | ||
| effect->magnitude = (int16_t)new_magnitude; |
There was a problem hiding this comment.
You are absolutely right. The misunderstanding definitely came from me trying to regroup everything during the split, but it violates the separation of concerns.
HidFFB should be the one responsible for setting the base state ( effect->magnitude and effect->offset ) since it decodes the USB HID reports from the game. EffectsCalculator should only handle the DSP and filter logic. By mixing them, it obscured the flow of data.
I have reverted this: HidFFB now assigns the magnitude and offset directly just like it did before, and then it calls EffectsCalculator::updateEffectReconstruction(...) purely to push the new samples into the temporal structure for the reconstruction filter.
| ParsedCommand newCmd = cmd; | ||
| newCmd.target = handler; | ||
| if(newCmd.target == nullptr || !(cmd.target->isValidCommandId(cmd.cmdId, CMDFLAG_STR_ONLY))){ | ||
| if(newCmd.target == nullptr || !(newCmd.target->isValidCommandId(cmd.cmdId, CMDFLAG_STR_ONLY))){ |
There was a problem hiding this comment.
The order was changed to prevent redundant zero-torque commands and potential FFB glitches. Previously, axis->setEffectTorque(0) was called at the beginning of every calculation cycle, only to be overwritten a few microseconds later at the end of the function with the actual calculated force. This caused double calls to the motor driver per cycle. Now, setEffectTorque(0) is only called if the effects system is inactive ( !isActive() ). If it is active, the torque is updated only once at the end with the final calculated value.
| } | ||
| TMC4671Biquad(const TMC4671Biquad_t bq) : params(bq){} | ||
| TMC4671Biquad(const Biquad& bq,bool enable = true){ | ||
| #ifdef USE_DSP_FUNCTIONS |
There was a problem hiding this comment.
In a previous commit ( refactor: simplify Biquad filter implementations and TMC init ), we cleaned up the duplicated #ifdef USE_DSP_FUNCTIONS logic by centralizing the coefficient sign inversion directly inside the Biquad class ( calcBiquad ). This ensures the Biquad class now natively outputs the coefficients in the correct format for both DSP and non-DSP modes, which allowed us to remove the duplicated logic here in TMC4671.h .
Regarding sharing the exact same struct: the Biquad software filter uses float coefficients, while the TMC4671 hardware controller requires int32_t values scaled by 1 << 29 (Q29 format) for its registers. Because of this necessary hardware conversion step, we cannot share the exact same struct type between them, but the duplicate logic has been successfully removed.
| break; | ||
| } | ||
|
|
||
| case FFB_EFFECT_RAMP: |
There was a problem hiding this comment.
you right, I add a selector to exclude hard computing for ramp.
c53e1a4 to
9fb72f4
Compare
fix: compilation issue