KT motor controllers -- Flexible OpenSource firmware for BMSBattery S/Kunteng KT motor controllers (0.25kW up to 5kW)

Fine to hear, that it's working! Can you log the communication on your smartphone and upload the logfile, please? This would be great!!
Where are the blue, green and the yellow wires connected on the PCB of the controller?
index.php


regards
stancecoke
 
stancecoke said:
Fine to hear, that it's working! Can you log the communication on your smartphone and upload the logfile, please? This would be great!!
Sorry but this is not my priority -- but I will be back to this later. Also, I would prefer to work for an app to work for our OpenSource firmware so I would not mind about their app, etc. I would instead look at maybe Arduino Android OpenSource code projects using this type of module and try to communicate with it and send/receive data to/from our firmware.
 
Looking at the way I apply PI controller, it is different from Stancecoke:
- me:
Code:
pwm_set_duty_cycle_target (actual_duty_cycle_target + PI_out_value);
- Stancecoke:
Code:
pwm_set_duty_cycle (PI_out_value);

Yes we are limiting to avoid negative values, as PWM can't take negative values. I got someone making the same question on Arduino forum and a very good answer -- also the results we can get with 3 different options, and yes, Stancecoke option should be the best but even like that, seems far from ideal since with negative errors on PI controller, the pwm will be immediately set to zero.

Since I am using duty_cycle_target instead, it adds a delay to increase/decrease the duty_cycle value, so maybe doing like Stancecoke + using duty_cycle_target will be a good solution.

The output of the PID is a signed value that should be passed to a routine that can drive
the motor either direction according to the sign. (...) You must have fast-decay mode (synchronous rectification) on
the H-bridge for servo drive like this, otherwise the system is highly non-linear as a whole.

But the best one may even be using the synchronous rectification PWM (as mentioned on that forum message) that we are using on our firmware, unlike the original firmware -- but that would be a big change on how the motor control works, so I think it can be explored in future.
 
casainho said:
Stancecoke option should be the best but even like that, seems far from ideal since with negative errors on PI controller, the pwm will be immediately set to zero.

??? I don't understand your argumentation... The output (= duty cycle) is the sum of the proportional part and the integral part. The duty cycle will be dominated by the integral part most time, as the proportional part will be near zero most time. So a negativ error will of course reduce the duty cycle (that's the sense of the control loop) but will not set it to zero immediately....

You are using actual_duty_cycle_target as a kind of "integral" part, but I'm fearing you haven't yet understood the working principle of a pi controller. :-(

regards
stancecoke
 
stancecoke said:
??? I don't understand your argumentation... The output (= duty cycle) is the sum of the proportional part and the integral part. The duty cycle will be dominated by the integral part most time, as the proportional part will be near zero most time. So a negativ error will of course reduce the duty cycle (that's the sense of the control loop) but will not set it to zero immiately....

