Skip to content

Instantly share code, notes, and snippets.

@spirilis
Last active August 29, 2015 14:06
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 spirilis/1eafb083808e61ebbba4 to your computer and use it in GitHub Desktop.
Save spirilis/1eafb083808e61ebbba4 to your computer and use it in GitHub Desktop.
TI MSP430 Wolverine + AS3935 Franklin Lightning Sensor - Energia logger sketch
/* Wolverine Lightning Sensor Logger
*
* A sensor logging application for the TI MSP430 Wolverine which utilizes
* the Austria Microsystems AS3935 Franklin Lightning Sensor to sense lightning
* strikes and log them, timestamped, into the Wolverine's FRAM.
*/
#include <SPI.h>
#include <Franklin.h>
#include <RTC_B.h>
#include <string.h>
// Lightning Event backing store
#define MAXIMUM_EVENT_COUNT 3276
// Tunables to adjust how often it may increase noise floor, and how long it should
// wait with no reports of noise until it may lower the noise floor.
#define HIGHNOISE_DELAY_PERIOD 2000
#define LOWNOISE_DELAY_PERIOD 8000
typedef struct {
int16_t strikeDistance;
uint8_t timestamp[8];
} LightningEvent;
__attribute__((section(".text")))
uint16_t event_count = 0;
__attribute__((section(".text")))
LightningEvent events[MAXIMUM_EVENT_COUNT];
//
Franklin lsensor(2, 5, 0x732); // AS3935 over SPI, CS=2, IRQ=5
boolean cliAvailable = false; // Indicates CLI data is waiting to be processed
volatile boolean enableSerialInput = true;
volatile boolean flipflop_IndoorOutdoor = false;
void setup()
{
Serial.begin(115200);
rtc.begin();
if (!rtc.restore()) {
rtc.begin(MONDAY, 9, 1, 2014, 7, 42, 00);
rtc.save();
}
Serial.println("BEGIN");
// Initialize AS3935 Franklin Lightning Sensor
SPI.begin();
SPI.setClockDivider(F_CPU / 2000000);
SPI.setDataMode(SPI_MODE1);
SPI.setBitOrder(MSBFIRST);
lsensor.begin();
if (lsensor.getState() == FRANKLIN_STATE_UNKNOWN) {
Serial.println("ERROR: AS3935 Franklin Lightning Sensor not found.");
Serial.flush();
suspend();
}
Serial.print("AS3935 sensor found; tuning parameters: ");
lsensor.setSignalThreshold(3); Serial.print("SignalThreshold=3 ");
lsensor.setIndoors(true); Serial.print("AFEGain=Indoors ");
Serial.print("NoiseFloor="); Serial.print(lsensor.getNoiseFloor()); Serial.print("uVrms ");
Serial.println();
// Save RTC info to FRAM once a second
rtc.attachPeriodicInterrupt(1, RTCTick);
rtc.setTimeStringFormat(false, true, false, false, true); // Wed, Jan 12 2014 1:28:52 PM type of notation
// Configure PUSH1 to toggle Serial enabled / Serial disabled
pinMode(PUSH1, INPUT_PULLUP);
attachInterrupt(PUSH1, handlePUSH1, FALLING);
// Configure PUSH2 to toggle Indoor/Outdoor mode
pinMode(PUSH2, INPUT_PULLUP);
attachInterrupt(PUSH2, toggleIndoorOutdoor, FALLING);
// Status LEDs
pinMode(RED_LED, OUTPUT);
digitalWrite(RED_LED, LOW);
pinMode(GREEN_LED, OUTPUT);
digitalWrite(GREEN_LED, LOW);
}
void loop()
{
int i;
static uint32_t noisy_millis = 0;
static int nfl = lsensor.getNoiseFloorBits(); // Loaded once
int oldnfl;
Serial.flush(); // flush TX buffers before entering LPM3 (with enableSerialInput==false)
while ( !lsensor.available() && !Serial.available() && (millis()-noisy_millis) < HIGHNOISE_DELAY_PERIOD && !flipflop_IndoorOutdoor ) {
if (!enableSerialInput)
sleep(200);
else
delay(20);
}
// Check for CLI input
if (Serial.available()) {
if (enableSerialInput) {
runCliRxStateMachine();
if (cliAvailable) {
wrap_processCLI();
}
} else {
while (Serial.available())
Serial.read(); // Drain data (it's garbled in LPM3 anyway)
}
}
// Did PUSH2 get pressed? Flip-flop the AFEGain settings
if (flipflop_IndoorOutdoor) {
flipflop_IndoorOutdoor = false;
if (lsensor.getIndoorOutdoor()) {
lsensor.setIndoors(false);
Serial.println("AS3935 AFEGain = Outdoor");
// Two flashes means we've enabled Outdoor mode
delay(100);
digitalWrite(GREEN_LED, LOW);
delay(60);
digitalWrite(GREEN_LED, HIGH);
} else {
// Just one flash means we've enabled Indoor mode
lsensor.setIndoors(true);
Serial.println("AS3935 AFEGain = Indoor");
}
delay(100);
digitalWrite(GREEN_LED, LOW);
}
// Check the AS3935 sensor status
i = lsensor.getState();
switch (i) {
case FRANKLIN_STATE_LIGHTNING:
// This is what we're here for!
i = lsensor.getStormDistance();
if (event_count < MAXIMUM_EVENT_COUNT) {
// Commit strike distance and time/datestamp to FRAM
events[event_count].strikeDistance = i;
rtc.getTime( &(events[event_count].timestamp[0]) );
event_count++;
} else {
Serial.print("WARNING: Lightning Strike found (distance=");
Serial.print(i);
Serial.println(") but FRAM memory is exhausted!");
}
break;
case FRANKLIN_STATE_NOISY:
// Raise Noise Floor 2 points
if ( (millis()-noisy_millis) > HIGHNOISE_DELAY_PERIOD ) {
nfl += 2;
if (nfl > 7)
nfl = 7;
if (nfl != oldnfl) {
lsensor.setNoiseFloorBits(nfl);
Serial.print("NOISY; raised noise floor to "); Serial.print(lsensor.getNoiseFloor()); Serial.println("uVrms");
}
}
noisy_millis = millis();
break;
case FRANKLIN_STATE_LISTENING:
if ( (millis() - noisy_millis) >= LOWNOISE_DELAY_PERIOD ) {
oldnfl = nfl;
nfl--;
if (nfl < 0)
nfl = 0;
if (nfl != oldnfl) {
lsensor.setNoiseFloorBits(nfl);
Serial.print("Reducing noise floor to "); Serial.print(lsensor.getNoiseFloor()); Serial.println("uVrms");
}
noisy_millis = millis();
}
break;
}
}
void RTCTick()
{
rtc.save();
}
/* Serial Command Line Interface; receive commands stored into cliRxBuffer[],
* then handle/process them using processCLI()
*/
char cliRxBuffer[256];
unsigned int cliRxBufferIndex = 0;
void runCliRxStateMachine()
{
int c;
while (Serial.available()) {
c = Serial.read();
if (cliAvailable || c == '\r' || c == '\n' || c == '|') {
cliAvailable = true;
// After receiving a full line, ignore anything else until wrap_processCLI() has run.
} else {
if (cliRxBufferIndex < 254)
cliRxBuffer[cliRxBufferIndex++] = c;
}
}
}
// Tokenize the received command
char *argv[16]; // Up to 16 total arguments allowed in a command
unsigned int tokenizeCommand(char *rxbuf) // Returns # of arguments found
{
int i = 0, ptr = 0;
do {
argv[i] = rxbuf+ptr;
while (ptr < 255 && rxbuf[ptr] != ' ') // Find first whitespace
ptr++;
rxbuf[ptr++] = '\0'; // Nullify it
while (ptr < 255 && rxbuf[ptr] == ' ') // Nullify all contiguous whitespace after...
rxbuf[ptr++] = '\0';
i++; // We should be at the next token now
} while (i < 16 && ptr < 255);
return i+1;
}
void wrap_processCLI()
{
processCLI();
cliRxBufferIndex = 0;
memset(cliRxBuffer, 0, 256);
cliAvailable = false;
}
const char *helpText = "Wolverine Franklin Lightning Logger\r\n"
"CLI syntax:\r\n"
"help - Print this help\r\n"
"count - Get a count of lightning events currently logged\r\n"
"limit - Get the max # of events storable in the FRAM buffer\r\n"
"retrieve <#> - Retrieve the #'th entry (starting at 0), or \"all\" pulls all events.\r\n"
"purge - Purge all lightning events from FRAM buffer\r\n"
"disable - Place AS3935 in low-power off mode\r\n"
"enable - Wake AS3935 into listening mode\r\n"
"params - Display working parameters for AS3935\r\n"
"spike - Change Spike Rejection threshold (anti-disturber)\r\n"
"sigthr - Change Signal Threshold (anti-disturber)\r\n"
"strike - Change Strike Threshold (min#/17min to report as lightning)\r\n"
"time - Print current date & time from RTC\r\n";
void processCLI()
{
int argc;
int i, j;
char timebuf[64];
argc = tokenizeCommand(cliRxBuffer);
if (!strcmp(argv[0], "help")) {
Serial.println(helpText);
return;
}
if (!strcmp(argv[0], "count")) {
Serial.print("Lightning Strike FRAM buffer count: ");
Serial.println(event_count);
return;
}
if (!strcmp(argv[0], "limit")) {
Serial.print("Max # of events storable in FRAM: ");
Serial.println(MAXIMUM_EVENT_COUNT);
return;
}
if (!strcmp(argv[0], "retrieve")) {
if (argc < 2) {
Serial.println("ERROR: Syntax: retrieve <#>");
return;
}
if (!strcmp(argv[1], "all") || !strcmp(argv[1], "ALL")) {
for (i = 0; i < event_count; i++) {
dumpEvent(i);
}
if (i == 0) {
Serial.println("No events recorded.");
}
} else {
i = atoi(argv[1]);
if (i >= event_count) {
if (!event_count) {
Serial.println("ERROR: No lightning strikes have been recorded.");
} else {
Serial.print("ERROR: Only "); Serial.print(event_count);
Serial.println(" lightning strike events have been recorded!");
}
return;
}
dumpEvent(i);
}
return;
}
if (!strcmp(argv[0], "purge")) {
Serial.print("Purged "); Serial.print(event_count); Serial.println(" entries.");
event_count = 0;
return;
}
if (!strcmp(argv[0], "disable")) {
lsensor.power(false);
Serial.println("AS3935 sensor powered down");
return;
}
if (!strcmp(argv[0], "enable")) {
lsensor.power(true);
Serial.println("AS3935 sensor powered up");
return;
}
if (!strcmp(argv[0], "params")) {
Serial.println("AS3935 parameters:");
Serial.print("AFEGain: ");
if (lsensor.getIndoorOutdoor()) {
Serial.println("Indoors");
} else {
Serial.println("Outdoors");
}
Serial.print("SpikeRej: ");
Serial.println(lsensor.getSpikeRejection());
Serial.print("SignalThresh: ");
Serial.println(lsensor.getSignalThreshold());
Serial.print("StrikeThresh: ");
Serial.println(lsensor.getStrikeThreshold());
Serial.print("NoiseFloor: ");
Serial.print(lsensor.getNoiseFloor()); Serial.println("uVrms");
i = lsensor.getState();
if (i != FRANKLIN_STATE_POWERDOWN) {
if (i == FRANKLIN_STATE_NOISY) {
Serial.println("State: Excessive noise");
} else {
Serial.println("State: Listening");
}
} else {
Serial.println("State: Powerdown");
}
return;
}
if (!strcmp(argv[0], "spike")) {
if (argc < 2) {
Serial.println("Syntax: spike <0-15>");
} else {
i = atoi(argv[1]);
lsensor.setSpikeRejection(i);
Serial.print("Spike Rejection set to "); Serial.println(lsensor.getSpikeRejection());
}
return;
}
if (!strcmp(argv[0], "sigthr")) {
if (argc < 2) {
Serial.println("Syntax: sigthr <0-15>");
} else {
i = atoi(argv[1]);
lsensor.setSignalThreshold(i);
Serial.print("Signal Threshold set to "); Serial.println(lsensor.getSignalThreshold());
}
return;
}
if (!strcmp(argv[0], "strike")) {
if (argc < 2) {
Serial.println("Syntax: strike <event threshold min# strikes within 17min>");
} else {
i = atoi(argv[1]);
lsensor.setStrikeThreshold(i);
i = lsensor.getStrikeThreshold();
Serial.print("Strike Threshold set to: ");
Serial.println(i);
}
return;
}
if (!strcmp(argv[0], "time")) {
rtc.getTimeString(timebuf);
Serial.print("Current date/time: ");
Serial.println(timebuf);
return;
}
if (strlen(argv[0]) > 0) {
Serial.print("Command not found: "); Serial.println(argv[0]);
}
}
void dumpEvent(int idx)
{
char timestr[64];
int16_t dist;
Serial.print(idx);
Serial.print(": ");
// Interpret storm distance
dist = events[idx].strikeDistance;
if (dist == 0) {
Serial.print("Overhead - ");
} else if (dist < 0) {
Serial.print("Out of Range - ");
} else {
Serial.print(dist); Serial.print(" km - ");
}
// Print timestamp
rtc.getTimeString(timestr, &(events[idx].timestamp[0]));
Serial.println(timestr);
}
// Pushbutton configuration - PUSH1 = Disable/Enable Serial (allowing LPM3 when Serial disabled)
void handlePUSH1()
{
if (!digitalRead(PUSH1))
enableSerialInput ^= 1;
}
void toggleIndoorOutdoor()
{
flipflop_IndoorOutdoor = true;
digitalWrite(GREEN_LED, HIGH);
wakeup();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment