Replicate Waveform Exactly on Seperate Pin

So I've got a waveform that's high for 16ms and low for 2ms repeating forever. I need to recreate that and offsets of that waveform time exact on a different pin. Right now, I've got an interrupt on the rising edge for the 16ms low/high transistion, and delayMicroseconds() that determines the offset and duration of the high and low pulses. Trouble is, I can only get it to trigger the sequence once, and I'm not sure the code I have is as fast and accurate as it could be. Any suggestions?
Here is the code:

//3 is interrupt for Timing
//4 through 11 are Time dependent receivers
//3-11 Pins 12-19
char incomingByte = 0;

int pins[9] =
{
  3,4,5,6,7,8,9,10,11
};

void timing()
{
  //Serial.println("This is a 0ms Event");
 // if(Serial.available() > 0)
 // {
    incomingByte = Serial.read();
    Serial.print("I received");
    Serial.println(incomingByte);

     pinMode(10, OUTPUT);
     digitalWrite(10, HIGH);
     delayMicroseconds(16000);
      digitalWrite(10, LOW);
      delayMicroseconds(2000);
      digitalWrite(10, HIGH);
 }
//}

void setup() {
  // put your setup code here, to run once:

for(int i=0;i<8;i++)
{
  pinMode(pins[i], INPUT);
}
pinMode(3,INPUT);
attachInterrupt(1,timing,RISING);

//Begins Serial
Serial.begin(115200);
}

void loop() {
  // put your main code here, to run repeatedly:

}

Here is an image of the waveform I'm trying to duplicate

Trouble is that with this code the replicated waveform seems to shift through the full period of the original one. I need it to be locked in.
Any help would be very appreciated.

Don't do serial prints in an ISR

Edit: What do you mean "and offsets" DC offsets?

I mean shifting that waveform in two millisecond intervals to the right, once I've figured out how to make it accurate. If I can't use delay(), millis() doesn't function, how am I supposed to do what I described? The serial is just there for testing, but I will need to be able to pass serial data into the interrupts

You can't use Serial.print in an interrupt routine, because interrupts, which Serial requires, are disabled until the interrupt code finishes.

I would use the very minimum code I could in the interrupt: This should duplicate the input waveform on pin 2 and output it, offset on pin 3. It won't necessarily be exact, because micros() counts by 4, but it'll be close.

const int inputPin = 2;
const int outputPin = 3;

unsigned long offset;
unsigned long changeTime;
bool level;
bool gotChange = false;
bool counting = false;

void timing() {
  changeTime = micros();
  level = digitalRead(inputPin);
  gotChange = true;
}

void setup(){
  // set up your pins
  // set offset value in microseconds
  // set up Serial here if you need it
}

void loop() {
  if ( gotChange) {
    gotChange = false;
    counting = true;
  }

  if (counting ) {
    if ( micros() - changeTime >= offset ) {
      digitalWrite(outputPin, level );
      counting = false;
    }
  }
}

benl11235:
I mean shifting that waveform in two millisecond intervals to the right, once I've figured out how to make it accurate. If I can't use delay(), millis() doesn't function, how am I supposed to do what I described? The serial is just there for testing, but I will need to be able to pass serial data into the interrupts

If you mean you want to send offset data by Serial, to the sketch, you don't need that Serial data passed into the interrupt routine.

Gather Serial data in loop(), and change the offset accordingly. Have a look at the code I just posted. It may not do all you want, but it should show you a pretty good technique. With this technique, you don't care how long your pulses are. It will just follow them, with the offset you put in.

oops.. forgot one important thing. use CHANGE on the input pin, rather than RISING.

Of course:
Make variables shared with the main code volatile

LarryD:
Of course:
Make variables shared with the main code volatile

That too!

One good place to start is over at:

You want to go down to the section titled "Timer interrupts" and study it a bit.

With that said, your code is going to run once every time you hit D3. And unless you have got a veeerrry fast (and steady) finger, I don't think that you are going to get the timing that you want.

Now if you want to hit a button to get the show rolling, then use a volatile variable and set that to true when you hit your button. Then use the state of this variable to call your "timing ()" function.

But what I think that you really want is to have an interrupt generated every so often and then act on that.

