Serial Freeze on Serial Input with Interrupts

Hi everyone,

My program is a fan controller that reads the fan speed via interrupts (hall sensor) on pin 2 and sets the speed to the fan motor via pin 6.

I'm using serial read (parseInt) so I can type in the desired fan speed, but when I do, Serial stops displaying new lines. I suspect this is because of buffering associated with the serial read and that mucking with the priority of the interrupt.

Thoughts?

// Pin Defs
const int FANpin = 2;  
const int CONTROLpin = 6;

// Variables
int CONTROLvalue = 0;  // Fan Control Value (20-255)
int NbTopsFan; // Counts fan spins
int Calc; // Fan Calculation Output

typedef struct{  // Defines the structure for multiple fans and their dividers
  char fantype;
  unsigned int fandiv;
}fanspec;

fanspec fanspace[3]={{0,1},{1,2},{2,8}}; // Type of fan
char fan = 1;   // Type 1

// Interrupt Function
void rpmcalc () { 
  NbTopsFan++; // Count fan rotations each interrupt
}

// Setup
void setup() {                
  Serial.begin(9600);
  pinMode(FANpin, INPUT); 
  pinMode(CONTROLpin, OUTPUT);
  attachInterrupt(0, rpmcalc, RISING); 
}

// Main
void loop() {
   NbTopsFan = 0;	// Reset fan spin count
   
   if (Serial.available() > 0) {
     cli();	 //Disable interrupts...not sure if working
     CONTROLvalue = Serial.parseInt();  // Entering anything freezes serial output
     analogWrite(CONTROLpin, CONTROLvalue);  // Write the new speed value
   }
  
   sei();	    // Enables interrupts to begin counting fan spins
   delay (1000);    // Wait 1 second
   cli();	    // Disable interrupts

   Calc = ((NbTopsFan * 60)/fanspace[fan].fandiv); 
   // Times NbTopsFan (which is approximately the frequency the fan is spinning at) by 60 seconds before dividing by the fan's divider
   
   Serial.print("Fan Value: ");
   Serial.print(CONTROLvalue);
   Serial.print(" / Fan Speed:");
   Serial.print(Calc, DEC);
   Serial.println(" RPM");
   delay(500);   
}

Oh, and disabling sei/cli() with the delay makes the serial input functionality work - but...then I don't get any fan readings. :blush:

Serial (Tx and Rx) require interrupts without them it looks up. parseInt() waits for chars but it can't get them as interrupts are turned off.

NbTopsFan; this suould be volatile. Why an int it can't be -ve.

Mark

It would take an awfully fast fan before you need interrupts to get the speed.

Without learning BlinkWithoutDelay (which you should at some point soon anyway) you could try instead of:

   sei();	    // Enables interrupts to begin counting fan spins
   delay (1000);    // Wait 1 second
   cli();	    // Disable interrupts

No interrupt and get rid of the rest of the sei()'s and cli()'s.

declare these variables up top:

unsigned long startTime, waitTime = 1000UL; // for a 1 second timing loop

byte pinStateHistory;

this goes where the count test above was:

  NbTopsFan = 0;
  pinStateHistory = 0; // this might need to be 1, not sure just now, not a big deal
  startTime = millis();
  while ( millis() - startTime >= waitTime )
  {
      pinStateHistory = ( pinStateHistory * 2 ) & 3; // shift the last reads and mask off bits 2-7
      pinStateHistory += digitalRead( FANpin ); // add the current read, now last and current reads are 1 value

      if ( pinStateHistory == 1 )  // last read was LOW, current is HIGH, RISING edge detected
      {
          NbTopsFan++;
      }
  }

And that should give you your rpm count for the second tested.

Please note that this code blocks all other execution just as badly as delay().
You don't have to do this and the fix does not require interrupts.

I don't know whether your signal is fast enough to actually need interrupts, but if it does then turning interrupts on and off and delaying the sketch to count how many interrupts are received in a period is a horrible way to do it. Far better to have an interrupt handler which increments a global counter (which should be volatile, and unsigned, and big enough to hold the maximum number of interrupts you expect in your sample period) and then write some code which runs at regular intervals which determines how many interrupts have occurred in the preceding period. Use the technique demonstrated in blink without delay to run the code at regular intervals.

If your volatile counter needs to be bigger than a byte, when you need to read it in code running in the main context you should disable interrupts, copy the volatile variable to a local temporary variable, and re-enable interrupts.

One approach to counting interrupts is to zero the counter each time you read it. A better approach is to keep a copy of the previous counter value and subtract that from the new value to see how much the counter has changed by. That avoids needing to zero the counter, which means the volatile data is read-only in the main context, which reduces the scope for bugs caused by race conditions.

Rather than fiddling with the interrupts and CLI/SEI just use Serial.available() and Serial.read() to read one byte at a time into a buffer and convert the data to an INT separately.

What range of numbers do you want to send from the PC? If it is just 0-255 then you just need to send a single byte.

...R