on my mega 2650 I would like to continuously output a speed with Serial.println, which is computed by counting pulses from external interrupts. If these come very quickly, the serial output will stall. Is there an idea how to print serially despite external interrupts?
on my mega 2650 I would like to continuously output a speed with Serial.println, which is computed by counting pulses from external interrupts. If these come very quickly, the serial output will stall. Is there an idea how to print serially despite external interrupts?
Pi
Yes, keep the work that you do inside an ISR to the absolute minimum, offload as much as you can to the main program. Otherwise, you will see effects like that. If you're going to ask, "but how?", then post your code as suggested above.
aarg:
Yes, keep the work that you do inside an ISR to the absolute minimum, offload as much as you can to the main program. Otherwise, you will see effects like that. If you're going to ask, "but how?", then post your code as suggested above.
Here's the code, 3 pins connected to a encoder controling one out pin for a transistor. (some kind of BLDC motor) entire revolution is 4096 counts
#define encoderPINA 2
#define encoderPINB 3
#define encoderPinZ 18
#define SP1 5
#define pos1_ON 1000 //encoder pos1 is switcht on
#define pos1_OFF 1100 //encoder pos1 is switcht off
#define pos2_ON 1300 //encoder pos2 is switcht on
#define pos2_OFF 1400 //encoder pos2 is switcht off
#define pos3_ON 1600 //encoder pos3 is switcht on
#define pos3_OFF 1700 //encoder pos3 is switcht off
#define pos4_ON 1900 //encoder pos4 is switcht on
#define pos4_OFF 2000 //encoder pos4 is switcht off
#define pos5_ON 2200 //encoder pos5 is switcht on
#define pos5_OFF 2300 //encoder pos5 is switcht off
#define pos6_ON 2500 //encoder pos6 is switcht on
#define pos6_OFF 2600 //encoder pos6 is switcht off
//global variables:
volatile unsigned int timerRPM;
volatile unsigned int timerDisp;
volatile bool fZrising;
volatile int encoderCount;
int currentEncCount;
int lastEncCount;
volatile unsigned long encoderErrors; //possible error detection
volatile byte _lastEncoded;
int choice; // to hold user input
//------------------------------------------------------------------------------
ISR(TIMER0_COMPA_vect){ //timer ISR, 1ms
timerRPM++;
timerDisp++;
}
//------------------------------------------------------------------------------
void ISR_pin18_rising(){ //external interrupt on rising edge pin18
fZrising = 1;
}
//------------------------------------------------------------------------------
void ISR_pin2_change(){ //external interrupt on change pin2
doEncoderCount();
}
//------------------------------------------------------------------------------
void ISR_pin3_change(){ //external interrupt on change pin3
doEncoderCount();
}
//------------------------------------------------------------------------------
void doEncoderCount(){
byte EncoderPhaseA = digitalRead(encoderPINA); // Bit0
byte EncoderPhaseB = digitalRead(encoderPINB); // Bit1
byte currentEncoded = (EncoderPhaseB << 1) | EncoderPhaseA;
int sum = (_lastEncoded << 2) | currentEncoded;
switch(sum){
case 0b1011:
case 0b0010:
case 0b0100:
case 0b1101:
encoderCount--;
break;
case 0b0111:
case 0b1110:
case 0b1000:
case 0b0001:
encoderCount++;
break;
default:
encoderErrors++;
break;
}
_lastEncoded = currentEncoded;
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(500000);
while (!Serial) { // wait for serial port to connect. Needed for native USB port only
;
}
Serial.println();
Serial.println("(A)ngle, (r)pm");
Serial.println();
while (Serial.available() == 0) { //Wait for input
}
choice = Serial.read();
switch (choice) {
case 'a':
case 'A':
Serial.println("Angle");
break;
case 'r':
case 'R':
Serial.println("rpm");
break;
default:
Serial.println("default: rpm");
Serial.println();
choice = 'r';
}
hwInit();
}
//------------------------------------------------------------------------------
void hwInit() {
pinMode(SP1, OUTPUT);
pinMode(encoderPinZ, INPUT);
attachInterrupt(digitalPinToInterrupt(18), ISR_pin18_rising, RISING);
attachInterrupt(digitalPinToInterrupt(2), ISR_pin2_change, CHANGE);
attachInterrupt(digitalPinToInterrupt(3), ISR_pin3_change, CHANGE);
TCCR0A=(1<<WGM01); //Set the CTC mode, millis() funzt dann nicht mehr!
OCR0A=0xF9; //Value for ORC0A for 1ms
TIMSK0|=(1<<OCIE0A); //Set the interrupt request
TCCR0B|=(1<<CS01); //Set the prescale 1/64 clock
TCCR0B|=(1<<CS00);
encoderErrors = 0;
sei();
}
//------------------------------------------------------------------------------
void loop() {
// start the cycle / set the flag when passing the zero position
if ( fZrising ) {
encoderCount = 0;
fZrising = 0;
timerRPM = 0;
}
//get actual position
currentEncCount = encoderCount;
bool pos1 = (pos1_OFF > currentEncCount) && (currentEncCount >= pos1_ON );
bool pos2 = (pos2_OFF > currentEncCount) && (currentEncCount >= pos2_ON );
bool pos3 = (pos3_OFF > currentEncCount) && (currentEncCount >= pos3_ON );
bool pos4 = (pos4_OFF > currentEncCount) && (currentEncCount >= pos4_ON );
bool pos5 = (pos5_OFF > currentEncCount) && (currentEncCount >= pos5_ON );
bool pos6 = (pos6_OFF > currentEncCount) && (currentEncCount >= pos6_ON );
//switch the transitor:
bool transistorOn = pos1 || pos2 || pos3 || pos4 || pos5 || pos6;
digitalWrite(SP1, transistorOn);
//choice=='r': display rpm at the end of revolution:
if (choice == 114) {
if (currentEncCount >= 4090) {
//calculate and display rpm's possible at end of each revolution
float rpm = ((float)(60000) / ((float)timerRPM)) ;
Serial.println((int)rpm);
}
}
//choice=='w' (or default all): display angle in encoder increments:
else{
if (currentEncCount != lastEncCount){
Serial.println(currentEncCount);
lastEncCount = currentEncCount;
}
}
}
may be a solution is to pass the rpm value to a second arduino (via IOs) and this one communicates to the Terminal
One significant issue you have (which might look like the serial port stalling) is that you need to disable interrupts when manipulating multi-byte variables shared with an interrupt. If you are testing the variables you can disable interrupts and copy them. If you are resetting them then you need to do so with interrupts disabled.
Try the following changes in loop() and see what happens:
void loop() {
unsigned int timerRPM_temp;
// start the cycle / set the flag when passing the zero position
if ( fZrising ) {
fZrising = 0;
noInterrupts();
encoderCount = 0;
timerRPM = 0;
interrupts();
}
//get actual position
noInterrupts();
currentEncCount = encoderCount;
timerRPM_temp = timerRPM;
interrupts();
bool pos1 = (pos1_OFF > currentEncCount) && (currentEncCount >= pos1_ON );
bool pos2 = (pos2_OFF > currentEncCount) && (currentEncCount >= pos2_ON );
bool pos3 = (pos3_OFF > currentEncCount) && (currentEncCount >= pos3_ON );
bool pos4 = (pos4_OFF > currentEncCount) && (currentEncCount >= pos4_ON );
bool pos5 = (pos5_OFF > currentEncCount) && (currentEncCount >= pos5_ON );
bool pos6 = (pos6_OFF > currentEncCount) && (currentEncCount >= pos6_ON );
//switch the transitor:
bool transistorOn = pos1 || pos2 || pos3 || pos4 || pos5 || pos6;
digitalWrite(SP1, transistorOn);
//choice=='r': display rpm at the end of revolution:
if (choice == 114) {
if (currentEncCount >= 4090) {
//calculate and display rpm's possible at end of each revolution
float rpm = ((float)(60000) / ((float)timerRPM_temp)) ;
Serial.println((int)rpm);
}
}
//choice=='w' (or default all): display angle in encoder increments:
else {
if (currentEncCount != lastEncCount) {
Serial.println(currentEncCount);
lastEncCount = currentEncCount;
}
}
}
I disabled all interrupts but you could choose to disable just the RPM interrupts.
Disclaimer: I haven't dealt with AVR timer code a lot. But I see that there is an sei() at the end of the timer register initialization code. Isn't it recommended to turn interrupts off before executing that code, with a cli()?
ToddL1962:
One significant issue you have (which might look like the serial port stalling) is that you need to disable interrupts when manipulating multi-byte variables shared with an interrupt. If you are testing the variables you can disable interrupts and copy them. If you are resetting them then you need to do so with interrupts disabled.
Try the following changes in loop() and see what happens:
Thanks for your proposal. It performs a bit (10% more rpm without stalling) better. Maybe there is just not enough time at the end of the revolution (4090-4096 incs) to send the rpm.
ToddL1962:
I disabled all interrupts but you could choose to disable just the RPM interrupts.
I can't find a Arduino funktion for dis/enabling separate interrupts. Do you know how?
PiriRoter:
I can't find a Arduino funktion for dis/enabling separate interrupts. Do you know how?
Individual interrupts are disabled / enabled by clearing / setting the corresponding interrupt enable bit in the appropriate interrupt mask register as defined in the processor's datasheet.
I think your fundamental issue is that you shouldn't be trying to write data to the Serial output on EVERY pass through the loop function. That's way too fast for a human to read it anyway. Just do your Serial printing every 10 or every 100 passes through loop.
In the following sketch the serial output is only used every 250ms. Here, too, there are 'dropouts' at higher speeds (> 500 rpm), i.e. no more printing. Perhaps such speeds are simply no longer detectable, the doEncoderCount function delivers 4096 increments per revolution, at 600 rpm that is 40960 per second.
Is that even possible to do with an Arduino?
Are (external) interrupts temporarily stored if they cannot be processed at the moment (e.g. noInterrupts ...)?
I also printed out the encoder errors (states that shouldn't actually exist) in the sketch. Even at low speed, very many appeared. That means that instead of the expected change of state only exactly one track, both or neither with an ext. Int. have changed. I have no idea where that came from.
#define encoderPINA 2
#define encoderPINB 3
#define encoderPinZ 18
#define SP1 5
#define DLED 13 //debug LED on board
#define pos1_ON 1000 //encoder pos1 is switcht on
#define pos1_OFF 1100 //encoder pos1 is switcht off
#define pos2_ON 1300 //encoder pos2 is switcht on
#define pos2_OFF 1400 //encoder pos2 is switcht off
#define pos3_ON 1600 //encoder pos3 is switcht on
#define pos3_OFF 1700 //encoder pos3 is switcht off
#define pos4_ON 1900 //encoder pos4 is switcht on
#define pos4_OFF 2000 //encoder pos4 is switcht off
#define pos5_ON 2200 //encoder pos5 is switcht on
#define pos5_OFF 2300 //encoder pos5 is switcht off
#define pos6_ON 2500 //encoder pos6 is switcht on
#define pos6_OFF 2600 //encoder pos6 is switcht off
//global variables:
volatile unsigned int timerRPM;
volatile unsigned int timerDisp;
volatile bool fZrising;
volatile int encoderCount;
int currentEncCount;
int lastEncCount;
volatile unsigned long encoderErrors; //possible error detection
volatile byte _lastEncoded;
int choice; // to hold user input
int stateLED = 0; ////////////////debug/////////////
//------------------------------------------------------------------------------
ISR(TIMER0_COMPA_vect){ //timer ISR, 1ms
timerRPM++;
timerDisp++;
}
//------------------------------------------------------------------------------
void ISR_pin18_rising(){ //external interrupt on rising edge pin18
fZrising = 1;
}
//------------------------------------------------------------------------------
void ISR_pin2_change(){ //external interrupt on change pin2
doEncoderCount();
}
//------------------------------------------------------------------------------
void ISR_pin3_change(){ //external interrupt on change pin3
doEncoderCount();
}
//------------------------------------------------------------------------------
void doEncoderCount(){
byte EncoderPhaseA = digitalRead(encoderPINA); // Bit0
byte EncoderPhaseB = digitalRead(encoderPINB); // Bit1
byte currentEncoded = (EncoderPhaseB << 1) | EncoderPhaseA;
int sum = (_lastEncoded << 2) | currentEncoded;
switch(sum){
case 0b1011:
case 0b0010:
case 0b0100:
case 0b1101:
encoderCount--;
break;
case 0b0111:
case 0b1110:
case 0b1000:
case 0b0001:
encoderCount++;
break;
default:
encoderErrors++;
break;
}
_lastEncoded = currentEncoded;
}
//------------------------------------------------------------------------------
void setup() {
hwInit();
ask();
stateLED = 0;
}
//------------------------------------------------------------------------------
void ask() {
while (!Serial) { // wait for serial port to connect. Needed for native USB port only
;
}
Serial.println();
while (Serial.available() == 0) { //Wait for input
}
choice = Serial.read();
switch (choice) {
case 'w':
case 'W':
Serial.println("Angle");
break;
case 'r':
case 'R':
Serial.println("rpm");
break;
default:
Serial.println("default: rpm");
Serial.println();
choice = 'r';
}
}
//------------------------------------------------------------------------------
void hwInit() {
Serial.begin(500000);
pinMode(DLED, OUTPUT); //debug LED
pinMode(SP1, OUTPUT);
digitalWrite(DLED, 0);
pinMode(encoderPinZ, INPUT);
attachInterrupt(digitalPinToInterrupt(18), ISR_pin18_rising, RISING);
attachInterrupt(digitalPinToInterrupt(2), ISR_pin2_change, CHANGE);
attachInterrupt(digitalPinToInterrupt(3), ISR_pin3_change, CHANGE);
TCCR0A=(1<<WGM01); //Set the CTC mode, millis() funzt dann nicht mehr!
OCR0A=0xF9; //Value for ORC0A for 1ms
TIMSK0|=(1<<OCIE0A); //Set the interrupt request
TCCR0B|=(1<<CS01); //Set the prescale 1/64 clock
TCCR0B|=(1<<CS00);
encoderErrors = 0;
sei();
}
//------------------------------------------------------------------------------
void loop() {
unsigned int timerRPM_temp;
// start the cycle / set the flag when passing the zero position
if ( fZrising ) {
fZrising = 0;
noInterrupts();
encoderCount = 0;
timerRPM = 0;
interrupts();
}
//get actual position
noInterrupts();
currentEncCount = encoderCount;
timerRPM_temp = timerRPM;
interrupts();
//switch the transistor:
bool pos1 = (pos1_OFF > currentEncCount) && (currentEncCount >= pos1_ON );
bool pos2 = (pos2_OFF > currentEncCount) && (currentEncCount >= pos2_ON );
bool pos3 = (pos3_OFF > currentEncCount) && (currentEncCount >= pos3_ON );
bool pos4 = (pos4_OFF > currentEncCount) && (currentEncCount >= pos4_ON );
bool pos5 = (pos5_OFF > currentEncCount) && (currentEncCount >= pos5_ON );
bool pos6 = (pos6_OFF > currentEncCount) && (currentEncCount >= pos6_ON );
bool transistorOn = pos1 || pos2 || pos3 || pos4 || pos5 || pos6;
digitalWrite(SP1, transistorOn);
//choice=='r': display rpm at the end of revolution:
if (choice == 114) {
float rpm;
//calculate rpm at the end of the rotation:
if (currentEncCount >= 4090) {
rpm = ((float)(60000) / ((float)timerRPM_temp)) ;
}
//display every 205ms:
if (timerDisp>250) {
timerDisp = 0;
noInterrupts();
//Serial.print((int)rpm);
Serial.println(encoderErrors);
encoderErrors = 0;
interrupts();
}
}
//choice=='w' (or default all): display angle in encoder increments:
else {
if (currentEncCount != lastEncCount) {
Serial.println(currentEncCount);
lastEncCount = currentEncCount;
}
}
}
PiriRoter:
Are (external) interrupts temporarily stored if they cannot be processed at the moment (e.g. noInterrupts ...)?
"stored" is not the right word, they are postponed. An external interrupt is latched in hardware. Interrupt sources continue to assert a request if interrupts are disabled. They will be serviced (IRQ vector will be executed) as soon as interrupts are re-enabled.