I’m building a portable data logger that consists of a rotary encoder from which you can select an index, and pressing the pushbutton records a time stamp on a SD card.
The hardware is: Teensy 3.1, SD Adaptor, 12-step rotary encoder, 1 led (for feedback).
I’m planning to power this device on a LiPo 100mAh battery, so power consumption is critical. From my inexperience working with interrupts and lower power modes, this is some of the reasoning and questions:
- I’ll keep track of time using the time.h library. I don’t have physical room for a RTC, so I’ll use now() to get the time stamp. Can I put the CPU clock to sleep? In case I cannot do that, but I want to bring the clock speed down to 2MHz for lowering the consumption, how can I adjust the time readings? multiply the time by the speed ratio 96/2=58?
- Considering the issue above, can I use Sleep mode on LowPower_Teensy3 library, or what would be the best trade-off to keep time, and save power?
- I’ll put the Teensy to sleep, and use interrupts on the encoder channels and push button to wake it up. So I need 3 wakeup pins. Hibernate or Sleep functions seems to only accept 1 wakeup pin, or can I define multiple ones?
This is the code I have so far:
I’d appreciate if someone can answer these questions, and tell me if I'm approaching this project correctly. Thanks in advance
The hardware is: Teensy 3.1, SD Adaptor, 12-step rotary encoder, 1 led (for feedback).
I’m planning to power this device on a LiPo 100mAh battery, so power consumption is critical. From my inexperience working with interrupts and lower power modes, this is some of the reasoning and questions:
- I’ll keep track of time using the time.h library. I don’t have physical room for a RTC, so I’ll use now() to get the time stamp. Can I put the CPU clock to sleep? In case I cannot do that, but I want to bring the clock speed down to 2MHz for lowering the consumption, how can I adjust the time readings? multiply the time by the speed ratio 96/2=58?
- Considering the issue above, can I use Sleep mode on LowPower_Teensy3 library, or what would be the best trade-off to keep time, and save power?
- I’ll put the Teensy to sleep, and use interrupts on the encoder channels and push button to wake it up. So I need 3 wakeup pins. Hibernate or Sleep functions seems to only accept 1 wakeup pin, or can I define multiple ones?
This is the code I have so far:
Code:
#include <SD.h>
#include <LowPower_Teensy3.h>
TEENSY3_LP LP = TEENSY3_LP();
#include <Time.h>
// SD Card
// CS = pin #10
// DI = pin #11
// DO = pin #12
// CLK = pin #13
#define encoderPinA 5
#define encoderPinB 6
#define clearButton 7
#define ledPin 15
#define CHARS_EVENT 13
volatile unsigned int encoderPos = 0; // a counter for the dial
unsigned int lastReportedPos = 1; // change management
static boolean rotating=false; // debounce management
static boolean pressed=false;
// interrupt service routine vars
boolean A_set = false;
boolean B_set = false;
boolean alreadyLit = false;
static boolean saving = false;
static boolean encoderReset = false;
File myFile;
int numEvents = 0;
int sleepCounter = 0;
void setup() {
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
Serial.println(F("Init"));
pinMode(10, OUTPUT);
if (!SD.begin(10)) {
Serial.println(F("initialization failed!"));
return;
}
Serial.println(F("initialization done."));
setTime(2,19,20,1,8,2014);
Serial.println("Creating portable.txt...");
myFile = SD.open("portable.txt", FILE_WRITE);
else {
// if the file didn't open, print an error:
Serial.println("error opening events.txt");
}
numEvents = countEventsFile(); //it will print the events too
printAllEventsInFile();
countEventsFile();
pinMode(encoderPinA, INPUT);
pinMode(encoderPinB, INPUT);
pinMode(clearButton, INPUT);
pinMode(ledPin, OUTPUT);
// turn on pullup resistors
digitalWrite(encoderPinA, HIGH);
digitalWrite(encoderPinB, HIGH);
digitalWrite(clearButton, HIGH);
attachInterrupt(encoderPinA, doEncoderA, CHANGE);
attachInterrupt(encoderPinB, doEncoderB, CHANGE);
attachInterrupt(clearButton, doButton, FALLING);
}
void loop() {
rotating = true; // reset the debouncer
//pressed = true;
if (saving){
Serial.println("Saving timestamp");
cli();
recordEventToFile();
countEventsFile();
sei();
digitalWrite(ledPin, HIGH);
delay(50);
digitalWrite(ledPin, LOW);
saving = false;
}
if (lastReportedPos != encoderPos) {
Serial.print("Index:");
Serial.println(encoderPos, DEC);
lastReportedPos = encoderPos;
alreadyLit = false;
}
if (encoderPos >= 12 && encoderPos < 16){
encoderPos = 0;
} else if (encoderPos <= 4294967295 && encoderPos > 4294967290 ){
encoderPos = 11;
}
if (encoderReset){
Serial.println("Encoder Reset");
for (int i = 0; i<1; i++){
digitalWrite(ledPin, HIGH);
delay(50);
digitalWrite(ledPin, LOW);
delay(90);
}
encoderReset = false;
} else if ((encoderPos == 0) && (!alreadyLit)){
Serial.print("liiiight");
digitalWrite(ledPin, HIGH);
delay(20);
digitalWrite(ledPin, LOW);
alreadyLit = true;
}
if (digitalRead(clearButton) == LOW ) {
}
if (sleepCounter > 1000){
Serial.println("going to sleep");
sleepCounter = 0;
delay(3000);
//LP.Sleep();
}
sleepCounter++;
}
// Interrupt on A changing state
void doEncoderA(){
if ( rotating ) delay (1); // wait a little until the bouncing is done
// Test transition, did things really change?
if( digitalRead(encoderPinA) != A_set ) { // debounce once more
A_set = !A_set;
// adjust counter + if A leads B
if ( A_set && !B_set )
encoderPos += 1;
rotating = false;
}
}
// Interrupt on B changing state, same as A above
void doEncoderB(){
if ( rotating ) delay (1);
if( digitalRead(encoderPinB) != B_set ) {
B_set = !B_set;
// adjust counter - 1 if B leads A
if( B_set && !A_set )
encoderPos -= 1;
rotating = false;
}
}
void doButton(){
if (!pressed){
pressed = true;
//Serial.println("pressed");
}
delay(400);
pressed = false;
int counter = 0;
while (digitalRead(clearButton) == LOW){
counter++;
if (counter > 500){
encoderPos = 0;
encoderReset = true;
break;
}
}
if (counter <= 500){
saving = true;
}
}
void recordEventToFile(){
myFile = SD.open("portable.txt", FILE_WRITE);
if (myFile) {
Serial.print(encoderPos);
Serial.print(" ");
Serial.println(now());
if (encoderPos < 10){ // add a 0 before
myFile.print("0");
}
myFile.print(encoderPos);
myFile.print(" ");
myFile.println(now());
myFile.close();
Serial.println("New event written in portable.txt");
} else {
// if the file didn't open, print an error:
Serial.println("error opening portable.txt");
}
numEvents++;
printAllEventsInFile();
}
void printAllEventsInFile(){
myFile = SD.open("portable.txt");
if (myFile) {
for (int i=0; i < numEvents; i++){
char oneEvent[CHARS_EVENT];
for (int j=0; j < CHARS_EVENT; j++){ //10 not 12, since we want to skip the null and eol at the end
char inputChar = char(myFile.read());
oneEvent[j] = inputChar;
}
myFile.read(); //skip the null
myFile.read(); //skip the eol
//events[i] = atol(oneEvent); //in case I want to store them all
long currentEvent = atol(oneEvent); // put together the array of characters into a long.
Serial.print("oneEvent: ");
Serial.println(oneEvent);
//Serial.println(events[i]); in case I'm storing them in events array
//Serial.print("currentEvent: ");
//Serial.println(currentEvent);
}
myFile.close();
} else {
Serial.println("error opening portable.txt");
}
}
int countEventsFile(){
myFile = SD.open("portable.txt");
if (myFile) {
int counter = 0;
char inputChar = myFile.read();
while (myFile.available()) {
inputChar = myFile.read();
counter++;
}
counter++; //add one to adjust, dunno why. 59 vs 60, which divides by 12
int numEvents = counter/(CHARS_EVENT + 2); // taking into account null and eol
Serial.println("countEventsFile:");
Serial.println(numEvents);
myFile.close();
return numEvents;
} else {
Serial.println("error opening portable.txt");
}
}
I’d appreciate if someone can answer these questions, and tell me if I'm approaching this project correctly. Thanks in advance