How to: calculate a period of time?

I would like to change a little bit the example of knocks with a piezoelectric to measure the number of knocks in a selected time (one minute, for example).

However, i don´t know how to write it for calculate a period of time. what should be the syntax?

Do you understand what i try to do?

Any idea? Thanks!!

You could use millis()

eg:

int count = 0;

if(millis() < 60000){
if(button == HIGH)
count++;
}

That would then count the number of presses/knocks in 60 seconds.

You could also do it many other ways, which i'm sure someone else will say in a moment, once they've criticised my code for some reason or another :stuck_out_tongue:

Mowcius

Well mowcius, you should know better. That's some hidious code. If I made a penny for each bug I spotted in that code I'd be a millionaire

:wink:

Yeah what Mow suggested is perfect for simple applications. If you require any major precision check out the timing ICs from Maxim (A quick search will uncover the IC I speak of)

Just a thought, watch out for debounce and various other electronic phenomenon when you're catching "events".

(Switch Debouncing - The Lab Book Pages)

Well mowcius, you should know better. That's some hidious code. If I made a penny for each bug I spotted in that code I'd be a millionaire

How many spaces did I do wrong then? ;D

Well a few after thoughts, probably should have done:

void setup() {
Serial.begin(9600); //Start serial at 9600baud
int count = 0; //Set count to 0
delay(1000); //Wait 1 second

while(millis() < 61000){ //While code has been running for less then 60 seconds:
if(button == HIGH){ //If button is high/knock etc then:
count++; //Increase count by one
}
}
Serial.println(count); //Print count to serial
} //End of code - after this, the code does nothing

void loop(){
//Nothing to go here
}

Mowcius

Thanks mowcius,

Your idea runs well. However, it only runs one time.

Thanks also to TeamMCS for your inputs and link.

My idea is to made a measurement in a period. Form example, every five minutes, to measure the number of knocks in 30 seconds; as well to know the measurement from other sensor, for example temperature, but in that case just only punctual measurement at the end of the 30 seconds.

Here is a pseudocode of what i want to do:

// knocks measurement

Declare the pins
led pin
knock pin
Declare the variables
knockcounter

Procedure: knocks measurement
Reset the knockcounter to 0
Start a period of time (30 seconds, for example)
if the pin read something higher than the therehold,
increase 1 the knockscounter
change the state of the led
at the end of the period of time, know the number of total knocks

prodecure: for example read the temperature with other sensor

void setup()
start the serial

void loop()
start the procedure countknocks
start the procedure read temperature
show the total number of knocks on serial
show the temperature on serial
delay for 5 minutes

and here is the code that i have right now. I only show the procedure related to knocks. The temperature or any other sensor is not a problem. However, it doesn´t work, because the serial even show knocks=0 and the led don´t change at all...

// these constants won't change:
const int ledPin = 13;      // led connected to digital pin 13
const int knockSensor = 0;  // the piezo is connected to analog pin 0
const int threshold = 1;  // threshold value to decide when the detected sound is a knock or not

// these variables will change:
int sensorReading = 0;      // variable to store the value read from the sensor pin
int ledState = LOW;         // variable used to store the last LED status, to toggle the light
unsigned long knockcount=0; // start the knock counter
unsigned long millis();

unsigned long readknocks(){
  sensorReading = analogRead(knockSensor);
  if (millis()<30000){       
    // if the sensor reading is greater than the threshold:
    if (sensorReading >= threshold) {
    // toggle the status of the ledPin:
      ledState = !ledState; 
      knockcount=knockcount+1;  
    // update the LED pin itself:        
      digitalWrite(ledPin, ledState);
    // send the string "Knock!" back to the computer, followed by newline
    } 
  }
  return knockcount;
}


void setup() {
 pinMode(ledPin, OUTPUT); // declare the ledPin as as OUTPUT
 Serial.begin(9600);       // use the serial port
}

void loop() {
  // read the sensor and store it in the variable sensorReading:
  readknocks();                      //start the measuring time
  Serial.print("Knocks: ");          // prepare to print the results
  Serial.println(knockcount,DEC);    // print the number of knocks
//in the future here i will also read a temperature sensor and show it
  knockcount=0;                      //reset the knocks counter
  delay(3000);   //in the future it will be 5 minutes or more...
 }

Any idea about how could i improve the code?
On the other hand, do you think that it is better to use the procedure used in the example "blinkwithoutdelay" included in arduino IDE?

