I have built an interface circuit (attached) to control a BLDC motor using back-emf sensing (although it does have Hall sensors I’m not using them) and am planning to run it with the attached published sketch.
As I plan to use the circuit to drive different motors of varying power, I would like to be able to modify the duty cycle of the PWM output and also, if possible the frequency, without having to continually adjust the code and reload it.
Maybe this can be done using a couple of 10k pots connected to certain pins and some adjustments to the code?
Also, the duty cycle is modified at startup to get the motor going and different motors will probably need different duty cycle and delay regimes to work well but I’m not confident as to how to adjust them in the sketch.
you can send commands to the Arduino thru the Serial monitor (under tools).
pcRead() captures signed numeric integers and single letter commands. Modify it to suit your needs. setup() needs to invoke "Serial.begin (9600)".
// -----------------------------------------------------------------------------
// process single character commands from the PC
void
pcRead (void)
{
static long val = 0;
static int sign = 1;
if (Serial.available()) {
int c = Serial.read ();
switch (c) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
val = c - '0' + (10 * val);
Serial.println (val);
return;
case ' ':
val = 0;
sign = 1;
return;
case '-':
sign = -1;
return;
case 'B':
dumpBuf();
break;
case 'b':
SetSpeed(0);
setDir(D_SHORT);
break;
case 'C':
clearBuf();
break;
case 'c':
capture = val;
val = 0;
break;
case 'D':
debug = val;
val = 0;
break;
case 'd':
status();
break;
case 'K':
pidKd = val / 100.0;
val = 0;
break;
case 'M':
maxPwm = val;
val = 0;
break;
case 'm':
mode = val;
val = 0;
break;
case 'R':
SetSpeed(H_MAX);
break;
case 'r':
setDir(D_CW == hDir ? D_CCW : D_CW);
break;
case 'S':
SetSpeed(H_MAX < val * sign ? H_MAX : val * sign);
setDir(0 < val ? D_CCW : D_CW);
mode = M_MAN;
Serial.print (" pcRead: hPwm ");
Serial.println (hPwm);
val = 0;
sign = 1;
break;
case 's': // stop
SetSpeed(0);
status();
pos = 0;
maxPer = minPer = 0;
break;
case 't':
mode = M_CTL;
target = val * sign;
val = 0;
sign = 1;
Serial.print ("pcRead: ");
Serial.print (" target ");
Serial.println (target);
isrCnt = maxPer = minPer = 0;
// setDir (target < pos ? D_CCW : D_CW);
// SetSpeed (200);
break;
case 'v':
Serial.println (VERSION);
break;
break;
case '?':
Serial.println (" [0-9] 10*val + digit");
Serial.println (" sp val = 0");
Serial.println (" - sign = -1");
Serial.println (" B dumpBuf()");
Serial.println (" b brake");
Serial.println (" C clearBuf ()");
Serial.println (" c set capture (1 ISR, 2 PID");
Serial.println (" D set debug");
Serial.println (" d status()");
Serial.println (" K set pidKd (x100)");
Serial.println (" M set maxPwm");
Serial.println (" m set mode");
Serial.println (" P set pos = spd = 0");
Serial.println (" R run max speed");
Serial.println (" r reverse");
Serial.println (" S set speed/directio (+/- 0-255)");
Serial.println (" s set pos = spd = 0 and status ()");
Serial.println (" t set target pos and enable ctlPid");
Serial.println (" v version");
break;
default:
break;
}
}
}
i'm confused by your comment to adjust the duty cycle of a PWM waveform. is that exactly what PWM is, modulates the duty cycle of the output.
As I’m new to Arduino I don’t recognise many of the commands you’ve written but my question was to ask if there was a hardware way to make adjustments to the duty cycle, even if they are then fed back to me via the serial monitor, rather than through the sketch itself.
I can see various places in the sketch where duty cycle is specified, including for the startup regime and I also wanted to know how and where to adjust the sketch to meet the needs of different motors.
the arduino environment has a higher level function, analogWrite() that sets the PWM duty for a specific pin with a range from 0-255.
i'm not sure what your code is doing. It write directly to atmel registers which i'm not familiar with. In some cases it seems to write a duty cycle (0-255) to a register, in other cases it looks like it's turning a bit on or off.
the voltage on a pot could be used to read an analog input from 0-1023 which could be scaled to 0-255 to control an pwm output.
See analogRead() and analogWrite() on the reference page.
i think my pcRead() demonstrates how you can pass values and commands at run-time to affect the code and query state. modifiy it to do you you want..
Ok I think I get the idea but as I’m a novice at the coding side if things, if I just wanted to query the duty cycle and rpm of the motor at a moment during the run time what would the code be and where would I insert it within the 185 lines of the sketch? I assume the values will automatically be sent to serial monitor to be viewed anytime?
since i'm not familiar with brushless motors, except possibly that an alternating or pulsed voltage is applied to them to drive them more like AC motors, and certainly not familiar with the code you posted, can you explain what you hope to do, where you got the code and what you believe it does? (now i'm curious about how this code works)
are you running this code now or are you evaluating it for possible use.
since you said you're not familiar with arduino, you may not know that the IDE provides some infra-structure that avoids some programming details. these include requiring a setup() that is invoked once and a loop() that is invoked repeatedly.
loop() invokes SET_PWM_DUTY() which include a Serial.println() to report the duty cycle. isn't this doing what you asked?
The code was published at: Sensorless BLDC motor control with Arduino - DIY ESC - SIMPLE PROJECTS so is pre-written. I'm slowly going through a guide to get enough understanding to be able to make small and simple mods to it or to at least know which lines are responsible for certain types of actions. As I am close to completing the circuit build I am preparing for running the sketch.
As for what it does the best I can offer is some info (attached) that I acquired recently. It will probably make more immediate sense to you (I'm a physicist by background with a smattering of electronics but am good at construction)
I understand the terms setup and loop for code run once and repeatedly.
I can't find the exact code you mentioned in the sketch but there is this near the end:
as for rpm, it doesn't look like it's being tracked in the code. It would need to time the duration between rotation events. Based on BLDC Controller implementation it sounds like the BEMF events recognize a rotation phase which is used to control when the 3 outputs are changed
perhaps all that is needed is to measure the time (micros()) between any one event (e.g. AH_CL) and calculate the RPM from that value. that could be reported in loop() as well.
unsigned long usecLst = 0;
unsigned long usec;
long dUsec;
usec = micros();
dUsec = usec - usecLst;
usecLst = usec;
but I think invoking Serial print in each loop cycle would produce a flood of output as well as delay processing (~1 msec / character). So perhaps report every second
unsigned long msecLst = 0;
unsigned long msec;
msec = millis();
if ( (msec - msecLst) > 1000) {
msecLst = msec;
Serial.print(duty);
Serial.print (", ");
Serial.println (dUsec);
}
Thank you that’s very helpful. Can these last two pieces of code all start on line 100, one after the other, just before the end of loop?
AH-CL is one of the phase pulses? So the time interval between any two pulses is linked to the revs/sec (depending on the number of poles in the motor) and hence rpm.
i'm a bit leery about posting code with testing. i hope you get the idea
i'm suggesting that the micros case be added to one of the BEMF events to measure speed (dt). it could be after line 103. The variable definitions could outside the function so that they are globally accessible, after line 100, as you said, and their value remain persistent between usage.
unsigned long usecLst = 0;
unsigned long usec;
long dUsec;
void BEMF_A_RISING(){
ADCSRB = (0 << ACME); // Select AIN1 as comparator negative input
ACSR |= 0x03; // Set interrupt on rising edge
usec = micros();
dUsec = usec - usecLst;
usecLst = usec;
}
the millis case would replace the Serial.print line in SET_PWM_DUTY(). Those variable definitions should either be outside the function (before "void SET_PWM_DUTY()") or declared static (e.g. "static unsigned long msec = 0;") inside SET_PWM_DUTY().
i hope the rational is clear for variable placement, the difference between static and dynamic variables. Dynamic variable are allocated on the stack and their values lost between invocations of a function (outside its scope) unless defined static. variables defined outside the scope ("{}") of any function are static.
1.3M resistors in those dividers need to be high voltage types, or series strings of lower voltage resistors.
You probably should experiment with adding a little capacitance at all the analog inputs to reduce noise spikes.
You use 500V FETs in a 400V circuit, assuming there's going to be no voltage spikes (there will be with high voltage switching circuits). Rate the devices generously. For this kind of voltage IGBTs are more robust.
100 ohm gate resistors are high, this means the gate voltage isn't tightly controlled by the drivers, so add 15V zeners across every gate/source at the FETs to prevent over-voltage on the gate due to capacitive coupling to the high dV/dt on the drains.
With high power high voltage bridges protection measures are important, things fail pretty much instantly if there's something wrong. I'd add good high frequency decoupling on the 400V supply across each half-bridge to keep the supply from jumping around all over the place.
I'd also consider adding current sensing and implementing hardware instant shutdown on over-current (shunt resistor on low-side, a comparator and flipflop that gates the control signals - more flexible gate driver chips have an enable input specifically to allow this).
gcjr:
i hope the rational is clear for variable placement, the difference between static and dynamic variables. Dynamic variable are allocated on the stack and their values lost between invocations of a function (outside its scope) unless defined static. variables defined outside the scope ("{}") of any function are static.
Hi Greg, To make it clearer for me I have integrated your code into the attached doc (Pages exported as Word) and indicated your suggestions in blue. However, you indicate the two types of variables and, while I understand the difference between the two, I have no idea what to use. Do they depend on what is being used to determine dt?
the use of micro() was to measure the time between events which i assume means a full rotation and from which you can determine speed: 60 * 106 / dtUsec.
the use of millis() is to reduce the frequency of duty-cycle and speed to once every second. of course you can change the rate (i.e. 1000)
next time, post your code within the code tags (the </> icon)
Thank you for your suggestions. If I could take them each in turn.
I'm planning to use a small motor at around 30-50V to start with and then work up in motor size and power but I' building the control unit to be able to handle 400V and also, perhaps more importantly, the inductive spikes that will inevitably be encountered.
1.3M resistors: When it comes to using the 400V DC for the motor, what high voltage values are you suggesting and do they need to be >1W rating?
Caps on the analogue inputs to the Arduino (A2, A3): In the 10-100nF range?
500V FETs: It has been suggested that I place a 10uF non-electrolytic cap across the whole 400V DC supply to help soak up any spikes and, as the attached circuit shows (with some of your suggestions included in grey) there is already a Schottky diode across each FET to divert the spikes away. Will all these be useful?
Gate resistors: Would reducing them to 22R be an alternative? Otherwise, I can clamp the voltage with some 15V BZX85s
Decoupling and over-current protection: I'm not sure what decoupling the 400V supply would look like in terms of circuit and components. Similarly with the current sensing but if necessary I can revisit those ideas in the future.
gcjr:
the use of micro() was to measure the time between events which i assume means a full rotation and from which you can determine speed: 60 * 106 / dtUsec.
the use of millis() is to reduce the frequency of duty-cycle and speed to once every second. of course you can change the rate (i.e. 1000)
Ok. But do I need to add more specific static or global variables for the code to do what I'm after?
i had assumed you would know what to do with the rotation period measurement made using micro(). if you want to see the speed as RPM, you'll need to do the calculation i described and Serial.print that value, not the time in usec.
i've assumed that if you want to experiment with the controller, that you would understand what the code is doing and be able to modify it. very often, a significant portion of code is developed to support test, debugging and the collection of data. code evolves over time and often the architecture of code is different when it include these capabilities vs. when it doesn't.
Yes I can do the calculation. I was referring to the ‘event’ that was being used to get the us time interval but I guess that’s already referred to in the code your created.
it looks like there are six function, 2 for winding(?) apply a positive or ground path to each. I assumed (?) that these 6 functions are invoked in sequence, time dependent on BEMF cycles.
so i assume measuring the time between the occurrence of any one these functions measures the time of rotation, from which speed can be calculated.
That all sounds very reasonable. What I was trying to confirm, not very well it seems, is that the event chosen to measure the time interval is determined only by the place the relevant code is entered into the sketch rather than by specifying additional variables or statements in the setup section.
Based on your valid assumptions, to get the rpm outputted I should use the following as the final line of the sketch.
Serial.println (60 * 10^6 / dtUsec) ;
or do I have to use the pow (10, power index) function?
yes, measurement of speed (time) will depend on where it is measured and maintaining the value previously read to calculate a delta requires proper place of the value so that it remains persistent and accessible in other functions for display (i.e. global)