SMPTE Timecode reader ICP pin assignments.

Hello all,

I’ve spent a few days now researching how to try to port the code below, which I found on this forum in some older threads.

To state the problem, the code is written for an Arduino Uno it seems, but I am using a Mega 2560 and have been unsuccessful in getting any input to show up on the screen.

The screen, a DF Robot SKU0009, works just fine and stops at the “Finished setup” message (line 149).

I believe this is caused by the fact that ICP1 on the Uno is placed on pin D8, while the documentation on the 2560 has it assigned to D47. However, redefining the input pin as seen on line 18 doesn’t seem to work. I’ve also tried pins 17-21 and 52-53 from some googling.

I’m about 99 % sure that the problem doesn’t lie with my connection circuit, but if there isn’t a clear coding issue, I’ll gladly take tips on how to change it.

 // Code from forum post Dec 12, 2007
//
//

// include the library code:
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);


#define one_time_max          600 // these values are setup for PA video
#define one_time_min          400 // It's the durstion of a one and zero with a little bit of room for error. 
#define zero_time_max          1050 // 
#define zero_time_min          950 // 


#define icpPin 47      // ICP input pin on arduino !!! WAS 8 IN ORIGINAL CODE !!!
//#define one_time_max      475 // these values are setup for NTSC video
//#define one_time_min      300 // PAL would be around 1000 for 0 and 500 for 1
//#define zero_time_max    875 // 80bits times 29.97 frames per sec
//#define zero_time_min    700 // equals 833 (divide by 8 clock pulses)

#define end_data_position   63
#define end_sync_position   77
#define end_smpte_position     80

volatile unsigned int pin = 13;
volatile unsigned int bit_time;   // volatile instructs the variable to be stored in RAM
volatile boolean valid_tc_word;   // booleon can be either of two values true or false
volatile boolean ones_bit_count;  // booleon can be either of two values true or false
volatile boolean tc_sync;         // booleon can be either of two values true or false
volatile boolean write_tc_out;    // booleon can be either of two values true or false
volatile boolean drop_frame_flag; // booleon can be either of two values true or false

volatile byte total_bits;   //this stores a an 8-bit unsigned number
volatile byte current_bit;  //this stores a an 8-bit unsigned number
volatile byte sync_count;   //this stores a an 8-bit unsigned number

volatile byte tc[8];         //this stores a an 8-bit unsigned number
volatile char timeCode[11];  //this stores a an 8-bit unsigned number


/* ICR interrupt vector */
ISR(TIMER1_CAPT_vect)   //ISR=Interrupt Service Routine, and timer1 capture event
{
  //toggleCaptureEdge
  TCCR1B ^= _BV(ICES1); //toggles the edge that triggers the handler so that the duration of both high and low pulses is measured.

  bit_time = ICR1; //this is the value the timer generates

  //resetTimer1
  TCNT1 = 0;

  if ((bit_time < one_time_min) || (bit_time > zero_time_max)) // this gets rid of anything that's not what we're looking for
  {
    total_bits = 0;
  }
  else
  {
    if (ones_bit_count == true) // only count the second ones pluse
      ones_bit_count = false;
    else
    {
      if (bit_time > zero_time_min)
      {
        current_bit = 0;
        sync_count = 0;
      }
      else //if (bit_time < one_time_max)
      {
        ones_bit_count = true;
        current_bit = 1;
        sync_count++;
        if (sync_count == 12) // part of the last two bytes of a timecode word
        {
          sync_count = 0;
          tc_sync = true;
          total_bits = end_sync_position;
        }
      }

      if (total_bits <= end_data_position) // timecode runs least to most so we need
      { // to shift things around
        tc[0] = tc[0] >> 1;

        for (int n = 1; n < 8; n++) //creates tc[1-8]
        {
          if (tc[n] & 1)
            tc[n - 1] |= 0x80;

          tc[n] = tc[n] >> 1;
        }

        if (current_bit == 1)
          tc[7] |= 0x80;
      }
      total_bits++;
    }

    if (total_bits == end_smpte_position) // we have the 80th bit
    {
      total_bits = 0;
      if (tc_sync)
      {
        tc_sync = false;
        valid_tc_word = true;
      }
    }

    if (valid_tc_word)
    {
      valid_tc_word = false;

      timeCode[10] = (tc[0] & 0x0F) + 0x30; // frames  this converst from binary to decimal giving us the last digit
      timeCode[9] = (tc[1] & 0x03) + 0x30; // 10's of frames this converst from binary to decimal giving us the first digit
      timeCode[8] =  ':';
      timeCode[7] = (tc[2] & 0x0F) + 0x30; // seconds
      timeCode[6] = (tc[3] & 0x07) + 0x30; // 10's of seconds
      timeCode[5] =  ':';
      timeCode[4] = (tc[4] & 0x0F) + 0x30; // minutes
      timeCode[3] = (tc[5] & 0x07) + 0x30; // 10's of minutes
      timeCode[2] = ':';
      timeCode[1] = (tc[6] & 0x0F) + 0x30; // hours
      timeCode[0] = (tc[7] & 0x03) + 0x30; // 10's of hours

      drop_frame_flag = bit_is_set(tc[1], 2); //detects whether theree is the drop frame bit.

      write_tc_out = true;
    }
  }
}


