Timer1 arduino makes Serial not work

2019-05-23 10:04发布

问题:

Running the code below, when I send any character in the Serial arduino is not printing "a" back. I think it's something wrong with the timer1 code but it should work cause this code was given by my teacher in C class.

void setup() {

    Serial.begin(115200);

    //http://www.instructables.com/id/Arduino-Timer-Interrupts/?ALLSTEPS
    noInterrupts();
    TCCR1A = 0;// set entire TCCR1A register to 0
    TCCR1B = 0;// same for TCCR1B
    TCNT1  = 0;//initialize counter value to 0
    // set compare match register for 1000000hz increments with 8 bits prescaler
    OCR1A = 1;// = (16*10^6) / (1000000*8) - 1 (must be <65536)
    // turn on CTC mode
    TCCR1B |= (1 << WGM12);
    // Set CS11 bit for 8 prescaler. Each timer has a different bit code to each prescaler
    TCCR1B |= (1 << CS11);  
    // enable timer compare interrupt
    TIMSK1 |= (1 << OCIE1A);
    interrupts();

}

void loop() {

    if (Serial.available()) {

        Serial.println("a");

    }

}

回答1:

The way you set TCCR1A and B is all correct.

See the 660-pg ATmega328 datasheet pgs. 132~135 for more help & info if you want to know where to look from now on for low-level help.

However, you have 2 major problems, 1 minor problem, and 1 recommendation.

Here are the 2 major problems are completely breaking your code:

  1. Since you are enabling the Timer Compare Match 1A interrupt ("TIMSK1 |= (1 << OCIE1A);"), you MUST also define the Interrupt Service Routine (ISR) which will be called when this happens, or else you will have run-time (but not compile-time) problems. Namely, if you do not define the ISR for Output Compare Match A, once the Output Compare A interrupt occurs, the processor will get stuck in an infinite, empty, dummy ISR created for you by the compiler, and your main loop will not progress (see code below for proof of this).

Add this to the bottom of your code:

ISR(TIMER1_COMPA_vect)
{
  //insert your code here that you want to run every time the counter reaches OCR1A
}
  1. It takes a couple microseconds to step into an ISR, and a couple microseconds to step out of an ISR, plus whatever time is required to run your code IN the ISR, you need to use an OCR1A value that is large enough that the ISR even has time to execute, rather than being continually called so quickly that you never exit the ISR (this would lock up your code essentially into an infinite loop....which is happening in your case as well).
    I recommend you call an ISR no more often than every 10us. Since you are using CTC mode (Clear Timer on Compare match), with a prescaler of 8, I recommend setting OCR1A to nothing less than 20 or so. OCR1A = 20 would call the ISR every 10us. (A prescaler of 8 means that each Timer1 tick take 0.5us, and so OCR1A = 20 would call the ISR every 20*0.5 = 10us).

If you set OCR1A = 20, and add the ISR code as described above, your code will run just fine.

1 Minor problem:

It is good practice to set OCR1A after you configure the rest of the timer, or else under some situations the timer may not start counting (see "Thorsten's" comment here: http://www.righto.com/2009/07/secrets-of-arduino-pwm.html)

So, move OCR1A = 20; to after your last TCCR1B line and before your TIMSK1 line.

1 recommendation:

Get rid of "noInterrupts" and "interrupts". They are not needed here.

Now, here is a code I wrote which will better demonstrate what you're trying to do, and what I'm talking about:

/*
timer1-arduino-makes-serial-not-work.ino
-a demo to help out this person here: http://stackoverflow.com/questions/28880226/timer1-arduino-makes-serial-not-work
By Gabriel Staples
http://electricrcaircraftguy.blogspot.com/
5 March 2015
-using Arduino 1.6.0
*/

//Note: ISR stands for Interrupt Service Routine

//Global variables
volatile unsigned long numISRcalls = 0; //number of times the ISR is called

void setup() 
{
  Serial.begin(115200);

  //http://www.instructables.com/id/Arduino-Timer-Interrupts/?ALLSTEPS
//  noInterrupts(); //Not necessary
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1000000hz increments with 8 bits prescaler
  OCR1A = 20;// = (16*10^6) / (1000000*8) - 1 (must be <65536) //better to put this line AFTER configuring TCCR1A and B, but in Arduino 1.6.0 it appears to be ok here (may crash code in older versions, see comment by "Thorsten" here: http://www.righto.com/2009/07/secrets-of-arduino-pwm.html
  // turn on CTC mode [Clear Timer on Compare match---to make timer restart at OCR1A; see datasheet pg. 133]
  TCCR1B |= (1 << WGM12); 
  // Set CS11 bit for 8 prescaler [0.5us ticks, datasheet pg. 135]. Each timer has a different bit code to each prescaler
  TCCR1B |= (1 << CS11);
  // enable timer compare match 1A interrupt; NOW YOU *MUST* SET UP THE CORRESPONDING ISR OR THIS LINE BREAKS THE CODE
  TIMSK1 |= (1 << OCIE1A);

//  OCR1A = 20;// = (16*10^6) / (1000000*8) - 1 (must be <65536) //SETTING OCR1A TO 1 OR 2 FOR SURE BREAKS THE CODE, as it calls the interrupt too often
//  interrupts();

  Serial.println("setup done, input a character");
}

void loop() 
{
  if (Serial.available()) 
  {
    Serial.read(); //read and throw away the first byte in the incoming serial buffer (or else the next line will get called every loop once you send the Arduino a char)
    Serial.println("a");

    //also print out how many times OCR1A has been reached by Timer 1's counter 
    noInterrupts(); //turn off interrupts while reading non-atomic (>1 byte) volatile variables that could be modified by an ISR at any time--incl while reading the variable itself.
    unsigned long numISRcalls_copy = numISRcalls;
    interrupts();
    Serial.print("numISRcalls = "); Serial.println(numISRcalls_copy);
  }

//  Serial.println("test");
//  delay(1000);
}

//SINCE YOU ARE ENABLING THE COMPARE MATCH 1A INTERRUPT ABOVE, YOU *MUST* INCLUDE THE CORRESPONDING INTERRUPT SERVICE ROUTINE CODE
ISR(TIMER1_COMPA_vect)
{
  //insert your code here that you want to run every time the counter reaches OCR1A
  numISRcalls++;
}

Run it and see what you think.

Proof that "Major Problem 1" above is real (at least as far as I understand it--and based on tests on an Arduino Nano, using IDE 1.6.0):
This code below compiles, but will not continue to print the "a" (it may print it once, however). Note that for simplicity-sake I commented out the portion waiting for serial data, and simply told it to print an "a" every half second:

void setup() {

    Serial.begin(115200);

    //http://www.instructables.com/id/Arduino-Timer-Interrupts/?ALLSTEPS
    TCCR1A = 0;// set entire TCCR1A register to 0
    TCCR1B = 0;// same for TCCR1B
    TCNT1  = 0;//initialize counter value to 0
    // set compare match register for 1000000hz increments with 8 bits prescaler
    OCR1A = 20;// = (16*10^6) / (1000000*8) - 1 (must be <65536)
    // turn on CTC mode
    TCCR1B |= (1 << WGM12);
    // Set CS11 bit for 8 prescaler. Each timer has a different bit code to each prescaler
    TCCR1B |= (1 << CS11);  
    // enable timer compare interrupt
    TIMSK1 |= (1 << OCIE1A);
}

void loop() {
    //if (Serial.available()) {
    //    Serial.println("a");
    //}

    Serial.println("a");
    delay(500);
}

//ISR(TIMER1_COMPA_vect)
//{
//  //insert your code here that you want to run every time the counter reaches OCR1A
//}

This code below, on the other hand, works, and the "a" will continue to print out. The only difference between this one and the one just above is that this one has the ISR declaration uncommented at the bottom:

void setup() {

    Serial.begin(115200);

    //http://www.instructables.com/id/Arduino-Timer-Interrupts/?ALLSTEPS
    TCCR1A = 0;// set entire TCCR1A register to 0
    TCCR1B = 0;// same for TCCR1B
    TCNT1  = 0;//initialize counter value to 0
    // set compare match register for 1000000hz increments with 8 bits prescaler
    OCR1A = 20;// = (16*10^6) / (1000000*8) - 1 (must be <65536)
    // turn on CTC mode
    TCCR1B |= (1 << WGM12);
    // Set CS11 bit for 8 prescaler. Each timer has a different bit code to each prescaler
    TCCR1B |= (1 << CS11);  
    // enable timer compare interrupt
    TIMSK1 |= (1 << OCIE1A);
}

void loop() {
    //if (Serial.available()) {
    //    Serial.println("a");
    //}

    Serial.println("a");
    delay(500);
}

ISR(TIMER1_COMPA_vect)
{
  //insert your code here that you want to run every time the counter reaches OCR1A
}

Extra Resources:

  1. I keep a running list of the most helpful Arduino resources I come across at the bottom of an article I wrote here: http://electricrcaircraftguy.blogspot.com/2014/01/the-power-of-arduino.html. Check them out.

  2. Especially look at the first links, under the "Advanced" section, by Ken Shirriff and Nick Gammon. They are excellent!

Please vote this answer up if it solves your problem, and accept it as the correct answer; thanks!

Sincerely,
Gabriel Staples
http://www.ElectricRCAircraftGuy.com/



回答2:

Gabriel Staples is quite correct, the reason you are not seeing the "a" is because you have not provided an ISR handler for the interrupt. Thus the compiler-generated code jumps back to address 0x0000 and your sketch restarts.

An alternative to providing the "empty" ISR handler is this:

EMPTY_INTERRUPT (TIMER1_COMPA_vect);

With the EMPTY_INTERRUPT handler there I got a response (the "a") with OCR1A as low as 1:

OCR1A = 1;

Although one has to wonder why you enable interrupts if you aren't planning to do anything with them.

More information about interrupts on the Arduino.



回答3:

you wrote 2 times this register

TCCR1B |= (1 << WGM12);
TCCR1B |= (1 << CS11);

while I think that it could probably be:

TCCR1A |= (1 << WGM12);
TCCR1B |= (1 << CS11);

Probably the only mistake is there, because you forgot to set TCCR1A and you set 2 times the other one.



回答4:

TCCR1A |= (1 << WGM12); is a bitwise operation (bitwise OR). In this particular case is setting only one bit of TCCR1A, the one in position WGM12. TCCR1B |= (1 << CS11); is setting a different bit in position CS11



回答5:

Depending on what the program needs to do with such a fast interrupt, e.g. generating an high speed clock on an output pin, one can set it in hardware using COM bits in TCCR1A (out of my memory the 4 most significative bits) to toggle the output on a pin associated with the timer with non need to write ISR() callback do handle the timer interrupt in software.



标签: timer arduino