Help with Interrupts and PWM read code.

Can someone please help me with the following code, it was written to read 4 channels from a RC receiver, I have tried to extend the code to read 8 channels but it won’t read past the fifth channel.

The author of the original code pointed to Chapter 13 of the atMega328 data sheet for help with the PINB & B00000010 part of the code where I think my problem lies, but I haven’t been able to work it out.

Any help would be gratefully appreciated.

Ken

//code by Kamran A on you tube

unsigned long timer[9];
byte last_channel[8];
int input[8];

void setup() {
  PCICR |= (1 << PCIE0);
  PCMSK0 |= (1 << PCINT0);//pin 8...Rx Channel 1
  PCMSK0 |= (1 << PCINT1);//pin 9...Rx Channel 2
  PCMSK0 |= (1 << PCINT2);//pin 10...Rx Channel 3
  PCMSK0 |= (1 << PCINT3);//pin 11...Rx Channel 4
  PCMSK0 |= (1 << PCINT4);//pin 12...Rx Channel 5
  PCMSK0 |= (1 << PCINT5);//pin 5...Rx Channel 6
  PCMSK0 |= (1 << PCINT22);//pin 6...Rx Channel 7
  PCMSK0 |= (1 << PCINT23);//pin 7...Rx Channel 8
  Serial.begin(9600);
}

void loop() {
  print();
}

ISR(PCINT0_vect) {
  timer[0] = micros();
  // channel 1 ---------------
  if(last_channel[0] == 0 && PINB & B00000001 ) {
    last_channel[0] = 1;
    timer[1] = timer[0];
  }
  else if(last_channel[0] == 1 && !(PINB & B00000001) ) {
    last_channel[0] = 0;
    input[0] = timer[0] - timer[1];
  }
  
  // channel 2 ---------------
  if(last_channel[1] == 0 && PINB & B00000010 ) {
    last_channel[1] = 1;
    timer[2] = timer[0];
  }
  else if(last_channel[1] == 1 && !(PINB & B00000010) ) {
    last_channel[1] = 0;
    input[1] = timer[0] - timer[2];
  }
  
  // channel 3 ---------------
  if(last_channel[2] == 0 && PINB & B00000100 ) {
    last_channel[2] = 1;
    timer[3] = timer[0];
  }
  else if(last_channel[2] == 1 && !(PINB & B00000100) ) {
    last_channel[2] = 0;
    input[2] = timer[0] - timer[3];
  }
  
  // channel 4 ---------------
  if(last_channel[3] == 0 && PINB & B00001000 ) {
    last_channel[3] = 1;
    timer[4] = timer[0];
  }
  else if(last_channel[3] == 1 && !(PINB & B00001000) ) {
    last_channel[3] = 0;
    input[3] = timer[0] - timer[4];
  }
  
  // channel 5 ---------------
  if(last_channel[4] == 0 && PINB & B00010000 ) {
    last_channel[4] = 1;
    timer[5] = timer[0];
  }
  else if(last_channel[4] == 1 && !(PINB & B00010000) ) {
    last_channel[4] = 0;
    input[4] = timer[0] - timer[5];
  }
  
  // channel 6 ---------------
  if(last_channel[5] == 0 && PINB & B00100000 ) {
    last_channel[5] = 1;
    timer[6] = timer[0];
  }
  else if(last_channel[5] == 1 && !(PINB & B00100000) ) {
    last_channel[5] = 0;
    input[5] = timer[0] - timer[6];
  }
  
  // channel 7 ---------------
  if(last_channel[6] == 0 && PINB & B01000000 ) {
    last_channel[6] = 1;
    timer[7] = timer[0];
  }
  else if(last_channel[6] == 1 && !(PINB & B01000000) ) {
    last_channel[6] = 0;
    input[6] = timer[0] - timer[7];
  }
  
  // channel 8 ---------------
  if(last_channel[7] == 0 && PINB & B10000000 ) {
    last_channel[7] = 1;
    timer[8] = timer[0];
  }
  else if(last_channel[7] == 1 && !(PINB & B10000000) ) {
    last_channel[7] = 0;
    input[7] = timer[0] - timer[8];
  }

}

void print() {
  Serial.print(input[0]);
  Serial.print(" - ");
  Serial.print(input[1]);
  Serial.print(" - ");
  Serial.print(input[2]);
  Serial.print(" - ");
  Serial.print(input[3]);
  Serial.print(" - ");
  Serial.print(input[4]);
  Serial.print(" - ");
  Serial.print(input[5]);
  Serial.print(" - ");
  Serial.print(input[6]);
  Serial.print(" - ");
  Serial.println(input[7]);
}

This is my code to attach interrupts to pins for RC controllers it should allow you to have as many inputs as there are pins.

to attach a pin use

pciSetupRC(PinNumger);

there is two additional function
Alive() see if we are getting new values
Print(uint16_t SpamDelay) shows you how to get the values from the array

This is to help you with getting the additional pins the code came from a larger class i created and may not be 100% complete although it works as a class

volatile uint8_t  GroupStartPin[3]  = {0,8,14};
volatile uint8_t  GroupEndPin[3]    = {7,13,19};
union Mask{
	volatile uint32_t All;
	volatile uint8_t  Port[4];
};

Mask Now;
volatile uint32_t Changed; //volatile uint8_t  mask[3];						// Place holder for general Mask Calculations
volatile uint32_t M;    //Localizes altered Mask
volatile uint32_t PCintLast; //volatile uint8_t  PCintLast[3];					// looking fo pin changes using bianary
volatile uint8_t  PinArray[20];                 // Key = Pin Val = Position List of pins that could be used as ping inputs:
volatile uint8_t  PinNumber[22] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, -1, -1, 14, 15, 16, 17, 18, 19};                 // Key = Bit Val = Position List of pins that could be used as ping inputs:
volatile uint8_t  BitNumber[20] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21};                 // Key = Pin Val = Position List of pins that could be used as ping inputs:
volatile uint16_t RCedgeTime[20];      // Pulse HIGH start time for RCInputs & PingInputs

#define RCInputs 3
volatile uint32_t rcPinMask; //volatile uint8_t  rcPinMask[3]; // RC Pin Mask for active RC Pins
volatile uint16_t rcValue[RCInputs];            // interval [1000;2000] 
volatile uint16_t rcValueX[RCInputs];           // interval [0;65535]  
volatile uint16_t rcValueMin[RCInputs];         //  
volatile uint16_t rcValueMax[RCInputs];         // 
volatile uint16_t rcValueCenter[RCInputs];      //  
volatile uint32_t AliveBits = 0;                // a reading Since Last Test,
volatile int8_t   RCIsAlive = 0;
volatile bool     FailSafe = true;
volatile uint8_t  RCPinCtr = 0;                 // Number of Pins Activated
volatile uint16_t RCEdgeTime[RCInputs];      // Pulse HIGH start time for RCInputs & PingInputs

static inline void RCTimers(uint16_t cTime) {
	//cTime Note micros() return a uint32_t, but it is not usefull to keep the whole bits => we keep only 16 bits
	M = Changed & rcPinMask;// only keep RC Data
	if(!M)return;                    //No RC Data move on
	for (uint8_t Position = 0; (Position < RCPinCtr); Position++) {	// Cycle throug all the pins
		uint8_t Bit = BitNumber[PinNumber[Position]];
		if (M >> Bit & 1) {        // See if the pin has changed
			if ((Now.All >> Bit & 1)) RCEdgeTime[Position] = cTime; //Pulse went HIGH store the start time
			else {                         // Pulse Went low calculate the duratoin
				uint16_t dTime = cTime - RCEdgeTime[Position]; // Calculate the change in time
				if (900 < dTime && dTime < 2100){    // Time is within proper RC range 
					rcValue[Position] = dTime;		 // only save proper PWM Pulse
					rcValueMin[Position] = min(rcValueMin[Position], dTime); // only save proper PWM Pulse
					rcValueMax[Position] = max(rcValueMax[Position], dTime); // only save proper PWM Pulse
				}
				rcValueX[Position] = dTime; // Lets Store any duration up to 65535 micro seconds
				AliveBits = AliveBits | 1 << Position;
			}
		}
	}
}

