• Hello ES! We could use some help to get us past the finish line on building the new knowledgebase for the forum.
    Can you donate? Please see our fundraising page. Thank you!

TSDZ8 OSF (open source firmware)

Hi everyone,

Any help would be much appreciated; I'm running a TSDZ8 with SW102 display. Everything runs fine, but the SW102 doesn’t actually save any setting changes (max speed, wheel circumference, max power limit, etc.). After making changes, it always reverts back to the default values.

Any idea how to fix this? Or could it be that the settings are saved but simply not shown correctly on the configuration display?

OSF used:
TSDZ8: OSF_TSDZ8_860C_KATANA_2_V00_01.35.hex (GitHub - mstrens/OSF_860C)
SW102: sw102-otaupdate-20.1C.5-1-1.zip (GitHub - emmebrusa/Color_LCD_860C: Color LCD 860C for TSDZ2 Open Source Firmware)

Thanks in advance!
Normally changes should be saved when you exit. If I remember you have to exit totally (so going back up to normal display). On 860C it is done pressing several times (shortly) on power on/off button. I presume it is the same for SW102 but I am not sure
 
Normally changes should be saved when you exit. If I remember you have to exit totally (so going back up to normal display). On 860C it is done pressing several times (shortly) on power on/off button. I presume it is the same for SW102 but I am not sure
Thanks a lot for the quick reply! I tried it that way without success. But after setting all the other parameters, I reflashed both firmwares. After that, I was able to adjust all the settings without any problems. A bit strange, but everything is working perfectly now!
 
Hi everyone,

Any help would be much appreciated; I'm running a TSDZ8 with SW102 display. Everything runs fine, but the SW102 doesn’t actually save any setting changes (max speed, wheel circumference, max power limit, etc.). After making changes, it always reverts back to the default values.

Any idea how to fix this? Or could it be that the settings are saved but simply not shown correctly on the configuration display?

OSF used:
TSDZ8: OSF_TSDZ8_860C_KATANA_2_V00_01.35.hex (GitHub - mstrens/OSF_860C)
SW102: sw102-otaupdate-20.1C.5-1-1.zip (GitHub - emmebrusa/Color_LCD_860C: Color LCD 860C for TSDZ2 Open Source Firmware)

Thanks in advance!
To save changes you have to change in "bike" menu "password" to "no"
 
Assembled and tested my TSDZ8+SW102 already (after 5 hours of playing with wires colors))). KATANA_2_V00_01.35. Power delivery is very noticeable after TSDZ2B ("overclocked" to 700W via OSF even). A bit quieter and smoother in operation.
I can really see that it doesn't strain and works "relaxed." The integrity/solidness of the construction is also very noticeable (because on the TSDZ2B from the first few kilometers - there was a feeling that it was broken already and everything was playing, rattling and swaying)
...well, at least these are my initial observations... perhaps after 100-200 km, everything will be the same as with its predecessor... although I really hope not :)

P.S. during first 15 km, I had to raise the ACD idle three times, from 157 to 190 already :) (and ADC max on 440 0_o )

If you need to test something specific on SW102, please let me know, and I will be happy to do so.
 
Last edited:
@HazzaHodgson
I already said that when you activated the option dynamic lead angle 2, the new code is not called and so OSF works still with the "old" foc_angle.
I further checked the code about the dynamic lead angle and I just noticed that when you select option 1, then the code is executed and calculate a lead_angle named "lead_angle_LUT_256" but currently this field is not used.
When I wrote the code for dynamic lead angle, I planned to first calculate it in parallel with the current foc_angle but without applying it really. So I planned to compared lead_angle_LUT_256 to ui8_g_foc_angle on a display (or the pc) but I could not perform the test immediately and so started developing other possible improvement.
As I made those changes some days ago, I forgot to inform you.

So the test you made with dynamic lead angle 1 and 2 are not significant. Sorry for this.

