emmebrusa / TSDZ2-Smart-EBike-1

TSDZ2 Open Source Firmware adapted to VLCD5-VLCD6-XH18 displays
GNU General Public License v3.0
131 stars 35 forks source link

Improving motor torque response #63

Open dzid26 opened 1 year ago

dzid26 commented 1 year ago

Feature: slower motor torque increase rate at the startup. @emmebrusa
I was thinking of adding this feature myself, but then I noticed that it was already implemented: https://github.com/emmebrusa/TSDZ2-Smart-EBike-1/blob/9f22efbac8baab6c2644afe1cefad0974b1be647/src/controller/ebike_app.c#L701-L711 I would like to set PWM_DUTY_CYCLE_RAMP_UP_INVERSE_STEP_STARTUP=254 to reduce the launching torque surge. Any bad experiences with this since it's commented out?

Additionally, I was thinking to check if ui8_battery_current_filtered_x10 is less small value, then ui8_duty_cycle_ramp_down_inverse_step=PWM_DUTY_CYCLE_RAMP_UP_INVERSE_STEP_MIN, because I am suspecting that for very low PWMs the generated torque is less than gearbox friction and thus undetectable by the rider. That would reduce the torque delay a little bit, especially if the slower ramp is used.

EDIT: illustration of the issue - simplified step response: image

emmebrusa commented 1 year ago

I had added this feature in C3 version. The stock display version was quickly removed for other issues and therefore there is no feedback. With the 860C version, I had two reports of the blue gear breaking at the start. Thinking that the cause was this feature I deleted it. I haven't had any reports of it since.

Try increasing PWM_DUTY_CYCLE_RAMP_UP_INVERSE_STEP_MIN a little.

To get an immediate response on the trails, I enabled "Assist whitout pedaling", I don't like it but it works.

dzid26 commented 1 year ago

Ok. Since I don't think blue gear can be affected by this, I will go ahead with enabling the startup rate limiting feature for myself and testing.

I am thinking though that alternatively I could achieve the same effect by simply lowering the first cadence breakpoint to 20rpm to 0rpm and rising PWM_DUTY_CYCLE_RAMP_UP_INVERSE_STEP_MIN=254 in this part of code:

ui8_duty_cycle_ramp_up_inverse_step = map_ui8((uint8_t)(ui16_wheel_speed_x10>>2),
                (uint8_t)10, // 10*4 = 40 -> 4 kph
                (uint8_t)50, // 50*4 = 200 -> 20 kph
                (uint8_t)ui8_duty_cycle_ramp_up_inverse_step_default,
                (uint8_t)PWM_DUTY_CYCLE_RAMP_UP_INVERSE_STEP_MIN);
        ui8_tmp = map_ui8(ui8_pedal_cadence_RPM,
                (uint8_t)20, // 20 rpm
                (uint8_t)70, // 70 rpm
                (uint8_t)ui8_duty_cycle_ramp_up_inverse_step_default,
                (uint8_t)PWM_DUTY_CYCLE_RAMP_UP_INVERSE_STEP_MIN);

My feeling is that it wouldn't matter that this signal is relatively slow, as my objective is to make that initial torque increase slower so I can "modulate" its value with the pedal force.