You are using actual_duty_cycle_target as a kind of "integral" part, but I'm fearing you haven't yet understood the working principle of a pi controller. :-(
Well, I hope to understand better now -- and yes, I was wrong. I just did like that, like you do but I am setup the duty_cycle_target instead. I tested both PI controllers for current and speed and I even went to test the P and I coefficients until I got the controllers running well. And I looked at duty_cycle_target after PI output values and yes, it does not goes to 0 value but keeps almost constant.
I got the current controller working well. The speed controller to work well, without oscillating to much (without load), needed to have lower PI coefficients and was to slow for my taste... but with load on the wheel, works very well and kind of fast response.

The PI coefficients can be changed, if needed, on main.h:
Code:
#define MOTOR_CURRENT_PI_CONTROLLER_KP_DIVIDEND	10
#define MOTOR_CURRENT_PI_CONTROLLER_KP_DIVISOR	1
#define MOTOR_CURRENT_PI_CONTROLLER_KI_DIVIDEND	4
#define MOTOR_CURRENT_PI_CONTROLLER_KI_DIVISOR	1

#define WHEEL_SPEED_PI_CONTROLLER_KP_DIVIDEND	35
#define WHEEL_SPEED_PI_CONTROLLER_KP_DIVISOR	3
#define WHEEL_SPEED_PI_CONTROLLER_KI_DIVIDEND	25
#define WHEEL_SPEED_PI_CONTROLLER_KI_DIVISOR	5
I also moved some code to ebike_app.c, like the state machine that looks for motor being stuck.
Also now the speed controller controls the wheel speed and not the motor speed as before.
I tested the changes on my direct driver motor Q85, S06S, 24V battery. The code is on master branch.
 
I took a closer look to your PI-controller code now. There are two questions:
1. Why do you set the integral part to zero immediately, if the target is zero? This will cause massive regen on a direct drive and even with a freewheeled motor this will cause high inertia forces on the rotor.

2. Why do you allow negative values for the integral part? This makes no sense as the duty cycle can't be negative, as you've already written above?

Code:
void pi_controller (struct_pi_controller_state *pi_controller)
{
  static int16_t i16_error;
  static int16_t i16_p_term;

  i16_error = pi_controller->ui8_target_value - pi_controller->ui8_current_value; // 255-0 or 0-255 --> [-255 ; 255]
  i16_p_term = (i16_error * pi_controller->ui8_kp_dividend) >> pi_controller->ui8_kp_divisor;

  // reset integral term while target value = 0
  if (pi_controller->ui8_target_value == 0) { pi_controller->i16_i_term = 0; }
  pi_controller->i16_i_term += (i16_error * pi_controller->ui8_ki_dividend) >> pi_controller->ui8_ki_divisor;
  if (pi_controller->i16_i_term > 255) { pi_controller->i16_i_term = 255; }
  if (pi_controller->i16_i_term < -254) { pi_controller->i16_i_term = -254; }

  pi_controller->i16_controller_output_value = i16_p_term + pi_controller->i16_i_term;
  if (pi_controller->i16_controller_output_value > 255) { pi_controller->i16_controller_output_value = 255; }
  if (pi_controller->i16_controller_output_value < -254) { pi_controller->i16_controller_output_value = -254; }
}

regards
stancecoke
 
Just a minor point I'd thought I'd bring to the attention of you guys - throughout the main branch code there are several misspellings of the word 'throttle' with 'throotle'. A windows search finds instances of this in the following files:-

ebike_app.lst
ebike_app.asm
main.h
ebike_app.c
config-example.h
Configuration_tool.java

....what I don't know is if any of these occurences affect the way in which the code runs. Some are just in comment lines but others seem to occur in definition type statements which may be important - I don't know.
Probably all of no importance, but maybe worth you guys checking just to be sure.. :wink:
 
geofft said:
Just a minor point I'd thought I'd bring to the attention of you guys - throughout the main branch code there are several misspellings of the word 'throttle' with 'throotle'.
Done. Thanks.
 
stancecoke said:
I took a closer look to your PI-controller code now. There are two questions:
1. Why do you set the integral part to zero immediately, if the target is zero? This will cause massive regen on a direct drive and even with a freewheeled motor this will cause high inertia forces on the rotor.

2. Why do you allow negative values for the integral part? This makes no sense as the duty cycle can't be negative, as you've already written above?
1. because I saw one time a problem with start up and I wanted to reset PI controller. Don't cause massive/max defined regen current because I am setting duty_cycle_target and not duty_cycle.
That "controller" of duty_cycle_target adds delay, I may rethink that because the idea is a protection to avoid to fast increase/decrease of duty_cycle. Maybe the algorithm can impose limitations only if the rate is over max possible value....
I changed the PI controller per you suggestion and seems to work well.

2. I wanted the PI controller to be more generic and I was limiting after the output value. Again, I just changed as your suggestion and simplified the code, as we may never need that more generic PI controller.

geofft said:
ebike_app.lst
ebike_app.asm
Just a note that these are temporary files and are not important, should be replaced when you build again the firmware.
 
honya96 said:
Just an interesting find - adaptto controller has MSP430F248T processor (16Mhz, 48kb, 4kb ram) so its not "much better"?!
Good to know. If you have a link for pictures and other tecnhical details, would be great if you share.
 
Search for: Adaptto controller teardown.

If you want to see any specific photo, I can take.

From technical details, interesting is that it can work as a boost converter and charge over 84V with just 24V supply. Voltage and current is set on display.

Supply is connected through big external inductor and capacitors to "+" of the controller/battery and any phase wire (with motor connected) noise can be heard from motor while charging so its also using its inductance?

And my personal overall opinion on the controller - not worth the money.

If enough developers interested, we can do all this with cheap kunteng.
 
Added pictures from internals of LCD5: https://opensourceebikefirmware.bitbucket.io/development/Motor_controllers--BMSBattery_S_series--LCD_control_panel.html

Added notes about the original mobile app: https://opensourceebikefirmware.bitbucket.io/development/Motor_controllers--BMSBattery_S_series--Kunteng_mobile_app.html

13-2.png


117-1.png
 
casainho said:
Added notes about the original mobile app

Hmm, so it seems, that the bluetooth system is really poor.... I think we can do it much better. But we need to know, where the wires from the BT-Module are connected to on the controller PCB :wink:

regards
stancecoke
 
stancecoke said:
Hmm, so it seems, that the bluetooth system is really poor.... I think we can do it much better. But we need to know, where the wires from the BT-Module are connected to on the controller PCB :wink:
This is not a priority for me. Maybe someone else will but the bluetooth controller version and want to help.
 
Stancecoke, I am thinking how to improve the torque sensor code -- do you have suggestions?

The main issues is that value fluctuates a lot every cadence... I would like to detect a good value from start..
 
casainho said:
Stancecoke, I am thinking how to improve the torque sensor code -- do you have suggestions?
The main issues is that value fluctuates a lot every cadence... I would like to detect a good value from start..
The torque-signal is a kind of sinusoidal whithin one crank revolution, that's physics. So I suggest to combine your and my solutions in the way, the "Forumscontroller" does it.

Code:
#if CONTROL_MODE == CONTROL_MODE_TORQUE                      //human power control mode
#if defined(SUPPORT_XCELL_RT) || defined(SUPPORT_SEMPU_V1) || defined(SUPPORT_SEMPU)
    power_poti = poti_stat/102300.0* curr_power_poti_max*power_human*(1+spd/20.0); //power_poti_max is in this control mode interpreted as percentage. Example: power_poti_max=200 means; motor power = 200% of human power
#ifdef SUPPORT_TORQUE_THROTTLE                              //we want to trigger throttle just by pedal torque
    if (abs(torque_instant)>torque_throttle_min)            //we are above the threshold to trigger throttle
    {
      double power_torque_throttle = abs(torque_instant/torque_throttle_full*poti_stat/1023*curr_power_max);  //translate torque_throttle_full to maximum power
      power_throttle = max(power_throttle,power_torque_throttle); //decide if thumb throttle or torque throttle are higher
      power_throttle = constrain(power_throttle,0,curr_power_max); //constrain throttle value to maximum power 
    }
#endif

....

power_poti = min(power_poti,thermal_limit+(curr_power_max-thermal_limit)*constrain(spd/thermal_safe_speed,0,1)); //thermal limiting

    if ((power_throttle) > (power_poti))                     //IF power set by throttle IS GREATER THAN power set by poti (throttle override)
    {
        myPID.SetTunings(pid_p_throttle,pid_i_throttle,0);   //THEN throttle mode: throttle sets power with "agressive" p and i parameters        power_set=throttle_stat/1023.0*power_max;
        power_set = power_throttle;
    }
    else                                                     //ELSE poti mode: poti sets power with "soft" p and i parameters
    {
        myPID.SetTunings(pid_p,pid_i,0);
        power_set = power_poti;
}

Read the throttle value, the direct torque-signal and the human power (human power averaged over one, or at least a half crank revolution) and give the highest value to the motor. With this, the support will start from zero speed, it will oszillate at startup, but it will be smooth from the point, where the human power value is higher than the simple torque value, the thumb throttle as highest priority and overrides the other values...

regards
stancecoke
 
hi guys,

finally got my st-link dongle in the mail. so i can start playing with this project.

i got my controller reprogrammed and motor sort of running (cuts out a a certain speed)

but i got a weird problem. i can't program the controller without doing something "weird" with the java tool. once the make clean commando is run (in the cmd window) i have to issue around 10 "exit" commando's for it to proceed with the make and prog.

second problem; when i started with this this afternoon, my lcd was working. later in the day it stopped updating for some reason. i was changing all kinds of things when i noticed, so can't say for certain what caused this. could it be software? i remember i went in to the settings on the display around the time it stopped updating. (i checked C11, LCD protocol. its on 0)

i am using the master branch.
 
nieles said:
hi guys,

finally got my st-link dongle in the mail. so i can start playing with this project.

i got my controller reprogrammed and motor sort of running (cuts out a a certain speed)

but i got a weird problem. i can't program the controller without doing something "weird" with the java tool. once the make clean commando is run (in the cmd window) i have to issue around 10 "exit" commando's for it to proceed with the make and prog.

second problem; when i started with this this afternoon, my lcd was working. later in the day it stopped updating for some reason. i was changing all kinds of things when i noticed, so can't say for certain what caused this. could it be software? i remember i went in to the settings on the display around the time it stopped updating. (i checked C11, LCD protocol. its on 0)

i am using the master branch.
Great!!

Sorry, can't help with 1. issue - maybe Stancecoke. But I think you need to post screenshots / more details.

About 2., I really don't know. Again, at least put some pictures and details about your LCD model.
 
hi,

about point 1: i narrowed it down to a part of the make file. i put some debug statements in the makefile (the clean part)
Makefile (clean part only)
Code:
ENTF = cmd /C del

clean:
	@echo "Cleaning files..."
	@clean.bat
	@echo "dbg1"
	@$(ENTF) *.asm 
	@echo "dbg2"
	@$(ENTF) *.rel
	@$(ENTF) *.lk
	@$(ENTF) *.lst
	@$(ENTF) *.rst
	@$(ENTF) *.sym
	@$(ENTF) *.cdb
	@$(ENTF) *.map
	@$(ENTF) *.elf
	@$(ENTF) *.adb 
	@echo "Done."

this is the output on the terminal:
Code:
C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT>make clean -f Makefile_windows --debug
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

This program built for i386-pc-mingw32
Reading makefiles...
Updating goal targets....
 File `clean' does not exist.
Must remake target `clean'.
Cleaning files...

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT>cd stdperiphlib\src

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src>del *.asm
Could Not Find C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src\*.asm

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src>del *.rel
Could Not Find C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src\*.rel

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src>del *.lk
Could Not Find C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src\*.lk

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src>del *.lst
Could Not Find C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src\*.lst

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src>del *.rst
Could Not Find C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src\*.rst

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src>del *.sym
Could Not Find C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src\*.sym

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src>del *.cdb
Could Not Find C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src\*.cdb

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src>del *.map
Could Not Find C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src\*.map

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src>del *.elf
Could Not Find C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src\*.elf

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src>del *.bin
Could Not Find C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src\*.bin

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib\src>cd..

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT\StdPeriphLib>cd..
dbg1
Microsoft Windows [Version 10.0.16299.192]
(c) 2017 Microsoft Corporation. All rights reserved.

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT>exit
dbg2
Microsoft Windows [Version 10.0.16299.192]
(c) 2017 Microsoft Corporation. All rights reserved.

C:\Users\Niels\Documents\GitHub\BMSBattery_S_controllers_firmware-ALT>

note that i needed to do the "exit" to get "dbg2".

EDIT: found the issue.. for some reason it was using sh.exe instead of cmd.exe (according to google) i solved this by removing MinGW and WINAVR paths from my Environment variables.
This issue has been discussed in another thread. In short: There is not
a "real" bug in the makefile. It's more a problem of the make utility.
In the makefile there are just some commands which depend on the Windows
"shell". So the needed shell is cmd.exe (MS Windows) not sh.exe
("unix-shell"-compatible). If the mingw-make version (as in WinARM 6/06)
finds a sh.exe it uses it and seems to ignore the SHELL variable. sh.exe
can not handle some of the commands in the makefile. If sh.exe is not
found it falls back to the cmd.exe and everything works as expected.
That's why I suggest to rename sh.exe so make does not find it and uses
cmd.exe instead. This is just a workaround to get things going without
addtional downloads. Better solutions:
- use a make.exe based on the 3.81 sources
- rewrite the makefiles to avoid MS-Windows specific commands

I will modify the makefiles to be "sh.exe"-compatible as soon as I have
some time. So the code can be build with older version of make and on
non-MS-Windows-hosts without modifications. Contributions of modified
makefiles are very welcome.

Martin Thomas

about issue 2:
i have the S-LCD3. just checked with a different controller(S06P) and the display is working.
 
You are doing incorrect for clean, should be this:
Code:
make -f Makefile_windows clean

And to build the code:
Code:
make -f Makefile_windows
 
actually it doesnt matter where the 'clean' word is. my command and your command yield the same outcome.

i found an ever better fix:

Code:
clean:
	@echo "Cleaning files..."
	@clean.bat
	-rm -f *.asm 
	-rm -f *.rel
	-rm -f *.lk
	-rm -f *.lst
	-rm -f *.rst
	-rm -f *.sym
	-rm -f *.cdb
	-rm -f *.map
	-rm -f *.elf
	-rm -f *.adb 
	@echo "Done."

this works even if i put MinGW and WINAVR back in path.
 
@Casainho - Can you share your 48V battery specs?
I wonder if you are able to test at 40A with your direct motor, so you can tune the firmware for higher powers. I am still not confident to test it..

I believe the phase current controll at zero/near zero speed is not ok yet.

Edit: 48v13Ah? Probably 13s6p 2.2Ah 10A LG cells so its ok for quite long 60A bursts if you bypass the bms.
 
honya96 said:
I believe the phase current controll at zero/near zero speed is not ok yet.

We are not controlling the max value of the phase current. The phase current is only used for doing the simplyfied FOC.
Even this control is not active at very low speed.

Code:
 if (ui16_motor_speed_erps > MOTOR_ROTOR_ERPS_START_INTERPOLATION_60_DEGREES)
    {
      // read here the phase B current: FOC Id current
      ui8_adc_id_current = UI8_ADC_PHASE_B_CURRENT;

      if (ui8_adc_id_current > 127) { ui8_angle_correction++; }
      else if (ui8_adc_id_current < 125) { ui8_angle_correction--; }
}

regards
stancecoke
 
honya96 said:
@Casainho - Can you share your 48V battery specs?
I wonder if you are able to test at 40A with your direct motor, so you can tune the firmware for higher powers. I am still not confident to test it..

I believe the phase current controll at zero/near zero speed is not ok yet.

Edit: 48v13Ah? Probably 13s6p 2.2Ah 10A LG cells so its ok for quite long 60A bursts if you bypass the bms.
Yes, I think my battery is that one 48v13Ah, that I bought from BMSBattery.

I am not motivated to test/develop for controllers I don't use... like that of 40A. Anyway, I would do like that: choose a "constant" current like 4A and measure the voltage at STM8 ADC input pin, repeat with other currents like 6A and 8A. I expect the behavior is the same for higher currents.
Look, STM8 measures 2 different currents:
1. battery/"motor" current that is measured with the power shunt and is amplified using an opamp, where the final voltage is an input to STM8 ADCx pin
2. phase B current that is measured with the hall current sensor power and outputs a voltage "directly"(no amplification) to an input to STM8 ADCx pin

I bet that a more powerful controller as different than S06S and S12S:
- related to 1. -- has a different gain on the shunt itself (different shunt value) or on the opamp so we just need to know the gain and we can measure with lower currents, no need to use near the max current
- related to 2. -- we just need to know the gain of hall current sensor and for that we need to read the datasheet of it

I can help to guide you doing 1. and 2. -- you would need a power supply that can holds like 10A or maybe less. Or a battery with a fast fuse. A multimeter for reading voltage on STM8 ADCx pins.
 
Back
Top