Yeah, it’s with PWM, using interrupts indeed. It’s also not the KV rating of the motor, this motor goes more than 4000 rpm no load, and I can get it up to 3000 with the fan blades attached, so approx 300 rads/sec.
I don’t know what it is, but I know if I chop out all the complicated stuff it goes faster, and the more stuff I chop out the better. I also noticed that the current consumption is quite high.
In my code, I can adjust the motor timing i.e. the difference between sensor and electrical angle. So I can use that to compensate for any repeating, regular bias in either the electrical angle or the sensor angle, due to time to execute code or whatever.
And yet no matter what I do, it always flakes and stalls at around 115 to 130 rads per second. I really don’t know, but I do know that even if I solve this there are other problems right around the corner, with arduino and C to do this kind of thing you actually have to know C. I don’t. Trying to get in there and modify the library in various hacky ways is very difficult and time consuming and stressful.
What I know will probably work in a finite, defined time, is the waveform generation approach, and I can probably do it even with micropython and a pico. That’s what I intend to use in the future for other stuff so I don’t mind spending time overcoming my lack of knowledge there. The pico is a relatively good design that’s open source, low cost, generally pretty good, with a sizeable community that’s growing rapidly. Micropython is also way less stressful to deal with. Every tiny thing I do in arduino always has a question mark hanging over it re if it will work. Compiling also takes forever even on my desktop…
I am sure I will have plenty of problems in micropython, but at least over there I’m not always going in circles.
My plan is to get a good waveform generator going and never touch arduino again unless I have to. It’s one strength was supposed to be that it’s fast, but now I see that debugging things that move fast is not practical in arduino anyway. I’m glad other people are on the case, but I’m tired of it for now.
Here is the code I’m trying to use on the b-G431B-ESC1 board for a waveform generator. It’s far from perfect, I gave it an angle setting mode that may be used for sensor calibration etc. at low speeds. However I would not use that, I would just set the open loop to 0.1 rads per second or something and gather data that way. That was probably a waste of time and I think I will get rid of it, but it can wait. I was going to try to make it plug into the rest of simpleFOC so I could use the sensor calibration and stuff, maybe even try torque mode with my new setup, but I just don’t have time for any detours and I immediately ran into problems getting SimpleFOC to compile on the mega2560. It wont’ even print to serial, get stuck on something before even starting the setup code. I don’t know, I am just going to abandon arduino and redo things in Micropython. It still helps a lot to have the C code to look at as a framework to show the logic and stuff, that’s important for sure and I appreciate that. And the waveform generation stuff is worked out, so there’s that.
Thanks Deku for the serial communicator thing, I have used it a lot.
#include <SimpleFOC.h>
// NUMBER OF POLE PAIRS, NOT POLES
BLDCMotor motor = BLDCMotor(7);
// MUST USE 6PWM FOR B-G431 DRIVER
BLDCDriver6PWM driver = BLDCDriver6PWM(A_PHASE_UH, A_PHASE_UL, A_PHASE_VH, A_PHASE_VL, A_PHASE_WH, A_PHASE_WL);
float goal_speed =0;
float v=1;
int mode = 0;
float angle_for_angle_mode = 0;
void SerialComm()
{
switch(Serial.read())
{
case 'T': goal_speed = Serial.parseFloat(); Serial.print("T");break;
case 't': Serial.print("T "); Serial.println(goal_speed); break;
case 'V': v = Serial.parseFloat(); Serial.print("V");break;
case 'v': Serial.print("V"); Serial.println(v); break;
// case 'S': p_gain = Serial.parseFloat(); Serial.print("P");break;
case 'e': Serial.print("e"); Serial.println(motor.shaft_angle); break;
case 'A': angle_for_angle_mode = Serial.parseFloat(); break;
case 'M': Serial.print("mode changed"); mode = int(Serial.parseFloat()); break;
// case 'I': diff_filter.Tf = Serial.parseFloat(); Serial.print("I");break;
// case 'i': Serial.print("f:"); Serial.println(diff_filter.Tf); break;
// case 'D': d_gain = Serial.parseFloat(); Serial.print("d_gain set");
// case 'd': Serial.print("d_gain is:"); Serial.println(d_gain); break;
// case 'O': i_windup_limit = Serial.parseFloat(); Serial.print("i_windup_limit set");
//case 'o': Serial.print("windup limit is:"); Serial.println(i_windup_limit); break;
// case 'U': setpoint = Serial.parseFloat(); Serial.print("S"); break;
// case 'u': Serial.print("s:"); Serial.println(setpoint); break;
}
}
void e_val_req(){
Serial.println(motor.shaft_angle);
}
void setup() {
Serial.begin(115200);
Serial.println("test serial");
// driver config
// power supply voltage [V]
driver.voltage_power_supply = 24;
driver.init();
// link the motor and the driver
motor.linkDriver(&driver);
FOCModulationType::SinePWM;
// limiting motor movements
motor.voltage_limit = 3; // [V]
motor.velocity_limit = 520; // [rad/s]
// open loop control config
motor.controller = MotionControlType::velocity_openloop;
// init motor hardware
motor.init();
//accelerate
motor.voltage_limit = 2;
goal_speed = 2;
pinMode(PB6, INPUT_PULLUP);//might want to change that to INPUT_PULLUP at some point, wasn't sure if that would work
attachInterrupt(digitalPinToInterrupt(PB6), e_val_req, RISING);
// for (int j = 0; j<(maxradianspersec*accel_step_divisions); j++){
/// for (int i = 0; i<accel_loops; i++){
// motor.move(j/accel_step_divisions);
// }
// motor.voltage_limit = 2.4+(3.6*(float(j)/(maxradianspersec*accel_step_divisions)));
// Serial.println(motor.voltage_limit);
// }
}
void loop() {
switch(mode)
{
case 0:
motor.controller = MotionControlType::velocity_openloop;
for (int q = 0; q<10;q++){
for(int j=0;j<10;j++){
if (motor.target < goal_speed-0.3){
motor.target = motor.target+0.075;
}
if (motor.target > goal_speed){
motor.target = motor.target-0.075;
}
for (int i=0;i<20;i++){ // Motor.move goes at about 37khz with a for loop, probably a bit faster this way, with multiple one after the other. actually no because a for loop comparison is extremely fast compared to the code in motor.move but whatever.
motor.move();
motor.move();
motor.move();
motor.move();
motor.move();
}
motor.voltage_limit = v;
}
//Serial.println("t");
SerialComm();
}
break;
case 1:
motor.controller = MotionControlType::angle_openloop;
for (int w = 0 ; w < 10; w++){
SerialComm();
motor.move(angle_for_angle_mode);
motor.voltage_limit = v;
}
break;
}
}
Oh and I solved the open loop clicking, again, this time I’m pretty sure for real. I changed several suspected things at once, I think it’s something to do with what happens when the data types are coerced when the difference between the microsecond counters are close to zero, but maybe that has as much merit as my last hypothesis. I just know this code works, when I sub it in in place of the velocityopenloop stuff.
// Function (iterative) generating open loop movement for target velocity
// - target_velocity - rad/s
// it uses voltage_limit variable
float BLDCMotor::velocityOpenloop(float target_velocity){
// get current timestamp
static float other_shaft_angle = 0;// there is a mysterious flake out that occurs once per rotation, using a local variable helps a bit.
unsigned long now_us = _micros();
float delta_angle = 0;
// calculate the sample time from last call
//float Ts = (now_us - open_loop_timestamp) * 1e-6f;
unsigned long micros_int_dt = now_us-open_loop_timestamp;
// quick fix for strange cases (micros overflow + timestamp not defined)
if(micros_int_dt <= 0 || micros_int_dt > 1000) {
micros_int_dt = 1000;
}
delta_angle = (target_velocity*float(now_us-open_loop_timestamp))/1000000;
// calculate the necessary angle to achieve target velocity
other_shaft_angle = fmod((other_shaft_angle + delta_angle), _2PI);
shaft_angle = other_shaft_angle;
// for display purposes
shaft_velocity = target_velocity;
// use voltage limit or current limit
float Uq = voltage_limit;
if(_isset(phase_resistance)){
Uq = _constrain(current_limit*phase_resistance + fabs(voltage_bemf),-voltage_limit, voltage_limit);
// recalculate the current
current.q = (Uq - fabs(voltage_bemf))/phase_resistance;
}
// set the maximal allowed voltage (voltage_limit) with the necessary angle
setPhaseVoltage(Uq, 0, _electricalAngle(other_shaft_angle, pole_pairs));
// save timestamp for next call
open_loop_timestamp = now_us;
return Uq;
}
BTW looks like someone already created the arduino board files and stuff for the Wraith32:STM32 Arduino based Electronic Speed Controller - RC Groups
I can confirm, there it is in the arduino ide under stm32 boards, electronic speed controllers, wraith32, v1. However the boards aren’t as common as I had though. Still, good board. It has an UART, too, which is handy, the pins are exposed as pads on the board.
The lepton is still better, if I just get a few more pins broken out and some better connectors, but still a cool idea to use a commodity board, I’m pretty sure if it’s supported under arduino it would work pretty well. Remember the motor.move command takes 200 microseconds to run on the Lepton anyway, so the electrical angle isn’t getting updated that fast not matter what you do. To smooth out the signal a bit and use a peripheral instead of doing everything in one mcu does make sense. It occurred to me today that the angle could be set and then the generator peripheral could ramp smoothly to the new position. It’s basically like a trinamic2208 stepper motor driver but with three phase output instead of two, which is exactly what I have thought many times I needed.
I assume the main reason no one has done this is that most people don’t care that much about vibration etc. which is fair enough. However it does free the CPU and have many advantages. Well, looks like it’s promising for my purposes anyway so cheers.
Again, it’s basically a peripheral to the CPU. Suppose the data transfer rate is a megabit. That’s not going to introduce much latency, in fact I bet you could get it down to a lot less than 200 microseconds between outputting the value and having your program flow back. And when you are doing high speed motion it’s imposible to get such smoothness. No one criticises the idea of using a PWM peripheral for generating a waveform of good quality, after all. Or a stepper motor driver.
edit:
ok, I got the wraith32 boards today. As mentioned above, someone made a board file for arduino so that’s good. I inspected them and they look promising. They have the pins for the uart and the SWD port broken out to pads. The MCU says stm32f051k66 on it, which would be the version with only 32 kb of flash. The lepton has 64 kb and still the open loop code only just sneaks in under the wire. I would also have to figure out the pins that go to the fortior driver to use in the arduino code, which wouldn’t take that long but still.
It has one 50milliohm sense resistor before the half h-bridge circuits. So you can measure total current apparently, somehow. I don’t see any op amps on there but there is a little thing that might be one. It’s possible it just goes straight to an ADC pin and is only good for relatively crude current measurments, but if that is so I don’t know how they do sensorless commutation with a sine wave.
I think probably the commutation is done by measuring the current and optimizing it to the lowest point.
I was not able to get it working without modification with my gimbal motor. As with most other sensorless driver chips, it appears to be confused by the motor characteristics, unable to start the motor. Therefore I could not use it as it is, in any case BlHeli cannot reverse direction, so I would need even more stuff to do that.
Ultimately this may have promise, however given that these boards are not open source, not widely available, and the flash is small, there are no other pins broken out so you don’t get any other options for ports etc, it is quite hard to access the pins, I decided not to pursue this any more. A next generation lepton is a wiser strategy for the long term, something that can acutally be used for a wider range of applications, and therefore be supported better. It would probably be a challenge to get interrupts and all other peripherals working on this board, for instance. There is the board file but it’s probably not thoroughly done, the guy seems to have done a quick job and was surprised serial worked…
A next generation lepton based around the G431 as mentioned in my previous approach appears to be the wisest course of action. I’m a bit hesitant because I don’t need that much CPU power for my own application, but using open source widely used components is important so it’s important for me to use the better CPU, with higher clock rate and op amps for current sense and floating point, as that will be more useful to others and be more broadly supported.
The current plan is basically to make a board that is like the G431 board but with all the expensive stuff chopped out, and pins broken out better etc. A cross between the lepton and the G431 board basically, with the best features of both, minus the kind of less useful ones from the G431 board.