If you want you could easily
- compare the 2 fields on PC (using uprobe) or on display (if you fill lead_angle_LUT_256 in another field (in the code that fill the frame sent to 860C) so "misusing" some display field
- change the firmware in order to fill ui8_g_foc_angle with lead_angle_LUT_256 in the ISR 1 where ui8_g_foc_angle is currently calculated based on the current.

If you do the one or the other, please let me know the result.

I have also a question:
I saw that you converted all fields about current from uint8_t to uint16_t. in uint8_t, the max value in A is 255*16/100 = 40A.
Did you had current that exceeded 40A or did you change only for safety.
I could change my code also to uint16_t if it is really needed but I prefer not to do so because keeping uint8_t makes maintenance of code much easier (when comparing with mbrusa changes).
I presume that even if you exceed 40A, it should be on very rare situation. One easy and probably acceptable solution to be to add a test that clamp ui8 current to 255 when filling ui8 field from a ui16 value. Then we could keep ui8 fields like in TSDZ2 firmware.
What do you think about this?
 
dynamic update of the hall positions
I have not looked at the code too much, but in case you are running halls through the whole RPM range you can save some computational power by only relying on halls in the 0 to 100~ rpm scenario and then blend into sensorless FOC, that's how vesc does it and it works really well. Halls are only needed to figure out the initial rotor position, after that they are pretty much useless.
 
@ HazzaHodgson
I am trying to find the changes you made for dynamic lead.
By the way I noticed an error in your motor.c in the position of ) on the second line (and the last one
if ((ui8_controller_duty_cycle_target < ui8_g_duty_cycle) // requested duty cycle is lower than actual || (ui16_controller_adc_battery_current_target < (ui16_adc_battery_current_filtered) // requested current is lower than actual || (ui16_adc_motor_phase_current > ui16_adc_motor_phase_current_max) // motor phase is to high // || (ui16_hall_counter_total < (HALL_COUNTER_FREQ / MOTOR_OVER_SPEED_ERPS)) // Erps is to high || (ui16_adc_voltage < ui16_adc_voltage_cut_off) // voltage is to low || (ui8_brake_state) )) {
Can you copy the same line please I don't get you 😂 I didn't edit your code exactly I'll get the changes for you
 
I tried to see what you had to change to be able to compile about dynamic . I do not see it. I presume changes where related to other changes that you made on your side.
On my side, I tried to compile with my actual version of motor.c (deaply modified to increase precision of rotor position using a PLL + more decimal + dynamic update of hall position + interpolation of LUT Sinus), I do not get compilation errors when I activates dynamic lead 1 or 2.

Important note : I just checked and dynamic lead 2 is not activated. The function that perform it are not yet called. So the test you made with option 2 is just the same as with no dynamic lead.
Ahhh that's a shame. Still the old way still feels good then! I wonder what caused the slight instability over (0)
Also here's what I did to compile
Top of motor.c I added #include <math.h>
— the angle brackets tell the compiler to look in the standard library include path, not your project folder.
In the make file I changed LDLIBS =
To
LDLIBS += -lm
I also removed inline from the lines in motor.c in the block #if (DYNAMIC_LEAD_ANGLE == (1))
calculate_id_part1
calculate_id_part2
Now this gave me a successful compile
If it actually called the dynamic lead and compiled correctly I'd never know, but I did feel like it had better top end
But this could also be a consequence of increasing battery amps
Would it ever compile without calling/using it if I had dynamic angle set to (1)
Or would it tell me otherwise and fail the build ?
 
@HazzaHodgson
I already said that when you activated the option dynamic lead angle 2, the new code is not called and so OSF works still with the "old" foc_angle.
I further checked the code about the dynamic lead angle and I just noticed that when you select option 1, then the code is executed and calculate a lead_angle named "lead_angle_LUT_256" but currently this field is not used.
When I wrote the code for dynamic lead angle, I planned to first calculate it in parallel with the current foc_angle but without applying it really. So I planned to compared lead_angle_LUT_256 to ui8_g_foc_angle on a display (or the pc) but I could not perform the test immediately and so started developing other possible improvement.
As I made those changes some days ago, I forgot to inform you.

So the test you made with dynamic lead angle 1 and 2 are not significant. Sorry for this.

If you want you could easily
- compare the 2 fields on PC (using uprobe) or on display (if you fill lead_angle_LUT_256 in another field (in the code that fill the frame sent to 860C) so "misusing" some display field
- change the firmware in order to fill ui8_g_foc_angle with lead_angle_LUT_256 in the ISR 1 where ui8_g_foc_angle is currently calculated based on the current.

If you do the one or the other, please let me know the result.

I have also a question:
I saw that you converted all fields about current from uint8_t to uint16_t. in uint8_t, the max value in A is 255*16/100 = 40A.
Did you had current that exceeded 40A or did you change only for safety.
I could change my code also to uint16_t if it is really needed but I prefer not to do so because keeping uint8_t makes maintenance of code much easier (when comparing with mbrusa changes).
I presume that even if you exceed 40A, it should be on very rare situation. One easy and probably acceptable solution to be to add a test that clamp ui8 current to 255 when filling ui8 field from a ui16 value. Then we could keep ui8 fields like in TSDZ2 firmware.
What do you think about this?
I changed only phase amps to go to 50a, which was bottlenecked by ui8 hence changing it to ui16. Then battery amps to 30. Then this caused a massive domino effect where I had to convert all amps stuff to ui16 everything else the actual values will be the same limits. I do have 1 bug though I'm not quite sure on how to fix it, when I go over 1250 watts my display wraps round and the number lingers around 250 until I drop below 1250 again. I think it's an overflow problem but I wonder if I can fix it with firmware because I don't have the gear to flash my display

I can have a play around when I'm home, I can't probe using pc because I got the cheap Chinese JTAG which will pop my controller if powered on when connected to pc D:
 
@HazzaHodgson
I already said that when you activated the option dynamic lead angle 2, the new code is not called and so OSF works still with the "old" foc_angle.
I further checked the code about the dynamic lead angle and I just noticed that when you select option 1, then the code is executed and calculate a lead_angle named "lead_angle_LUT_256" but currently this field is not used.
When I wrote the code for dynamic lead angle, I planned to first calculate it in parallel with the current foc_angle but without applying it really. So I planned to compared lead_angle_LUT_256 to ui8_g_foc_angle on a display (or the pc) but I could not perform the test immediately and so started developing other possible improvement.
As I made those changes some days ago, I forgot to inform you.

So the test you made with dynamic lead angle 1 and 2 are not significant. Sorry for this.

If you want you could easily
- compare the 2 fields on PC (using uprobe) or on display (if you fill lead_angle_LUT_256 in another field (in the code that fill the frame sent to 860C) so "misusing" some display field
- change the firmware in order to fill ui8_g_foc_angle with lead_angle_LUT_256 in the ISR 1 where ui8_g_foc_angle is currently calculated based on the current.

If you do the one or the other, please let me know the result.

I have also a question:
I saw that you converted all fields about current from uint8_t to uint16_t. in uint8_t, the max value in A is 255*16/100 = 40A.
Did you had current that exceeded 40A or did you change only for safety.
I could change my code also to uint16_t if it is really needed but I prefer not to do so because keeping uint8_t makes maintenance of code much easier (when comparing with mbrusa changes).
I presume that even if you exceed 40A, it should be on very rare situation. One easy and probably acceptable solution to be to add a test that clamp ui8 current to 255 when filling ui8 field from a ui16 value. Then we could keep ui8 fields like in TSDZ2 firmware.
What do you think about this?
Good news or bad news first? 😩

I'll start with bad
I couldn't monitor it properly in the end because I think the old advance was interfering or some shit

Good news

I thought frock it and just wired it in

I still get my startup kick

Battery pull is extremely efficient, I was flat out in a low ISH gear going 19, 5amps battery
In the video I see my shift down twice, gear 4 (42t-11t, I'm not sure what t gear 4 is) going uphill only pulling 17a battery

Sorry for shaky video it's awkward recording while tryna bike 😂

I'm not sure if motor foc or cadence is reading correctly the advance correctly yet or not. You could just map motor foc to the actual angle (-15 -+30) mapped from 0 to 45 or something.


Now I'm not sure if it's placebo and it's ignored the angle altogether because I'm used to brute forcing this motor or if it's meant to feel a bit softer. But I had limited testing, only went round the block a few times. I'll give some more feedback tomorrow when I have time 😎
 
After riding to work and back today giving me a chance to test a bit more, I've got a Quick update on the dynamic lead-angle test:
I’ve got the new code running(or it's ignoring it) — fully replaced the old FOC logic, ui8_g_foc_angle and related stuff are set to 0, so this should be using only the new dynamic calculation.
Off the line it still feels smooth, but acceleration up to around 20 mph is noticeably slower than before. Once I’m above that speed, the torque really drops off — if I shift up, battery amps rise (for example, 17 A → 25 A) but it barely accelerates. It seems like the advance isn’t scaling properly with speed or load.
The lead_angle_LUT_256 value I spoofed to the display only sits between 0 and 4, even when RPM increases. That makes me think it’s either not applying at all or the scaling is way off.
Could you confirm the correct way to actually activate the dynamic lead-angle — i.e., which variable needs to receive the calculated value to replace the FOC angle used for PWM? I’d like to compare it with how I’ve wired it in to see if I’ve missed something.
Current setup:
Dynamic lead-angle = 1
FOC multiplier = default (seems ignored)
Field-weakening min = 200 ERPS
lead_angle_LUT_256 mapped to display slot 14 for testing
The motor runs fine and efficiency seems good, but it feels like it’s running on a static advance instead of adapting dynamically. Any pointers on confirming activation or fixing the scaling would be great.
 
In the code I published on gitbub (in test branch) I noticed that when you select dynamic 1 or 2, it has no effect. The program continue to use g_foc_angle like with option 0. If you force g_goc_angle to 0, it means that there is no lead advance anymore.

I suggest to wait one or 2 days. I am trying to prepare a new version (in test branch) were lead angle would effectively be calculated and applied depending on the dynamic lead angle option.
 
In the code I published on gitbub (in test branch) I noticed that when you select dynamic 1 or 2, it has no effect. The program continue to use g_foc_angle like with option 0. If you force g_goc_angle to 0, it means that there is no lead advance anymore.

I suggest to wait one or 2 days. I am trying to prepare a new version (in test branch) were lead angle would effectively be calculated and applied depending on the dynamic lead angle option.
Yes yes I read your reply, I used lead_angle_LUT_256 where "ui8_g_foc_angle current calculation " is
I got the same result as the monitored value. I even tried changing in ebikeapp.c the motor foc to map angle from -15 to +30 as foc 0-45. It would be 26 on idle and dropped to 22 under load. I nearly toasted my motor. It got extremely hot to touch and see external temp sensor picture(was 54 at hottest then I decided to take a pic ) wonder what internal temp would've been 😂 this was only from a 5 minute ride. I went back to your original code with my tune and I really think this simple code is the best..I've also experienced with dynamic leads and stuff not long ago and I got the same amps wasted as heat rather than torque I just did a 1 mile stretch which is uphill from the town centre back to my house. I was full throttle all the way pulling 30 battery amps. When I got in I went to feel motor temp and it was cold to the touch. External temp sensor showed 14 degrees so I guess that's ambient temp outside 🤷‍♂️ if you're unsuccessful with the dynamic lead route I think the best thing to play with would be a hybrid. It can use your old code up until a certain wheel speed(rather than motor speed) then it can try settle speed rather than trying to accelerate more (this is how factory firmware feels like) it would save some power at the top end while keeping your reliable old method until then

1 question I do have is how does field weakening work in osf? Is it usually intergrated into the lead angle calculation or does it run separately? Any debug monitors I can map to display to see if it's working? Etc 1 active 0 inactive
 

Attachments

  • 20251007_215701.JPG
    20251007_215701.JPG
    902.8 KB · Views: 9
Last edited:
Yes yes I read your reply, I used lead_angle_LUT_256 where "ui8_g_foc_angle current calculation " is
I got the same result as the monitored value. I even tried changing in ebikeapp.c the motor foc to map angle from -15 to +30 as foc 0-45. It would be 26 on idle and dropped to 22 under load. I nearly toasted my motor. It got extremely hot to touch and see external temp sensor picture(was 54 at hottest then I decided to take a pic ) wonder what internal temp would've been 😂 this was only from a 5 minute ride. I went back to your original code with my tune and I really think this simple code is the best..I've also experienced with dynamic leads and stuff not long ago and I got the same amps wasted as heat rather than torque I just did a 1 mile stretch which is uphill from the town centre back to my house. I was full throttle all the way pulling 30 battery amps. When I got in I went to feel motor temp and it was cold to the touch. External temp sensor showed 14 degrees so I guess that's ambient temp outside 🤷‍♂️ if you're unsuccessful with the dynamic lead route I think the best thing to play with would be a hybrid. It can use your old code up until a certain wheel speed(rather than motor speed) then it can try settle speed rather than trying to accelerate more (this is how factory firmware feels like) it would save some power at the top end while keeping your reliable old method until then

1 question I do have is how does field weakening work in osf? Is it usually intergrated into the lead angle calculation or does it run separately? Any debug monitors I can map to display to see if it's working? Etc 1 active 0 inactive
You must really take care with lead angle (foc) because if the value is wrong, you can damage the motor because a big part of the current can generate heat instead of mechanical power.

About field weakening, there is a flag to activate it or not. It is not integrated into the lead angle.
You can see the principle in CCU80_1_IRQHandler() (in motor.c). It changes the value of ui8_fw_hall_counter_offset that is used to position the magnetic flux. I never looked deaply at it (I do not really understand it). I just copied from TSDZ2.

For you information, I just put on github for 860C display a new version in the branch test_1_hall_position_in_q88.
There are major changes in this version and I did not tested it at all. So be very carefull if you try it.
Main changes are:
- rotor position is calculated in a format q8_8. It means that it has 8 bits more as decimal to increase precision
- rotor positioning should be improves by the uses of a PLL that should better estimate the rotor velocity and the position.
- PWM calculated to generate the magnetic flux uses a table (like a sinus table) of 256 items to represent the 360°. Precision of PWM is increased because OSF interpolates now between 2 entries (based on the added decimals in rotor position)
Those changes are always active in the code.
On top of this, there are some new options that can be activated or not using #define in main.h.
#define DYNAMIC_LEAD_ANGLE (0) // (0) no dynamic
// (1) dynamic based on Id and a PID + optimiser
// (2) dynamic based on Idc and a optimiser (= esc)
#define DYNAMIC_HALL_POSITION_UPDATE (0) // (0) no dynamic update of u16_hall_angle_table_Q8_8[]
// (1) Dynamic update (call Update_LUT_periodic() in ebike_app_periodic

DYNAMIC_HALL_POSITION_UPDATE = 1 let oxs automatically update the position of the hall sensors. With option 0, the positions of hall sensors are hard coded based on the measures I made on my motor but it could be (not sure) than other motors have slightly different positions.

DYNAMIC_LEAD_ANGLE options allows to tests 3 ways of calculating lead angle.
Option 0 : foc angle is calculated like before taking care of the current and the foc_multiplier
Option 1 : uses a PID and an ESC (optimiser) trying to minimise Id (unproductive current). This is quite complex and used many parameters that should be fine tuned (if code is already working)
Option 2 : uses only an ESC trying to minimise the battery current. It make also a first estimate of the lead angle based on erps instead of the current. It is much simple to understand and to fine tune that option 1

As said, I did not tested all those changes. In order to reduce the risk of having a bad lead angle (motor heating), the current version always applies the value like in option 0. Still, when option 1 or 2 is activated, OSF calculates also a second lead angle and send the value to the 860C display in the field that is normally used for cadence pedal. This allows to display on the 860C both lead angle (the foc angle and the one from option 1 or 2) and to reduce the risk (foc angle being applied).
If the tests show that the values from option 1 or 2 are valid, It will be very to change the code in order to really apply them instead of the foc_angle and to start seeing the real effect.

I would appreciate if you could tests those changes but I can't give any warranty. There are probably bugs because there were so many changes.
If you decide to test it, I would say:
- do not try with high current.
- Start with the 2 #define on 0 and see if motor still runs with no load , do not produce abnormal noise and do not consume more current than with old version.
- if OK, you can try DYNAMIC_HALL_POSITION_UPDATE. I do not expect a big difference.
- If still OK, activate DYNAMIC_LEAD_ANGLE = 2 (the risk is lower) and check on the display if the 2 values for lead angle are consistent. Please note that I think (not 100% sure) that there is a difference in the units used to display the lead angles. The old one (foc angle) s probably converted by the 860C it self in degree while the new one (put in field cadence) is in the internal units and so have to be multiplied by 360/256 to get degree.
- I sugest to avoid testing DYNAMIC_LEAD_ANGLE = 1 because it is much more complex and requires a lot more CPU. So if option 2 is ggod, I would keep it and never use this one.
 
You must really take care with lead angle (foc) because if the value is wrong, you can damage the motor because a big part of the current can generate heat instead of mechanical power.

About field weakening, there is a flag to activate it or not. It is not integrated into the lead angle.
You can see the principle in CCU80_1_IRQHandler() (in motor.c). It changes the value of ui8_fw_hall_counter_offset that is used to position the magnetic flux. I never looked deaply at it (I do not really understand it). I just copied from TSDZ2.

For you information, I just put on github for 860C display a new version in the branch test_1_hall_position_in_q88.
There are major changes in this version and I did not tested it at all. So be very carefull if you try it.
Main changes are:
- rotor position is calculated in a format q8_8. It means that it has 8 bits more as decimal to increase precision
- rotor positioning should be improves by the uses of a PLL that should better estimate the rotor velocity and the position.
- PWM calculated to generate the magnetic flux uses a table (like a sinus table) of 256 items to represent the 360°. Precision of PWM is increased because OSF interpolates now between 2 entries (based on the added decimals in rotor position)
Those changes are always active in the code.
On top of this, there are some new options that can be activated or not using #define in main.h.
#define DYNAMIC_LEAD_ANGLE (0) // (0) no dynamic
// (1) dynamic based on Id and a PID + optimiser
// (2) dynamic based on Idc and a optimiser (= esc)
#define DYNAMIC_HALL_POSITION_UPDATE (0) // (0) no dynamic update of u16_hall_angle_table_Q8_8[]
// (1) Dynamic update (call Update_LUT_periodic() in ebike_app_periodic

DYNAMIC_HALL_POSITION_UPDATE = 1 let oxs automatically update the position of the hall sensors. With option 0, the positions of hall sensors are hard coded based on the measures I made on my motor but it could be (not sure) than other motors have slightly different positions.

DYNAMIC_LEAD_ANGLE options allows to tests 3 ways of calculating lead angle.
Option 0 : foc angle is calculated like before taking care of the current and the foc_multiplier
Option 1 : uses a PID and an ESC (optimiser) trying to minimise Id (unproductive current). This is quite complex and used many parameters that should be fine tuned (if code is already working)
Option 2 : uses only an ESC trying to minimise the battery current. It make also a first estimate of the lead angle based on erps instead of the current. It is much simple to understand and to fine tune that option 1

As said, I did not tested all those changes. In order to reduce the risk of having a bad lead angle (motor heating), the current version always applies the value like in option 0. Still, when option 1 or 2 is activated, OSF calculates also a second lead angle and send the value to the 860C display in the field that is normally used for cadence pedal. This allows to display on the 860C both lead angle (the foc angle and the one from option 1 or 2) and to reduce the risk (foc angle being applied).
If the tests show that the values from option 1 or 2 are valid, It will be very to change the code in order to really apply them instead of the foc_angle and to start seeing the real effect.

I would appreciate if you could tests those changes but I can't give any warranty. There are probably bugs because there were so many changes.
If you decide to test it, I would say:
- do not try with high current.
- Start with the 2 #define on 0 and see if motor still runs with no load , do not produce abnormal noise and do not consume more current than with old version.
- if OK, you can try DYNAMIC_HALL_POSITION_UPDATE. I do not expect a big difference.
- If still OK, activate DYNAMIC_LEAD_ANGLE = 2 (the risk is lower) and check on the display if the 2 values for lead angle are consistent. Please note that I think (not 100% sure) that there is a difference in the units used to display the lead angles. The old one (foc angle) s probably converted by the 860C it self in degree while the new one (put in field cadence) is in the internal units and so have to be multiplied by 360/256 to get degree.
- I sugest to avoid testing DYNAMIC_LEAD_ANGLE = 1 because it is much more complex and requires a lot more CPU. So if option 2 is ggod, I would keep it and never use this one.
Great news, thanks for all the work! I did try showing advanced lead angle 1, tested different math calculations to try show angle on screen etc. I always got the same few results. It either showed 0 with no load then jumped to 255 with any power level, just stuck at 255 no matter what and rarely it would be 0 with no load then I saw it climb fast to 255 (with any load) so to rule out the value just displaying wrong I tested it live and motor temp and performance top end confirmed it so I will leave that one for now. I've got a really nice build setup rn with original ui8_foc , I will write it to my "testing" branch to save state and merge your changes into "testing foc". Your last update was really quite easy to merge as it was mostly just added code but do you think if your most recent changes will conflict a lot with my ui16 changes? If not I'll merge, try a low amp test build tomorrow evening 🙂 I just got a live watt meter for my battery to show real time wattage which will be helpful
 
Great news, thanks for all the work! I did try showing advanced lead angle 1, tested different math calculations to try show angle on screen etc. I always got the same few results. It either showed 0 with no load then jumped to 255 with any power level, just stuck at 255 no matter what and rarely it would be 0 with no load then I saw it climb fast to 255 (with any load) so to rule out the value just displaying wrong I tested it live and motor temp and performance top end confirmed it so I will leave that one for now. I've got a really nice build setup rn with original ui8_foc , I will write it to my "testing" branch to save state and merge your changes into "testing foc". Your last update was really quite easy to merge as it was mostly just added code but do you think if your most recent changes will conflict a lot with my ui16 changes? If not I'll merge, try a low amp test build tomorrow evening 🙂 I just got a live watt meter for my battery to show real time wattage which will be helpful
Thanks for testing and for the feedback. Still I am not sure about which tests you made and the result.

Could you please confirm:
1) Did you first test with just the version I provided yesterday (without trying to merge with your code). At this stage, it is much better to start testing with my code only otherwise it will be very confusing.
2) Did you test with the 2 new parameters set on 0
3) with the 2 new parameters set on 0, is the motor running with no load? Does it make abnormal noise?
4) with the 2 new parameters set on 0, is the motor running fine under load (not heating). Do not try to test very high current because there is no change expected compared to the version in main branch about that.

Note: if this work, it would already be great because, it means that all the changes to increase angle resolution and to use PLL are OK.
5) Did to made test, setting DYNAMIC_HALL_POSITION_UPDATE = 1; Did the motor still work fine? I do not expect that you see a difference because it should not have a big impact on the way the rotor position is calculated when PLL is working wel.

Note: if activating DYNAMIC_HALL_POSITION_UPDATE = 1 is not worst than with 0, I suggest to keep it on 1 for future test.

6) it seems you tested DYNAMIC_LEAD_ANGLE = 1. I said in my post not to use this option at this stage. It is quite complex to set up.

