Does SimpleFOC work with botwheels?

Thanks for the responses. I have ordered an other board and will come back soon :slight_smile:

Hi,

I got a new B-G431B-ESC1 and got it working with a hub wheel. Torque control, Velocity control and Angle mode is all working fine. After setup, on arduino serial monitor, I get:

MOT: sensor_direction==CW
MOT: PP check: OK!
MOT: Zero elec. angle: 5.24
MOT: No current sense.
MOT: Ready.
Motor commands sketch | Initial motion control > torque/voltage : target 2V.

I have added the below first line in the program and second line under build_opt.h

LowsideCurrentSense currentSense = LowsideCurrentSense(0.003f, -64.0f / 7.0f, A_OP1_OUT, A_OP2_OUT, A_OP3_OUT); // in main program
-DHAL_OPAMP_MODULE_ENABLED  // under build_opt.h

The sensor pins are connected to Hall Sensors of the motor. Does it mean current sensing is not working on the board, or am I missing something?

Please check the current sensing examples. There are a few things to consider, like linking the current sense to both the motor and driver, and initialising the current sense.

Can you kindly share which example are you referring to? I am still starting with simpleFOC and could not find. The example in Arduino is not for this module.

Hi @Praveen_Kumar ,

Unfortunately we can’t provide examples to cover everyone’s hardware… you’ll have to find one that is “close” to what you’re doing, and adapt it.

We also have general documentation, including code examples, that explains how to use the current sensing: Current Sensing | Arduino-FOC

Ok. Thank you. What you have done till now is already awesome. Let me check if something makes sense to me and will try implement it. Even if current sense is not there, it still already works.

Once you have some specific code, if its still not working you can always post it here and people can take a look.

But at the moment you have to initialise the current sensing properly… if you only added the object, you still need to call driver.linkCurrentSense() and motor.linkCurrentSense() and currentsense.init()…

The docs give quite detailed instructions.

Finally I killed the B-G431B-ESC1 board. Below is the code used. The motor was working on 12v perfectly. Then I shifted to 24v after modifying the code and it was still working fine. Angle control, Current sensing, commands, etc. Motor is capable of up-to 40v 10A

Then I used command MT for torque control mode and next command was M10 (target angle of 10 rad). The motor started wobbling, shaking and instantly saw the magic white smoke from the board.

Now, I can program the board, but the serial monitor shows an error saying “MOT: Failed to notice movement.” I know I damaged the board, and I’ve already ordered new ones. But I need help figuring out why it happened so I don’t ruin more new boards. If anyone can explain, I’d really appreciate it. I’ve already messed up two boards, and I want to avoid doing it again.

Any suggestions are welcome.

#include <SimpleFOC.h>

BLDCMotor motor = BLDCMotor(15);
BLDCDriver6PWM driver = BLDCDriver6PWM(A_PHASE_UH, A_PHASE_UL, A_PHASE_VH, A_PHASE_VL, A_PHASE_WH, A_PHASE_WL);
LowsideCurrentSense currentSense = LowsideCurrentSense(0.003f, -64.0f / 7.0f, A_OP1_OUT, A_OP2_OUT, A_OP3_OUT);

HallSensor sensor(PB6, PB7, PB8, 15);
void doA() {sensor.handleA();}
void doB() {sensor.handleB();}
void doC() {sensor.handleC();}

Commander command = Commander(Serial);
void onMotor(char* cmd) {
  command.motor(&motor, cmd);
}

void setup() {
  sensor.init();
  sensor.enableInterrupts(doA, doB, doC);
  motor.linkSensor(&sensor);
  driver.voltage_power_supply = 12;
  driver.init();
  motor.linkDriver(&driver);
  motor.controller = MotionControlType::torque;
  motor.PID_velocity.P = 0.2;
  motor.PID_velocity.I = 10;
  motor.PID_velocity.D = 0.1;
  motor.voltage_limit = 12;
  motor.LPF_velocity.Tf = 0.01;
  motor.P_angle.P = 20;
  motor.velocity_limit = 50;
  Serial.begin(115200);
  motor.useMonitoring(Serial);
  motor.init();
  motor.initFOC();
  motor.target = 2;
  command.add('M', onMotor, "motor");
  Serial.println(F("Motor commands sketch | Initial motion control > torque/voltage : target 2V."));
  _delay(1000);
}

void loop() {
  motor.loopFOC();
  motor.move();
  command.run();
}

When you are in torque mode and you command M10, it will pump 10A into the motor, which may be a bit too much for a bare ESC. At the sane time, your PS may limit the current by reducing the output voltage. This may result in oscilations which may cause significant BEMF, something which the ESc1 doesn‘t like st all. In my code I monitor input voltage, temperature and encoder. As soon as something is weird, I shut down. In addition, I built some hardware to dissipate excess BEMF. No more dead ESCs since then.

