ATMega 328 to control an ESC (PWM or DShot)

You had made a post some time ago about running 12 ESC's but that was on a Mega I believe. I "assumed" is was auto sensing and down to 50/490Hz would work but perhaps not. I'd prefer to avoid using a different ESC only because the change in width would necessitate a new SLS printed mount.

Hi @exidous

I also wrote some equivalent code on the Mega for Oneshot125 as well, I'm just copying bit's of code across to a new sketch targeting the Uno instead.

Hi @exidous

The following code will output Oneshot125 on pin digital pin D9. The OCR1A register sets the pulse width with values between 0 (min), 1000 (standby), 1500 (mid speed) and 2000 (full speed) and anything in between.

The Atmega328P timer 1 doesn't have oneshot functionality per se, but instead has been programmed to repeat at a frequency of 1kHz (1ms period). This is controlled by the ICR1 register.

Here's the code:

// Arduino Uno Oneshot125 on digital pin D9
void setup() 
{
  pinMode(9, OUTPUT);                               // Set digital pin D9 to an output
  TCCR1A = _BV(WGM11) | _BV(COM1A1);                // Enable the PWM output OC1A on digital pin 9          
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);     // Set fast PWM and prescaler of 1 on timer 1 
  ICR1 = 15999;                                     // Set the timer 1 frequency 1kHz (1ms period) 16MHz / (15999 + 1) = 1000
  OCR1A = 1000;                                     // Set the Oneshot125 duty-cycle between 0 (min) and 2000 (max)
}

void loop() {}

Does this look right?

const byte
IN1 = 4,
IN2 = 5,
IN3 = 6,
IN4 = 7,
MotorPin = 9;

void setup ()
{
  pinMode ( IN1, INPUT_PULLUP );
  pinMode ( IN2, INPUT_PULLUP );
  pinMode ( IN3, INPUT_PULLUP );
  pinMode ( IN4, INPUT_PULLUP );
  pinMode ( MotorPin, OUTPUT);                      // Set digital pin D9 to an output
  TCCR1A = _BV(WGM11) | _BV(COM1A1);                // Enable the PWM output OC1A on digital pin 9          
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);     // Set fast PWM and prescaler of 1 on timer 1 
  ICR1 = 15999;                                     // Set the timer 1 frequency 1kHz (1ms period) 16MHz / (15999 + 1) = 1000
  OCR1A = 1000;                                     // Set the Oneshot125 duty-cycle between 0 (min) and 2000 (max)
 }

void loop ()
{
  if ( digitalRead ( IN1 ) == LOW )
  {
    analogWrite(MotorPin, 1250); // 1/4 speed
  }
  else if ( digitalRead ( IN2 ) == LOW )
  {
    analogWrite(MotorPin, 1500); // 1/2 speed
  }
  else if ( digitalRead ( IN3 ) == LOW )
  {
    analogWrite(MotorPin, 1750); // 3/4 speed
  }
  else if ( digitalRead ( IN4 ) == LOW )
  {
    analogWrite(MotorPin, 2000); // full speed
  }
  else
  {
    analogWrite(MotorPin, 1000); // stand still
  }
}

@exidous Yes that's right, except In your code you don't require the analogWrite() function anymore. Just replace it by loading the OCR1A register with the same pulse width value instead.

For example:

analogWrite(MotorPin, 1250);

becomes this:

OCR1A = 1250;

and so on..., for each speed in your code.

Oh neat,

I'll test it out. thanks.

Patrick

I'm getting a period of 83.3us. Grounding 1-4 makes no change in duty.

const byte
IN1 = 4,
IN2 = 5,
IN3 = 6,
IN4 = 7,
MotorPin = 9;

