299 lines
7.1 KiB
C++
299 lines
7.1 KiB
C++
//#define DEBUG
|
|
|
|
#include <Arduino.h>
|
|
#include <avr/sleep.h>
|
|
|
|
#ifdef DEBUG
|
|
#include <SoftwareSerial.h>
|
|
#endif
|
|
|
|
|
|
/*
|
|
|
|
Wiring:
|
|
|
|
+---u---+
|
|
10k R to +5V -| 1 8 |- +5V
|
|
"Speed" potentiometer (RV3) -| 2 7 |- "Off" potentiometer (RV1)
|
|
"On" potentiometer (RV2) -| 3 6 |- On/Off switch (active low)
|
|
GND -| 4 5 |- Servo PWM signal
|
|
+-------+
|
|
*/
|
|
|
|
static const uint8_t PinServo = 0;
|
|
static const uint8_t PinSwitch = 1;
|
|
static const uint8_t PinOffPosition = A1;
|
|
static const uint8_t PinOnPosition = A2;
|
|
static const uint8_t PinSpeed = A3;
|
|
|
|
static const float TransitionTimeMin = 1;
|
|
static const float TransitionTimeMax = 5000;
|
|
static const uint16_t SleepTime = 10000;
|
|
|
|
static const uint16_t ServoMinPulse = 544;
|
|
static const uint16_t ServoMaxPulse = 2400;
|
|
|
|
|
|
static const uint16_t ServoPulseInterval = 20;
|
|
static const uint16_t ServoStabilizeDelay = 100;
|
|
|
|
|
|
int offPosition; // 0 - 180 degrees, read from "Off" potentiometer
|
|
int onPosition; // 0 - 180 degrees, read from "On" potentiometer
|
|
int speed; // 0 - 1023, read from "Speed" potentiometer
|
|
float degreesPerMilli; // Degrees per millisecond
|
|
|
|
|
|
bool lastOn; // If the switch was on when it was last flipped
|
|
int startPosition; // The position the servo was in when the switch was flipped
|
|
unsigned long startTime; // The last time the switch was flipped
|
|
unsigned long idleTime; // The last time anything happened
|
|
|
|
bool servoEnabled; // Whether or not pulses should be sent to the servo
|
|
int servoPosition; // The desired position of the servo
|
|
unsigned long servoPulseTime; // The last time the servo pulse was sent
|
|
|
|
|
|
#ifdef DEBUG
|
|
SoftwareSerial debug(-1,3);
|
|
#endif
|
|
|
|
|
|
// Forward declarations
|
|
void pulseServo(unsigned long currentTime);
|
|
bool updateSettings();
|
|
inline int getNewPosition(unsigned long currentTime, int targetPosition) __attribute__((always_inline));
|
|
void sleepUntilSwitch();
|
|
|
|
float floatMap(float x, float in_min, float in_max, float out_min, float out_max);
|
|
|
|
|
|
void setup()
|
|
{
|
|
#ifdef DEBUG
|
|
debug.begin(9600);
|
|
debug.println("RailroadSwitch starting");
|
|
#endif
|
|
|
|
pinMode(PinOffPosition, INPUT);
|
|
pinMode(PinOnPosition, INPUT);
|
|
pinMode(PinSwitch, INPUT_PULLUP);
|
|
pinMode(PinServo, OUTPUT);
|
|
|
|
offPosition = -1;
|
|
onPosition = -1;
|
|
speed = -1;
|
|
|
|
updateSettings();
|
|
|
|
lastOn = false;
|
|
startPosition = offPosition;
|
|
servoPosition = offPosition;
|
|
|
|
servoPulseTime = 0;
|
|
servoEnabled = true;
|
|
|
|
#ifdef DEBUG
|
|
debug.println("Started");
|
|
#endif
|
|
}
|
|
|
|
|
|
void loop()
|
|
{
|
|
unsigned long currentTime = millis();
|
|
|
|
if (servoEnabled)
|
|
pulseServo(currentTime);
|
|
|
|
bool isOn = (digitalRead(PinSwitch) == LOW);
|
|
|
|
// If the switch changed, start from the last known position
|
|
if (isOn != lastOn)
|
|
{
|
|
#ifdef DEBUG
|
|
debug.println("State changed");
|
|
#endif
|
|
|
|
lastOn = isOn;
|
|
startPosition = servoPosition;
|
|
startTime = currentTime;
|
|
idleTime = startTime;
|
|
}
|
|
|
|
int targetPosition = isOn ? onPosition : offPosition;
|
|
if (servoPosition != targetPosition)
|
|
{
|
|
servoPosition = getNewPosition(currentTime, targetPosition);
|
|
servoEnabled = true;
|
|
idleTime = currentTime;
|
|
}
|
|
else if (servoEnabled)
|
|
{
|
|
// Ensure we pulsed the last position
|
|
currentTime = millis();
|
|
|
|
delay(ServoPulseInterval);
|
|
pulseServo(currentTime);
|
|
delay(ServoPulseInterval);
|
|
|
|
// Stop sending pulses when we reach the destination, to prevent
|
|
// power consumption and possible buzzing
|
|
servoEnabled = false;
|
|
idleTime = currentTime;
|
|
}
|
|
|
|
|
|
// Don't update the settings while the servo is moving, as the
|
|
// voltage drop can cause fluctuating results
|
|
if (!servoEnabled && currentTime - idleTime >= ServoStabilizeDelay)
|
|
{
|
|
if (updateSettings())
|
|
{
|
|
// Immediately go to the new position
|
|
currentTime = millis();
|
|
servoPosition = isOn ? onPosition : offPosition;
|
|
|
|
delay(ServoPulseInterval);
|
|
pulseServo(currentTime);
|
|
delay(ServoPulseInterval);
|
|
|
|
idleTime = currentTime;
|
|
}
|
|
}
|
|
|
|
// Stay awake for a bit to allow changes to the on and off positions
|
|
// to take effect immediately
|
|
if (currentTime - idleTime >= SleepTime)
|
|
sleepUntilSwitch();
|
|
}
|
|
|
|
|
|
void pulseServo(unsigned long currentTime)
|
|
{
|
|
if (currentTime - servoPulseTime < ServoPulseInterval)
|
|
return;
|
|
|
|
int pulseWidth = map(servoPosition, 0, 180, ServoMinPulse, ServoMaxPulse);
|
|
|
|
// This will mess with the millis() result, but for what we're doing
|
|
// that is acceptable to get a stable pulse.
|
|
noInterrupts();
|
|
|
|
digitalWrite(PinServo, HIGH);
|
|
delayMicroseconds(pulseWidth);
|
|
digitalWrite(PinServo, LOW);
|
|
|
|
interrupts();
|
|
|
|
|
|
servoPulseTime = currentTime;
|
|
}
|
|
|
|
|
|
bool updateSettings()
|
|
{
|
|
int newOffPosition = map(analogRead(PinOffPosition), 0, 1023, 0, 180);
|
|
int newOnPosition = map(analogRead(PinOnPosition), 0, 1023, 0, 180);
|
|
int newSpeed = analogRead(PinSpeed);
|
|
|
|
if (newOffPosition != offPosition || newOnPosition != onPosition || newSpeed != speed)
|
|
{
|
|
offPosition = newOffPosition;
|
|
onPosition = newOnPosition;
|
|
speed = newSpeed;
|
|
|
|
float transitionTime = floatMap(speed, 0, 1023, TransitionTimeMin, TransitionTimeMax);
|
|
degreesPerMilli = abs((float)(onPosition - offPosition)) / transitionTime;
|
|
|
|
#ifdef DEBUG
|
|
debug.println("Positions changed");
|
|
debug.print(" Off position : "); debug.println(offPosition);
|
|
debug.print(" On position : "); debug.println(onPosition);
|
|
debug.print(" Transition time: "); debug.println(transitionTime);
|
|
debug.print(" Degrees / milli: "); debug.println(degreesPerMilli);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
int getNewPosition(unsigned long currentTime, int targetPosition)
|
|
{
|
|
int newPosition;
|
|
unsigned long timePassed = currentTime - startTime;
|
|
|
|
if (targetPosition > startPosition)
|
|
{
|
|
newPosition = startPosition + ((float)timePassed * degreesPerMilli);
|
|
if (newPosition > targetPosition)
|
|
newPosition = targetPosition;
|
|
}
|
|
else
|
|
{
|
|
newPosition = startPosition - ((float)timePassed * degreesPerMilli);
|
|
if (newPosition < targetPosition)
|
|
newPosition = targetPosition;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
debug.println("getNewPosition");
|
|
debug.print(" Time passed : "); debug.println(timePassed);
|
|
debug.print(" Start position : "); debug.println(startPosition);
|
|
debug.print(" Target position: "); debug.println(targetPosition);
|
|
debug.print(" New position : "); debug.println(newPosition);
|
|
#endif
|
|
|
|
return newPosition;
|
|
}
|
|
|
|
|
|
void sleepStart()
|
|
{
|
|
// Turn ADC off
|
|
ADCSRA &= ~_BV(ADEN);
|
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
|
|
|
// Set sleep bit and halt the CPU
|
|
sleep_enable();
|
|
sei();
|
|
sleep_cpu();
|
|
|
|
// ...goooood morning!
|
|
cli();
|
|
}
|
|
|
|
|
|
void sleepEnd()
|
|
{
|
|
sleep_disable();
|
|
ADCSRA |= _BV(ADEN);
|
|
|
|
sei();
|
|
}
|
|
|
|
|
|
void sleepUntilSwitch()
|
|
{
|
|
while (1)
|
|
{
|
|
// Enable pin change interrupts
|
|
GIMSK |= _BV(PCIE);
|
|
|
|
// Set up pin change mask
|
|
PCMSK = digitalPinToBitMask(PinSwitch);
|
|
|
|
sleepStart();
|
|
PCMSK = 0;
|
|
sleepEnd();
|
|
}
|
|
}
|
|
|
|
|
|
float floatMap(float x, float in_min, float in_max, float out_min, float out_max)
|
|
{
|
|
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
|
} |