7) Did you test DYNAMIC_LEAD_ANGLE = 2. Can you confirm that motor is running fine (no noise, not heating) like with option 0. This should be the case as OSF calculates the lead angle but do not use it to manage the magnetic flux. In this version, it still apply foc_angle.
Please note that displaying a value like 255 for the lead angle is not abnormal. Foc_angle is always a positive number between 0 and about 30 (=42° due to the *360/256 conversion)
With DYNAMIC_LEAD_ANGLE = 2, the lead angle can now vary between -5 and +30°. Negative angle are the same as positive angle when adding 360°. So -5° is the same as 355°. As internaly, 360° is represented by 256, it means that 255 is the same as 255*360/256 = 358° = -2°. So it is close to 0 and not abnormal when motor speed is 0.
Still, when the motor speed increases, the lead angle should also increase. With the current setup defined in motor.c, when motor speed is 3000 rpm, the lead angle should be between 25° and 30° which correspond to 17 and 21 on display (*246/360).
If this is not the case, there is some bug in this part of code.
8) So, can you say the value you get on display for lead angle (in field cadence) when the motor is running fast (e.g. around 3000 t/m) with no load.

When those points are clarified and eventual bugs solved, we could look to fine tune the parameters and check the motor under higher current (increasing some limits, replacing u8 by u16 if needed and improving throttle curve, ...)
 
