Hello everyone this is my second post in relation to an ESC project ive been working on for a while now I am done the basics of its design, designed a few PCBs and put in all the wanted features to the code now I have: the BEMF set up, 32khz square wave variable duty PWM and bootstrap drivers for the highside and lowside MOSFETs (PWM on highside) on all 3 phases so they all can be N channel, digital speed and reverse input, stall detection and a few basic beeps on start up and to indicate motor stall. The 2700 and 1000kv A2212 motors run in closed loop mode just fine with no load and I have attached the waveforms from the 1000kv motor which look pretty similar to a yellow commercial version so i think the closed loop is working fine, but my problem comes when I attach a small prop to act as a load to the motor.
In this case the motor works okay at low speeds but when the speed reaches roughly 50-60% power the motor and waveforms go from looking even, sounding even and maintaining a steady speed to making an almost grinding/radio static sound as it rotates, producing for lack of better words "wonky and erratic" waveforms (photos below), and oscillating between high speeds (think jumping from 70 to 60% power and then back to 70 for a second then up to 90 really quick and back down to a steady 80%) all while getting very hot, it also makes the static sound with no load when I break it with my finger only briefly though as when I move my finger it goes back to smooth closed loop spinning.
I have tried multiple fixes such as different PWM frequencies, Setting the code and driver inputs up so that its the lowsides getting hit with the PWM to manage the speed instead of it hitting the highsides, adding different decoupling capacitors on the power supply rails, pull down resistors on the MOSFETs gates and a bunch of little code tweaks in relation to the dead time between steps and how the motor starts off spinning from open to closed loop. So far none of this has worked and I have been unable to diagnose what's causing my ESC to have this sudden I assume forced switch to a sort of open loop from the closed loop control at high power under load if anyone has more experience with power electronics and has an input on what might be causing this I would love to hear from you.
I have attached my code, waveform photos, THT schematic and a photo of the THT pcb ive designed (although both smd and tht versions run into basically the same issue tht is easier to reprogram so I'm trying to solve it on the tht version first). Thanks in advance Harry.
My code for the Atmega328: ( I annotate as I code so sorry for some of the spelling
)
#define DELAY_CYCLES(n) __builtin_avr_delay_cycles(n) //clock cycle based delay (1 = 62.5nS @16MHZ)
volatile long DutyMicros = 0;
volatile long pulseTime = 0;
volatile long triggerPoint = 0;
volatile long risePoint = 0;
volatile long fallPoint = 0;
volatile long FirstHigh = 0;
volatile long SecondHigh = 0;
volatile int PulseStarted = 0;
volatile int PrevState = 0;
volatile int FilteredDuty = 0;
volatile int SetDutyVariable = 0;
volatile int UpdateDuty = 0;
volatile int StepCount = 0;
volatile int spinDirection = 0;
volatile int StartRevving = 0;
volatile int ChngDir = 0;
volatile int UpdateStallWatchdog = 0;
volatile int RisingSteps = 0;
volatile int FallingSteps = 0;
long HoldUpnumber = 0;
int DutyPercent = 0;
int motorSpinning = 0;
int motorPrevOff = 1;
int lastSetDuty = 0;
int ElecCycles = 0;
int BPCnt = 0;
int ChangingSteps = 0;
int motorStalled = 0;
int StallCheckCycles = 0;
int StallCheckTimeout = 0;
int AsetPWM = B01100001;
int BsetPWM = B10000010;
int CsetPWM = B00100010;
int ALow = B10100000;
int BLow = B10000100;
int CLow = B00100100;
void setup() {
DDRD = (1<<PD2) + (1<<PD5) + (1<<PD7) + (1<<PD3); //Sets all outputs
DDRB = (1<<PB1) + (1<<PB2);
//enables pin changes for speed input
PCICR = B00000101; //sets portD and B to have interput inputs
PCMSK2 = B00010000; //sets portD4 to trigger the speed interupt
PCMSK0 = B00100000; //sets portB5 trigger the direction change interupt
//timer1 PWM pins D9 and D10 setup
TCCR1A = 0;
TCCR1B = 0;
TCCR1A = (1<<COM1A1) + (1<<COM1B1) + (1<<WGM11); //phase correct pwm
TCCR1B = (1<<WGM13) + (1<<CS10);
ICR1 = 250; //set 32 kHz
OCR1A = 0; //set % duty
OCR1B = 0; //set % duty
//timer2 PWM pin D3 setup
TCCR2A = 0;
TCCR2B = 0;
TCCR2A = (1<<COM2A0) + (1<<COM2B1) + (1<<WGM20); //phase correct pwm
TCCR2B = (1<<CS20) + (1<<WGM22);
OCR2A = 250; //set 32 kHz
OCR2B = 0; //set % duty
//internal comparator setup
ACSR = B00011000; //enables the ISR
ADCSRA = (0<<ADEN); // disable the ADC module
ADCSRB = (1<<ACME); // enable the MUX selector for negative input of comparator
ACSR |= B00000011; //sets rising edge interupt
ADMUX = 001; // Select A1 as comparator negative input
ICR1 = 25; //set 320 kHz for startup tone
OCR2A = 25; //set 320 kHz for startup tone
StartUpSound(); //Plays a tone through the motor showing esc has started succesfully and is waiting in standby
ICR1 = 250; //set back to 32 kHz
OCR2A = 250; //set back to 32 kHz
//sets all phases to floating to stand by
PORTD = B10100100;
TCCR2A = 0;
TCCR1A = 0;
OCR1A = 0; //sets PWM to 0% duty
OCR1B = 0;
OCR2B = 0;
}
void loop() { //CLOSED MIAN CONTROL LOOP WITH BEMF, REVERSE AND HIGH SPEED STALL DETECT IMPLEMENTED
if (UpdateDuty == 1){ //check to see if the duty is in need of an update
DutyPercent = FilteredDuty; //pulls duty from the interupt generating it into a stable variable for the loops use
UpdateDuty = 0; //ensures loop only runs once
}
if ((UpdateStallWatchdog == 1) && (motorStalled == 0)){ //Checking the flag set by the stall Watchdog if the motor is running
ChangingSteps = 1; //saving said flag thats telling the loop the watchdog says the motor is still spinning
UpdateStallWatchdog = 0;
}
if ((StallCheckCycles <= 150) && (motorStalled == 0)){ //if it hasnt been long enough to check for a stall this runs
StallCheckCycles++; //increase the counter
StallCheckTimeout = 0; //set stall check flag off
DELAY_CYCLES(1);
}
else if ((StallCheckCycles > 150) && (motorStalled == 0)){ //if it has been long enough to check for a stall this runs
StallCheckTimeout = 1; //set stall check flag on
StallCheckCycles = 0; //reset the stall timer
}
if ((lastSetDuty >= 170) && (motorStalled == 0) && (StallCheckTimeout == 1) && (ChangingSteps == 1)){ //this runs if the motor is still running
ChangingSteps = 0; //resets the watchdog output to see if it stays reset or the watchdog fixes it meaning the motor is still spinning
StallCheckTimeout = 0; //resets the timeout flag
}
else if ((lastSetDuty >= 170) && (motorStalled == 0) && (StallCheckTimeout == 1) && (ChangingSteps == 0)){ //this runs if the motor has stalled
cli();
PORTD = B10100100; //set motor to floatig
TCCR2A = 0;
TCCR1A = 0;
OCR1A = 0; //sets PWM to 0% duty
OCR1B = 0;
OCR2B = 0;
motorStalled = 1; //ensures nothing runs until the speed input drops to be off and the motor can restart safely
StallCheckTimeout = 0;
StallSound(); //plays stall sound
sei();
}
if ((StartRevving == 1) && (lastSetDuty < 50) && (ChngDir == 1)){ //sets the spin direction
spinDirection = 1;
ChngDir = 0;
}
else if ((StartRevving == 0) && (lastSetDuty < 50) && (ChngDir == 1)){ //sets the spin direction
spinDirection = 0;
ChngDir = 0;
}
if ((DutyPercent >= 50) && (motorStalled == 0)){ //if the duty is above 0 the motor needs to be spinning so duty cycle is set
OCR1A = DutyPercent; //sets PWM duty
OCR1B = DutyPercent;
OCR2B = DutyPercent;
lastSetDuty = DutyPercent;
motorSpinning = 1;
if (motorPrevOff == 1){ //for if the motor was just off and needs to start
OCR1A = 55; //sets PWM to low duty
OCR1B = 55;
OCR2B = 55;
cli(); // disables interupts so it can start in open-loop
ElecCycles = 0; //forces the motor electrical cycles variable to 0
while (ElecCycles < 4){ //runs while the motor has done less than 8 full step 1-6 cycles
ElecCycles++; //goes up every full loop
for (StepCount = 1; StepCount <= 6; StepCount++){ // for loop to make push the motor through all 6 steps sequentially
if (spinDirection == 0) { TakeStep(StepCount); } //take the next step of the sequence
else if (spinDirection == 1) { TakeRevStep(StepCount); } //take the next reverse step of the sequence
if ((ElecCycles == 4) && (StepCount == 6)) {
StepCount = 2;
sei();
if (spinDirection == 0) { TakeStep(StepCount); } //take the next step of the sequence
else if (spinDirection == 1) { TakeRevStep(StepCount); } //take the next reverse step of the sequence
}
else { DELAY_CYCLES(40000); }
}
}
motorPrevOff = 0; // tells the code the motor is now running
StallCheckCycles = 0; //resets stall timer as motor starts
StallCheckTimeout = 0;
}
}
if ((motorSpinning == 1) && (DutyPercent < 50)) { //if motor needs to be turned off
//sets all phases to floating
PORTD = B10100100;
TCCR2A = 0;
TCCR1A = 0;
OCR1A = 0; //sets PWM to 0% duty
OCR1B = 0;
OCR2B = 0;
lastSetDuty = 0; //resets variables for next start
motorSpinning = 0;
motorPrevOff = 1;
motorStalled = 0;
}
}
ISR (ANALOG_COMP_vect) {//analog comparator interput for step change
if ((StepCount & 1) && (ACSR & B00100000)){ // if step is odd and rising edge was just triggered
StepCount++; //increment step by 1
RisingSteps = 1;
if (StepCount > 6) {StepCount = 1;} //If step is larger than 6 set step back to 1
if (spinDirection == 0) { TakeStep(StepCount); } //take the next step of the sequence
else if (spinDirection == 1) { TakeRevStep(StepCount); } //take the next reverse step of the sequence
}
else if ((!(StepCount & 1)) && (!(ACSR & B00100000))) { //if not odd step must be even and falling edge was just triggered
StepCount++; //increment step by 1
FallingSteps = 1;
if (StepCount > 6) {StepCount = 1;} //If step is larger than 6 set step back to 1
if (spinDirection == 0) { TakeStep(StepCount); } //take the next step of the sequence
else if (spinDirection == 1) { TakeRevStep(StepCount); } //take the next reverse step of the sequence
}
if ((RisingSteps == 1) && (FallingSteps == 1)){
UpdateStallWatchdog = 1;
RisingSteps = 0;
FallingSteps = 0;
}
}
ISR(PCINT2_vect){ //interput vector that does all the speed input signal detecting
triggerPoint = micros(); //stores the time of interput start as micros
//this part calcs duty cycle pulse
if ((PIND & B00010000) && (PrevState == 0)){ //if input pin just went high and was just low
PrevState = 1; //records that the input pin is currently high
}
else if ((!(PIND & B00010000)) && (PrevState == 1)){ //if the input pin just went low and was just high
PrevState = 0; //records that the input pin is currently low
fallPoint = triggerPoint; //sets the duty fall point
}
//this part calcs frequency using the time of a full pulse (high to low and back to high again)
if ((PrevState == 1) && (PulseStarted == 0)){ //if input is high and its the start of a new full pulse
FirstHigh = triggerPoint; //saves the micros time
risePoint = triggerPoint; //sets the duty risepoint
PulseStarted = 1; //records that the new full pulse has started
}
else if ((PrevState == 1) && (PulseStarted == 1)){ //if the input is high again and a full pulse was started on prev high
SecondHigh = triggerPoint; //saves the micros time
DutyMicros = (fallPoint - risePoint); //calcs the duty in microseconds
pulseTime = (SecondHigh - FirstHigh); //calcs the total time it took to pulse from high (start of new pulse) to low and back to high again giving the time in micros of the full pulse
PulseStarted = 0; //records that the next high will be a new full pulse
SetDutyVariable = 1; //tells interput to check and calucate the duty to be pushed to main loop for setting
}
if ((SetDutyVariable == 1) && (pulseTime >= 100) && (pulseTime <= 10000) && (DutyMicros < pulseTime) && (DutyMicros > 1)){ //Filter to make sure the raw signal can be mapped properly
FilteredDuty = map(DutyMicros, 1, pulseTime, 45, 250); //maps the duty cycle using micros freq as top into a rough percentage
if (FilteredDuty < 50){ //constrains duty to ensure its within the correct range
FilteredDuty = 0;
}
else if (FilteredDuty > 245){
FilteredDuty = 245;
}
UpdateDuty = 1; //sets an update flag to be seen by the main loop
SetDutyVariable = 0; //ensures this loop only runs when told to
}
else if (SetDutyVariable == 1){
SetDutyVariable = 0; //ensures this loop only runs when told to
}
}
ISR (PCINT0_vect) { //pin change interupt vector for direction change
if ((PINB & B00100000)){ //checks if pin 13 went high rev
StartRevving = 1; //sets flag for the main loop
ChngDir = 1;
}
else if (!(PINB & B00100000)){ //checks if pin 13 went low fwrd
StartRevving = 0; //sets flag for the main loop
ChngDir = 1;
}
}
void TakeStep(int stepNum){ //Forward steps setting function
switch(stepNum){
case 1://step1: A, PWM. B, low. C, floating/bemfsense.
TCCR1A = 0; //turn off unwanted highsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
PORTD = BLow; //set the lowsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
TCCR2A = AsetPWM; //set the wanted highside pulsing
ADMUX = 3; //set A3 as negative side comparator input (phase C)
ACSR |= 0x03; //sets rising edge interupt
break;
case 2: //step2: A, PWM. C, low. B, floating/bemfsense.
TCCR1A = 0; //turn off unwanted highsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
PORTD = CLow; //set the lowsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
TCCR2A = AsetPWM; //set the wanted highside pulsing
ADMUX = 2; //set A2 as negative side comparator input (phase B)
ACSR &= ~0x01; //sets falling edge interupt
break;
case 3: //step3: B, PWM. C, low. A, floating/bemfsense.
TCCR2A = 0; //turn off unwanted highsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
PORTD = CLow; //set the lowsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
TCCR1A = BsetPWM; //set the wanted highside pulsing
ADMUX = 1; //set A1 as negative side comparator input (phase A)
ACSR |= 0x03; //sets rising edge interupt
break;
case 4: //step4: B, PWM. A, low. C, floating/bemfsense.
TCCR2A = 0; //turn off unwanted highsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
PORTD = ALow; //set the lowsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
TCCR1A = BsetPWM; //set the wanted highside pulsing
ADMUX = 3; //set A3 as negative side comparator input (phase C)
ACSR &= ~0x01; //sets falling edge interupt
break;
case 5: //step5: C, PWM. A, low. B, floating/bemfsense.
TCCR2A = 0; //turn off unwanted highsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
PORTD = ALow; //set the lowsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
TCCR1A = CsetPWM; //set the wanted highside pulsing
ADMUX = 2; //set A2 as negative side comparator input (phase B)
ACSR |= 0x03; //sets rising edge interupt
break;
case 6: //step6: C, PWM. B, low. A, floating/bemfsense.
TCCR2A = 0; //turn off unwanted highsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
PORTD = BLow; //set the lowsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
TCCR1A = CsetPWM; //set the wanted highside pulsing
ADMUX = 1; //set A1 as negative side comparator input (phase A)
ACSR &= ~0x01; //sets falling edge interupt
break;
}
}
void TakeRevStep (int revstepNum){ //reverse steps setting function
switch(revstepNum){
case 1: //Step 1 C, PWM. B, low. A, floating/bemfsense.
TCCR2A = 0; //turn off unwanted highsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
PORTD = BLow; //set the lowsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
TCCR1A = CsetPWM; //set the wanted highside pulsing
ADMUX = 1; //set A1 as negative side comparator input (phase A)
ACSR |= 0x03; //sets rising edge interupt
break;
case 2: //step5: C, PWM. A, low. B, floating/bemfsense.
TCCR2A = 0; //turn off unwated highsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
PORTD = ALow; //set the lowsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
TCCR1A = CsetPWM; //set the wanted highside pulsing
ADMUX = 2; //set A2 as negative side comparator input (phase B)
ACSR &= ~0x01; //sets falling edge interupt
break;
case 3: //step4: B, PWM. A, low. C, floating/bemfsense.
TCCR2A = 0; //turn off unwated highsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
PORTD = ALow; //set the lowsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
TCCR1A = BsetPWM; //set the wanted highside pulsing
ADMUX = 3; //set A3 as negative side comparator input (phase C)
ACSR |= 0x03; //sets rising edge interupt
break;
case 4: //step3: B, PWM. C, low. A, floating/bemfsense.
TCCR2A = 0; //turn off unwated highsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
PORTD = CLow; //set the lowsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
TCCR1A = BsetPWM; //set the wanted highside pulsing
ADMUX = 1; //set A1 as negative side comparator input (phase A)
ACSR &= ~0x01; //sets falling edge interupt
break;
case 5: //step2: A, PWM. C, low. B, floating/bemfsense.
TCCR1A = 0; //turn off highsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
PORTD = CLow; //set the lowsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
TCCR2A = AsetPWM; //set the highside pulsing
ADMUX = 2; //set A2 as negative side comparator input (phase B)
ACSR |= 0x03; //sets rising edge interupt
break;
case 6://step1: A, PWM. B, low. C, floating/bemfsense.
TCCR1A = 0; //turn off highsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
PORTD = BLow; //set the lowsides
DELAY_CYCLES(3); //delay for deadtime to prevent shoot through
TCCR2A = AsetPWM; //set the highside pulsing
ADMUX = 3; //set A3 as negative side comparator input (phase C)
ACSR &= ~0x01; //sets falling edge interupt
break;
}
}
void StartUpSound(){ //program to play 3 short higher tone beeps to indicate the ESC has completed its startup and is ready for use
cli(); //disables interupts
TCCR1A = 0; //turn off highsides
TCCR2A = 0;
OCR1A = 0; //sets PWM to no duty
OCR1B = 20; //sets PWM duty
OCR2B = 15; //sets PWM duty
PORTD = BLow; //sets B low with A & C floating.
for (BPCnt = 0; BPCnt <= 10000; BPCnt++){ //for loop to generate a beep
TCCR2A = AsetPWM; //set phase A to be pulsing
DELAY_CYCLES(90); //small delay
TCCR1A = 0; //turn off highsides
TCCR2A = 0;
DELAY_CYCLES(100); //small delay
TCCR1A = CsetPWM; //set phase C to be pulsing
DELAY_CYCLES(90); //small delay
TCCR1A = 0; //turn off highsides
TCCR2A = 0;
DELAY_CYCLES(100); //small delay
}
HoldUpnumber = 0;
while (HoldUpnumber < 10000){ //while loop to introduce some rough (~ 7 millisecond) delay.
HoldUpnumber++;
DELAY_CYCLES(5);
}
for (BPCnt = 0; BPCnt <= 10000; BPCnt++){ //for loop to generate a beep
TCCR2A = AsetPWM; //set phase A to be pulsing
DELAY_CYCLES(90); //small delay
TCCR1A = 0; //turn off highsides
TCCR2A = 0;
DELAY_CYCLES(100); //small delay
TCCR1A = CsetPWM; //set phase C to be pulsing
DELAY_CYCLES(90); //small delay
TCCR1A = 0; //turn off highsides
TCCR2A = 0;
DELAY_CYCLES(100); //small delay
}
HoldUpnumber = 0;
while (HoldUpnumber < 125000){ //while loop to introduce some rough (~ 7 millisecond) delay.
HoldUpnumber++;
DELAY_CYCLES(5);
}
for (BPCnt = 0; BPCnt <= 12500; BPCnt++){ //for loop to generate a beep
TCCR2A = AsetPWM; //set phase A to be pulsing
DELAY_CYCLES(90); //small delay
TCCR1A = 0; //turn off highsides
TCCR2A = 0;
DELAY_CYCLES(100); //small delay
TCCR1A = CsetPWM; //set phase C to be pulsing
DELAY_CYCLES(90); //small delay
TCCR1A = 0; //turn off highsides
TCCR2A = 0;
DELAY_CYCLES(100); //small delay
}
sei(); //re-enables interupts
}
void StallSound(){ //program to make the motor play a short low tone beep to indicate the motor has stalled
TCCR1A = 0; //turn off highsides
TCCR2A = 0;
PORTD = B10100100; //turn off lowsides
OCR1A = 0; //sets PWM to 0% duty
OCR1B = 75; //sets PWM to medium duty
OCR2B = 75; //sets PWM to medium duty
PORTD = BLow; //sets B low with A & C floating
for (BPCnt = 0; BPCnt <= 12500; BPCnt++){ //for loop to generate a beep
TCCR2A = AsetPWM; //set phase A to be pulsing
DELAY_CYCLES(180); //small delay
TCCR1A = 0; //turn off highsides
TCCR2A = 0;
DELAY_CYCLES(200); //small delay
TCCR1A = CsetPWM; //set phase C to be pulsing
DELAY_CYCLES(180); //small delay
TCCR1A = 0; //turn off highsides
TCCR2A = 0;
DELAY_CYCLES(200); //small delay
}
PORTD = B10100100; //set motor to floatig
TCCR2A = 0;
TCCR1A = 0;
OCR1A = 0; //sets PWM to 0% duty
OCR1B = 0;
OCR2B = 0;
}
Schematic:
PCB photos:
Waveform Photos: (1: low speed no load, 2: med speed no load, 3: high speed no load, 4: low speed load, 5: med speed load, 6: high speed load, (where it really gets "wonky")).
If anyone needs more information or for me to do more tests and get more waveforms, Etc. Please let me know and thanks for reading this far, I hope to hear your input soon ![]()











