Rover 5 Infrared Code Processing Notes

ATmega328 Timer Programming to Emit Infrared Codes

Here are some excerpts from my second program, that clocks-out/emits the Infrared Codes that I recorded with my first program:

// For the moment, Timer2 is clocking out our 38kHz pulses,
// to be modulated/gated on&off by Timer1, which is clocking
// out the contents of:
//   numPulsesStored in pulseLengths[250] in ircodes.h, using:
//   nextPulseOutputNdx, which initially = 0;
//
const Timer2::portActionType timer2FlopAandB =
  (Timer2::portActionType)
  (Timer2::TOGGLE_A_ON_COMPARE | Timer2::TOGGLE_B_ON_COMPARE);

// Apparently Timer2 only works if both OCR2A and OCR2B are flopping.
// Probably this is due to the CTC-TOP is stored in OCR2A, and we've
// set up a Pin Change Interrupt on OCR2B.
// Presumably we could change this to use OCR2A-only for both.
// Something to try later on.
// Note: I tried switching to OCR2A, and perhaps didn't implement this
// correctly, as this change causes the program to spew out gibberish
// (and not work).
//
const Timer2::portActionType timer2FlopSetting =
  timer2FlopAandB;

// 38kHz Compare Match Value can be calculated by:
//   OCR2A = (F_CPU / 38000L / 2) - 1;  // zero relative
// This is with Timer2::PRESCALE_1 set.
//
const Timer2::prescaleActionType timer2preScaleTwoFiveSixClock =
  Timer2::PRESCALE_256;

const Timer2::prescaleActionType timer2preScaleOneClock =
  Timer2::PRESCALE_1;

const Timer2::prescaleActionType timer2preScaleNoClock =
  (Timer2::prescaleActionType)0;

const Timer2::prescaleActionType timer2preScaleSetting =
#ifdef RUN_SLOW_LED_SPEED
  timer2preScaleTwoFiveSixClock;
#else
  timer2preScaleOneClock;
#endif

void setUpTimer2( void)
{
  Timer2::pwmModeType ctcMode = 2;    // 2: CTC, top = OCR2A.
    
  Timer2::setMode(
    ctcMode, timer2FlopSetting,
#ifdef DEBUGT2
    timer2preScaleSetting
#else
    timer2preScaleNoClock
#endif
  );

  // Pin Change Interrupt Control Register (See Datasheet section 13.2.4).
  // PCIE2 = Any change on any Enabled Pin of PCINT[23:16]
  // will cause the PCI2 ISR to be called.
  // PCINT[23:16] pins are enabled individually in the
  // PCMSK2 Register.
  //
  PCICR |= bit( OC2PCIEBIT);    // Enable pin change interrupts for D0 to D7.

  // Pin Change Interrupt Flag Register (See Datasheet section 13.2.5).
  // This bit is set when a pin change happens on any of
  // PCINT[23:16], and is cleared when the ISR is called.
  //
  PCIFR |= bit( OC2PCIFBIT);    // clear any outstanding interrupts.

  // Pin Change Mask Register 2 (See Datasheet section 13.2.6).
  // Pin change interrupt from Timer2's OC2B output.
  // A Pin Mapping Table can be found here:
  //   http://brittonkerin.com/cduino/pin_map.html
  //
  PCMSK0 = 0;              // Clear all these out first.
  PCMSK1 = 0;
  PCMSK2 = 0;
  OC2PCMSKR = bit( OC2PCPIN);  // We want Pin 3 only, or Pin 11.

// setTimer2PulseDuration( timer2CompareValue);
// Do the following instead of calling setTimer2PulseDuration().
//
  OCR2A = timer2CompareValue;
#ifdef USEPCITIMER2B
  OCR2B = timer2CompareValue;
#else
  OCR2B = 0;
#endif
  TCNT2 = 0;

  // Initially we don't want Timer2 blinking out IR codes :-(,
  // so let's disconnect its clock.
  //
#ifndef DEBUGT2
  stopTimer2( false);
#endif
}

// It may take a full loop all the way through the timer for the OCR2x
// to trip, which may be explaining the flipped output for the 2nd half
// of the IR pulse output :-(.
//
void setTimer2PulseDuration( byte pulseDuration)
{
  stopTimer2( false);  // Clean out any other nasty bits, so only ours go in.

  TCCR2A |= timer2FlopSetting; // but we have to Flop both A and B, so
  TCCR2B |= timer2preScaleSetting;  // let's set it.

  OCR2A = pulseDuration;
#ifdef USEPCITIMER2B
  OCR2B = pulseDuration;       // We don't really care about this output,
#else
  OCR2B = 0;
#endif
  TCNT2 = 0;                   // Re-start Timer2 at zero.
}

// ISR( TIMER1_COMPA_vect) just returns when numPulsesStored <= 0.
//
// ISR( TIMER1_COMPA_vect) also stops both Timers and returns if:
//
//     nextPulseOutputNdx >= numPulsesStored
//
// meaning that we've clocked out all the stored pulse durations, and
// we should stop everything now.
//
// Timer1 will set Timer2 in turn to either NO_CLOCK or PRESCALE_256,
// alternating between the two for each entry in pulseLengths[].
//
// For the moment, Timer1 is clocking out the contents of:
//   numPulsesStored in pulseLengths[250], using:
//   nextPulseOutputNdx, which initially = 0;
//
// 38kHz Compare Match Value can be calculated by:
//   OCR1A =  (F_CPU / 38000L / 2) - 1;  // zero relative
//
// For debugging w/LED's, our IR "38kHz" rate is:
//   ~31.5Hz, or 31.75mS.
//
// So debugging w/LED's durations, we want Timer1's:
//   "Long"  pulse to be 31.75mS.*30 = 952mS.
//   "Short" pulse to be 31.75mS.*15 = 476mS.
//   "Off"   pulse to be 31.75mS.*10 = 318mS.
//
// The OCR1 value to get close to that with PRESCALE_256 is:
// 16,000,000Hz / PRESCALE_256 = 62500Hz.
// 62500Hz / 31.5Hz = ~1984.
//
// Our "real" durations (as captured) should be as near as possible to:
//   longTime  = 80u; (actually: 80 * 100uS. = 8mS.)
//   shortTime = 45u;
//   offTime   = 35u;
//
const Timer1::portActionType timer1FlopOnlyA =
  Timer1::TOGGLE_A_ON_COMPARE;

const Timer1::portActionType timer1FlopSetting =
  timer1FlopOnlyA;

const Timer1::prescaleActionType timer1preScaleTwoFiveSixClock =
  Timer1::PRESCALE_256;

const Timer1::prescaleActionType timer1preScaleSetting =
  timer1preScaleTwoFiveSixClock;
  
const Timer1::prescaleActionType timer1preScaleNoClock =
  (Timer1::prescaleActionType)0;
  
void Timer1::setMode(
  pwmModeType pwmMode, portActionType portAction,
  prescaleActionType preScaleAction)
{
  if (pwmMode < 0 || pwmMode > 15)  // Sanity check.
    return;

  // Let's not risk inheriting any left-over garbage settings
  // by using "|=", thus we don't need to set them = 0 first.
  //
  TCCR1A = (Modes[pwmMode][0]) | portAction;  
  TCCR1B = (Modes[pwmMode][1]) | preScaleAction;
}  // end of Timer1::setMode.

void setUpTimer1( void)
{
  Timer1::pwmModeType ctcMode = 4;    // 4: CTC, top = OCR1A.

  Timer1::setMode(
    ctcMode, timer1FlopSetting,
#ifdef DEBUGT1
    timer1preScaleSetting);
#else
    timer1preScaleNoClock);
#endif
  startTimer1();
  
#ifndef DEBUGT1
  stopTimer1();    // We don't need it blinking until we start clocking
#endif             // out IR codes.
}    

const byte timer2CompareValue =
#ifdef RUN_SLOW_LED_SPEED
  249;    // Counts 250 times.