@HazzaHodgson
As you said that lead angle did not increase really (except going to 255), I already checked the code.
I found a bug that could explain the issue.
In motor.c there is a line (1800) defining a macro.
This line should be #define Q8_8_DEG(x) ((int16_t)((x) * 65536 / 360))
instead of
#define Q8_8_DEG(x) ((int16_t)((x) * 256 / 360))
You can change it in your copy or download the new version I uploaded on github (always in the test branch)
 
@HazzaHodgson
I just realized that trying to compare on the display the old foc_value with the new lead angle value when DYNAMIC_LEAD_ANGLE = 2 is not 100% significant in the current code.
I will try to explain the logic of DYNAMIC_LEAD_ANGLE = 2.
OSF calculates a i16_lead_base_q8_8 that is an unoptimised lead angle proportional to the motor speed (rpm). The set up says to increase from 0 (rpm = 0) up to a max of 30° (rpm = 3000t/m).
On top of this, OSF uses an ESC logic to see if a small increase/decrease (small offset in step of 1° but limitted to range -5/+5°) would reduce the average Idc current bus.
This final lead angle is the sum of the lead base and the offset.
There are some more code for safety to limit the range to -5°/+30° and to avoid fast changes (slew rate).
In the updated version (with the fix of today), lead base should be correct.
Still ESC can't find the good offset because the final lead angle is not yet applied to the motor (for safety I still used foc_angle).

