Last active
May 16, 2016 17:12
-
-
Save WasabiFan/e3efe2505de1bf5c64ae to your computer and use it in GitHub Desktop.
Control logic for aerial release wireless trigger.
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
#if ROLE == ROLE_SKY | |
// returns smoothed uncalibrated altitude | |
float getAltitude() { | |
if (!bmpConnected) { | |
return -1; | |
} | |
sensors_event_t event; | |
bmp.getEvent(&event); | |
float temperature; | |
bmp.getTemperature(&temperature); | |
float seaLevelPressure = SENSORS_PRESSURE_SEALEVELHPA; | |
float currentAlt = bmp.pressureToAltitude(seaLevelPressure, event.pressure, temperature) * METERS_TO_FEET_RATIO; | |
return smoothAltitude(currentAlt); | |
} | |
// outputs a running average of the last n (ALTITUDE_SMOOTHING) altitudes | |
float smoothAltitude(float newAlt) { | |
// cycle every time this is called | |
lastAltitudes[lastAltitudeIndex] = newAlt; | |
lastAltitudeIndex++; | |
if (lastAltitudeIndex >= ALTITUDE_SMOOTHING) { | |
lastAltitudeIndex = 0; | |
} | |
// get average | |
float sum = 0; | |
for (int i = 0; i < ALTITUDE_SMOOTHING; i++) { | |
sum += lastAltitudes[i]; | |
} | |
return sum / ALTITUDE_SMOOTHING; | |
} | |
// this is to check and see if our altimeter is just returning 0s and reset the arduino | |
void checkAltimeterDataIntegrity() { | |
// only check when our altitude is at the end in order to avoid the initial {0,0,0...} | |
if (lastAltitudeIndex != ALTITUDE_SMOOTHING) { | |
return; | |
} | |
bool allZeros = true; | |
for (int i = 0; i < ALTITUDE_SMOOTHING; i++) { | |
if (lastAltitudes[i] != 0.0f) { | |
allZeros = false; | |
} | |
} | |
if (allZeros) { | |
Serial.println("RESETTING ARDUINO"); | |
digitalWrite(resetPin, LOW); | |
} | |
} | |
#endif |
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
#pragma once | |
#define METERS_TO_FEET_RATIO (1250./381.) | |
#define ROLE_GROUND 0 | |
#define ROLE_SKY 1 | |
// SET THE TARGET ROLE HERE ------------------------------- | |
#define ROLE ROLE_SKY | |
#if ROLE != ROLE_SKY && ROLE != ROLE_GROUND | |
#error "ROLE must be ROLE_SKY or ROLE_GROUND" | |
#endif | |
// time in between "heartbeat" signals - determine connection health and recieve back altitude | |
#define HEARTBEAT_INTERVAL_MILLIS 300 | |
// The threshold when the indicator LED changes | |
#define ALTITUDE_TARGET_THRESH_FEET 100 | |
// Angle when servo is in "trigger" position | |
#define TRIGGER_SERVO_ANGLE 90 | |
// Angle when servo is in reset/ready position | |
#define RESET_SERVO_ANGLE 0 | |
// Number of altitude samples to average | |
#define ALTITUDE_SMOOTHING 10 | |
// Time (in ms) that the indicator LED will flash for when a command is successfully registered | |
#define COMMAND_SIG_DURATION 100 |
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
void initializeRadio() { | |
printf_begin(); | |
radio.begin(); | |
radio.enableAckPayload(); | |
radio.enableDynamicPayloads(); | |
radio.setPALevel(RF24_PA_MAX); | |
// open communication lines based on role | |
#if ROLE == ROLE_GROUND | |
radio.openWritingPipe(pipeNames[0]); | |
radio.openReadingPipe(primaryPipeIndex, pipeNames[1]); | |
#elif ROLE == ROLE_SKY | |
radio.openWritingPipe(pipeNames[1]); | |
radio.openReadingPipe(primaryPipeIndex, pipeNames[0]); | |
#endif | |
radio.startListening(); | |
radio.printDetails(); | |
radio.writeAckPayload(primaryPipeIndex, &ackVal, 1); | |
} | |
#if ROLE == ROLE_SKY | |
void handleAck() { | |
Serial.println(F("Heartbeat byte received")); | |
// Get the altitude value | |
float altitude = getAltitude(); | |
// Construct the packet. Has a one-byte header. | |
byte packetData[sizeof(float) + 1] = { heightInitialVal }; | |
// Get a pointer to the last 4 bytes of the packet buffer | |
// and set it to the altitude value | |
float* packetFloatPortion = (float*)&packetData[1]; | |
*packetFloatPortion = altitude; | |
validateAck(sendData(&packetData, sizeof(packetData))); | |
} | |
#endif | |
bool validateAck(byte ackResponse) { | |
bool isCorrectResponse = (ackResponse == ackVal); | |
if (isCorrectResponse) { | |
Serial.println(F("Comms healthy")); | |
} | |
else { | |
Serial.print(F("Bad ack received: ")); | |
Serial.println(ackResponse); | |
Serial.println(F("This probably means that the connection is unstable.")); | |
} | |
#if ROLE == ROLE_GROUND | |
isCommHealthy = isCorrectResponse; | |
// We have validated that data was successfully sent; we can consider this a heartbeat. | |
lastHeartbeat = millis(); | |
#endif | |
return isCorrectResponse; | |
} | |
// receives numBytes bytes from the radio into recvBuf | |
bool receiveData(void* recvBuf, uint8_t numBytes) { | |
if (radio.available()) { | |
radio.read(recvBuf, numBytes); | |
// Prep an ack for the next time we receive data | |
radio.writeAckPayload(primaryPipeIndex, &ackVal, 1); | |
return true; | |
} | |
else { | |
return false; | |
} | |
} | |
// receives a single byte from the radio | |
byte receiveByte() { | |
byte val = 0; | |
receiveData(&val, 1); | |
return val; | |
} | |
// Sends arbitrary data of length numBytes from the given address | |
byte sendData(void* val, int numBytes) { | |
byte recvByte; | |
if (radio.available()) { | |
Serial.println(F("Data available before request was sent! This probably means something was not read correctly.")); | |
} | |
// Print bytes that are going to be sent | |
Serial.print(F("Sending data {")); | |
for (int i = 0; i < numBytes; i++) | |
{ | |
Serial.print(((byte*)val)[i]); | |
if (i < numBytes - 1) { | |
Serial.print(F(", ")); | |
} | |
} | |
Serial.println(F("}")); | |
// Stop listening temporarily so we can write | |
radio.stopListening(); | |
bool writeSuccess = radio.write(val, numBytes); | |
radio.startListening(); | |
if (writeSuccess) { | |
if (!radio.available()) { | |
Serial.println(F("Empty receive buffer! Was expecting an ack.")); | |
return 0; | |
} | |
else { | |
radio.read(&recvByte, 1); | |
radio.writeAckPayload(primaryPipeIndex, &ackVal, 1); | |
Serial.println(F("Received ack")); | |
} | |
} | |
else { | |
Serial.println(F("Unknown radio write failure")); | |
return 0; | |
} | |
return recvByte; | |
} | |
// send a single byte on the radio | |
byte sendByte(byte val) | |
{ | |
return sendData(&val, 1); | |
} |
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
#include "Config.h" | |
// Standard libraries | |
#include <Arduino.h> | |
#include <printf.h> | |
// Radio comms | |
#include <RF24_config.h> | |
#include <nRF24L01.h> | |
#include <RF24.h> | |
#if ROLE == ROLE_GROUND | |
// Smart switch utils | |
#include "~Switch.h" | |
#elif ROLE == ROLE_SKY | |
// Barometric pressure sensor | |
#include <Wire.h> | |
#include <Adafruit_Sensor.h> | |
#include <Adafruit_BMP085_U.h> | |
// Servo controller | |
#include <Servo.h> | |
#endif | |
RF24 radio(7, 8); | |
// radio comm lines | |
byte pipeNames[][6] = { *(byte*)"1Node", *(byte*)"2Node" }; | |
byte primaryPipeIndex = 1; | |
// radio command bytes | |
const byte triggerVal = 0b11111111; // 255 decimal | |
const byte resetVal = 0b10101010; // 170 decimal | |
const byte heightInitialVal = 0b01010101; // 85 decimal | |
const byte ackVal = 0b11110000; // 240 decimal | |
#if ROLE == ROLE_GROUND | |
// can be digital pins | |
uint8_t triggerButtonPin = A0; | |
uint8_t resetButtonPin = A1; | |
uint8_t calibButtonPin = A2; | |
Switch triggerButton = Switch(triggerButtonPin); | |
Switch resetButton = Switch(resetButtonPin); | |
Switch calibButton = Switch(calibButtonPin); | |
// can be digital pins | |
uint8_t indicatorLEDGreenPin = 5; | |
uint8_t indicatorLEDBluePin = 3; | |
uint8_t indicatorLEDRedPin = 6; | |
unsigned long lastHeartbeat = 0; | |
bool isCommHealthy = false; | |
unsigned long lastCommandTime = 0; | |
float rawCalibrationAltitude = 0; | |
bool isAltitudeCalibrated = false; | |
float lastAltitude = NAN; // NAN indicates that altitude is not stored yet | |
float rawLastAltitude = NAN; | |
#elif ROLE == ROLE_SKY | |
// The code is in place to automatically reset the arduino if problems arise. | |
// It hasn't been proven working, so we aren't wiring it up. | |
uint8_t resetPin = 9; | |
// Should be a PWM pin | |
uint8_t servoPin = 5; | |
Servo actuationServo; | |
float lastAltitudes[ALTITUDE_SMOOTHING]; | |
int lastAltitudeIndex; | |
// Parameter is sensor ID | |
Adafruit_BMP085_Unified bmp = Adafruit_BMP085_Unified(10085); | |
bool bmpConnected = false; | |
// We use a digital output pin to power the altimiter so that we | |
// don't need to splice wires. | |
// TODO: power this from an actual power pin | |
uint8_t bmpPowerPin = 4; | |
#endif | |
void setup() | |
{ | |
Serial.begin(115200); | |
initializeRadio(); | |
#if ROLE == ROLE_GROUND | |
pinMode(indicatorLEDGreenPin, OUTPUT); | |
pinMode(indicatorLEDBluePin, OUTPUT); | |
pinMode(indicatorLEDRedPin, OUTPUT); | |
#elif ROLE == ROLE_SKY | |
actuationServo.attach(servoPin); | |
actuationServo.write(RESET_SERVO_ANGLE); | |
pinMode(resetPin, OUTPUT); | |
digitalWrite(resetPin, HIGH); | |
// we don't have enough power pins, so we just power it from an output pin | |
pinMode(bmpPowerPin, OUTPUT); | |
digitalWrite(bmpPowerPin, HIGH); | |
// initialize altimeter | |
Serial.print(F("BMP085 connecting... ")); | |
if (bmp.begin()) { | |
bmpConnected = true; | |
Serial.println(F("Succeeded")); | |
} | |
else { | |
Serial.println(F("Failed")); | |
} | |
#endif | |
} | |
#if ROLE == ROLE_GROUND | |
void loop() { | |
triggerButton.poll(); | |
resetButton.poll(); | |
calibButton.poll(); | |
// Check for and handle incoming bytes first to make sure | |
// that buffers are clear. If they aren't, acks may not work correctly. | |
if (radio.available()) { | |
Serial.println(F("Data available")); | |
byte recvByte = receiveByte(); | |
if (recvByte == heightInitialVal) { | |
// TODO: Figure out why the first byte isn't already gone | |
// Declare a buffer to store the packet and read the data into it | |
byte buffer[sizeof(float) + 1]; | |
receiveData(&buffer, sizeof(buffer)); | |
// Dereference the data portion of the packet as a float | |
rawLastAltitude = *(float*)(&(buffer[1])); | |
if (isAltitudeCalibrated) { | |
lastAltitude = rawLastAltitude - rawCalibrationAltitude; | |
} | |
else { | |
lastAltitude = rawLastAltitude; | |
} | |
Serial.println("Got altitude " + String(lastAltitude)); | |
} | |
else { | |
Serial.println(F("Received unknown header data!")); | |
} | |
} | |
// Require a long press to activate | |
if (triggerButton.longPress()) { | |
Serial.println(F("Sending trigger byte")); | |
if (validateAck(sendByte(triggerVal))) | |
lastCommandTime = millis(); | |
} | |
else if (resetButton.longPress()) { | |
Serial.println(F("Sending reset byte")); | |
if (validateAck(sendByte(resetVal))) | |
lastCommandTime = millis(); | |
} | |
else if (calibButton.pushed()) { | |
if (isnan(rawLastAltitude)) { | |
// if we haven't received an altitude yet, we can't calibrate | |
Serial.println(F("Tried to calibrate, but haven't received altitude packet yet.")); | |
} | |
else { | |
rawCalibrationAltitude = rawLastAltitude; | |
Serial.println("New calibrated base altitude: " + String(rawCalibrationAltitude)); | |
isAltitudeCalibrated = true; | |
lastCommandTime = millis(); | |
} | |
} | |
else if (millis() - lastHeartbeat >= HEARTBEAT_INTERVAL_MILLIS) { | |
// Re-use ack byte for heartbeat | |
Serial.println(F("Sending ping")); | |
validateAck(sendByte(ackVal)); | |
} | |
//Serial.println("Current calibrated altitude: " + String(lastAltitude)); | |
bool altitudeAboveThreshold = isAltitudeCalibrated && !isnan(lastAltitude) && lastAltitude >= ALTITUDE_TARGET_THRESH_FEET; | |
if (millis() - lastCommandTime <= COMMAND_SIG_DURATION) { | |
setStatusLED(255, 255, 0); // Yellow | |
} | |
else if (isCommHealthy && altitudeAboveThreshold) { | |
setStatusLED(0, 255, 0); // Green | |
} | |
else if (isCommHealthy) { | |
setStatusLED(0, 0, 255); // Blue | |
} | |
else { | |
setStatusLED(255, 0, 0); // Red | |
} | |
} | |
#elif ROLE == ROLE_SKY | |
void loop() { | |
if (radio.available()) { | |
byte recvByte = receiveByte(); | |
switch (recvByte) { | |
case triggerVal: | |
Serial.println(F("Trigger byte received")); | |
actuationServo.write(TRIGGER_SERVO_ANGLE); | |
break; | |
case resetVal: | |
Serial.println(F("Reset byte received")); | |
actuationServo.write(RESET_SERVO_ANGLE); | |
break; | |
case ackVal: | |
handleAck(); | |
break; | |
default: | |
Serial.print(F("Received unknown signal: ")); | |
Serial.println((int)recvByte); | |
break; | |
} | |
checkAltimeterDataIntegrity(); | |
} | |
} | |
#endif | |
#if ROLE == ROLE_GROUND | |
void setStatusLED(int r, int g, int b) { | |
analogWrite(indicatorLEDRedPin, r); | |
analogWrite(indicatorLEDGreenPin, g); | |
analogWrite(indicatorLEDBluePin, b); | |
} | |
#endif | |
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
/* | |
Switch.cpp | |
Copyright (C) 2012 Albert van Dalen http://www.avdweb.nl | |
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License | |
as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. | |
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty | |
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at http://www.gnu.org/licenses . | |
Version 20-4-2013 | |
_debouncePeriod=50 | |
Version 22-5-2013 | |
Added longPress, doubleClick | |
Version 1-12-2015 | |
added process(input) | |
Version 15-1-2016 | |
added deglitching | |
..........................................DEGLITCHING.............................. | |
________________ _ | |
on | | | | _ | |
| | | | | | | |
| |_| |___| |__ | |
analog off_____|_____________________________|____________________________ | |
________________ _ _ | |
input _____| |_| |___| |_______________________________ | |
poll ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ | |
equal 0 1 1 0 1 1 1 1 1 1 1 1 0 0 0 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 | |
deglitchPeriod <--------><-- <-- <- <--------><--------><-------- | |
___________________________ | |
deglitched _________________| |__________________ | |
deglitchTime ^ ^ ^ ^ ^ ^ ^ | |
..........................................DEBOUNCING............................. | |
debouncePeriod <--------------------------------> | |
_________________________________ | |
debounced _________________| |____________ | |
_ _ | |
_switched _________________| |_______________________________| |__________ | |
switchedTime ^ ^ | |
********************************************************************************** | |
........................................DOUBLE CLICK.............................. | |
__________ ______ | |
debounced ________| |_______| |_____________________________ | |
poll ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ | |
_ _ | |
pushed _________| |________________| |__________________________________ | |
pushedTime ^ ^ | |
doubleClickPeriod <-------------------------------------> | |
_ | |
_doubleClick ___________________________| |__________________________________ | |
........................................LONG PRESS................................ | |
___________________________ | |
debounced ________| |___________________________ | |
poll ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ | |
longPressPeriod <---------------> | |
_ _ | |
_switched _________| |_________________________| |________________________ | |
__________ | |
longPressDisable ___________________________| |_________________________ | |
_ | |
_longPress ___________________________| |__________________________________ | |
*/ | |
#include "Arduino.h" | |
#include "~Switch.h" | |
Switch::Switch(byte _pin, byte PinMode, bool polarity, int debouncePeriod, int longPressPeriod, int doubleClickPeriod, int deglitchPeriod) : | |
pin(_pin), polarity(polarity), deglitchPeriod(deglitchPeriod), debouncePeriod(debouncePeriod), longPressPeriod(longPressPeriod), doubleClickPeriod(doubleClickPeriod) | |
{ | |
pinMode(pin, PinMode); | |
switchedTime = millis(); | |
debounced = digitalRead(pin); | |
} | |
bool Switch::poll() | |
{ | |
input = digitalRead(pin); | |
return process(); | |
} | |
bool Switch::process() | |
{ | |
deglitch(); | |
debounce(); | |
calcDoubleClick(); | |
calcLongPress(); | |
return _switched; | |
} | |
void inline Switch::deglitch() | |
{ | |
ms = millis(); | |
if (input == lastInput) equal = 1; | |
else | |
{ | |
equal = 0; | |
deglitchTime = ms; | |
} | |
if (equal & ((ms - deglitchTime) > deglitchPeriod)) // max 50ms, disable deglitch: 0ms | |
{ | |
deglitched = input; | |
deglitchTime = ms; | |
} | |
lastInput = input; | |
} | |
void inline Switch::debounce() | |
{ | |
ms = millis(); | |
_switched = 0; | |
if ((deglitched != debounced) & ((ms - switchedTime) >= debouncePeriod)) | |
{ | |
switchedTime = ms; | |
debounced = deglitched; | |
_switched = 1; | |
longPressDisable = false; | |
} | |
} | |
void inline Switch::calcDoubleClick() | |
{ | |
_doubleClick = false; | |
if (pushed()) | |
{ | |
_doubleClick = (ms - pushedTime) < doubleClickPeriod; // pushedTime of previous push | |
pushedTime = ms; | |
} | |
} | |
void inline Switch::calcLongPress() | |
{ | |
_longPress = false; | |
if (!longPressDisable) | |
{ | |
_longPress = on() && ((ms - pushedTime) > longPressPeriod); // true just one time between polls | |
longPressDisable = _longPress; // will be reset at next switch | |
} | |
} | |
bool Switch::switched() | |
{ | |
return _switched; | |
} | |
bool Switch::on() | |
{ | |
return !(debounced^polarity); | |
} | |
bool Switch::pushed() | |
{ | |
return _switched && !(debounced^polarity); | |
} | |
bool Switch::released() | |
{ | |
return _switched && (debounced^polarity); | |
} | |
bool Switch::longPress() | |
{ | |
return _longPress; | |
} | |
bool Switch::doubleClick() | |
{ | |
return _doubleClick; | |
} |
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
/* | |
Switch.h | |
Copyright (C) 2012 Albert van Dalen http://www.avdweb.nl | |
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License | |
as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. | |
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty | |
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at http://www.gnu.org/licenses . | |
*/ | |
#ifndef SWITCH_H | |
#define SWITCH_H | |
class Switch | |
{ | |
public: | |
Switch(byte _pin, byte PinMode = INPUT_PULLUP, bool polarity = LOW, int debouncePeriod = 50, int longPressPeriod = 300, int doubleClickPeriod = 250, int deglitchPeriod = 10); | |
bool poll(); // Returns 1 if switched | |
bool switched(); // will be refreshed by poll() | |
bool on(); | |
bool pushed(); // will be refreshed by poll() | |
bool released(); // will be refreshed by poll() | |
bool longPress(); // will be refreshed by poll() | |
bool doubleClick(); // will be refreshed by poll() | |
protected: | |
bool process(); // not inline, used in child class | |
void inline deglitch(); | |
void inline debounce(); | |
void inline calcDoubleClick(); | |
void inline calcLongPress(); | |
unsigned long deglitchTime, switchedTime, pushedTime, ms; | |
const byte pin; | |
const int deglitchPeriod, debouncePeriod, longPressPeriod, doubleClickPeriod; | |
const bool polarity; | |
bool input, lastInput, equal, deglitched, debounced, _switched, _longPress, longPressDisable, _doubleClick; | |
}; | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment