Go Down

Topic: OneShot125 ESC (Read 4689 times) previous topic - next topic


Sep 08, 2018, 10:23 pm Last Edit: Sep 08, 2018, 10:31 pm by Juckix
Hi Juckix,

I implemented Oneshot125 for the Arduino Mega in this Arduino forum thread: https://forum.arduino.cc/index.php?topic=520395.0. Please find the zip file MegaOneshot125.zip at the bottom of the thread.

The Arduino Nano uses timer 1 on outputs OCR1A (D9) and OCR1B (D10). You should be able to get Oneshot125 on these two pins by just deleting references to the timers 3, 4 and 5 on the Mega, as well as the additional OC1C channel on timer 1, which the Nano doesn't have.

The best place to learn about setting up PWM for the Arduino Nano is from the Atmega328P datasheet. There are a number of PWM modes, but the most useful for controlling brushless ESCs are called fast PWM (single slope) and phase and frequency correct PWM (dual slope).

As an example, here's some code that uses standard dual slope PWM to control two servos at 50Hz on D9 and D10. To generate 490Hz for brushless multi-rotor ESCs, just change the ICR1 register to from 20000 to 2040, (ORC1x registers: 1000 = min throttle, 2000 = max throttle):

Code: [Select]
// Set-up hadware PWM on the Arduino UNO at 50Hz on pins 9 and 10
void setup() {
  // Initialise timer 1 for phase and frequency correct PWM
  pinMode(9, OUTPUT);                         // Set digital pin 9 (D9) to an output
  pinMode(10, OUTPUT);                        // Set digital pin 10 (D10) to an output
  TCCR1A = _BV(COM1A1) | _BV(COM1B1);         // Enable the PWM outputs OC1A, and OC1B on digital pins 9, 10
  TCCR1B = _BV(WGM13) | _BV(CS11);            // Set phase and frequency correct PWM and prescaler of 8 on timer 1
  ICR1 = 20000;                               // Set the PWM frequency to 50Hz
  OCR1A = 1500;                               // Centre the servo on D9
  OCR1B = 1500;                               // Centre the servo on D10

void loop() {
  OCR1A = 1000;                               // Move the servo to min position on D9 
  OCR1B = 1000;                               // Move the servo to min position on D10
  delay(1000);                                // Wait for 1 second
  OCR1A = 2000;                               // Move the servo to max position on D9
  OCR1B = 2000;                               // Move the servo to max position on D10
  delay(1000);                                // Wait for 1 second

Warning: Please remove the propellers if you're testing this code with brushless motors.
Code does indeed create 1-2ms pulses at 50Hz. Thanks! Although I believe Oneshot125 uses 125-255 microsecond pulses? (https://oscarliang.com/oneshot125-esc-quadcopter-fpv/)

I will post some code I wrote that also creates the right signal, tested with an ESC.

I also have to ask what the ICR1 registry does, as I don't really understand what's written about it in the datasheet. (but I'll keep reading)


Sep 08, 2018, 10:30 pm Last Edit: Sep 08, 2018, 10:33 pm by Juckix
Here's the code that I wrote that works on a Nano:

Code: [Select]

#include <SoftwareSerial.h>
#include <Servo.h>

int pinA0 = 14;

/// Takes a pin number and a throttle value 0-100 and sets appropriate PWM on that pin for Oneshot125 ESCs
void oneshot125(int pin, int throttle){
  int mc_s = map(throttle, 0, 100, 125, 255); // Convert from received 0-100 throttle to 125-255 microsecond value
  if(pin == 9)
    OCR1A = mc_s; // Write value to register directly, cycles happen to be 1 microsecond with current config
  } else if (pin == 10) {
    OCR1B = mc_s;
  } else {
    Serial.println("oneshot125(): Invalid pin");

void setup() {
  Serial.begin(9600); // Debug
  pinMode(9, OUTPUT); // OCR1A PWM pin
  pinMode(10, OUTPUT); // OCR1B PWM pin
  TCCR1A = 0; // Reset PWM registers
  TCCR1B = 0;
  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM13) | _BV(WGM12) | _BV(WGM11) | _BV(WGM10); // Not sure what the COM bits decide, WGM bits select FastPWM mode
  TCCR1B = _BV(CS11); // Clock prescale set to 64? (datasheet says 8?), fq is 16MHz/64/256 = 976.563Hz (oscope says 977.532Hz)
  //TCCR1B = _BV(CS10); // Clock prescale set to 8? (datasheet says 1?), fq is 16MHz/8/256 = 7.8125kHz (oscope says 7.8203kHz)
  OCR1A = 0; // Set duty cycle to 0%
  OCR1B = 0;
  pinMode(pinA0, INPUT); // Used for a potentiometer input
  Serial.println("Setup complete;");

void loop() {
  int in = analogRead(pinA0); // Read potentiometer position
  in = map(in, 0, 1023, 0, 100); // Map to 0-100 throttle
  delay(2); // Simulate flight controller logic loop
  oneshot125(9, in); // Output throttle variable "in" via the oneshot125() function

This sweeps the throttle according to a potentiometer input, creating 125.00-255.00 microsecond pulses at 977Hz.

I don't understand how the frequency is picked since the clock prescaler seems different from the datasheet, and I don't know how to use the ICR1 registry to adjust the pulse frequency as MartinL showed, but there you have it.

The following resource was also useful for experimenting with, and a lot more easily digestible than the Atmel datasheets: https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM


Sep 09, 2018, 12:02 am Last Edit: Sep 09, 2018, 12:02 am by MartinL
Hi Juckix,

Code does indeed create 1-2ms pulses at 50Hz. Thanks! Although I believe Oneshot125 uses 125-255 microsecond pulses? (https://oscarliang.com/oneshot125-esc-quadcopter-fpv/)
If you follow the link I provided in my previous response, you'll find an implementation of Oneshot125 on the Arduino Mega: https://forum.arduino.cc/index.php?topic=520395.0.

While it's straightforward to create continuous Oneshot125 style pulses in the 125us to 250us range, it's more difficult to create a manually triggered single pulse.

The issue is that the Uno, Nano, Mega and other AVR microcontrollers, as well as the ARM based Due, do not have the internal hardware to nessary to create a manually triggered oneshot pulse. However, it's possible to circumvent this problem with the use of Interrupt Service Routines (ISRs).

On the other hand, the SAMD21 based Arduinos such as the Arduino Zero and the MKR series, and I imagine the STM32 microcontrollers used in the latest flight controllers, do have oneshot functionality built into at least some of their timers.

Go Up