#else
// 38kHz Compare Match Value can be calculated by the following,
// (this is with Timer2::PRESCALE_1 set, zero-relative):
//
  (F_CPU / 38000L / 2) - 1;
#endif

enum {
  NEXT_STATE_IS_OFF,
  NEXT_STATE_IS_LONG,
  NEXT_STATE_IS_SHORT,
  NEXT_STATE_IS_GAP
};

// Timer1 is gating Timer2 pulses, by doing:
//
//   TCCR2A &= ~Timer2::TOGGLE_B_ON_COMPARE;
//
// periodically, according to the pulse durations read from ircodes.h.
//
ISR( TIMER1_COMPA_vect)
{
  static boolean gapFound = false;
  int nextPulseOutputDuration = pulseLengths[nextPulseOutputNdx++];

  if (numPulsesStored <= 0)
    return;                  // No need for all the stoppage just below.

  if (nextPulseOutputNdx >= numPulsesStored )
  {
#ifdef DEBUG_OSCOPE_PROBE_CONNECTORS
      nextPulseOutputNdx = 0;   // Just keep repeating the current pulses,
                                // so we can jiggle the connectors until
                                // a signal appears on the 'Scope.
                                // Note: To do that Connector debugging,
                                // set the Trigger-Mode=None, and the
                                // Memory Size to the minimum (360).
#else  
    stopTimer2( true);
    stopTimer1();
#endif
    return;     // Our work here is done.
  }

  // 0 = Off, 1 = Long, 2 = Short, 3 = Gap.
  //
  if (nextPulseOutputDuration < -2)
  {
    queueFakeSettingUpdatedMsg(
      "\n*** Gap: nextPulseOutputNdx", (nextPulseOutputNdx - 1) / 2);
    queueFakeSettingUpdatedMsg(
      "*** Gap: nextPulseOutputDuration", nextPulseOutputDuration);

    nextState = NEXT_STATE_IS_GAP;      // Can't really do this
    nextPulseOutputDuration = gapDuration;    // at present: *= -10;

    gapFound = true;
  }
  else if (nextPulseOutputDuration <= offDuration)
  {
    nextState = NEXT_STATE_IS_OFF;
  }
  else if (nextPulseOutputDuration <= shortDuration)
  {
    nextState = NEXT_STATE_IS_SHORT;
  }
  else if (nextPulseOutputDuration <= longDuration)
  {
    nextState = NEXT_STATE_IS_LONG;
  }
  else
  {
    queueFakeSettingUpdatedMsg(
      "Mystery nextPulseOutputNdx", (nextPulseOutputNdx - 1) / 2);
    queueFakeSettingUpdatedMsg(
      "Mystery nextPulseOutputDuration now", nextPulseOutputDuration);
  }
  operateTimer2( nextState, nextPulseOutputDuration);
}
// End of TIMER1_COMPA_vect.

// 0 = Off, 1 = Long, 2 = Short, 3 = Gap.
//
void operateTimer2( int nextState, int nextPulseOutputDuration)
{
  queueFakeSettingUpdatedMsg( "\nnextStateOT", nextState);

  // "/ 2" so we don't count the "Off" times, to better match up with the
  // comment indices in ircodes.h.
  //
  queueFakeSettingUpdatedMsg(
    "nextPulseOutputNdx", (nextPulseOutputNdx - 1) / 2);

  queueFakeSettingUpdatedMsg(
    "nextPulseOutputDuration", nextPulseOutputDuration);

  switch (nextState)
  {
  case NEXT_STATE_IS_LONG:  // Enable Toggle OC2* on Compare Match.
  case NEXT_STATE_IS_SHORT:
    setTimer2PulseDuration( timer2CompareValue);
    break;

  case NEXT_STATE_IS_OFF:  // Disable Toggle OC2* on Compare Match.
  case NEXT_STATE_IS_GAP:
    stopTimer2( true);
    break;
  }

  // Set how long we'll wait 'til the next Interrupt fires.
  //
  setTimer1PulseDuration( nextPulseOutputDuration);
}

Back to Previous Page...