- Document that frequency is now global & max 8 channels
- Overclocking documentation 'Beyond 400 Hz'
- Add Saleae logic screenshots
- Add a ToC
- Comparison with Servo
- Publish on the Arduino Library Manager
Generates 400 Hz PWM control pulses for servos and multicopter electronic speed controllers (ESC). By varying the pulse and period width frequencies of well over 400 Hz can be obtained (provided the attached hardware can handle the signals). Can control 8 ESC's and/or Servo's at the same time out of the box but that can be inncreased or decreased by simply changing the PULSE400_MAX_CHANNELS
constant in the Pulse400.h
file.
Provides the following front-end classes
- Esc400: Control motor (esc) as a single object
- Multi400: Control multiple motors (bank) as a single object
- Servo400: Drop in replacement for the Arduino Servo library
Install the TimerOne library with the Arduino library manager ( Sketch > Include Library > Manage libraries > ).
Download the Pulse400 library from here. Unzip it in your Arduino libraries folder and rename the directory from Pulse400-master to just Pulse400. Restart the Arduino IDE and you should be able to open and run the included examples.
The standard Arduino Servo library can be sued to control electronic speed controllers for multicopters, but it's not really suited for it. It runs at 50Hz and is difficult to improve because the pulses are generated in a sequential manner (using multiple timers if needed). A standard ESC PWM pulse takes up 2.5 milliseconds so outputting 4 PWM signals in sequence takes up 10ms and would require at most a 100 Hz signal.
The Pulse400 class is not meant to be used directly in a sketch. Instead use one (or more) of the Esc400/Multi400/Servo400 front-end classes or write your own. They work together fine, so you can use a Multi400 object to control your quadcopter's 4 motors while controlling your camera pan & tilt with two Servo400 objects.
The Esc400 and Multi400 classes allow you to manipulate the minimum and maximum pulse width that is used and they also allow you to tune the period setting. By changing the period setting from the default 2500 a lower value and adapting the minimum and maximum pulse to fit within that period you can increase the frequency even further. Setting the period width or the frequency has a global effect on all PWM streams generated by Pulse400. See below for further explanation.
The main challenge was to keep a list of PWM channels (queue) and keep it sorted on (often rapidly changing) pulse width at all times so that the interrupt service routine can quickly access the next pin that needs to be flipped without too many calculations. This was done by keeping two separate queues, and ACTive and an ALTernate one which can be edited by the main code. Whenever the ALTernate queue has been updated the main code sets a switch_queue flag which signals to the interrupt handler that whenever a new period starts it should switch from the ACTive queue to the ALTernate queue, which then becomes the ACTive queue.
The Esc400 class controls one PWM channel, so you basically create one for each motor.
Method | Description |
---|---|
begin( int8_t pin | Initializes the object and attaches it to a pin. |
speed( uint16_t v ) | Sets the speed for the ESC, value must be between 0 (min throttle) and 1000 (max throttle) |
speed() | Retrieves the current speed. |
range( uint16_t min, uint16_t min ) | Defines the mapping of the min - max throttle value (0 - 1000) to a pulse length in microseconds. |
end() | Detaches the object from the pin. |
N
#include <Pulse400.h>
int pin[] = { 4, 5, 6, 7 };
Esc400 motor[4];
void setup() {
for ( int m = 0; m < 4; m++ )
motor[m].begin( pin[m] );
delay( 1000 );
for ( int m = 0; m < 4; m++ )
motor[m].speed( 200 );
delay( 1000 );
for ( int m = 0; m < 4; m++ )
motor[m].speed( 0 );
}
void loop() {
}
Use this class to control up to 8 motors (bank) as a single object. Setting the motor speeds for multiple motors is a lot more efficient than setting it for one motor at a time (as the Esc400 class does). To be able to efficiently generate a waveform the Pulse400 class must maintain a list (queue) of steps (pin or or off) to execute. This list must be sorted by pulse width. For each change in speed the list must be inspected and ossible resorted which is relatively expensive in MCU time. By using the Multi400 class and setting the speeds for all motors at once, the generator only needs to sort the list once per bank and not once per motor.
Method | Description |
---|---|
begin( int8_t pin0, int8_t pin1, ..., int8_t pin7 ) | Initializes the object and attaches it to a series of pins. All arguments but the first are optional, specify at least 1 and at most 8 ESCs/motors. |
setSpeed( int16_t v0, int16_t v0,..., int16_t v7 ) | Sets the speed for all ESCs at the same time, value must be between 0 (min throttle) and 1000 (max throttle). |
speed( uint8_t no, uint16_t v ) | Sets the speed for the ESC identified by 'no', value must be between 0 (min throttle) and 1000 (max throttle). |
speed( uint8_t no ) | Retrieves the current speed for ESC 'no'. |
range( uint16_t min, uint16_t min ) | Defines the mapping of the min - max throttle value (0 - 1000) to a pulse length in microseconds. |
end() | Detaches the object from the attached pins |
#include <Pulse400.h>
int pin[] = { 4, 5, 6, 7 };
Multi400 motors;
void setup() {
motors.begin( pin[0], pin[1], pin[2], pin[3] );
delay( 1000 );
motors.setSpeed( 200, 200, 200, 200 );
delay( 1000 );
motors.setSpeed( 0, 0, 0, 0 );
motors.end();
}
void loop() {
}
The Servo400 class strives to be an exact copy of the standard Arduino Servo class. The default PWM frequency is 400 Hz, but that can be changed with the pulse400.frequency()
method.
Method | Description |
---|---|
attach( int pin ) | Attach the given pin to the next free channel, sets pinMode, returns channel number or 0 if failure |
attach( int pin, int min, int max ) | As above but also sets min and max values for writes |
detach() | Detaches the object from the pin |
write( int value ) | If value is < 200 its treated as an angle, otherwise as pulse width in microseconds |
writeMicroseconds( int value ) | Write pulse width in microseconds |
read() | Returns current pulse width (int) as an angle between 0 and 180 degrees |
readMicroseconds() | returns current pulse width (int) in microseconds for this servo |
attached() | return true if this servo is attached, otherwise false |
/* Sweep
by BARRAGAN <http://barraganstudio.com>
This example code is in the public domain.
modified 8 Nov 2013
by Scott Fitzgerald
http://www.arduino.cc/en/Tutorial/Sweep
*/
#include <Pulse400.h>
Servo400 myservo; // create servo object to control a servo
// twelve servo objects can be created on most boards
int pos = 0; // variable to store the servo position
void setup() {
myservo.attach(9); // attaches the servo on pin 9 to the servo object
pulse400.frequency( PULSE400_50HZ ); // Slow it down to the 'standard' 50 Hz
}
void loop() {
for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
// in steps of 1 degree
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
}
The example code and method descriptions are taken from the Servo library documentation and adapted slightly for Servo400.
The Pulse400 class is the actual PWM generator that is used by the Esc400, Multi400 and Servo400 front-end classes. A (singleton) object named pulse400
is automatically instantiated when Pulse400.h is included.
Method | Description |
---|---|
attach( int8_t pin, int8_t force_id = -1 ) | Attaches the specified pin and allocates a PWM channel for it. Returns a channel id or -1 on failure (no more channels available). The optional second argument forcibly sets the channel id. |
detach( int_8 id_channel ) | Detaches the pin and frees the channel |
pulse( int8_t id_channel, uint16_t pulse_width, bool no_update = false) | Sets the pulse width for the specified channel. Set no_update to true to delay updating the PWM generator. Call the update() method after setting a set of channnels. The pulse_width argument takes values from 1 to period length (normally 2500). |
pulse( int8_t id_channel ) | Returns the current pulse for the specified channel. |
update() | Updates the PWM generation queue after a (series of) speed updates. |
frequency( uint16_t f ) | Set the frequency for the Pulse400 PWM generator. The frequency can be set between 29 and about 2000 Hz. (with a severely restricted maximum pulse time) |