src/Okapi.cpp
Attributes
| Name | |
|---|---|
| const char *const | okapiChips |
| const char *const | okapiWords |
| const char *const | okapiChipWords |
Attributes Documentation
variable okapiChips
static const char *const okapiChips = {"SDCard", "Clock", "BME280", "SensorBus", "Charger", "Backup"};
variable okapiWords
static const char *const okapiWords = {"LoggingStarted", "NewLogFile", "RowNotWritten"};
variable okapiChipWords
static const char *const okapiChipWords = {"ClockSet"};
Source code
//Okapi library
#include <Okapi.h>
#include <Arduino.h>
Okapi::Okapi(board Model_, build Specs_) : ADC_OB(0x48), ADC_Ext(0x49), IO(0x20)
{
NumADR_OB = 6; //Clock, IO Expander, ADC_OB, ADC_Ext, DAC, BME
uint8_t ob[6] = {0x68, 0x20, 0x48, 0x49, 0x62, 0x77};
memcpy(I2C_ADR_OB, ob, 6);
// VSwitch_Pin = 3;
// VSwitch_Pin = 12; //DEBUG!??
// THESE ARE DIFFERENT NUMBERS THAN IN THE HEADER FILE!!!!!
RTCInt = 2;
LogInt = 27;
FeatherRTS = 31;
FeatherCTS = 30;
FeatherGPIO = 29;
CS_Ext = 24;
// WDHold = 255; //Null pins
// BatSwitch = 255; //Null pins
// BatteryDivider = 2.0;
// if(Specs_ == Build_A) {
// NumADR_OB = 6;
// }
// else if(Specs_ == Build_B) {
// //NULL
// }
Model = Model_; //Store model info locally
Specs = Specs_; //Store build info locally
}
bool Okapi::begin(uint8_t *Vals, uint8_t NumVals, String header_)
{
pinMode(C0, OUTPUT); //Allow for high power control
pinMode(C1, OUTPUT);
pinMode(I2C_SW, OUTPUT);
pinMode(Sw_Bus_Prime, OUTPUT);
pinMode(Sw_Bus_Sec, OUTPUT);
digitalWrite(Sw_Bus_Prime, LOW);
digitalWrite(Sw_Bus_Sec, LOW);
powerAuto(); //Get main power running
pinMode(AuxLED, OUTPUT);
digitalWrite(AuxLED, LOW); //Turn built in LED on
delay(25);
digitalWrite(Sw_Bus_Prime, HIGH);
digitalWrite(Sw_Bus_Sec, HIGH);
delay(500);
acceptAddresses(Vals, NumVals, header_); //The sketch's sensor addresses (bounded copy) and header
i2cState(INTERNAL);
RTC.begin(); //Initalize RTC
RTC.clearAlarm(); //
ADC_OB.begin();
ADC_Ext.begin();
DAC.begin(0x62);
DAC.SetRef(BUFFERED_VREF); //Set buffer configuration //FIX! Make variable if need to set greater than 4.096v?
if(!bme280.begin(0x77)) { //Initalize onboard temp/pressure/RH sensor (BME280)
Serial.println("BME280 init: FAIL");
OnBoardError = true;
BMEError = true;
}
Serial.begin(38400); //DEBUG!
Serial.print("Lib = ");
Serial.println(LibVersion);
bool schema1 = readIdentity(); //Serial number and hardware version from Page 0 (Schema 1), else the last 8 bytes (Schema 0)
if(!schema1) HWVersion = String(Model); //Schema 0: the model number the sketch declared
serialTimeSet(); //A YYMMDDHHMMSS string waiting on Serial sets the clock; then the timestamp
attachLoggerInterrupts(true); //LED pins, SD chip select, file times, the alarm and the button (PCINT)
attachExtInt(); //The external-interrupt counter, if setExtInt() named a pin
I2Ctest();
clockTest();
SDtest();
// BatTest();
enviroStats(); //Only print out enviromental variables if BME is on board
//FIX! Add Feather test??
ledReport(); //The self-test results on the RGB LED, then "Ready to Log"
//The logger's own report at boot, for its first status row: the first fault the
//self-tests found, else LoggingStarted (unit, kind 16).
if(SDCardMissing) Pages.latchFault(0x01);
else if(SDTestFailed) Pages.latchFault(0x05);
else if(ClockError) Pages.latchFault(0x21);
else if(BMEError) Pages.latchFault(0x41);
else if(SensorError) Pages.latchFault(0x61);
Pages.latchNotice(0xF0);
BootReport = Pages.report();
Pages.acknowledge();
NewLog = true; //Set flag to begin new log file
LED_Color(OFF);
return !(OnBoardError || SensorError || TimeError || SDCardMissing);
}
// The data file's header row: the on-board columns, then the sketch's Header,
// then Note. Note is always the last column and carries no comma after it.
String Okapi::dataHeader()
{
return "Time [UTC], PresOB [mBar], RH_OB [%], TempOB [C], Temp RTC [C], VBeta [mV], VPrime [mV], ISolar [mA], IBeta [mA]," + Header + "Note";
}
void Okapi::enviroStats()
{
Serial.print("Temp = ");
Serial.print(bme280.getTemperature());
Serial.println("C");
Serial.print("Pressure = ");
Serial.print(bme280.getPressure());
Serial.println(" mBar");
Serial.print("RH = ");
Serial.print(bme280.getHumidity());
Serial.println("%");
}
String Okapi::getOnBoardVals()
{
//Get onboard temp, RTC temp, and battery voltage, referance voltage
// float VRef = analogRead(VRef_Pin);
// float Vcc = 3.3; //(1.8/VRef)*3.3; //Compensate for Vcc using VRef
// Serial.println(Vcc); //DEBUG!
// float TempData = 0; //FIX!!! Dumb!
// if(Model < Model_2v0) { //For older thermistor models
// float Val = float(analogRead(ThermSense_Pin));
// float Comp = (1.8/3.3)*1024.0/analogRead(VRef_Pin); //Find compensation value with VRef due to Vcc error
// if(Model == 0) Comp = 1.0; //Overide comp calculation since many v0.0 models do not have ref equiped
// Val = Val*Comp*(Vcc/1024.0); //Compensate for ref voltage error
// // float Vout = Vcc - Val;
// // Serial.println(Val); //DEBUG!
// // Serial.println(Vout); //DEBUG!
// TempData = TempConvert(Val, Vcc*Comp, 10000.0, A, B, C, D, 10000.0);
// TempData = TempData - 273.15; //Get temp from on board thermistor
// }
// delay(10);
// float BatVoltage = GetBatVoltage(); //Get battery voltage, Include voltage divider in math
// Temp[3] = Clock.getTemperature(); //Get tempreture from RTC //FIX!
// powerAuto(); //Turn on power //FIX??
pinMode(ADC_Sense_SW, OUTPUT); //DEBUG!!!!!!!!!!!!!!!!!!
digitalWrite(ADC_Sense_SW, HIGH); //Enable reading of battery lines //FIX! Shorten to reduce current draw!
// float VBeta = 0;
// float ISolar = 0; //FIX! Adjust after changing gain of amp??
// float VPrime = 0;
// float IBeta = 0;
float VBeta = ADC_OB.readADC_SingleEnded(0);
float VPrime = ADC_OB.readADC_SingleEnded(2);
digitalWrite(ADC_Sense_SW, LOW); //Enable reading of battery lines
VBeta = VBeta*0.1875;
VPrime = VPrime*0.1875;
float ISolar = ADC_OB.readADC_SingleEnded(1)*0.01875; //FIX! Adjust after changing gain of amp??
float IBeta = (ADC_OB.readADC_SingleEnded(3)*0.1875 - 2500)/1.5;
float RTCTemp = RTC.getTemp(); //Get Temp from RTC
getTime(); //FIX!
// if(Model< Model_2v0) return LogTimeDate + "," + String(RTCTemp) + "," + String(VBeta) + ",";
return LogTimeDate + "," + String(bme280.getString()) + String(RTCTemp) + "," + String(VBeta) + "," + String(VPrime) + "," + String(ISolar) + "," + String(IBeta) + ",";
}
String Okapi::readStr(uint8_t LineIndex, uint32_t DataIndex) //Pass index (working backwards from most recent log)
{
// Serial.println(Val); //Echo to serial monitor
// SD.begin(SD_CS); //DEBUG!
// SD.chdir("/"); //Return to root to define starting state
// IO.PinMode(6, OUTPUT, A); //DEBUG!
// IO.DigitalWrite(6, LOW, A); //DEBUG!
SD.chdir("/"); //The card's root
SD.chdir(SN); //Move into this logger's folder, named by its serial number
File DataFile = SD.open(FileNameC, FILE_READ);
// if the file is available, read from it:
if (DataFile) {
DataFile.seek(DataIndex); //Run to starting location
for(int i = 0; i < LineIndex; i++) {
DataFile.readStringUntil('\n'); //Read out previous lines
}
return DataFile.readStringUntil('\n'); //Return desired line
// return 0;
}
// if the file isn't open, pop up an error:
else {
// return -1;
}
DataFile.close();
// IO.PinMode(6, OUTPUT, A); //DEBUG!
// IO.DigitalWrite(6, HIGH, A); //DEBUG!
}
void Okapi::addDataPoint(String (*Update)(void)) //Reads new data and writes data to SD
{
String Data = "";
i2cState(EXTERNAL);
Data = (*Update)(); //Run external update function
i2cState(INTERNAL); //DEBUG!
bme280.begin(0x77); //DEBUG!
Data = getOnBoardVals() + Data + Note; //Prepend on board readings; Note column last
Note = ""; //One row's worth of notes
if(logStr(Data) != 0) Pages.latchNotice(0xF2); //RowNotWritten
LogCount++; //FIX??
fillPages(); //Okapi's reading of itself: Page 2, Page 3, Block 0
reportRows(); //The status file: a row for the logger and every watched sensor with something to report
}
void Okapi::afterLogEvent() //After an alarm-driven row: the backhaul
{
if(LogCount >= LogCountPush && PowerState == 0) { //If enough logs have been recorded and main battery power is available - backhaul //REPLACE WITH TIMER TEST!
IO.digitalWrite(FeatherEN, HIGH, MCP23018::Port::B); //Turn on Feather power
// for(int i = 0; i < 10; i++) { //DEBUG!
// Serial.println("START BACKHAUL"); //DEBUG!
// delay(100);
// }
//delay(20);
//Serial.end();
//delay(20);
//Serial.begin(38400); // Use different rate too?
//delay(20);
// Delay to make transmission work
unsigned long Timeout = millis();
while((millis() - Timeout) < 1000); // Give transmission some time. (This must be substantial!)
Serial.println("START BACKHAUL"); //DEBUG!
//Serial.println("MID BACKHAUL"); //DEBUG!
for(int i = 0; i < LogCountPush; i++) { //Print out SD values
Serial.println(readStr(i, LastSDIndex));
}
//*/
LastSDIndex = SDIndex; //copy new value over
LogCount = 0;
Serial.println("END BACKHAUL"); //DEBUG!
Timeout = millis();
while(digitalRead(FeatherGPIO) && (millis() - Timeout) < 180000); //Wait for completerion or for timeout (180 seconds -- takes 2G/3G longer)
// while((millis() - Timeout) < 59000); //DEBUG!
// Give as much time as possible to complete the communications. Takes a while and can time out easily.
pinMode(FeatherGPIO, INPUT);
}
}
uint8_t Okapi::setVoltageRaw(uint16_t Val, bool Gain)
{
if(Val > 4095 || Val < 0) {
Serial.println("BANG!");
DAC.Sleep(ON); //Make device output open to avoid issues //FIX??
return 5; //Return out of range error
}
else {
// DAC.Sleep(OFF); //Make device is set to output
DAC.SetGain(Gain); //Set appropriate gain (default to 1x)
return DAC.setVoltage(Val); //Do not allow to set value to memory, return I2C status
}
}
uint8_t Okapi::setVoltage(float Val) //Interpolated nearest value from float
{
uint16_t BitValue = 0; //used to calculate the bit value to set the DAC to
if(Val > 5.0 || Val < 0.0) {
DAC.Sleep(ON); //Make device output open to avoid issues //FIX??
return 5; //Return out of range error
}
else if(Val >= 2.5) {
DAC.Sleep(OFF); //Make device is set to output
BitValue = floor(Val*819.2); //Set for 2x single multiple
if(BitValue > 4095) BitValue = 4095; //FIX?? Prevent wrap around error due to float rounding
DAC.SetGain(GAIN_2X); //Set 2x gain
return DAC.setVoltage(BitValue); //Return I2C status
}
else {
DAC.Sleep(OFF); //Make device is set to output
BitValue = floor(Val*1638.4); //Set for single multiple
if(BitValue > 4095) BitValue = 4095; //FIX?? Prevent wrap around error due to float rounding
DAC.SetGain(GAIN_1X); //Set unity gain
return DAC.setVoltage(BitValue); //Return I2C status
}
}
float Okapi::getVoltage(uint8_t Pin) //Get voltage external ADC from specified pin
{
i2cState(INTERNAL);
float Val = ADC_Ext.readADC_SingleEnded(Pin)*0.1875;
i2cState(EXTERNAL);
return Val;
}
uint8_t Okapi::powerAuto()
{
uint8_t DDR_Prev = DDRC; //Read port state to be able to return
DDRC = DDR_Prev | 0x0C; //Set C1 and C0 as output
DDRC = DDR_Prev & 0x7F; //Make PC7 (EN_BUS_PRIME) and input
uint8_t PortState = PORTC; //Do manipulation locally, then push to port
PortState = PortState & 0xF3; //Clear C0 and C1
PORTC = PortState; //Turn rail off to ensure valid measurment //FIX??
delay(2); //Cx rising edge to EN_BUS_PRIME rising (90%, ~3.0v) measured at 1.4ms, added ~50% margin of safety for robustness
PortState = PortState & 0xF3; //Clear C0 and C1
PortState = PortState | 0x08; //Set C1 HIGH, C0 LOW (Vbeta)
// PORTC = PORTC & 0xF3; //Clear C0 and C1
// PORTC = PORTC | 0x08; //Set C1 HIGH, C0 LOW (Vbeta)
PORTC = PortState; //Write values to port
// digitalWrite(C1, HIGH); //Set for Vbeta initally
// digitalWrite(C0, LOW);
delay(2); //Cx rising edge to EN_BUS_PRIME rising (90%, ~3.0v) measured at 1.4ms, added ~50% margin of safety for robustness
bool State = (PINC >> 7); //Read state of PC7
if(State) return 0; //Good
else {
PORTC = PORTC & 0xF3; //Clear C0 and C1
PORTC = PORTC | 0x04; //Set C1 LOW and C0 HIGH (Vprime)
delay(2); //Cx rising edge to EN_BUS_PRIME rising (90%, ~3.0v) measured at 1.4ms, added ~50% margin of safety for robustness
bool State = (PINC >> 7); //Read state of PC7
if(State) return 1; //Good, aux
}
return 2; //Error, power not good??
}
void Okapi::powerAux(uint8_t State) //UPDATE! 0 or 3 = OFF, 1 = V_Prime, 2 = V_Beta
{
State = State & 0b11; //Restrict to lowest 2 bits
uint8_t DDR_Prev = DDRC; //Read port state to be able to return
DDRC = DDR_Prev | 0x0C; //Set C1 and C0 as output
PORTC = PORTC & 0xF3; //Clear C1 and C2
PORTC = PORTC | (State << 2); //Set C1 and C0 appropriately
// Serial.println(PORTC); //DEBUG!
}
void Okapi::i2cState(bool State)
{
digitalWrite(I2C_SW, State);
// uint8_t PortVal = PORTC; //Read status //FIX??
// PortVal = PortVal & 0xDF; //Clear C5
// PortVal = PortVal | (State << 5); //Set C5 with appropriate value
// PORTC = PortVal; //Set port
}
uint8_t Okapi::chipFaults()
{
uint8_t f = 0;
if(SDCardMissing || SDTestFailed) f |= 0x01;
if(ClockError) f |= 0x02;
if(BMEError) f |= 0x04;
if(SensorError) f |= 0x08;
return f;
}
// Okapi's reading of itself, per the NW-Device-Specification Okapi appendix
// (hypothetical as of 2026-09-23). Block 1, power, waits on the power model
// (which of VBeta and VPrime is the LiPo, the solar scaling) and stays zero.
void Okapi::fillPages()
{
Pages.beginReading();
if(!BMEError) {
Pages.put16(0x50, (uint16_t)(int16_t)(bme280.getTemperature() * 100.0));
Pages.put16(0x52, (uint16_t)(bme280.getHumidity() * 100.0));
Pages.put32(0x54, (uint32_t)(bme280.getPressure() * 100.0));
}
Pages.put32(0x58, clockUnix()); //Clock: Unix seconds
Pages.put16(0x5C, (uint16_t)(int16_t)(RTC.getTemp() * 100.0));
Pages.put16(0x60, getExtIntCount(false));
Pages.put16(0x62, FileNum);
Pages.put32(0x64, LogInterval);
Pages.endReading(chipFaults());
}
static const char* const okapiChips[] = {"SDCard", "Clock", "BME280", "SensorBus", "Charger", "Backup"};
static const char* const okapiWords[] = {"LoggingStarted", "NewLogFile", "RowNotWritten"}; //unit kinds 16-18
static const char* const okapiChipWords[] = {"ClockSet"}; //kind 16 on Clock (0x30)
size_t Okapi::printStatus(Print& out, bool boot)
{
const NW_Report& r = boot ? BootReport : Pages.report();
const char* const* words = okapiWords; uint8_t n = 3;
if(r.chip() == 1) { words = okapiChipWords; n = 1; }
return Pages.printSnapshot(out, okapiChips, 6, LibVersion.c_str(), &r, words, n, OKAPI_LIBRARY_COMMIT, "", SKETCH_COMMIT); //A logger: its library is its firmware; the sketch stands where a library would
}
void Okapi::sleepNow() // here we put the arduino to sleep
{
/* Now is the time to set the sleep mode. In the Atmega8 datasheet
* http://www.atmel.com/dyn/resources/prod_documents/doc2486.pdf on page 35
* there is a list of sleep modes which explains which clocks and
* wake up sources are available in which sleep mode.
*
* In the avr/sleep.h file, the call names of these sleep modes are to be found:
*
* The 5 different modes are:
* SLEEP_MODE_IDLE -the least power savings
* SLEEP_MODE_ADC
* SLEEP_MODE_PWR_SAVE
* SLEEP_MODE_STANDBY
* SLEEP_MODE_PWR_DOWN -the most power savings
*
* For now, we want as much power savings as possible, so we
* choose the according
* sleep mode: SLEEP_MODE_PWR_DOWN
*
*/
// MCUCR = bit (BODS) | bit (BODSE);
// MCUCR = bit (BODS);
// wdt_disable(); //DEBUG!??
// power_adc_disable(); // ADC converter
// // power_spi_disable(); // SPI
// power_usart0_disable();// Serial (USART)
// power_timer1_disable();// Timer 1
// power_timer2_disable();// Timer 2
// ADCSRA = 0;
turnOffSDcard();
// digitalWrite(Ext3v3Ctrl, HIGH); //Turn off extenral rail
// SPI.end(); //Turn off SPI
// digitalWrite(SD_CS, LOW);
// pinMode(SD_CS, INPUT); //Disconnect SD chip slect pin
// pinMode(5, INPUT); //Set all SPI pins as inputs, will be reversed be beginning SPI again
// pinMode(6, INPUT);
// pinMode(7, INPUT);
// digitalWrite(VSwitch_Pin, LOW); //DEBUG!
keep_ADCSRA = ADCSRA;
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
cbi(ADCSRA,ADEN);
sleep_enable();
sleep_bod_disable();
sei();
sleep_cpu();
sleep_disable();
// pinMode(3, OUTPUT); //DEBUG!
// detachInterrupt(0); // disables interrupt 0 on pin 2 so the
// ADCSRA = 1; //Turn ADC back on
// digitalWrite(Ext3v3Ctrl, LOW); //turn external rail back on
// digitalWrite(SD_CS, HIGH);
// SPI.begin();
turnOnSDcard();
ADCSRA = 135; //DEBUG!
Serial.begin(38400);
// digitalWrite(VSwitch_Pin, HIGH); //DEBUG!
// pinMode(SD_CS, OUTPUT); //Disconnect SD chip slect pin
}
void Okapi::turnOffSDcard()
{
delay(6);
// disable SPI
// power_spi_disable(); // disable SPI clock
// DDRB &= ~((1<<DDB5) | (1<<DDB7) | (1<<DDB6) | (1<<DDB4)); // set All SPI pins to INPUT
// pinMode(SD_CD, INPUT);
// DDRC &= ~((1<<DDC0) | (1<<DDC1));
// pinMode(31, OUTPUT); //DEBUG!
// digitalWrite(31, LOW); //DEBUG!
pinMode(Sw_Bus_Prime, INPUT);
pinMode(Sw_Bus_Sec, INPUT);
pinMode(16, INPUT);
pinMode(17, INPUT);
// digitalWrite(8, LOW);
// digitalWrite(9, LOW);
Serial.end();
pinMode(8, INPUT);
pinMode(9, INPUT);
// digitalWrite(16, HIGH);
// digitalWrite(17, HIGH);
//digitalWrite(SD_CS, HIGH);
// digitalWrite(5, LOW);
// // Note: you must disconnect the LED on pin 13 or you’ll bleed current through the limit resistor
// // LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF); // wait 1 second before pulling the plug!
delay(6);
// digitalWrite(Ext3v3Ctrl, HIGH); //MODEL <= v1
// digitalWrite(Ext3v3Ctrl, LOW); //turn off external 3v3 rail
// digitalWrite(BatSwitch, LOW); //Turn off battery connection to sense divider
// powerAux(OFF); //turn off external 3v3 rail
// PowerOB(OFF); //Turn off battery connection to sense divider
// digitalWrite(31, HIGH); //DEBUG!
powerAux(OFF); //Turn off power
// digitalWrite(BatRailCtrl, HIGH);
delay(1);
digitalWrite(SD_CS, LOW);
delay(20);
// SPCR = SPCR & 0b11101111;
SPCR = 0;
power_spi_disable();
// SPI.end();
delay(10);
// pinMode(5, OUTPUT);d
// digitalWrite(5, LOW);
// DDRB &= ~((1<<DDB5));
// PORTB &= ~(1<<PORTB5); //Set port B5 (MOSI) LOW
// DDRB &= ~((1<<DDB5) | (1<<DDB7) | (1<<DDB6) | (1<<DDB4));
// PORTB |= ((1<<DDB5) | (1<<DDB7) | (1<<DDB6) | (1<<DDB4)); // set ALL SPI pins HIGH (~30k pullup)
// digitalWrite(SD_CS, LOW);
// pinMode(SD_CS, INPUT);
delay(6);
}
void Okapi::turnOnSDcard()
{
// pinMode(SD_CS, OUTPUT);
// SPI.begin();
// sd.begin(SD_CS);
// DDRB |= ((1<<DDB5));
// digitalWrite(SD_CS, HIGH);
// digitalWrite(Ext3v3Ctrl, HIGH); //turn off external 3v3 rail
// digitalWrite(BatSwitch, HIGH); //Turn off battery connection to sense divider
PowerState = powerAuto(); //Fix??
// PowerOB(ON); //Turn on battery connection to sense divider
// powerAux(ON); //turn on external 3v3 rail
delay(6); // let the card settle
// some cards will fail on power-up unless SS is pulled up ( & D0/MISO as well? )
// DDRC = DDRC | ((1<<DDC0) | (1<<DDC1));
// DDRB = DDRB | (1<<DDB7) | (1<<DDB5) | (1<<DDB4); // set SCLK(D13), MOSI(D11) & SS(D10) as OUTPUT
// Note: | is an OR operation so the other pins stay as they were. (MISO stays as INPUT)
// PORTB = PORTB & ~(1<<DDB7); // disable pin 13 SCLK pull-up – leave pull-up in place on the other 3 lines
power_spi_enable(); // enable the SPI clock
SPCR=keep_SPCR; // enable SPI peripheral
// delay(20);
// digitalWrite(BatRailCtrl, LOW);
// digitalWrite(Ext3v3Ctrl, LOW); //MODEL <= v1
delay(10);
// digitalWrite(3, HIGH); //DEBUG!
SD.begin(SD_CS, SD_SCK_MHZ(8));
// digitalWrite(3, LOW); //DEBUG!
}
Updated on 2026-09-23 at 22:44:32 +0000