void setup()
{
  lcd.begin (16, 2);
  pinMode(icpPin, INPUT);           // ICP pin (digital pin 8 on arduino) as input
  bit_time = 0;
  valid_tc_word = false;
  ones_bit_count = false;
  tc_sync = false;
  write_tc_out = false;
  drop_frame_flag = false;
  total_bits =  0;
  current_bit =  0;
  sync_count =  0;

  lcd.print("Finished setup");
  delay (1000);

  TCCR1A = B00000000; // clear all
  TCCR1B = B11000010; // ICNC1 noise reduction + ICES1 start on rising edge + CS11 divide by 8
  TCCR1C = B00000000; // clear all
  TIMSK1 = B00100000; // ICIE1 enable the icp

  TCNT1 = 0; // clear timer1
}

void loop()
{
  if (write_tc_out)
  {
    write_tc_out = false;
    if (drop_frame_flag)
      lcd.print("TC-[df] ");
    else
      lcd.print("TC-NO DROP FRAME");
    lcd.setCursor(0, 1);
    lcd.print((char*)timeCode);
    lcd.print("\r");
    lcd.setCursor(11, 1);
    lcd.print("......");
    delay (40);
    lcd.clear();
  }
}

arduino-simple-timecode-input.jpg

Maybe something in here - it HAS been done.
GOOGLE arduino timecode

With all due respect, I've read exactly every link on the first two pages of the search results. All things pertaining to the code I posted in my original post are, as I said, written for the Uno. The stuff written for the Mega doesn't feature the same code.

I've seen your name pop up in several of these threads though so I'm happy you took a glance here. Am I simply missing something?

lastchancename:
Maybe something in here - it HAS been done.
GOOGLE arduino timecode

What are your specific concerns porting from the larger chips back to the Uno?

The 1280 and 2560 use the same processor core... the differences are in the number of I/O and memory sizes, so code should slide across with minimal pain.

TBH, I’d read the code in a different way, simply bit-bashing the 80-bit code into a buffer, and asynchronously reading it out in main()

Doing so much ‘post processing’ inside the ISR is looking for trouble later, but I do like the effort at variable-speed reading at the beginning of the ISR you used.

It's more a question about the fact that I have a Mega and not an Uno. If I fits, I sits ^^. Also, I'm thinking about possibly including some other features such as custom MIDI controllers in the same case in the future, but that's for another time.

Also, I'd like to point out that I haven't written the original code, I don't want to take credit for that.

Again, the question is simply to get the input pin working correctly on a Mega.

I can’t immediately see why that code shouldn’t run on a MEGA-2560…

If your code copy is perfect, I guess you’re probably right in the icp pin…
See if you can find the 2560 datasheet, and identify if/how it’s pin management differs from the Uno on timer1.
I suspect the solution is going to be easy once you spot the difference,

Very interesting.

I'll link this page which talks specifically about the Mega's timers in comparison to the 168 and 328 chips. Not sure if it's the solution, but it's at least relevant and might be useful to others in the future.

http://sphinx.mythic-beasts.com/~markt/ATmega-timers.html

If nothing else, it's good to end a forum question with the actual solution, otherwise it only mentions the things that didn't work, right?

An interesting point seems to be that, according to

https://www.arduino.cc/en/Hacking/PinMapping2560

, pin 47 on the Mega chip doesn't seem assigned to one of the pin connectors on the board. I think I'll try switching to Timer5 using D49 as the ICP pin.

Great work.
I reckon you’re on the right track there.
Karma for tracking the issue sensibly!

Progress!

By looking at the pinout chart for the 2560, as well as the info on Mega timers, I was able to figure out that the reason the circuit didn’t work was that the original code used Timer1 whose input capture pin isn’t connected to an actual I/O pin on the board.

By essentially changing all the timer numbers from 1 to 5 (look for massive all-caps in the new code) and using digital pin 48 (that is, ICP5 on the Mega), I now have input signal.

However, now, the screen prints this.


link to image here

It seems like I’ve funked up the input connection circuit, but that’s a bit more manageable, and I’ll probably continue this in the electronics section. I’ll make sure to link back here though.

To sum it all up:

  • I couldn’t get the ICP1 pin used in the original code for an Uno to receive what I sent in on my Mega.
  • I realized that ICP1 isn’t connected to to a board pin connector on the Mega 2560 as it is on the Uno.
  • By changing the timer used from Timer1 to Timer5, whose ICP pin is connected to digital pin 48, I managed to get the input signal registered, in some manner.

Thanks for the help, lastchancename!

// Code from forum post Dec 12, 2007
//
//

// include the library code:
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);


#define one_time_max          600 // these values are setup for PA video
#define one_time_min          400 // It's the durstion of a one and zero with a little bit of room for error. 
#define zero_time_max          1050 // 
#define zero_time_min          950 // 


#define icpPin 48     // ICP input pin on arduino Mega 2560. Change to 8 for Uno.
//#define one_time_max      475 // these values are setup for NTSC video
//#define one_time_min      300 // PAL would be around 1000 for 0 and 500 for 1
//#define zero_time_max    875 // 80bits times 29.97 frames per sec
//#define zero_time_min    700 // equals 833 (divide by 8 clock pulses)

#define end_data_position   63
#define end_sync_position   77
#define end_smpte_position     80

volatile unsigned int pin = 13;
volatile unsigned int bit_time;   // volatile instructs the variable to be stored in RAM
volatile boolean valid_tc_word;   // booleon can be either of two values true or false
volatile boolean ones_bit_count;  // booleon can be either of two values true or false
volatile boolean tc_sync;         // booleon can be either of two values true or false
volatile boolean write_tc_out;    // booleon can be either of two values true or false
volatile boolean drop_frame_flag; // booleon can be either of two values true or false

volatile byte total_bits;   //this stores a an 8-bit unsigned number
volatile byte current_bit;  //this stores a an 8-bit unsigned number
volatile byte sync_count;   //this stores a an 8-bit unsigned number

volatile byte tc[8];         //this stores a an 8-bit unsigned number
volatile char timeCode[11];  //this stores a an 8-bit unsigned number


