Skip to content

Instantly share code, notes, and snippets.

@WasabiFan
Last active May 16, 2016 17:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save WasabiFan/e3efe2505de1bf5c64ae to your computer and use it in GitHub Desktop.
Save WasabiFan/e3efe2505de1bf5c64ae to your computer and use it in GitHub Desktop.
Control logic for aerial release wireless trigger.
#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
#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
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);
}
#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
/*
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;
}
/*
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