If the test up to here are succesful, I wil make a change to give the priority to the new final lead angle (over the foc_angle) and you could see the benefit of the ESC.
Then it will be possible to fine tune (max lead angle, at which speed, slew rate, range of offset,...).
I hope this clarifly.
 
So
Thanks for testing and for the feedback. Still I am not sure about which tests you made and the result.

Could you please confirm:
1) Did you first test with just the version I provided yesterday (without trying to merge with your code). At this stage, it is much better to start testing with my code only otherwise it will be very confusing.
2) Did you test with the 2 new parameters set on 0
3) with the 2 new parameters set on 0, is the motor running with no load? Does it make abnormal noise?
4) with the 2 new parameters set on 0, is the motor running fine under load (not heating). Do not try to test very high current because there is no change expected compared to the version in main branch about that.

Note: if this work, it would already be great because, it means that all the changes to increase angle resolution and to use PLL are OK.
5) Did to made test, setting DYNAMIC_HALL_POSITION_UPDATE = 1; Did the motor still work fine? I do not expect that you see a difference because it should not have a big impact on the way the rotor position is calculated when PLL is working wel.

Note: if activating DYNAMIC_HALL_POSITION_UPDATE = 1 is not worst than with 0, I suggest to keep it on 1 for future test.

6) it seems you tested DYNAMIC_LEAD_ANGLE = 1. I said in my post not to use this option at this stage. It is quite complex to set up.

7) Did you test DYNAMIC_LEAD_ANGLE = 2. Can you confirm that motor is running fine (no noise, not heating) like with option 0. This should be the case as OSF calculates the lead angle but do not use it to manage the magnetic flux. In this version, it still apply foc_angle.
Please note that displaying a value like 255 for the lead angle is not abnormal. Foc_angle is always a positive number between 0 and about 30 (=42° due to the *360/256 conversion)
With DYNAMIC_LEAD_ANGLE = 2, the lead angle can now vary between -5 and +30°. Negative angle are the same as positive angle when adding 360°. So -5° is the same as 355°. As internaly, 360° is represented by 256, it means that 255 is the same as 255*360/256 = 358° = -2°. So it is close to 0 and not abnormal when motor speed is 0.
Still, when the motor speed increases, the lead angle should also increase. With the current setup defined in motor.c, when motor speed is 3000 rpm, the lead angle should be between 25° and 30° which correspond to 17 and 21 on display (*246/360).
If this is not the case, there is some bug in this part of code.
8) So, can you say the value you get on display for lead angle (in field cadence) when the motor is running fast (e.g. around 3000 t/m) with no load.

When those points are clarified and eventual bugs solved, we could look to fine tune the parameters and check the motor under higher current (increasing some limits, replacing u8 by u16 if needed and improving throttle curve,
Sorry for misunderstanding, I mean I tested your code u uploaded near a week or so ago. I haven't had chance to test most recent. I'm doing some wiring on my bike tonight and will try compile you new version tonight :)
 