Now I understand. Thank you. Maybe I should limit the current to max 5A. I had put an external heat sink to dissipate the heat, but seems it wasn’t enough to think of only heat. The power supply is a 24v 10A lithium ion battery which can source upto 25A if needed. I guess all this together killed the board.

How fast does the board react to temperature and shut down power? For me, everything happened in 2-3 seconds. Do you have a link where I can refer to code and design? Especially the Back EMF hardware is interesting.

To power brown out it reacts very fast, temp is slower of course, since heat needs time to spread. Since you are using a battery, that should eat up your BEMF anyway, but at the cost of not being able to limit the current. Also be careful, if I remember correctly, SimpleFoc has no current limit in torque mode, it uses whatever you command as target.

BTW, I believe I killed all except fir one ESC1 by overvoltage. The board seems extremely sensitive to that.

Those boards are supposed to have ocp and ovp, but does it need software configuration of comparator?

@Grizzly, is your code available here? Request you to share if it is OK to share. In any case, I will try to stay away from torque mode for now.

Not directly and it has a lot of stuff around it. I can try to extract the essential part though and post, but I am not sure if I‘ll manage tomorrow.

The ESC1 doesn‘t have that, but it can measure voltage and current. Based on that you can do something in software, but it would not help against ov without additional hardware.

Sure. Whatever you share will help. Just waiting for the boards to arrive and will start testing.

in case you want current sensing, here is an example that gives total current. You can get current from each phase too, see the examples and there is also stuff on the forums exactly for this board

#include <SimpleFOC.h>

// NUMBER OF POLE PAIRS, NOT POLES, specific to the motor being used!
BLDCMotor motor = BLDCMotor(7); 
//this line must be changed for each board
BLDCDriver6PWM driver = BLDCDriver6PWM(A_PHASE_UH, A_PHASE_UL, A_PHASE_VH, A_PHASE_VL, A_PHASE_WH, A_PHASE_WL);
LowsideCurrentSense currentSense = LowsideCurrentSense(0.003, -64.0/7.0, A_OP1_OUT, A_OP2_OUT, A_OP3_OUT);
LowPassFilter diff_filter = LowPassFilter(0.05);
float goal_speed =0;
float v=2;
float v_diff=1;
float accel = 92;// in rads per second per second
float v_per_radsPS = 0.0232;
float accel_v_boost = 0.5;// voltage is increased during acceleration and deacceleration by this amount
bool voltage_override = 0;
float power_figure = 1.5;
float power_coeff = 0.00043;// the serial communicator could actually use an extra digit for this one.
float A, B, C;
float currentlf_now =0;
float prop_V= 0;
float min_V = 1;
float v_limit = 19;
float current_limit_slope = 1.6;// this is in milliamps pre rad per second
float current_limit_o_term = 200;//this is the current limit at zero rps, it may not trip with stall 
float maybe_o = 1;
void SerialComm(){ 
  if (Serial.available() > 0){
  switch(Serial.peek()){
      case 't': Serial.read(); Serial.print("t"); Serial.println(goal_speed); break;
      case 'c': Serial.read(); Serial.print("c"); Serial.println(accel); break;
      case 'v': Serial.read(); Serial.print("v"); Serial.println(motor.voltage_limit, 4); break;
      case 'n': Serial.read(); Serial.print("n"); Serial.println(v_diff); break;
      case 'p': Serial.read(); Serial.print("p"); Serial.println(v_per_radsPS, 4); break;
      case 'b': Serial.read(); Serial.print("b"); Serial.println(accel_v_boost); break;
      case 'o': Serial.read(); Serial.print("o"); Serial.println(voltage_override); break;
      case 's': Serial.read(); Serial.print("s"); Serial.println(motor.target); break;
      case 'f': Serial.read(); Serial.print("f"); Serial.println(power_coeff, 6); break;
      case 'g': Serial.read(); Serial.print("g"); Serial.println(currentSense.getDCCurrent(), 5); break;
      case 'i': Serial.read(); Serial.print("i"); Serial.println(get_mA(), 4); break;
      case 'j': Serial.read(); Serial.print("j"); Serial.println(min_V); break;
      case 'w': Serial.read(); Serial.print("w"); Serial.println(driver.voltage_power_supply); break;
      case 'k': Serial.read(); Serial.print("k"); Serial.println(v_limit); break;
      case 'y': Serial.read(); Serial.print("y"); Serial.println(current_limit_slope); break;
      case 'u': Serial.read(); Serial.print("u"); Serial.println(current_limit_o_term); break;
      case 'e': Serial.read(); Serial.print("e"); if (motor.shaft_angle >= 0){
           Serial.println(motor.shaft_angle, 3);
           }
           if (motor.shaft_angle < 0){
           Serial.println((_2PI-(-1*motor.shaft_angle)), 3);
           }
           break;
  case 'T': break;
  case 'C': break;
  case 'V':  break;
  case 'P':  break;
  case 'B':  break;
  case 'Y':  break;
  case 'U':  break;
  case 'O': break;
  case 'F':  break;
  case 'J':  break;
  case 'W': ;break;
  case 'K': ;break;
  default: Serial.read(); break; //if anything we don't recognize got in the buffer, clear it out or it will mess things up.
       
  }
}
  if (Serial.available() >= 9){
  switch(Serial.read())
  { 
    
  case 'T': goal_speed = Serial.parseFloat();break;
  case 'C': accel = Serial.parseFloat();break;
  case 'V': v_diff = Serial.parseFloat(); break;
  case 'P': v_per_radsPS = Serial.parseFloat(); break;
  case 'K': v_limit = Serial.parseFloat(); break;
  case 'B': accel_v_boost = Serial.parseFloat(); break;
  case 'Y': current_limit_slope = Serial.parseFloat(); break;
  case 'U': current_limit_o_term = Serial.parseFloat(); break;
  case 'O': 
    maybe_o = Serial.parseFloat(); // just in case the wrong data gets in somehow we don't want the voltage going crazy
    if (maybe_o < 1){
      voltage_override = 0;
      } 
    if (maybe_o >= 0.999){ 
      voltage_override = 1;
    }
    break;// if it's not one of these, ignore it.
  case 'F': power_coeff = Serial.parseFloat();break;
 // case 'W': driver.voltage_power_supply = Serial.parseFloat();break;
 // case 'J': min_V = Serial.parseFloat();break;
  
  }
  }
}
void overcurrent_trip(){// if it stalls this won't help except at higher powers, probably. Just helps prevent disaster
  float current_cap = current_limit_o_term + fabs(motor.target)*current_limit_slope;
  if (get_mA() > current_cap){
    voltage_override = 0;
  }
}

