Teensy + Sleep + Encoder interrupts

Status
Not open for further replies.

ishback

Member
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:
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
 
I can't really follow what you have here, you really need to simplify it for me to help. What pins do you want to be able to wake this with? Have you tried the examples in the library and then try to expand them?


Also isn't there a encoder library that you could use to make your sketch more readable?
 
I'm using this messy sketch instead of the encoder.h library because it's the one I found that misses less counts http://playground.arduino.cc/Main/RotaryEncoders#Example15

But I'm happy to come back to encoder.h if someone can help me - it will make my life easier too. This would be a simple sketch from which I can expand:

Code:
#include <Encoder.h>
#include <LowPower_Teensy3.h>

TEENSY3_LP LP = TEENSY3_LP();

Encoder myEnc(5, 6);

void setup() {
  Serial.begin(9600);
  Serial.println("Basic Encoder Test:");
  pinMode(LED_BUILTIN, OUTPUT);
}

long oldPosition  = -999;

void loop() {
  long newPosition = myEnc.read();
  if (newPosition != oldPosition) {
    oldPosition = newPosition;
    Serial.print(newPosition);
    Serial.print(" ");
    if (newPosition%4 == 0){
      Serial.print(newPosition/4);
    }
    Serial.println(" ");
  }
  digitalWrite(LED_BUILTIN, HIGH);
  delay(100);
  digitalWrite(LED_BUILTIN, LOW);
  //LP.Sleep(); //commented for now so I can read Serial
}

With this code, I get this printed, which is already missing counts o jumping.

Code:
-2  
-4 -1 
-8 -2 
-9  
-12 -3 
-8 -2 
-6  
0 0 
1  
4 1 
8 2 
11  
12 3 
8 2 
12 3 
19  
20 5 
39  
41  
40 10

I'm wondering if I'll lose even more counts as I keep doing more things inside loop(), since it keeps getting interrupted - 4 times every 'click' of the rotary encoder plus the bouncing. Is this the right way to do it?
 
Besides the issues with the encoder, I'd like to learn a bit more about the timekeeping and sleep, since it seems I cannot keep the time if I put the CPU to sleep? These questions are still valid to learn how can I better approach this project.

- 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?
 
Note, the Teensy 3.1 has an RTC built into it. You will need to solder a 32.768 kHz, 12.5 pF crystal to the bottom side of the board, and provide 3v power to Vbattery/ground to keep the RTC powered (http://pjrc.com/teensy/td_libs_Time.html#teensy3). I imagine when you wake up the CPU from being asleep, you could just query the RTC to see what time it is now.
 
I got the crystal so I can use the Teensy 3.1 RTC, but not sure why I need it. Does the LP.Sleep() function in the code above put the CPU to sleep? Because I was still able to read the time after waking it up. So either the function LP.Sleep() doesn't put the CPU to sleep or I'm not using it correctly and it doesn't actually never go to sleep.
 
from my (albeit limited perhaps) understanding of RTC modules in general; if the MCU has an RTC module, the circuit includes the necessary support (usually just a 32768Hz 'watch crystal' and a small battery), and the RTC module is enabled then it will keep time regardless of sleep states etc.

I've never seen datasheet info on any RTC modules of any MCUs mention any detriment to the RTC module due to use of any low power mode available on the devices I've looked at.
 
The Teensy 3.1 needs an extra crystal to be soldered to be able to use the RTC. But without that crystal and using the sleep() function, it keeps time. My question is (1) why I need the crystal? or (2) is the CPU not going to sleep with sleep() function and I should maybe use another function like hibernate() to save even more power (but still be able to use the interrupts) or (3) am I doing something wrong and it's not going to sleep as I think it is? I'm pretty sure is sleeping since USB Serial communication doesn't work.
 
But without that crystal and using the sleep() function, it keeps time.

How? the rtc needs the crystal to work. All sleep functions currently will disable the systick so you most definitely need the crystal.

Have tried measuring the current consumption while going to sleep. Do this with nothing attached, no SD, nothing, to make sure you see the power savings when you call the sleep routine.
 
I just measured the consumption with nothing attached and it's around 1mA, and with the SD card reader is 30mA, so here's the problem. Is there any way to reduce the consumption of the SD card? It's a Microsim Sandisk 4Gb. I understand they have a sleep mode while not operating, but this seems to be not enough. Also I'm wondering if there is any type of SD.stop() to cancel the SD.begin() that I could use before putting the Teensy to sleep, and do SD.begin() when I wake up?
 
Status
Not open for further replies.
Back
Top