Ok tried build with hall sensor dynamic 0 then 1 with my build and your build (no tweaks just purely yours) and I get no power to motor at all but I can see pwm duty cycle climb on display
 
ok i managed to get motor power on,
// In motor.c, inside CCU80_1_IRQHandler

- PHASE_U_TIMER_HW->CR1S = (uint32_t) ui16_a;
- PHASE_V_TIMER_HW->CR1S = (uint32_t) ui16_b;
- PHASE_W_TIMER_HW->CR1S = (uint32_t) ui16_c;

+ PHASE_U_TIMER_HW->CR1S = (uint32_t) ui16_a_pll;
+ PHASE_V_TIMER_HW->CR1S = (uint32_t) ui16_b_pll;
+ PHASE_W_TIMER_HW->CR1S = (uint32_t) ui16_c_pll;

motor sounds like a square wave controller now, i think halls are missmatched, i got motor blocked error after a coulple secs
 
Last edited:
ok i managed to get motor power on,
// In motor.c, inside CCU80_1_IRQHandler

- PHASE_U_TIMER_HW->CR1S = (uint32_t) ui16_a;
- PHASE_V_TIMER_HW->CR1S = (uint32_t) ui16_b;
- PHASE_W_TIMER_HW->CR1S = (uint32_t) ui16_c;

+ PHASE_U_TIMER_HW->CR1S = (uint32_t) ui16_a_pll;
+ PHASE_V_TIMER_HW->CR1S = (uint32_t) ui16_b_pll;
+ PHASE_W_TIMER_HW->CR1S = (uint32_t) ui16_c_pll;