Btw, I am also using Assist without pedaling, but it doesn't eliminate the torque delay for me. I don't know if I am using it wrong or what, but it fails a simple test I like to do. I slowly approach a curb without pedaling, then I climb a curb with the help of pedal force. By the time I feel the motor torque kick in at all I am already on top of the curb and now I am dealing with another delay to wait for the torque to unwind (but that's a different problem). After looking into the code, I don't really know where the delay would come from apart from what I already mentioned that we possibly are wasting time ramping the PWM duty cycle slowly from 0.

dzid26 commented 1 year ago

I tested this first:

I am thinking though that alternatively I could achieve the same effect by simply lowering the first cadence breakpoint to 20rpm to 0rpm and rising PWM_DUTY_CYCLE_RAMP_UP_INVERSE_STEP_MIN=254 in this part of code

and it was satisfactory - torque was rising slower and was more manageable going low speed through some uneven offroad.

The delay is still an issue and I am on the quest to get rid of it next. (If I keep pedaling (with no force), the delay is not present when I suddenly apply force. So it is strange because Assist without pedaling should make the delay the same when not pedaling. )

emmebrusa commented 1 year ago

Try to re-enable the function, you can see the difference.

A premise, I assume you are using Power assist and have calibrated the torque sensor.

With "Assist without perdaling" enabled, the lag cannot be caused by the cadence, it is definitely the motor torque not exceeding the resistant torque. Have you tried playing with the "Startup boost" parameters? The adjustment range is very wide. Another parameter which influences the response curve of the motor is "Pedal torque adc angle adj". By decreasing it (up to -20) the delay does not decrease, but the response is more gradual.

dzid26 commented 1 year ago

I tried everything you said which had some effect.

Lastly I tried simply do something opposite to what this issue is trying to achieve - that is I set the motor acceleration to 100%. Now the delay is not noticable. It makes the gear to be a bit jerky though. Something to do with motor commutation at low speed. I turned off the startup boost next. It still quick without - somehow even in power assist mode. In torque assist mode it is a rocket. I will do more testing, but my initial impression is that there is room for improvement of how the PWM duty cycle is ramped. Question is how to increase smoothness and ridibility without scaryfing the delay. Perhaps:

  1. it should not ramp from zero duty;
  2. additionally maybe instead setting the rate, maybe we should set a time in which the torque can achieve its maximum value
  3. or not the time but how many rotations it performed - e.g. maybe motor should achieve full target torque after 100 eRotations to try to achieve full torque before the end of pedal stroke.

Now that startup delay is so short, the turn-off delay feels very long. I have ideas how this can be improved. For example the motor should not rotate for longer than time since last off state. Or, again a better idea - the turn off time should depend on how much the pedals have traveled. If they haven't been moved at all, the motor should turn off instantly the moment the pedal torque is 0. I don't think it works the best currently because I always get a minimum of 200ms of torque hold after it is applied even with deceleration set to 100%.

I also checked the torque adc angle adj as you suggested and set it to -20, but I don't understandand the calculation yet and I don't feel a difference at the moment. How do I interpret that both - the yellow line and the axis are labeled "Weight" on this graph?: image I would like to lower the lower sensitivity of the torque sensor as the default is too sensitive when pressing lightly and then at the top end there is not much difference. I think I want linear torque sensor mapping but maybe it is already...

emmebrusa commented 1 year ago

@dzid26 I advise you not to go over 50% with the acceleration ramp (36V-36V), the blue gear may not like it. With 100% acceleration ramp, max PWM is reached faster. And the higher the PWM, the longer the deceleration time. It's normal. Your three "Perhaps" are to be explored, I'll look at them as soon as I have time.

Just think that after trying to minimize the overrun as much as possible, I changed my perspective. I realized that a delay is necessary, a too abrupt stop is not good. Now I have the deceleration ramp at 0% and I'm much better on the trails. I just need to be more careful when shifting gears. I also have to say that I use the 860C version, which is different. If 100% deceleration is still too much for you and you like to experiment, try gradually decreasing PWM_DUTY_CYCLE_RAMP_DOWN_INVERSE_STEP_MIN. Difficult to further decrease the delay, consider that 200ms is the control time of the stopped pedals. Eventually try to decrease, slightly, CADENCE_SENSOR_STANDARD_MODE_SCHMITT_TRIGGER_THRESHOLD.

Regarding "Pedal torque adc angle adj" it's strange that you don't notice a difference, perhaps because you have a very fast acceleration. I'll explain the purpose of this parameter, designed for hand-bike users who use the coaster brake version. The push with the arms is less than the one with the legs, therefore a high assistance is necessary. Without a freewheel/clutch, it may happen that after starting off, it is the engine that drags the arms, while it should always be the other way around. This parameter allows you to find a balance between the thrust of the arms and the thrust of the motor. In the graph you don't have to look at the yellow line, which can be different from motor to motor. You have to look at the blue one. To better understand, see the Torque_calibration.xlsx file and try changing the values of "offset adj", "range adj", "angle adj".

With a perfectly linear mapping the motor is not usable. Tried.

dzid26 commented 1 year ago

@dzid26 I advise you not to go over 50% with the acceleration ramp (36V-36V), the blue gear may not like it. With 100% acceleration ramp, max PWM is reached faster. And the higher the PWM, the longer the deceleration time. It's normal. Your three "Perhaps" are to be explored, I'll look at them as soon as I have time.

Sometimes the motor surges forward, but because my current limit is to 8Amp for now, nothing too serious. I have 48V motor with 52V battery.

Just think that after trying to minimize the overrun as much as possible, I changed my perspective. I realized that a delay is necessary, a too abrupt stop is not good.

Delay is fine when pedaling continuously at higher wheel speed. But when just starting, or trying to go over wood logs it destroys the balance completely and the bike drives me instead of me driving it.

Regarding "Pedal torque adc angle adj" it's strange that you don't notice a difference, perhaps because you have a very fast acceleration. I'll explain the purpose of this parameter, designed for hand-bike users who use the coaster brake version. The push with the arms is less than the one with the legs, therefore a high assistance is necessary. Without a freewheel/clutch, it may happen that after starting off, it is the engine that drags the arms, while it should always be the other way around. This parameter allows you to find a balance between the thrust of the arms and the thrust of the motor. In the graph you don't have to look at the yellow line, which can be different from motor to motor. You have to look at the blue one. To better understand, see the Torque_calibration.xlsx file and try changing the values of "offset adj", "range adj", "angle adj".

I must say it's hard to follow exactly what is going on with Excel and how it relates to Java and C calculation. The end result is somewhat satisfactory for me with adjusting offset, etc. But I couldn't figure out how in the code the torque target curve is generated. Too complex. (Btw, you should use $ signs is Excel (e.g. $D$2), so you don't manually copy the cell formulas! I am attaching one that Torque_calibration2.xlsx has it fixed. Also simplified convoluted formula in cells D6 and G9.
Anyway, it doesn't matter because in the end I just added simple pedal interpolation to the code. I think it can be that simple - the torque mapping doesn't have to be smooth.

            const uint16_t x_torque[] = {0U, 30U, ADC_TORQUE_SENSOR_RANGE_TARGET_MAX};
            const uint16_t y_torque[] = {0U, 10U, ADC_TORQUE_SENSOR_RANGE_TARGET_MAX};
            if (ui16_adc_pedal_torque_delta < x_torque[1]){
                ui16_adc_pedal_torque_delta = map_ui16(ui16_adc_pedal_torque_delta,
                    x_torque[0],
                    x_torque[1],
                    y_torque[0],
                    y_torque[1]);
            }else{
                ui16_adc_pedal_torque_delta = map_ui16(ui16_adc_pedal_torque_delta,
                    x_torque[1],
                    x_torque[2],
                    y_torque[1],
                    y_torque[2]);
            }

You may notice above that I was actually trying to invert the curve (slow increase at the bottom). Why because I am trying to solve another issue that little bit of pedal force creates too much initial torque. It doesn't matter during normal riding, but I am trying to make this motor work at low speed when doing "tricks" or climbing technical parts. But the motor still surges forward no matter that I flattened pedal mapping.

But I think I found the reason. When ui8_startup_assist_adc_battery_current_target is small, the minimum duty cycle that the motor settles on produces more current than needed. In theory, it should be fine, because I think ui8_adc_battery_current_filtered has a resolution of 0.16A, which doesn't sound like a lot of torque, But actually it is 4x worse because it is lost in those two shift rights of the filter:

ui8_temp |= ADC1->DB5RL
ui8_adc_battery_current_acc = (uint8_t)(ui8_temp >> 1) + ui8_adc_battery_current_acc;
        ui8_adc_battery_current_filtered = (uint8_t)(ui8_adc_battery_current_acc >> 1) + ui8_adc_battery_current_filtered;

Additionally not much happens below 20 duty cycle value. So I set minimum PWM:

            if (ui8_g_duty_cycle < 20U) {
                ui8_g_duty_cycle = 20U;
            }

This should reduce startup delay by between 1-84ms depending on ui8_controller_duty_cycle_ramp_up_inverse_step.

Second, if the current target is less than 1.6A (10 value), I limited the duty cycle to investigate the resolution problem:

if (ui8_adc_battery_current_target>10) {
            ui8_duty_cycle_target = PWM_DUTY_CYCLE_MAX;
        } else {
            ui8_duty_cycle_target = 20;
        }

This caused the motor to startup much gentler than before. (I am testing in Torque Assist mode on with ASSISTANCE_WITHOUT_PEDAL_ROTATION_THRESHOLD=120). Of course, it is just a hack and I need to take erps into account. But basically, the gist is to use Voltage (duty cycle) control instead of current control for low currents. Or maybe always... and treat the battery current as a safety limitation. BEMF constant needs to be introduced to calculate voltage correctly. Vq=BEMF+ R Itarget Vd=Itarget ωL

If 100% deceleration is still too much for you and you like to experiment, try gradually decreasing PWM_DUTY_CYCLE_RAMP_DOWN_INVERSE_STEP_MIN. Difficult to further decrease the delay, considering that 200ms is the control time of the stopped pedals. Eventually try to decrease, slightly, CADENCE_SENSOR_STANDARD_MODE_SCHMITT_TRIGGER_THRESHOLD.

I set CADENCE_SENSOR_STANDARD_MODE_SCHMITT_TRIGGER_THRESHOLD to 0 without any noticeable difference except that it reduced the turning-off delay significantly! I am not sure what problem it was trying to solve, or maybe there is another feature that shadows it, but I still get continuous torque when pedaling and no interruptions that I can notice due to repeated PAS pulses.

Next, I played a bit with SVM and TIM1. I saw you set it to 19Khz. I tried 15khz and 30khz. 15khz seems alright. 30khz seems like reaches a higher speed without the help of FOC. Any serious reason I shouldn't run it at 30khz? I doubt Mosfets care too much. The only thing is that at 30khz I experience turn-off delay again, even though CADENCE_SENSOR_STANDARD_MODE_SCHMITT_TRIGGER_THRESHOLD 0. Maybe some other parameter doesn't like faster sampling time... I need to investigate. Placebo - I just realized that I was flashing wrong binary at this point, due to a problem with flash.bat.

Eventually, I will create a few good commits, but right now I am still testing things.

dzid26 commented 1 year ago

Forget what I was writing earlier. I just made TSDZ2 - AMAZING by simply controlling PWM directly instead of using the current adc - during startup.

Now, I feel like much-complicated stuff that is in the firmware is a remittance of this one deficiency. Now I find some complex features like cadence detection, pedal curve mapping, torque rate limiting per cadence/wheelspeed, filters - were in one or another helping to deal with issues with a rough startup. Also safety aspect becomes almost as non-issue, if the bike doesn't lurch forward uncontrollably.

Removing torque discontinuities is the key to greatness:

For safety, the bike should feel like a bicycle at the startup and like an e-bike after launch - all while maintaining a smooth transition. Safety is automatically provided by the human being in the loop. The dismounted rider is more vulnerable and less in control, but as long as the pedal-assist gain is small and there is no delay, the bike should feel natural. In current software, this was completely circumvented by only allowing torque when pedaling (or with pedaling but with uncontrollable behavior at low speed).

I started with this simple code (pasted after assistance current calculation):

...
  ui8_duty_cycle_target  = ((uint16_t)ui8_adc_battery_current_target * 2U) + (ui16_motor_speed_erps / 4U);
...

Which already made the motor feel great at low speed. But only at one level, since it was the current target dependant. I realized that startup gain (when the rider is vulnerable) should be independent of the assistance level later on.

With this MOTOR_ACCELERATION and MOTOR_DECELERATION can be set to 100 and it doesn't stress the blue gear because it is loaded immediately and gradually. With this change, I don't see a reason to slow down the response. Additionally, I set CADENCE_SENSOR_STANDARD_MODE_SCHMITT_TRIGGER_THRESHOLD to 0 for the reduced turn-off delay.

Then, I added transitioning to no limitation above a certain pedal and above a certain cadence and I scaled pwm duty cycle depending on battery voltage:


        #define SMOOTH_START
        #ifdef  SMOOTH_START
        //Smooth startup torque by controlling current via PWM - more precise than current control
        #define SMOOTH_START_FACTOR  50U
        //if pedal above the threshold, add a ramp to the limit which quickly 
        #define SMOOTH_START_TAPER_OFF_PEDAL_THRESHOLD  ADC_TORQUE_SENSOR_RANGE_TARGET_MAX
        #define SMOOTH_START_TAPER_OFF_PEDAL_FACTOR  64U
        #define SMOOTH_START_TAPER_OFF_CADENCE_THRESHOLD  15U
        #define SMOOTH_START_TAPER_OFF_CADENCE_FACTOR 128U
        #define SMOOTH_START_FACTOR_DENOMINATOR  4U

        uint16_t pedal_torque = ui16_adc_pedal_torque - ui16_adc_pedal_torque_offset; //independent from advanced pedal mappings
        if(pedal_torque > UINT8_MAX){
            pedal_torque = UINT8_MAX;
        }
        uint32_t smooth_stroke_voltage_limit_x100 = pedal_torque * SMOOTH_START_FACTOR / SMOOTH_START_FACTOR_DENOMINATOR;

        if (pedal_torque > SMOOTH_START_TAPER_OFF_PEDAL_THRESHOLD){ //start ramping limit up above the threshold (pedal)
            smooth_stroke_voltage_limit_x100 += (uint16_t)(pedal_torque - SMOOTH_START_TAPER_OFF_PEDAL_THRESHOLD)*SMOOTH_START_TAPER_OFF_PEDAL_FACTOR/SMOOTH_START_FACTOR_DENOMINATOR;
        }
        if(ui8_pedal_cadence_RPM > SMOOTH_START_TAPER_OFF_CADENCE_THRESHOLD){ //start ramping limit up above the threshold (cadence)
            smooth_stroke_voltage_limit_x100 += (uint16_t)(ui8_pedal_cadence_RPM - SMOOTH_START_TAPER_OFF_CADENCE_THRESHOLD)*SMOOTH_START_TAPER_OFF_CADENCE_FACTOR/SMOOTH_START_FACTOR_DENOMINATOR;
        }
        //bemf 36V motor = 22*sqrt(3) / (1/0.00194) =0.074 V/erps
        //or bemf 36V motor = 0.0806 V/rad/s = 0.5 V/rev/s = 0.0633 V/erps  https://avdweb.nl/solar-bike/hub-motor/efficiency-bldc-motor-tongsheng-tsdz2-and-astro-3205-compared
        //bemf 48V motor 0.0633 * 48/36 = 0.0844

        uint16_t battery_voltage_x100 = ui16_battery_voltage_filtered_x1000 / 10U;

        uint32_t smooth_stroke_voltage_target_x100 = (uint32_t)smooth_stroke_voltage_limit_x100 + ((uint32_t)ui16_motor_speed_erps*8);
        if (smooth_stroke_voltage_target_x100 > (UINT16_MAX*2)){
            smooth_stroke_voltage_target_x100 = UINT16_MAX*2;
        }
        uint16_t stroke_duty_cycle_target = (uint16_t)(smooth_stroke_voltage_target_x100*256 / battery_voltage_x100);

        if (stroke_duty_cycle_target < (uint16_t)ui8_duty_cycle_target){
            ui8_duty_cycle_target = (uint8_t)stroke_duty_cycle_target;
        }
        #endif

This felt amazing. I could balance the bicycle in place, easily lift a wheel for the curb, slowly ride up the staircase, easily do wheelies, and even do power slides on gravel - all thanks to responsiveness and gentleness.

Then I formalized the code here https://github.com/emmebrusa/TSDZ2-Smart-EBike-1/compare/master...dzid26:TSDZ2-Smart-EBike:ebike_work, but by accident, I ripped the cable from the motor so I couldn't fully test it yet: Screenshot_20230602_043117_Photos

This implementation worked on my bike (48V@52V), but it should work with other motor and battery voltages. Any suggestions are welcomed on how to wrap it up? In particular, I am not sure if I should reference ADC_TORQUE_SENSOR_RANGE_TARGET_MAX or make it independent - does this represent the whole range of the pedal force? - it didn't feel like it.

emmebrusa commented 1 year ago

Too bad about the cable, it happens in experimentation. In this period I don't have time to elaborate, I will look later. Continue your trials and keep me updated.

dzid26 commented 11 months ago

I did a lot of testing and calibration. of the duty cycle based smooth start.
It makes this motor amazing for mountain biking and everyday cruising. No more jerking - it's responsive, yet smooth.

I am going to prepare PR draft for this.