Thanks for your ideas!!

You have this in readKnocks:

if (millis()<30000)

That will work the first time. After the first 30000 seconds that the arduino has been powered up, millis() will never return a value less than 30000.

You need to store that value that millis returns at the start of readKnocks, and then check that millis returns a value less than that time + 30000.

Hi there PaulS,

Thanks!

So, i suppose that you mean something like that?

 if (millis() - previousMillis > interval) {
     previousMillis = millis();

This i from the example of blinkwithoutdelay example of arduino webpage

Something like that, yes. Not exactly like that, though. More like this:

int readKnocks()
{
int knockCount = 0;
long startTime = millis();
while(millis() < startTime + 30000)
{
// Was that a knock I heard?
}
return knockCount;
}

Great thanks PaulS,

I didn´t had on mind that here is possible to use the loops by the use of while and for.... Thanks to focu my attention to that.

I will change my code and show here the results...
Cheers,

This...

while(millis() - startTime < 30000)

...will work a bit better when millis approaches its maximum value.

Ok, now the code looks like this:

int readknocks() {
  sensorReading = analogRead(knockSensor);
  long startTime = millis();
  while(millis() < startTime + 30000) {
     if (sensorReading >= threshold) {
         ledState = !ledState; 
         knockcount=knockcount+1;     
      digitalWrite(ledPin, ledState);
     }
    }
  return knockcount;
}

but i have some problems... it compiles, but it doesn´t run. I think that the problem is how i call the procedure:

void loop() {
  Serial.print("Knocks: ");          // prepare to print the results
  Serial.println(readknocks(),DEC);    // print the number of knocks
  knockcount=0;                      //reset the knocks counter
  delay(30000);                      // wait some time until to start to measure again the knocks
 }

I tryed by both methods:
1.- Serial.println(readknocks(),DEC);
2.-

readknocks();
Serial.println(knockcount,DEC);

i don´t know what i am doing bad... :-[

@Coding Badly, thanks for your code, i just saw it, so i will try it too. However, i don´t know what you mean with

will work a bit better when millis approaches its maximum value.

and what is the difference with the code proposed by PaulS. Thanks for share your knowledge!

it compiles, but it doesn´t run.

That statement is pretty vague. Does loop not execute? Does readKnocks not get called? Does readKnocks always return 0?

In readKnocks, you only read the sensor value once. You need to move the analogRead statement inside the while loop.

I don't have a clue how to describe the problem so I'll post an example. Assume millis is nearing the point when it wraps from 0xFFFFFFFF to 0x00000000. Something like this is what is executed...

  unsigned long startTime = 0xFFFFFFF0;  // millis();
  while(millis() < 0xFFFFFFF0 /*startTime*/ + 30000)

...which reduces to this...

  while(0xFFFFFFF0 < 0x00007520)

Oops, the condition is already false. The while loop is never executed.

The code I posted works like this...

  unsigned long startTime = 0xFFFFFFF0;  // millis();
  while(0xFFFFFFF0 /*millis()*/ - 0xFFFFFFF0 /*startTime*/ < 30000)

...which reduces to this...

  while(0 < 30000)

The first time it works. 29999 milliseconds later we have this...

  while(0x0000751F/*millis()*/ - 0xFFFFFFF0 /*startTime*/ < 30000)

...which reduces to this...

  while(29999 < 30000)

Still good. One millisecond later we have this...

  while(0x00007520/*millis()*/ - 0xFFFFFFF0 /*startTime*/ < 30000)

...which reduces to this...

  while(30000 < 30000)

And the loop is finished.

Oh, and startTime needs to be declared "unsigned long" instead of "long".

It requires more advanced knowledge of the ATmega168, but for my virgin post I'll propose an alternate method using timer 1 input capture and overflow interrupts.

untested...

volatile unsigned int knocks;
volatile boolean myState;

ISR(TIMER1_CAPT_vect) { // TIMER1 Input Capture Event, rising edge on PortB0
  knocks++;
  myState != myState;
  digitalWrite(myPin, myState);
};

ISR(TIMER1_OVF_vect) { // Timer1 Overflow
  static unsigned int overflow_counter = 0;
  overflow_counter++;
  
  // timer1 overflows at T1 = 65536
  // T1 increments at 1/8th of system clock (confirm this)
  // 10000 T1 overflows ~= 5 minutes
  if (overflow_counter == 10000) {  // report and reset every 5 minutes
    serial.print("Found ");
    serial.print(knocks, DEC);
    serial.println(" knocks.");
    knocks = 0;
    overflow_counter = 0;
  }
}

void setup() {
  mySerial.begin(115200);
  myState = false;
  TCCR1B = B11000010;  // Input Capture Noise Canceler = ON, Input Capture Edge Select = RISING, Prescaler = clk/8
  TIMSK1 |= B00100001;  // turn on T1 Input Capture and Overflow Interrupts
}

void loop() {
  // do laundry
}

I'm most familiar with ATmega1280 (ArduinoMega) so I'm not sure if PortB0 is pinned out on the 168, nor am I sure if Timer1 is used for anything else at a prescaler other than 1/8.

[Edit: it also presumes that you connect your piezo sensor to the PortB0 digital input instead of analog in... should be easy with circuit design, or this can be modified to use the ADC_vect interrupt when analog conversion is complete.]

[Edit Nov. 14: revised to capture good suggestions from Coding Badly]

untested...

And needs three changes...

volatile unsigned int knocks;
volatile boolean myState;
void setup() {
  mySerial.begin(115200);
  myState = false;
  TCCR1B = B11000010;  // Input Capture Noise Canceler = ON, Input Capture Edge Select = RISING, Prescaler = clk/8
  TIMSK1 |= B00100001;  // turn on T1 Input Capture and Overflow Interrupts
}

And two words of caution: Accessing either variable from outside an interrupt service routine requires first disabling interrupts. Using Serial in an interrupt service routine can cause problems.

Hi all,

@PaulS. You got it again! I made this change and now it runs perfectly! Measure for some seconds, and later wait for other short time until to came back again. So thanks your great help!

@Coding Badly. Thanks so much for your clear explanation. I am learning a lot thanks to the nice people of this forum, like you. I will use your inputs also. About the serial. this is only for the example, because my idea is not show the result by serial, but to save the result in a SD, but for discuss here, and debugging the code, it is more easy to do it by the use of serial. But you are right. Thanks for discover this problem!

@Mitch-CA. Thanks for start in this forum here! I will try also your code, however for me it sound such as chinese, because i am not familiar at all with things like this:

TIMSK1=B00100001

... On the other hand, i understand that your code reset the arduino every five minutes, right?
Is it better than the code that i have right now? or may be is better to send the arduino to sleep and made it get up after some time (5 minute, for example)?.

Thanks for your efforts and codes! :slight_smile:

@ Coding Badly: thanks for review and good suggestions. I revised my posted code.

@ madepablo: Thanks to this wonderful community, all the register addresses on the ATmega168 have been predefined, so you can treat them like variable names. To utilize all the potential of these microcontrollers you'll want to get familiar with the full datasheet...

And no, my code does not reset the Arduino every five minutes. Timer1 is a 16-bit counter, which (I believe) increments every 8 system clock ticks by default. My code watches for when it rolls-over again from MAX and becomes zero. This is the Overflow Interrupt.

16-bit timers overflow at 2^16 = 65536. So every 65536 * 8 system clock ticks you get a Timer1 overflow. Then within the Timer1 Overflow interrupt handler, every 10000th time it resets the knock count. At 16MHz system clock this is roughly every 5 minutes (I think every 9155th was the more exact calculation).

The answer to your question "is it better" of course is grey. But the advantage of using interrupts is that you can better control loading on the processor. If you needed your Arduino to do a lot of other tasks consider it might sometims be too busy to notice a "knock". With interrupts you can guarantee that a "knock" gets noticed.

Hi Mitch-CA,

Great! I see.... Definitely, i need to learn a lot! I will download the datasheet and understand all the possibilities that it opens! Thanks for the link!

Oh, i see. My idea is just only to made to arduino to read different sensores (temperature, pressure, humidity,...) ones per five minutes, or something like that. Those measurement take just only some millisecons. At the end, i want to leave to arduino to listen for knocks for 30 seconds. Then, i want to save all the acquired data: temperature, pressure,... and number of knocks, into a SD connected to arduino. So i don´t want to use the serial, or leave the arduino listening knocks forever...

So for save batteries (the devide will not be connected to a computer), may be to send arduino to sleep could a nice idea (i am still not hand on this topic). This is why i asked.

Thanks so much Mitch_CA!