/* ICR interrupt vector */
ISR(TIMER5_CAPT_vect)   //ISR=Interrupt Service Routine, and timer5 capture event. Change 5 TO 1 FOR UNO.
{
  //toggleCaptureEdge
  TCCR5B ^= _BV(ICES5); //toggles the edge that triggers the handler so that the duration of both high and low pulses is measured. Change 5 TO 1 FOR UNO.

  bit_time = ICR5; //this is the value the timer generates Change 5 TO 1 FOR UNO.

  //resetTimer1 Change 5 TO 1 FOR UNO.
  TCNT5 = 0;

  if ((bit_time < one_time_min) || (bit_time > zero_time_max)) // this gets rid of anything that's not what we're looking for
  {
    total_bits = 0;
  }
  else
  {
    if (ones_bit_count == true) // only count the second ones pluse
      ones_bit_count = false;
    else
    {
      if (bit_time > zero_time_min)
      {
        current_bit = 0;
        sync_count = 0;
      }
      else //if (bit_time < one_time_max)
      {
        ones_bit_count = true;
        current_bit = 1;
        sync_count++;
        if (sync_count == 12) // part of the last two bytes of a timecode word
        {
          sync_count = 0;
          tc_sync = true;
          total_bits = end_sync_position;
        }
      }

      if (total_bits <= end_data_position) // timecode runs least to most so we need
      { // to shift things around
        tc[0] = tc[0] >> 1;

        for (int n = 1; n < 8; n++) //creates tc[1-8]
        {
          if (tc[n] & 1)
            tc[n - 1] |= 0x80;

          tc[n] = tc[n] >> 1;
        }

        if (current_bit == 1)
          tc[7] |= 0x80;
      }
      total_bits++;
    }

    if (total_bits == end_smpte_position) // we have the 80th bit
    {
      total_bits = 0;
      if (tc_sync)
      {
        tc_sync = false;
        valid_tc_word = true;
      }
    }

    if (valid_tc_word)
    {
      valid_tc_word = false;

      timeCode[10] = (tc[0] & 0x0F) + 0x30; // frames  this converst from binary to decimal giving us the last digit
      timeCode[9] = (tc[1] & 0x03) + 0x30; // 10's of frames this converst from binary to decimal giving us the first digit
      timeCode[8] =  ':';
      timeCode[7] = (tc[2] & 0x0F) + 0x30; // seconds
      timeCode[6] = (tc[3] & 0x07) + 0x30; // 10's of seconds
      timeCode[5] =  ':';
      timeCode[4] = (tc[4] & 0x0F) + 0x30; // minutes
      timeCode[3] = (tc[5] & 0x07) + 0x30; // 10's of minutes
      timeCode[2] = ':';
      timeCode[1] = (tc[6] & 0x0F) + 0x30; // hours
      timeCode[0] = (tc[7] & 0x03) + 0x30; // 10's of hours

      drop_frame_flag = bit_is_set(tc[1], 2); //detects whether theree is the drop frame bit.

      write_tc_out = true;
    }
  }
}


void setup()
{
  lcd.begin (16, 2);
  pinMode(icpPin, INPUT);           // ICP pin (digital pin 8 on arduino) as input
  bit_time = 0;
  valid_tc_word = false;
  ones_bit_count = false;
  tc_sync = false;
  write_tc_out = false;
  drop_frame_flag = false;
  total_bits =  0;
  current_bit =  0;
  sync_count =  0;

  lcd.print("Finished setup.");
  delay (1000);

  TCCR5A = B00000000; // clear all Change 5 TO 1 FOR UNO.
  TCCR5B = B11000010; // ICNC1 noise reduction + ICES1 start on rising edge + CS11 divide by 8 Change 5 TO 1 FOR UNO.
  TCCR5C = B00000000; // clear all Change 5 TO 1 FOR UNO.
  TIMSK5 = B00100000; // ICIE1 enable the icp Change 5 TO 1 FOR UNO.

  TCNT5 = 0; // clear timer1 Change 5 TO 1 FOR UNO.
}

void loop()
{
  if (write_tc_out)
  {
    write_tc_out = false;
    if (drop_frame_flag)
      lcd.print("TC-[df] ");
    else
      lcd.print("TC-NO DROP FRAME");
    lcd.setCursor(0, 1);
    lcd.print((char*)timeCode);
    lcd.print("\r");
    lcd.setCursor(11, 1);
    lcd.print("......");
    delay (40);
    lcd.clear();
  }
}

Just to end the thread; It was a question of input signal level that caused the screen to malfunction. By increasing the signal gain, everything worked properly, so I'd say this is a working code.

I wonder why the code used ICP, the version I wrote here just used a normal interrupt pin.

Ah, Riva! I did have a look at your code several times during the project, and I want to say that I’ve been trying to find the original author of the code to credit them in this use, but I lost it in the hundreds of pages I looked at.

Nevertheless, again, I point out that I didn’t write the original code but merely adapted it to my needs. I am more of a basic C++, intermediate Python person and have only very basic knowledge of Arduinos. Basically, I couldn’t tell you the difference between an interrupt and an ICP pin.

I’m here to break stuff, trying to figure out the pieces and (hopefully) put them back together in working order.