Last active
August 29, 2015 14:06
-
-
Save spirilis/1eafb083808e61ebbba4 to your computer and use it in GitHub Desktop.
TI MSP430 Wolverine + AS3935 Franklin Lightning Sensor - Energia logger sketch
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
/* 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