So for the simplest form of your request, you could set up a timed interrupt (which is why I pointed you to that spot on Nick Gammon's site) and have that interrupt cycle every 2 ms. Each time it cycles you could increment a counter (eight times for your 16 ms, once for your 2 ms.)

For your staggered pulses, you would want to make the interrupt fire at whatever the overlap time (rate?) would be. Then count up and set your outputs high/low as needed.

Just a quick idea. I get the impression you want to generate your base waveform, but with adjustable varying delay. I don't know if the following would work, but it might be fun to try.

Suppose you take your base waveform and hook it to both UNO interrupt pins (2 and 3). Trigger one interrupt with the falling edge, the other interrupt with the rising edge. When the falling edge interrupt is triggered, use a delay, then write an output pin low. Likewise, but in reverse, for the other interrupt, using the same delay (the offset you want).

In the main loop, you could service a button (or two) to adjust delay forward and back.

Of course, the delay you specify might need to be trimmed somewhat to compensate for the unavoidable software delays of the processor.

Hope this is useful.

John Doner

jrdoner...

At no time did the OP mention a button. He has an incoming waveform he wants to duplicate, but with an offset in time.

lar3ry is correct. :wink: I'm actually hacking a 1990s digital typewriter, which has some really annoying keyboard polling practices.

I'll be sending letters and words to the program, and each individual letter is the waveform in my picture at some delay of 2-16ms on a plethora of input pins. More of a writeup later, but you guys know a lot more about interrupts than I did. I definitely have not heard of timer interrupts. I really like rootboy's idea of timer interrupts, it sounds a lot more accurate than my existing code.

I need a minimum of delay as possible, as the pulse for a particular letter is only 2ms long. If I miss it, I miss the letter till the next cycle. The offset data is preset, but the particular offset needed varies by the input letters. In addition there are 8 offsets that need to be done. I should be able to get away with one timer interrupt referenced against my reference waveform. Can I call an interrupt from an interrupt

I won't be able to adjust the delay forward and back, as it needs to constantly output the correct pulses

What do you guys think of this code? I've got 2 timer interrupts and a rising interrupt. The rising interrupt just enables the first timing interrupt with a HIGH duration, the timer interrupt fires and brings the voltage low and enables another interrupt for the time till HIGH again. I'm still debugging this, so I'd appreciate advice and errors.

My main issue is that the pin isn't held low for the requested 2 milliseconds. It's offset from the target waveform for 2 milliseconds, which is good. I think the issue is in the initialization of the second timer interrupt as changing the time doesn't do anything. Timer interrupt 1 is working great though

//3 is interrupt for Timing
//4 through 11 are Time dependent receivers
//3-11 Pins 12-19

//Curent Serial Byte(Letter or Number)
char incomingByte = 0;
//Previous Byte in the Serial Chain
char previousByte = 0;

//Iterator for Interrupt Values
volatile unsigned int maincount = 0;

//Time of High Period or Timer Duration
volatile unsigned int timerhigh;

//Output Pin for Timer Waveform
volatile unsigned int waveout;


//Time for Timer Delay in milliseconds, default tim
volatile unsigned int groundtime = 2000;

//Receiver Pins in an Array, except for 3, which serves as 0 source reference
int pins[9] =
{
  3,4,5,6,7,8,9,10,11
};

void rising()
{   
  // set up Timer 1
  TCCR1A = 0;  // normal mode
  TCCR1B = bit(WGM12) | bit(CS11);  // CTC, scale to clock / 8
  OCR1A = timerhigh;          // time before timer fires
  TIMSK1 = bit (OCIE1A);            // interrupt on Compare A Match

 }
//}

//Sets Pin Low, then sets up another timer interrupt for the jump back to high
ISR(TIMER1_COMPA_vect)
{
//Brings Pin Low for Keypress
pinMode(waveout,OUTPUT);
digitalWrite(waveout, LOW);

//Stop Timer 1
 TCCR1B = 0;                      // stop timer
 TIMSK1 = 0;                      // cancel timer interrupt

 // set up Timer 2
  TCCR2A = 0;  // normal mode
  TCCR2B = bit(WGM12) | bit(CS11);  // CTC, scale to clock / 8
  OCR2A = timerhigh;          // time before timer fires
  TIMSK2 = bit (OCIE1A);            // interrupt on Compare A Match
  
  //Cancel Rising Interrupt on D3
  EIFR = bit (INTF1); 

}

//Timer Interrupt for Low Period
ISR(TIMER2_COMPA_vect)
{
//Brings Output pin high
digitalWrite(waveout, HIGH);
  
//Stop Timer 2
 TCCR2B = 0;                      // stop timer
 TIMSK2 = 0;                      // cancel timer interrupt  

//Cancel Rising Interrupt on D3
 EIFR = bit (INTF1);   
}
void setup() {
  // put your setup code here, to run once:

//Sets all the pins for output use
for(int i=0;i<8;i++)
{
  pinMode(pins[i], INPUT);
}

//Sets Reference Pin as Input
pinMode(3,INPUT);


//Begins Serial
Serial.begin(115200);

TCCR1A = 0;  // normal mode
TCCR1B = 0;  // stop timer
TIMSK1 = 0;   // cancel timer interrupt
attachInterrupt(1,rising,RISING);

}

void loop() {

  //Time of Ground Drop(2 mS), Universal for all letters and offsets
  groundtime = 4000;     // spark time (4000 * 500 nS) = 2 mS
   
  // because of prescaler of 8, each unit is 500 nS (0.5 µS)
  
    // if(Serial.available() > 0);
   // {
   //Reads Serial Value and assigns to incomingByte
   incomingByte = Serial.read();
   
 // if (false)  // if we need to change the time, insert condition here ...
  //  {
  //  noInterrupts ();  // atomic change of the time amount

   //In this area, we'll set the output pin to be used by the timing interrupt, 
   //and the time delay off the default waveform present on pin 4
   
   //We can use switch case arrangement, don't know if that's the optimal setup though    
 switch(incomingByte)
 {
   default:
   //Do Nothing
   break;

   //Cases FOR ALL THE LETTERS
   case 'a':
   break;
   case 'b':
   break;
   case 'c':
   break;
   case 'd':
   break;
   case 'e':
   break;
   case 'f':
   break;
   case 'g':
   break;
   case 'h':
   break;
   case 'i':
   break;
   case 'j':
   break;
   case 'k':
   break;
   case 'l':
   break;
   case 'm':
   break;
   case 'n':
   break;
   case 'o':
   break;
   case 'p':
   break;
   case 'q':
   break;
   case 'r':
   //Output pin 17 Frequency Pin 10
   //microseconds
   timerhigh=4000;
   waveout=9;
   break;
   case 's':
   break;
   case 't':
   break;
   case 'u':
   break;
   case 'v':
   break;
   case 'w':
   break;
   case 'x':
   break;
   case 'y':
   break;
   case 'z':
   break;
   
   //Cases FOR ALL THE NUMBERS
   case '0':
   break;
   case '1':
   break;
   case '2':
   break;
   case '3':
   break;
   case '4':
   break;
   case '5':
   break;
   case '6':
   break;
   case '7':
   break;
   case '8':
   break;
   case '9':
   break;
   
   //Cases FOR ALL THE PUNCUATION
   case '.':
   break;
   case ',':
   break;
   case '!':
   break;
   case '"':
   break;
   case '#':
   break;
   case '

:
  break;
  case '%':
  break;
 
}
   
   
   
//Enables Rising Interrupt for 0ms detection
attachInterrupt(1,rising,RISING);
   
//Reenables interrupts after offset time change
interrupts ();
//   }
   //if(incomingByte == previousByte)
 //  {
     //nochange
  // }

}
//}

Let's see if I have this right.

Pin 3 is a constant square wave. When you receive a Serial byte, you want to set up a delay that ranges from 2 to 16 ms, and output that replicated waveform on one of the 8 output pins. So pins 4, 5, 6, 7, 8, 9, 10, and 11 will have a replicated waveform delayed 2, 4, 6, 8, 10, 12, and 16 ms, respectively, but only after a Serial byte is received.

You are interested only in the 2 ms LOW portion of the input waveform, and all the outputs can remain high until such time as you drive them low; the pin being driven LOW returning to HIGH 2 ms later.

Assuming I have the above correct, here are a few questions:

  1. Would you expect to receive Serial bytes faster than 18 ms apart? If so, do you expect to ever overflow the 64 byte Serial Buffer if you don't get back to reading the Serial buffer before outputting the 2 ms LOW pulse?

  2. What is the 17 in the coment //Output pin 17 Frequency Pin 10 , and am I correct in assuming that for an 'r', you want to send out a 2 ms LOW pulse, delayed 14 ms, and that it doesn't matter how long Pin 10 was HIGH before the 2 ms pulse? I am guessing that pin 17 is on the typewriter connector.

  3. What is the timing tolerance in the duration of the 2 ms pulse, both in the delay time and the duration?

I am asking these questions because while I like the idea of timer interrupts, I'd like to keep pursuing the method I posted, partly because I like it, and partly to give you another idea to test against the timer interrupt method.

What brand/model typewriter is it?

Many years ago, when I built my first computer, based on a Signetics 2650 chip, with 1K of memory, I wrote a routine to drive a fairly simple circuit that sent tilt/rotate codes to an IBM Selectric typewriter. It was pretty wierd, but it worked like a charm. The print routine set a pointer to the first character, and sent it out to the typewriter, and "print complete" interrupts coming back vectored to the routine and it just kept going until it hit a NULL terminator.