Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Break servoGenerator into header and implementation
- Loading branch information
1 parent
770a78c
commit cea38c9
Showing
2 changed files
with
198 additions
and
177 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
#include "ServoGenerator.h" | ||
|
||
#define TIMER_NUM 1 | ||
|
||
#define EXPCAT(A,B,C) EXPANDEDCONCATENATE(A,B,C) | ||
#define EXPANDEDCONCATENATE(A,B,C) A ## B ## C | ||
#define TIMER_ISR(N,V) TIMER_ISR_EXP(N,V) | ||
#define TIMER_ISR_EXP(N,V) TIMER ## N ## _ ## V ## _vect | ||
|
||
namespace { | ||
constexpr volatile uint8_t& TCCRA = EXPCAT(TCCR, TIMER_NUM,A); | ||
constexpr volatile uint8_t& TCCRB = EXPCAT(TCCR, TIMER_NUM,B); | ||
constexpr volatile uint8_t& TCCRC = EXPCAT(TCCR, TIMER_NUM,C); | ||
constexpr volatile uint8_t& TIFR = EXPCAT(TIFR, TIMER_NUM, ); | ||
constexpr volatile uint8_t& TIMSK = EXPCAT(TIMSK,TIMER_NUM, ); | ||
constexpr volatile uint16_t& ICR = EXPCAT(ICR, TIMER_NUM, ); | ||
constexpr volatile uint16_t& TCNT = EXPCAT(TCNT, TIMER_NUM, ); | ||
constexpr volatile uint16_t& OCRA = EXPCAT(OCR, TIMER_NUM,A); | ||
constexpr volatile uint16_t& OCRB = EXPCAT(OCR, TIMER_NUM,B); | ||
|
||
class Output{ | ||
public: | ||
Output(): highTime(0xffff), pinMask(0), pinReg(0) {} | ||
uint16_t highTime; | ||
uint8_t pinMask; | ||
volatile uint8_t* pinReg; | ||
bool enabled() const volatile { return pinMask != 0; } | ||
void disable() volatile { pinMask = 0; pinReg = 0; highTime = 0xffff; } | ||
void setPin(int p) volatile { | ||
pinMask = digitalPinToBitMask(p); | ||
pinReg = portOutputRegister(digitalPinToPort(p)); | ||
} | ||
void setHigh() const volatile { *pinReg |= pinMask; } | ||
void setLow() const volatile { *pinReg &= ~pinMask; } | ||
}; | ||
|
||
constexpr uint8_t MAX_OUTPUTS = 8; | ||
volatile Output output[MAX_OUTPUTS]; | ||
volatile uint8_t activeOutputs = 0; | ||
|
||
class Action{ //lawsuit | ||
public: | ||
uint16_t time; | ||
uint8_t channel; | ||
}; | ||
Action actions[MAX_OUTPUTS+1]; | ||
Action * next = actions; | ||
// constructor attribute makes this get run once before main | ||
void setupActions() __attribute__ ((constructor)); | ||
void setupActions() { | ||
actions[MAX_OUTPUTS] = {0xffff, 0}; | ||
for(uint8_t i=0; i<MAX_OUTPUTS; i++){ | ||
actions[i] = {0xffff, i}; | ||
} | ||
} | ||
|
||
constexpr uint8_t PRESCALAR = 8; | ||
//works for prescalars less than or equal to 16 | ||
constexpr uint8_t TICKS_PER_MS = (F_CPU / (PRESCALAR * 1e6L)); | ||
|
||
int16_t constexpr intervalFromMicros(uint32_t us){ | ||
return us * TICKS_PER_MS; | ||
} | ||
} | ||
|
||
namespace ServoGenerator{ | ||
void set(int channel, uint16_t us){ | ||
output[channel].highTime = intervalFromMicros(us); | ||
} | ||
|
||
void disable(int channel){ | ||
if(output[channel].enabled()){ | ||
output[channel].disable(); | ||
activeOutputs--; | ||
} | ||
} | ||
|
||
bool enable(int channel, int pin){ | ||
pinMode(pin, OUTPUT); | ||
output[channel].setPin(pin); | ||
if(!output[channel].enabled()){ | ||
activeOutputs++; | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
void setup(uint16_t refreshIntervalMicroseconds){ | ||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ | ||
// CTC, clear when TCNT == ICR, prescalar = 8 | ||
TCCRA = 0; | ||
TCCRB = _BV(WGM13) | _BV(WGM12) | _BV(CS11); | ||
|
||
// enable the ICF (TCNT==ICR) and OCRA (TCNT==OCRA) interrupt | ||
TIMSK |= _BV(ICIE1); | ||
TIMSK |= _BV(OCIE1A); | ||
|
||
ICR = intervalFromMicros(refreshIntervalMicroseconds); | ||
OCRA = 0xffff; | ||
|
||
// clear the timer count and pending interrupts | ||
TCNT = 0; | ||
TIFR |= _BV(OCF1A); | ||
TIFR |= _BV(ICF1); | ||
} | ||
} | ||
|
||
Servo::Servo(): channel(-1) { | ||
setup(20000); | ||
} | ||
Servo::Servo(uint16_t frameUs): channel(-1) { | ||
setup(frameUs); | ||
} | ||
bool Servo::attach(uint8_t arduinopin){ | ||
if(channel != -1) return false; //already attached | ||
|
||
//find open channel, try to attach | ||
uint8_t ch = -1; | ||
for(uint8_t i; i<MAX_OUTPUTS; i++){ | ||
if(!output[i].enabled()){ | ||
ch = i; | ||
break; | ||
} | ||
} | ||
if(ch == -1) return false; | ||
enable(ch, arduinopin); | ||
channel = ch; | ||
return true; | ||
} | ||
void Servo::detach(){ | ||
if(channel != -1) | ||
disable(channel); | ||
channel = -1; | ||
} | ||
void Servo::write(uint8_t sig){ | ||
if(channel != -1) | ||
set(channel, sig*10 + 600); // convert [0,180] to [600,2400] | ||
} | ||
void Servo::writeMicroseconds(uint16_t us){ | ||
if(channel != -1) | ||
set(channel, us); | ||
} | ||
bool Servo::attached() { | ||
return channel != -1; | ||
} | ||
} | ||
|
||
ISR(TIMER_ISR(TIMER_NUM, COMPA)){ | ||
do { | ||
// stops when it finds an output with OCRA=0xffff at | ||
// the end of actions[] | ||
output[next->channel].setLow(); | ||
next++; | ||
OCRA = next->time; | ||
} while (OCRA <= TCNT); | ||
} | ||
|
||
ISR(TIMER_ISR(TIMER_NUM, CAPT)){ | ||
//update highTime values from outputs | ||
for(uint8_t i=0; i<MAX_OUTPUTS; i++){ | ||
actions[i].time = output[actions[i].channel].highTime; | ||
} | ||
//sort actions by time | ||
for(uint8_t i=1; i<MAX_OUTPUTS; i++){ | ||
uint16_t time = actions[i].time; | ||
uint8_t j = i; | ||
while(j>0 && time < actions[j-1].time){ | ||
Action a = actions[j-1]; | ||
actions[j-1] = actions[j]; | ||
actions[j] = a; | ||
j--; | ||
} | ||
} | ||
//set all signals high | ||
//this preserves order because TCNT is monotonically increasing | ||
for(uint8_t i=0; i<activeOutputs; i++){ | ||
output[actions[i].channel].setHigh(); | ||
actions[i].time += TCNT; | ||
} | ||
|
||
next = actions; | ||
OCRA = next->time; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,186 +1,23 @@ | ||
#pragma once | ||
#include <util/atomic.h> | ||
|
||
#define TIMER_NUM 1 | ||
|
||
#define EXPCAT(A,B,C) EXPANDEDCONCATENATE(A,B,C) | ||
#define EXPANDEDCONCATENATE(A,B,C) A ## B ## C | ||
#define TIMER_ISR(N,V) TIMER_ISR_EXP(N,V) | ||
#define TIMER_ISR_EXP(N,V) TIMER ## N ## _ ## V ## _vect | ||
|
||
namespace { | ||
constexpr volatile uint8_t& TCCRA = EXPCAT(TCCR, TIMER_NUM,A); | ||
constexpr volatile uint8_t& TCCRB = EXPCAT(TCCR, TIMER_NUM,B); | ||
constexpr volatile uint8_t& TCCRC = EXPCAT(TCCR, TIMER_NUM,C); | ||
constexpr volatile uint8_t& TIFR = EXPCAT(TIFR, TIMER_NUM, ); | ||
constexpr volatile uint8_t& TIMSK = EXPCAT(TIMSK,TIMER_NUM, ); | ||
constexpr volatile uint16_t& ICR = EXPCAT(ICR, TIMER_NUM, ); | ||
constexpr volatile uint16_t& TCNT = EXPCAT(TCNT, TIMER_NUM, ); | ||
constexpr volatile uint16_t& OCRA = EXPCAT(OCR, TIMER_NUM,A); | ||
constexpr volatile uint16_t& OCRB = EXPCAT(OCR, TIMER_NUM,B); | ||
|
||
class Output{ | ||
public: | ||
Output(): highTime(0xffff), pinMask(0), pinReg(0) {} | ||
uint16_t highTime; | ||
uint8_t pinMask; | ||
volatile uint8_t* pinReg; | ||
bool enabled() const volatile { return pinMask != 0; } | ||
void disable() volatile { pinMask = 0; pinReg = 0; highTime = 0xffff; } | ||
void setPin(int p) volatile { | ||
pinMask = digitalPinToBitMask(p); | ||
pinReg = portOutputRegister(digitalPinToPort(p)); | ||
} | ||
void setHigh() const volatile { *pinReg |= pinMask; } | ||
void setLow() const volatile { *pinReg &= ~pinMask; } | ||
}; | ||
|
||
constexpr uint8_t MAX_OUTPUTS = 8; | ||
volatile Output output[MAX_OUTPUTS]; | ||
volatile uint8_t activeOutputs = 0; | ||
|
||
class Action{ //lawsuit | ||
public: | ||
uint16_t time; | ||
uint8_t channel; | ||
}; | ||
Action actions[MAX_OUTPUTS+1]; | ||
Action * next = actions; | ||
// constructor attribute makes this get run once before main | ||
void setupActions() __attribute__ ((constructor)); | ||
void setupActions() { | ||
actions[MAX_OUTPUTS] = {0xffff, 0}; | ||
for(uint8_t i=0; i<MAX_OUTPUTS; i++){ | ||
actions[i] = {0xffff, i}; | ||
} | ||
} | ||
|
||
constexpr uint8_t PRESCALAR = 8; | ||
//works for prescalars less than or equal to 16 | ||
constexpr uint8_t TICKS_PER_MS = (F_CPU / (PRESCALAR * 1e6L)); | ||
|
||
int16_t constexpr intervalFromMicros(uint32_t us){ | ||
return us * TICKS_PER_MS; | ||
} | ||
} | ||
#include "Arduino.h" | ||
#include <util/atomic.h> | ||
|
||
namespace ServoGenerator{ | ||
void set(int channel, uint16_t us){ | ||
output[channel].highTime = intervalFromMicros(us); | ||
} | ||
|
||
void disable(int channel){ | ||
if(output[channel].enabled()){ | ||
output[channel].disable(); | ||
activeOutputs--; | ||
} | ||
} | ||
|
||
bool enable(int channel, int pin){ | ||
pinMode(pin, OUTPUT); | ||
output[channel].setPin(pin); | ||
if(!output[channel].enabled()){ | ||
activeOutputs++; | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
void setup(uint16_t refreshIntervalMicroseconds){ | ||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ | ||
// CTC, clear when TCNT == ICR, prescalar = 8 | ||
TCCRA = 0; | ||
TCCRB = _BV(WGM13) | _BV(WGM12) | _BV(CS11); | ||
|
||
// enable the ICF (TCNT==ICR) and OCRA (TCNT==OCRA) interrupt | ||
TIMSK |= _BV(ICIE1); | ||
TIMSK |= _BV(OCIE1A); | ||
|
||
ICR = intervalFromMicros(refreshIntervalMicroseconds); | ||
OCRA = 0xffff; | ||
|
||
// clear the timer count and pending interrupts | ||
TCNT = 0; | ||
TIFR |= _BV(OCF1A); | ||
TIFR |= _BV(ICF1); | ||
} | ||
} | ||
void set(int channel, uint16_t us); | ||
void disable(int channel); | ||
bool enable(int channel, int pin); | ||
void setup(uint16_t refreshIntervalMicroseconds); | ||
|
||
class Servo{ | ||
uint8_t channel; | ||
Servo(): channel(-1) { | ||
setup(20000); | ||
} | ||
Servo(uint16_t frameUs): channel(-1) { | ||
setup(frameUs); | ||
} | ||
bool attach(uint8_t arduinopin){ | ||
//find open channel, try to attach | ||
uint8_t ch = -1; | ||
for(uint8_t i; i<MAX_OUTPUTS; i++){ | ||
if(!output[i].enabled()){ | ||
ch = i; | ||
break; | ||
} | ||
} | ||
if(ch == -1) return false; | ||
enable(ch, arduinopin); | ||
channel = ch; | ||
return true; | ||
} | ||
void detach(){ | ||
if(channel != -1) | ||
disable(channel); | ||
channel = -1; | ||
} | ||
void write(uint8_t sig){ | ||
if(channel != -1) | ||
set(channel, sig*10 + 600); // convert [0,180] to [600,2400] | ||
} | ||
void writeMicroseconds(uint16_t us){ | ||
if(channel != -1) | ||
set(channel, us); | ||
} | ||
bool attached() { | ||
return channel != -1; | ||
} | ||
public: | ||
Servo(); | ||
Servo(uint16_t frameUs); | ||
bool attach(uint8_t arduinopin); | ||
void detach(); | ||
void write(uint8_t sig); | ||
void writeMicroseconds(uint16_t us); | ||
bool attached(); | ||
}; | ||
} | ||
|
||
ISR(TIMER_ISR(TIMER_NUM, COMPA)){ | ||
do { | ||
// stops when it finds an output with OCRA=0xffff at | ||
// the end of actions[] | ||
output[next->channel].setLow(); | ||
next++; | ||
OCRA = next->time; | ||
} while (OCRA <= TCNT); | ||
} | ||
|
||
ISR(TIMER_ISR(TIMER_NUM, CAPT)){ | ||
//update highTime values from outputs | ||
for(uint8_t i=0; i<MAX_OUTPUTS; i++){ | ||
actions[i].time = output[actions[i].channel].highTime; | ||
} | ||
//sort actions by time | ||
for(uint8_t i=1; i<MAX_OUTPUTS; i++){ | ||
uint16_t time = actions[i].time; | ||
uint8_t j = i; | ||
while(j>0 && time < actions[j-1].time){ | ||
Action a = actions[j-1]; | ||
actions[j-1] = actions[j]; | ||
actions[j] = a; | ||
j--; | ||
} | ||
} | ||
//set all signals high | ||
//this preserves order because TCNT is monotonically increasing | ||
for(uint8_t i=0; i<activeOutputs; i++){ | ||
output[actions[i].channel].setHigh(); | ||
actions[i].time += TCNT; | ||
} | ||
|
||
next = actions; | ||
OCRA = next->time; | ||
} | ||
|