motor sounds like a square wave controller now, i think halls are missmatched, i got motor blocked error after a coulple secs
You are right. I forgot to update CCU80_1_IRQHandler. There are probably to many changes in this version (increasing precision with Q8_8, addding pll, ...) I think I found a bug in PLL code. I will try to fix it and to make PLL only an option (to see if it already works without PLL).
 
You are right. I forgot to update CCU80_1_IRQHandler. There are probably to many changes in this version (increasing precision with Q8_8, addding pll, ...) I think I found a bug in PLL code. I will try to fix it and to make PLL only an option (to see if it already works without PLL).
I wonder if its possible, but i had a thought about a potential architectural improvement that might make testing and debugging new features easier for the community.Would it be possible to encapsulate major experimental features (like the different DYNAMIC_LEAD_ANGLE strategies) into their own separate .c and .h files?Then, in the core files like motor.c, we could use the preprocessor directives to conditionally include and call the appropriate functions. For example, instead of having all the logic for options 1 and 2 within #if blocks inside motor.c, the main interrupt could look something like this:C

#if DYNAMIC_LEAD_ANGLE == 1
#include "foc_pid.h"
// ... then call a function from foc_pid.c
foc_pid_apply_angle();
#elif DYNAMIC_LEAD_ANGLE == 2
#include "foc_esc.h"
// ... then call a function from foc_esc.c
foc_esc_apply_angle();
#else
// ... use the original, stable logic that's already in motor.c
apply_original_simple_foc();
#endif

The main benefits I see to this approach would be
Modularity: Experimental code would be completely isolated, preventing accidental conflicts with the stable codebase.
Easier Debugging: It would be trivial to enable or disable entire features just by changing an #include, making it easier to pinpoint issues.
Readability: It would keep the core files like motor.c much cleaner and focused on their primary function.Just a thought! It might make it easier for people like me to test new branches and provide better feedback. Thanks for all your amazing work on this project.
On another note, i made a fun tweak to the default lead angle, it nudges up a lil depending on motor duty cycle, this allows me to gain so much more speed when in a lower gear and duty cycle maxed with low amps (it gets so tiring shifting with 11 speed 11-42t casette, its like driving a wagon :s) it works a bit like field weakening, i currently have no speed limit guardrail tho so it just keeps accelerating with no load xD
my "motor foc" as shown on display currently climbs to 19 from duty cycle which is probably a bit high, but i think it will have some good potential and its really simple


if (ui8_g_duty_cycle > 0) {
// calculate phase current.
if (ui8_g_duty_cycle > 10) {
ui16_adc_motor_phase_current = (uint16_t)(((uint32_t)ui16_adc_battery_current_filtered << 8) / ui8_g_duty_cycle);
} else {
ui16_adc_motor_phase_current = ui16_adc_battery_current_filtered;
}
if (ui8_foc_flag) { // We still use this flag to run the logic once per rotation

// --- Grenny gear foc angle ---

// 1. Calculate advance from Battery Amps (our old, reliable way)
uint8_t foc_from_current = (((uint16_t)ui16_adc_battery_current_filtered * (uint16_t)ui8_foc_angle_multiplicator) + 128) >> 8;

// 2. Calculate advance from Duty Cycle
// This will scale from 0 up to a max of, say, 15 degrees of advance.
// You can play with the '15' to make it more or less aggressive.
const uint8_t MAX_DUTY_CYCLE_ADVANCE = 15;
uint8_t foc_from_duty = ((uint16_t)ui8_g_duty_cycle * MAX_DUTY_CYCLE_ADVANCE) / 255;

// 3. Take the bigger of the two, so they don't "double buff"
uint8_t final_foc_angle;
if (foc_from_current > foc_from_duty) {
final_foc_angle = foc_from_current;
} else {
final_foc_angle = foc_from_duty;
}

// 4. Apply a final safety limit (same as before)
if (final_foc_angle > 29) {
final_foc_angle = 29;
}

ui8_g_foc_angle = final_foc_angle;

also, one more VERY IMPORTANT note i have for you,

i really think amp calculation is off. since i installed my watt meter (battery==meter==load) i see on it between 25-26.4amps
while on display i see 30. ive got it in an awkward position though because i dont want to make the DC leads too long since its the main load line, so i havent got exact numbers. Im gonna do some testing and move it into a better position over the weekend. If im right (95% sure of it) that will explain so much,
why ppl feel like theyve lost some power vs factory
EBikeBuilder on youtube, hes got a watt meter also and saw peaks of only 1150 watts
my motor running quite cool considering my firmware is saying 30bat amps, 50 phase amps(barely went over 40 as id hity duty cap pretty fast accelerating)
That I got the same amount of vdrop as i do on factory firmware
I get the same mileage as factory firmware (with 30a on battery and 29 foc cap)
so now i believe im running pretty much the same actual amps as factory firmware
may also skew your calculations and experiments up id look into it yourself too before committing too much to your new code
 
Back
Top