void setup() {
  Serial.begin(1000000);
  Serial.println("test serial2");
  // driver config
  // power supply voltage [V]
  
  driver.voltage_power_supply = 24;
 // driver.dead_zone = 0.1;
  driver.init();
 // driver.dead_zone = 0.1;
  // link the motor and the driver
  motor.linkDriver(&driver);
  currentSense.linkDriver(&driver);
  currentSense.init();
  currentSense.skip_align = true;
  FOCModulationType::SinePWM;
  motor.voltage_limit = 1;   // [V]
  motor.velocity_limit = 300; // [rad/s]
 
  motor.controller = MotionControlType::velocity_openloop;

  // init motor hardware
  motor.init();
  motor.voltage_limit = 2;
  goal_speed = 2;
}

unsigned long int ticks_diff(unsigned long int t2,unsigned long int t1){ //t2 should be after t1, this is for calculating clock times.
  if (t2<t1){//t2 must have wrapped around after t1 was taken
     return (4294967295-(t1-t2));
  }
     return (t2-t1);
} 
float get_mA(){// this is the estimated current being drawn from the power supply, not the actual motor current which is a bit different
   float x =0;
   x = currentlf_now*motor.voltage_limit/24;
   return  1000*((4.0384440932900223e-002)+3.4514090071108776e-002*x*30);// this is off by like 12 percent in some cases a polynomial of third order fits the data better but might flake out at higher than 500 mA so I didn't try it.
}
void loop() {
     static unsigned long int loop_clock_in = millis();
     unsigned long int loop_time = 0;
     float loop_time_s = 0;
//     unsigned long int inner_loop_time = 0;
     loop_time = ticks_diff(millis(), loop_clock_in);
     loop_clock_in=millis();
     loop_time_s = float(loop_time)/1000;
     if (motor.target < goal_speed-(accel*loop_time_s*1.5)){//rps not positive enough
           if (motor.target < 0){//counterclockwise rotation, deaccelerating
      motor.target = motor.target+accel*loop_time_s*0.7;
      motor.move();
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override;
     }
          if (motor.target >= 0){ //clockwise rotation, accelerating
      motor.target = motor.target+accel*loop_time_s;
      motor.move();
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override;
     }
     }
     
     if (motor.target>=goal_speed-(accel*loop_time_s*1.5)){//steady run phase
      if (motor.target<=goal_speed+(accel*loop_time_s*1.5)){ 
        motor.move();
      prop_V = (v_diff+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override; //constant run
      }
     }
     
     
     if (motor.target > goal_speed + (accel*loop_time_s*1.5)){ //rps too positive
           if (motor.target > 0){ //clockwise rotation, deaccelerating
      motor.target = motor.target-accel*loop_time_s*0.7; 
      motor.move();
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override;
     } 
          if (motor.target <= 0){
      motor.target = motor.target-accel*loop_time_s; //counterclockwise rotation, accelerating
      motor.move();
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override;
     }

     }
     if (prop_V < min_V){
      motor.voltage_limit = min_V*voltage_override;
     }
     else {
      motor.voltage_limit = prop_V;
     }
          if (prop_V > v_limit){
      motor.voltage_limit = v_limit;
     }
     
     for (int i=0;i<10;i++){ // shouldloop at about 37 khz on b-g431 board
     for (int q=0;q<5;q++){
     motor.move();
     motor.move();
     motor.move();
     motor.move();
     motor.move();
     }     
//     Serial.println(micros()-inner_loop_time);
//     inner_loop_time = micros();
     SerialComm();
     }
     currentlf_now = currentSense.getDCCurrent();
     currentlf_now = diff_filter(currentlf_now);
     overcurrent_trip();
}

The milliamps is not very accurate, but it’s repeatable.

1 Like

So, here we go. It is just a modified excerpt from my code, so may require some changes to compile. In addition to that I also monitor the magnetic field strength of the magnetic sensor, but that is very specific to my setup. If speed is of concern, you can optimize the code by not converting the variable voltage to a temperature, but doing the revers (convert the constant temperature limit to a constant voltage and compare the voltages). Also, the below code will always disable the driver if limits are exceeded. This may be suboptimal for overvoltage, where you may want to dissipate as much energy as possible, but that again might bear other risks.


static float Ntc2TempV(float ADCVoltage) 
{
	// Formula: https://www.giangrandi.org/electronics/ntc/ntc.shtml
	const float ResistorBalance = 4700.0;
	const float Beta  = 3425.0F;
	const float RoomTempI = 1.0F/298.15F; //[K]
	const float Rt = ResistorBalance * ((3.3F / ADCVoltage)-1);
	const float R25 = 10000.0F;
	
	float T = 1.0F/((log(Rt/R25)/Beta)+RoomTempI);
	T = T - 273.15;

	return T;
}


float GetTemp(int Pin)
{
	// float Temp=-1;
	float Temp = _readADCVoltageInline(Pin, currentSense.params); 
	// PrintLog("Raw temp: %.3f\n", Temp);
	Temp = Ntc2TempV(Temp);
	return Temp;
}



void loop()
{
	uint32_t TNow=millis(); 

	// Monitor the supply voltage
	static float LastVBus=0.0f;
	static float UMin=100.0f;
	static float UMax=0.0f; 
	static uint32_t TSNextTempLog=TNow;
	
	{
		float VBus = _readADCVoltageInline(A_VBUS, currentSense.params);            
		VBus = VBus * 10.7711;		
		
		if (LastVBus<=0.1F && VBus>0.1f) LastVBus = VBus;		// Initialization
		

		// In case of over/undervoltage, read it again to avoid sporadic errors
		if (VBus<UBB-UB_UNDER || VBus>UB_OVER)
		{
			float VBus = _readADCVoltageInline(A_VBUS, currentSense.params);            
			VBus = VBus * 10.7711;		
		}

		UMin = min(UMin, VBus);
		UMax = max(UMax, VBus);

		if (VBus<UBB-UB_UNDER && motor.enabled)
		{
			motor.disable();
			DisconnectDriverHW(true);
			Serial.printf("ERROR: Undervoltage detected (%.1fV/%.1f), stopping motor!", VBus, LastVBus);
		}
		
		if (VBus>UB_OVER && motor.enabled)
		{
			// For overvoltage this is probably not ideal!
			motor.disable();
			DisconnectDriverHW(true);
			Serial.printf("ERROR: Overvoltage detected (%.1fV/%.1f), stopping motor!", VBus, LastVBus);
		}
		else LastVBus = 0.9995*LastVBus + 0.0005*VBus;
	}
	
	// Monitor the driver temperature
	if (TSNextTempLog<=TNow)
	{
		float Temp = GetTemp();

		if (Temp>75 && motor.enabled)
		{
			motor.disable();
			DisconnectDriverHW(true);
			PrintLog("Driver temperature exceeds 75 degrees, driver disabled\n");
		}

		TSNextTempLog = TNow + 10000;
	}
}

Thank you. Will implement and check.