// port change Interrupt
ISR(PCINT2_vect) { //this ISR pins 0-7
	Now.Port[0] = PIND; // 0-7
	Now.Port[1] = PINB; // 8-13
	Now.Port[2] = PINC; // A0-A6
	Changed = Now.All ^ PCintLast;// doing a ^ between the current interruption and the last one indicates wich pin changed
	uint16_t cTime = micros();    // micros() return a uint32_t, but it is not usefull to keep the whole bits => we keep only 16 bits
	sei();                        // re enable other interrupts at this point, the rest of this interrupt is not so time critical and can be interrupted safely
	RCTimers(cTime);        // Calculate RC Data
	PCintLast = Now.All;          // we memorize the current state of all PINs in group

}




ISR(PCINT0_vect) { //this ISR is common to every receiver channel, it is call everytime a change state occurs on a pins 8-13 only need 9-11
	Now.Port[0] = PIND; // 0-7
	Now.Port[1] = PINB; // 8-13
	Now.Port[2] = PINC; // A0-A6
	Changed = Now.All ^ PCintLast;// doing a ^ between the current interruption and the last one indicates wich pin changed
	uint16_t cTime = micros();    // micros() return a uint32_t, but it is not usefull to keep the whole bits => we keep only 16 bits
	sei();                        // re enable other interrupts at this point, the rest of this interrupt is not so time critical and can be interrupted safely
	RCTimers(cTime);	      // Calculate RC Data
	PCintLast = Now.All;          // we memorize the current state of all PINs in group
}

ISR(PCINT1_vect) { //this ISR s A0~A5
	Now.Port[0] = PIND; // 0-7
	Now.Port[1] = PINB; // 8-13
	Now.Port[2] = PINC; // A0-A6
	Changed = Now.All ^ PCintLast;// doing a ^ between the current interruption and the last one indicates wich pin changed
	uint16_t cTime = micros();    // micros() return a uint32_t, but it is not usefull to keep the whole bits => we keep only 16 bits
	sei();                        // re enable other interrupts at this point, the rest of this interrupt is not so time critical and can be interrupted safely
	RCTimers(cTime);        // Calculate RC Data
	PCintLast = Now.All;          // we memorize the current state of all PINs in group
}

SIGNAL(TIMER0_COMPA_vect){ // Timer to check for signal loss
	sei();
	static int MSec=0;
	if(MSec++ < 10)return; // Check every 10 Miliseconds for change
	MSec = 0;
	RCIsAlive = (AliveBits > 0) ?   RCIsAlive+1 : RCIsAlive-1  ;
	RCIsAlive = constrain(RCIsAlive,-20,+20);// Must have 20 tests good or 20 tests bad for RCIsAlive to change states
	if ((RCIsAlive <= 0) && FailSafe){
		for (uint8_t c = 0;c < RCPinCtr ;c++) rcValue[c] = rcValueCenter[c];
	}
	AliveBits = 0;
}

void pciSetupRC(uint8_t pin){
	if(bitRead(PinLock,BitNumber[pin])) return;
	bitWrite(PinLock, BitNumber[pin], 1);
	bitWrite(rcPinMask, BitNumber[pin], 1);
	pciSetup( pin,  INPUT);
	PinArray[pin] = RCPinCtr;
	PinNumber[RCPinCtr] = pin;
	rcValue[RCPinCtr] = 1500;
	rcValueMin[RCPinCtr] = 1100;
	rcValueMax[RCPinCtr] = 1900;
	rcValueCenter[RCPinCtr] = 1500;
	RCPinCtr++;
}



void pciSetup(uint8_t pin, uint8_t InputType){
	pinMode(pin, InputType);// enable interrupt for pin...
	*digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin));  // enable pin
	PCIFR  |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
	PCICR  |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group
}

uint8_t Alive() {
	return(RCIsAlive > 0);
}

void Print(uint16_t SpamDelay) {
     static unsigned long _ATimer; 
     if ((unsigned long)(millis() - _ATimer) >= (t) ){ // print after SpamDelay miliseconds (No Windup)
_               ATimer = millis();
		if (RCPinCtr){
			if(!Alive()) {
				Serial.print("No Signal ");
			} else {
				for (int Ctr = 0; Ctr < RCPinCtr; Ctr++) {
					Serial.print(Ctr);
					Serial.print(F("-"));
					Serial.print(PinNumber[Ctr]);
					Serial.print(F(": "));
					Serial.print(PosTime(Ctr,0));
					Serial.print(F("\t"));
				}			
			}
		}


		Serial.println();
	}
}

Here is some code for 6 channell using the enableInterrupt library, which is in the Library Manager in the IDE.

#include <EnableInterrupt.h>
#define RX_PIN_THROTTLE  2 //input pins
#define RX_PIN_ROLL  3  
#define RX_PIN_PITCH  4   
#define RX_PIN_YAW  5     
#define RX_PIN_AUX1  6    
#define RX_PIN_AUX2  7 
PROGMEM const byte rxPins[6]={//makes the pinmode easier
  RX_PIN_YAW,RX_PIN_ROLL,RX_PIN_PITCH,RX_PIN_AUX1,RX_PIN_AUX2,RX_PIN_THROTTLE};
volatile byte rxState[4]={
  0,0,0,0};
volatile int rxPrev[6]={
  0,0,0,0,0,0};

void rxInit(){
  for(byte i=0;i<6;i++){
    pinMode(pgm_read_byte(&rxPins[i]),INPUT); 
    digitalWrite(pgm_read_byte(&rxPins[i]),HIGH);
  }
  enableInterrupt(RX_PIN_THROTTLE,rxGoesHigh1,RISING);
  enableInterrupt(RX_PIN_ROLL,rxGoesHigh2,RISING);
  enableInterrupt(RX_PIN_PITCH,rxGoesHigh3,RISING);
  enableInterrupt(RX_PIN_YAW,rxGoesHigh4,RISING);
  enableInterrupt(RX_PIN_AUX1,rxGoesHigh5,RISING);
  enableInterrupt(RX_PIN_AUX2,rxGoesHigh6,RISING);
}
void rxGoesHigh1(){
  enableInterrupt(RX_PIN_THRoTTLE,rxGoesLow1,FALLING);
  rxPrev[0]=micros();
}
void rxGoesLow1(){
  enableInterrupt(RX_PIN_THROTTLE,rxGoesHigh1,RISING);
  rxVal[0]=micros()-rxPrev[0];
}
void rxGoesHigh2(){
  enableInterrupt(RX_PIN_ROLL,rxGoesLow2,FALLING);
  rxPrev[1]=micros();
}
void rxGoesLow2(){
  enableInterrupt(RX_PIN_ROLL,rxGoesHigh2,RISING);
  rxVal[1]=micros()-rxPrev[1];
}
void rxGoesHigh3(){
  enableInterrupt(RX_PIN_PITCH,rxGoesLow3,FALLING);
  rxPrev[2]=micros();
}
void rxGoesLow3(){
  enableInterrupt(RX_PIN_PITCH,rxGoesHigh3,RISING);
  rxVal[2]=micros()-rxPrev[2];
}
void rxGoesHigh4(){
  enableInterrupt(RX_PIN_YAW,rxGoesLow4,FALLING);
  rxPrev[3]=micros();
}
void rxGoesLow4(){
  enableInterrupt(RX_PIN_YAW,rxGoesHigh4,RISING);
  rxVal[3]=micros()-rxPrev[3];
}
void rxGoesHigh5(){
  enableInterrupt(RX_PIN_AUX1,rxGoesLow5,FALLING);
  rxPrev[4]=micros();
}
void rxGoesLow5(){
  enableInterrupt(RX_PIN_AUX1,rxGoesHigh5,RISING);
  rxVal[4]=micros()-rxPrev[4];  
}

void rxGoesHigh6(){
  enableInterrupt(RX_PIN_AUX2,rxGoesLow6,FALLING);
  rxPrev[5]=micros();
}
void rxGoesLow6(){
  enableInterrupt(RX_PIN_AUX2,rxGoesHigh6,RISING);
  rxVal[5]=micros()-rxPrev[5];  
}

The values are storeed in rxVal[0-5] in order TAER12
It should be fairly easy to expand.

Take a look at the Arduino Uno pin out diagram

It's not simple to get all 8 pins on one Register with a Uno. .

PortD has 8 pins broken out, has the serial RX and TX pins which you want to use in your sketch. You may be able to load the sketch and use SoftwareSerial for the output.

PortB which you are trying to use, does not break out PB6 and PB7 on the UNO, and if you wanted to hack those pins on the ATmega 328 processor, the external oscillator is connected to them.

PortC only has 6 pins broken out.

I think you can do what you want on a Mega or else possible use on of the pin change interrupt libraries as suggested.

Help with Interrupts and PWN read code.

Do you mean PWM?

Pwn From Wikipedia, the free encyclopedia

Hmm.. I did mean PWM

I will try it on my Mega and see if I can get it to run there, in the mean time the alternative codes provided will be on my to do list this week end! Many thanks :slight_smile:

I am using a Nano to run my test code on but the available pins are much same as the Uno, I have learned something already! Despite having studied pin out diagrams many times I had not noticed the PB, PC and PD nor had I related them to being different ports, whatever that means :confused:

Thank you for your help!!

Ken

That code looks rather lengthy for what it is achieving. Hasn't the author heard of loops?

My page about Interrupts may help you.

In particular see the example code for pin change interrupts:

ISR (PCINT0_vect)
 {
 // handle pin change interrupt for D8 to D13 here
 }  // end of PCINT0_vect

ISR (PCINT1_vect)
 {
 // handle pin change interrupt for A0 to A5 here
 }  // end of PCINT1_vect

ISR (PCINT2_vect)
 {
 // handle pin change interrupt for D0 to D7 here
 }  // end of PCINT2_vect


void setup ()
  { 
  // pin change interrupt (example for D9)
  PCMSK0 |= bit (PCINT1);  // want pin 9
  PCIFR  |= bit (PCIF0);   // clear any outstanding interrupts
  PCICR  |= bit (PCIE0);   // enable pin change interrupts for D8 to D13
  }

And there is a table mapping pins to registers:

D0	  PCINT16 (PCMSK2 / PCIF2 / PCIE2)
D1	  PCINT17 (PCMSK2 / PCIF2 / PCIE2)
D2	  PCINT18 (PCMSK2 / PCIF2 / PCIE2)
D3	  PCINT19 (PCMSK2 / PCIF2 / PCIE2)
D4	  PCINT20 (PCMSK2 / PCIF2 / PCIE2)
D5	  PCINT21 (PCMSK2 / PCIF2 / PCIE2)
D6	  PCINT22 (PCMSK2 / PCIF2 / PCIE2)
D7	  PCINT23 (PCMSK2 / PCIF2 / PCIE2)
D8	  PCINT0  (PCMSK0 / PCIF0 / PCIE0)
D9	  PCINT1  (PCMSK0 / PCIF0 / PCIE0)
D10	  PCINT2  (PCMSK0 / PCIF0 / PCIE0)
D11	  PCINT3  (PCMSK0 / PCIF0 / PCIE0)
D12	  PCINT4  (PCMSK0 / PCIF0 / PCIE0)
D13	  PCINT5  (PCMSK0 / PCIF0 / PCIE0)
A0	  PCINT8  (PCMSK1 / PCIF1 / PCIE1)
A1	  PCINT9  (PCMSK1 / PCIF1 / PCIE1)
A2	  PCINT10 (PCMSK1 / PCIF1 / PCIE1)
A3	  PCINT11 (PCMSK1 / PCIF1 / PCIE1)
A4	  PCINT12 (PCMSK1 / PCIF1 / PCIE1)
A5	  PCINT13 (PCMSK1 / PCIF1 / PCIE1)

So you want two or three ISRs rather than one, and handle the appropriate pin in each one. (See the example code above).