void setup ()
{
  pinMode ( IN1, INPUT_PULLUP );
  pinMode ( IN2, INPUT_PULLUP );
  pinMode ( IN3, INPUT_PULLUP );
  pinMode ( IN4, INPUT_PULLUP );
  pinMode ( MotorPin, OUTPUT);                      // Set digital pin D9 to an output
  TCCR1A = _BV(WGM11) | _BV(COM1A1);                // Enable the PWM output OC1A on digital pin 9          
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);     // Set fast PWM and prescaler of 1 on timer 1 
  ICR1 = 15999;                                     // Set the timer 1 frequency 1kHz (1ms period) 16MHz / (15999 + 1) = 1000
  OCR1A = 1000;                                     // Set the Oneshot125 duty-cycle between 0 (min) and 2000 (max)
 }

void loop ()
{
  if ( digitalRead ( IN1 ) == LOW )
  {
    OCR1A = 1250; // 1/4 speed
  }
  else if ( digitalRead ( IN2 ) == LOW )
  {
    OCR1A = 1500; // 1/2 speed
  }
  else if ( digitalRead ( IN3 ) == LOW )
  {
    OCR1A = 1750; // 3/4 speed
  }
  else if ( digitalRead ( IN4 ) == LOW )
  {
    OCR1A = 2000; // full speed
  }
  else
  {
    OCR1A = 1000; // stand still
  }
}

Hi @exidous

On my scope I'm getting the correct pulse width an period. I think the issue is that the OCR1A register is loaded each time around the loop(), whereas in fact it only as to be called once, and loaded whenever there's a change of speed.

I'm thinking I may have damaged the board. I don't know when exactly but my original code would show a duty change but it doesn't now. I have a Trinket M0 laying around that might work for testing while I wait for the new Metro to show up.

Given it's arm and 48Mhz I don't think it's a simple pin rearrange.

I do also have an old 2580 mega laying around.

@exidous It shouldn't damage the board if it's just outputting a pulse signal to the ESC.

Hi @exidous

To check if the board is OK, disconnect the output pin, then run a sketch that calls analogWrite(MotorPin, 128) once in setup() and see if you get a 490Hz PWM, 50% duty-cycle on your scope.

It's working correctly. I'm getting 0-5v 50%duty at 500Hz.

I'm thinking it's the input that's borked. How would I test that?

Hi @exidous

It's good to see that the output's working again.

To only write to the OCR1A register whenever it changes, I've modified your code a smidgin. I've test the code and it works on my Arduino Uno:

const byte
IN1 = 4,
IN2 = 5,
IN3 = 6,
IN4 = 7,
MotorPin = 9;

void setup ()
{
  pinMode ( IN1, INPUT_PULLUP );
  pinMode ( IN2, INPUT_PULLUP );
  pinMode ( IN3, INPUT_PULLUP );
  pinMode ( IN4, INPUT_PULLUP );
  pinMode ( MotorPin, OUTPUT);                      // Set digital pin D9 to an output
  TCCR1A = _BV(WGM11) | _BV(COM1A1);                // Enable the PWM output OC1A on digital pin 9          
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);     // Set fast PWM and prescaler of 1 on timer 1 
  ICR1 = 15999;                                     // Set the timer 1 frequency 1kHz (1ms period) 16MHz / (15999 + 1) = 1000
  OCR1A = 1000;                                     // Set the Oneshot125 duty-cycle between 0 (min) and 2000 (max)
 }

void loop ()
{
  if ( digitalRead ( IN1 ) == LOW)
  {
    if (OCR1A != 1250)
    {
      OCR1A = 1250; // 1/4 speed
    }
  }
  else if ( digitalRead ( IN2 ) == LOW)
  {
    if (OCR1A != 1500)
    {
      OCR1A = 1500; // 1/2 speed
    }
  }
  else if ( digitalRead ( IN3 ) == LOW)
  {
    if (OCR1A != 1750)
    {
      OCR1A = 1750; // 3/4 speed
    }
  }
  else if ( digitalRead ( IN4 ) == LOW)
  {
    if (OCR1A != 2000)
    {
      OCR1A = 2000; // full speed
    }
  }
  else
  {
    if (OCR1A != 1000)
    {
      OCR1A = 1000; // stand still
    }
  }
}

@exidous It's probably worth check on your scope to verify that the pulse widths are correct. Standby (1000) gives 62.5us, maximum (2000) 125us.

I'm still seeing no change when I ground pins 4-7. My replacement board should be here tomorrow.

Question, isn't oneshot 125-250us?

Hi @exidous

Yes you're right, my apologies, I got that wrong.

In that case, you just need to double the values entered into the OCRA1 register.

Hopefully that will get your motor started?

I'll let you know tomorrow!

My board was bad.

The replacement seems to be working as it should. Now to test with the ESC!

const byte
IN1 = 4,
IN2 = 5,
IN3 = 6,
IN4 = 7,
MotorPin = 9;

void setup ()
{
  pinMode ( IN1, INPUT_PULLUP );
  pinMode ( IN2, INPUT_PULLUP );
  pinMode ( IN3, INPUT_PULLUP );
  pinMode ( IN4, INPUT_PULLUP );
  pinMode ( MotorPin, OUTPUT);                      // Set digital pin D9 to an output
  TCCR1A = _BV(WGM11) | _BV(COM1A1);                // Enable the PWM output OC1A on digital pin 9          
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);     // Set fast PWM and prescaler of 1 on timer 1 
  ICR1 = 15999;                                     // Set the timer 1 frequency 1kHz (1ms period) 16MHz / (15999 + 1) = 1000
  OCR1A = 2000;                                     // Set the Oneshot125 duty-cycle between 0 (min) and 2000 (max)
 }

void loop ()
{
  if ( digitalRead ( IN1 ) == LOW)
  {
    if (OCR1A != 2500)
    {
      OCR1A = 2500; // 1/4 speed
    }
  }
  else if ( digitalRead ( IN2 ) == LOW)
  {
    if (OCR1A != 3000)
    {
      OCR1A = 3000; // 1/2 speed
    }
  }
  else if ( digitalRead ( IN3 ) == LOW)
  {
    if (OCR1A != 3500)
    {
      OCR1A = 3500; // 3/4 speed
    }
  }
  else if ( digitalRead ( IN4 ) == LOW)
  {
    if (OCR1A != 4000)
    {
      OCR1A = 4000; // full speed
    }
  }
  else
  {
    if (OCR1A != 2000)
    {
      OCR1A = 2000; // stand still
    }
  }
}

I'm getting odd results. Inputs 1 & 4 are working as they should. 2 & 3 will go to the proper speed for a second then the ESC will ramp the speed down to 0.

Is it possible to ramp up or down between states? I think some of the issue is noise during the initial current spike. I'm going to try to add a really big cap on the power as a stop gap.

Hi @exidous

It's good to hear that you've got it partially working.

You could temporarily test ramping the speed up then down with a for() loop and delay:

const byte
IN1 = 4,
IN2 = 5,
IN3 = 6,
IN4 = 7,
MotorPin = 9;

void setup ()
{
  pinMode ( IN1, INPUT_PULLUP );
  pinMode ( IN2, INPUT_PULLUP );
  pinMode ( IN3, INPUT_PULLUP );
  pinMode ( IN4, INPUT_PULLUP );
  pinMode ( MotorPin, OUTPUT);                      // Set digital pin D9 to an output
  TCCR1A = _BV(WGM11) | _BV(COM1A1);                // Enable the PWM output OC1A on digital pin 9          
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);     // Set fast PWM and prescaler of 1 on timer 1 
  ICR1 = 15999;                                     // Set the timer 1 frequency 1kHz (1ms period) 16MHz / (15999 + 1) = 1000
  OCR1A = 2000;                                     // Set the Oneshot125 duty-cycle between 0 (min) and 4000 (max)
 }

void loop ()
{
  if ( digitalRead ( IN1 ) == LOW)
  {
    for (uint16_t i = 2000; i < 4000; i++)
    {
      OCR1A = i;
      delay(10);
    }
    for (uint16_t i = 4000; i >= 2000; i--)
    {
       OCR1A = i;
       delay(10);
    }
  }
}

For my quadcopter ESCs I soldered on Rubycon 470uF, 35V, low ESR ZLH series, electrolytic capacitors. Taking care to get the polarity the right way round.