Forum Index > Projects > LED Pegboard and Matrix Projects
 Lag/Latency Issues with my Video Peggy
 |  Printable Version
By: Anonymous: echan00 () on Saturday, February 05 2011 @ 12:40 PM PST (Read 4730 times)  
Anonymous: echan00

Hi All,

I am having latency issues with my video Peggy. I hope someone can help me troubleshoot this...

I am able to send serial test patterns from Mac to TWI and to my peggy with no problems.

I've also been able to setup quartz composer to transmit quart compositions or use the MirrorPeggy.pde processing sketch to transmit serial to my Arduino (and then to the Peggy). The problem is that even though it works, I have a lot of lag (~3-5 seconds per frame).

A little bit about my hardware/process:
- Peggy v2.2 with ATMEGA328
- Arduino UNO (connected via /dev/tty.usbmodem411)


I'd appreciate it if anyone could help!






       
   
By: Windell (offline) on Saturday, February 05 2011 @ 04:09 PM PST  
Windell

You should expect some lag with any of the "video peggy" schemes, although 3-5 seconds does sound quite excessive.

First: some of the lag *will* be on your computer. If you run the "mirror" processing sketch, you'll see some lag between movement and when it shows up on your computer screen. On my computer, it's about half a second. And, after processing the video to show it, it formats the data to go our your USB port, which may take equally long. You'll certainly have at least as much lag as that due to your computer alone.

Second, some of the lag is due to doing things the "complicated" way.

You have: Computer>USB Interface chip (8u2)>Arduino Board>I2C>Peggy


Using the "serial peggy" option might be considerably faster, depending on where the bottleneck is in your system:

Computer >USB interface (USB-TTL cable or equivalent) > Peggy


Windell H. Oskay
drwho(at)evilmadscientist.com
http://www.evilmadscientist.com/

Forum Evil Scientist
Evil Scientist

Status: offline

Registered: 06/15/06
Posts: 1932
Sunnyvale, CA

Profile Email Website  
   
By: Anonymous: echan00 () on Saturday, February 05 2011 @ 04:36 PM PST  
Anonymous: echan00

Hi Windell,

Thanks for the reply.

Maybe I wasn't clear enough about what I meant by "lag". The video on my peggy is choppy, i only see one frame per every 3-5 seconds...

Is that normal?





       
   
By: Anonymous: echan00 () on Saturday, February 05 2011 @ 05:02 PM PST  
Anonymous: echan00

Here is a youtube video of my peggy: http://www.youtube.com/watch?v=sfkK5jQ5e4k

I chose to use a simple quartz composition to keep the "lag" down





       
   
By: Windell (offline) on Sunday, February 06 2011 @ 12:51 AM PST  
Windell

Nope, that's not really a "lag" or "latency" problem, that's a "frame rate" problem.

When I tried this, I got a rate of about 15 fps, with under 1 s lag:
http://www.flickr.com/photos/oskay/3287784590/


In your case, it's hard to say exactly what the problem is. I would guess that it's set up to run at fast frame rate, but the data isn't reliably making it from point A to B to C, and that only occasionally does a full frame happen to get through. There could be a flaky hardware connection. It could be the case that you need to try and decrease the frame rate, maybe in the processing sketch, or maybe decrease the baud rate.

It could also be some issue with the serial interface on the Arduino UNO, as these other examples were all done before that came out, and the UNO does have a new serial configuration.

And again, you may also want to consider trying the "serial peggy" option as a way to simplify the setup.


Windell H. Oskay
drwho(at)evilmadscientist.com
http://www.evilmadscientist.com/

Forum Evil Scientist
Evil Scientist

Status: offline

Registered: 06/15/06
Posts: 1932
Sunnyvale, CA

Profile Email Website  
   
By: Anonymous: echan00 () on Wednesday, March 02 2011 @ 02:29 PM PST  
Anonymous: echan00

Hey Windell,

Interestingly, I found that the framerate problem with my video peggy disappears when I hold onto the SCL connection (and the problem returns when I let go).

Curious if this may be a problem with pull up resistors not enabled in my Arduino? I would imagine they are enabled by default?






       
   
By: Windell (offline) on Wednesday, March 02 2011 @ 04:11 PM PST  
Windell

Interesting. Yes, you can try manually turning on the pull-up resistors to see if that helps.


Windell H. Oskay
drwho(at)evilmadscientist.com
http://www.evilmadscientist.com/

Forum Evil Scientist
Evil Scientist

Status: offline

Registered: 06/15/06
Posts: 1932
Sunnyvale, CA

Profile Email Website  
   
By: Anonymous: echan00 () on Saturday, March 19 2011 @ 10:58 AM PDT  
Anonymous: echan00

Okay. I'm a bit lost now. It seems like disabling the pull up on the SCL in my arduino code has removed most of my frame rate issues. Its not perfect but its much better than previously. You can see below in my code I have this "digitalWrite(A5, LOW);"

Same as before, if I hold onto the SCL connection (I presume acting as a pull-down resistor), all the framerate problems are gone.

What do you think?


PHP Formatted Code

#include <Wire.h>

// Serial-to-TWI/I2C code, used to convert serial data from a PC/Mac to
// TWI/I2C data for a Peggy 2.0 with the VideoPeggyTwi firmware.
//

#define PEGGY_ADDRESS 34
#define TWI_FREQ 300000

void setup()
{
  Serial.begin(115200);

  UBRR0H = 0;
  UBRR0L = 16; // manually set 115200
  UCSR0A = (1<<U2X0);
  //UCSR0B = (1<<RXEN0) | (1<<TXEN0);

  //Serial.print("Sender Initialized...");
  Wire.begin();

  PORTC |=  (1<<PORTC5) | (1<<PORTC4); // enable pullups
  pinMode(A4, INPUT);
  pinMode(A5, INPUT);
  digitalWrite(A5, LOW);
  digitalWrite(A4, HIGH);


  // jack up the frequency for TWI, we need a pretty high
  // rate  from the TWI engine for this to handle 115k input
  // without getting buffer overruns
  TWSR &= ~(1<<TWPS0);
  TWSR &= ~(1<<TWPS1);
  TWBR = ((F_CPU / TWI_FREQ) - 16) / 2;

}

void loop()
{
  uint8_t count = Serial.available();
 
  if (count > 0)
  {
    // dont allow send too many bytes at once, dont want to exceed the buffer
    // size of the Wire library
    if (count > 16) count = 16;
 
    Wire.beginTransmission(PEGGY_ADDRESS);
    while (count-- > 0 )
    {
      uint8_t c = Serial.read();
      Wire.send(c);
    }
    Wire.endTransmission();  
  }
}





       
   
By: Windell (offline) on Sunday, March 20 2011 @ 09:03 PM PDT  
Windell

One thing to consider here: You have two Arduino-type devices connected together here. For proper operation, the I2C pull-ups should be enabled on EXACTLY one end of the communication. If both were enabled before, that might explain the problem.


Windell H. Oskay
drwho(at)evilmadscientist.com
http://www.evilmadscientist.com/

Forum Evil Scientist
Evil Scientist

Status: offline

Registered: 06/15/06
Posts: 1932
Sunnyvale, CA

Profile Email Website  
   
By: Anonymous: echan00 () on Friday, March 25 2011 @ 10:45 PM PDT  
Anonymous: echan00

Okay, so I no longer enable the pull ups on the Peggy and only enable the pull ups on the Arduino.

The frame rate problem may have gotten a little bit better, but still not quite compared to when i hold onto the SCL connection.

What else could it be?

Here is the arduino code:

PHP Formatted Code

#include <Wire.h>

// Serial-to-TWI/I2C code, used to convert serial data from a PC/Mac to
// TWI/I2C data for a Peggy 2.0 with the VideoPeggyTwi firmware.
//

#define PEGGY_ADDRESS 34
#define TWI_FREQ 300000

void setup()
{
  Serial.begin(115200);

  UBRR0H = 0;
  UBRR0L = 16; // manually set 115200
  UCSR0A = (1<<U2X0);
  //UCSR0B = (1<<RXEN0) | (1<<TXEN0);

  //Serial.print("Sender Initialized...");
  Wire.begin();

  PORTC |=  (1<<PORTC5) | (1<<PORTC4); // enable pullups
  pinMode(A4, INPUT);
  pinMode(A5, INPUT);
  digitalWrite(A5, HIGH);
  digitalWrite(A4, HIGH);


  // jack up the frequency for TWI, we need a pretty high
  // rate  from the TWI engine for this to handle 115k input
  // without getting buffer overruns
  TWSR &= ~(1<<TWPS0);
  TWSR &= ~(1<<TWPS1);
  TWBR = ((F_CPU / TWI_FREQ) - 16) / 2;

}

void loop()
{
  uint8_t count = Serial.available();
 
  if (count > 0)
  {
    // dont allow send too many bytes at once, dont want to exceed the buffer
    // size of the Wire library
    if (count > 16) count = 16;
 
    Wire.beginTransmission(PEGGY_ADDRESS);
    while (count-- > 0 )
    {
      uint8_t c = Serial.read();
      Wire.send(c);
    }
    Wire.endTransmission();  
  }
}
 




Here is the peggy code:

PHP Formatted Code

//#define FPS 144
#define FPS 90

// 25 rows * 13 bytes per row == 325
#define DISP_BUFFER_SIZE 325
#define MAX_BRIGHTNESS 15

#define TWI_SLAVE_ID 34

////////////////////////////////////////////////////////////////////////////////////////////
uint8_t frameBuffer[DISP_BUFFER_SIZE];

uint8_t *currentRowPtr = frameBuffer;
uint8_t currentRow=0;
uint8_t currentBrightness=0;


// Note: the refresh code has been optimized heavily from the previous version.
SIGNAL(TIMER0_COMPA_vect)
{      
        // there are 15 passes through this interrupt for each row per frame.
        // ( 15 * 25) = 375 times per frame.
        // during those 15 passes, a led can be on or off.
        // if it is off the entire time, the perceived brightness is 0/15
        // if it is on the entire time, the perceived brightness is 15/15
        // giving a total of 16 average brightness levels from fully on to fully off.
        // currentBrightness is a comparison variable, used to determine if a certain
        // pixel is on or off during one of those 15 cycles.   currentBrightnessShifted
        // is the same value left shifted 4 bits:  This is just an optimization for
        // comparing the high-order bytes.
        if (++currentBrightness >= MAX_BRIGHTNESS)  
        {
                currentBrightness=0;
                if (++currentRow > 24)
                {
                        currentRow =0;
                        currentRowPtr = frameBuffer;
                }
                else
                {
                        currentRowPtr += 13;
                }
        }

               
        ////////////////////  Parse a row of data and write out the bits via spi
        uint8_t currentBrightnessShifted = currentBrightness <<4;
       
        uint8_t *ptr = currentRowPtr + 12;  // its more convenient to work from right to left
        uint8_t p, bits=0;
         
        // optimization: by using variables for these two masking constants, we can trick gcc into not
        // promoting to 16-bit int (constants are 16 bit by default, causing the
        // comparisons to get promoted to 16bit otherwise)].  This turns out to be a pretty
        // substantial optimization for this handler
        uint8_t himask = 0xf0;  
        uint8_t lomask = 0x0f;
       
        // Opimization: interleave waiting for SPI with other code, so the CPU can do something useful
        // when waiting for each SPI transmission to complete
       
        p = *ptr--;
        if ((p & lomask) > currentBrightness)                   bits|=1;
        SPDR = bits;

        bits=0;
        p = *ptr--;
        if ((p & lomask) > currentBrightness)                   bits|=64;
        if ((p & himask) > currentBrightnessShifted)    bits|=128;
        p = *ptr--;
        if ((p & lomask) > currentBrightness)                   bits|=16;
        if ((p & himask) > currentBrightnessShifted)    bits|=32;
        p = *ptr--;
        if ((p & lomask) > currentBrightness)                   bits|=4;
        if ((p & himask) > currentBrightnessShifted)    bits|=8;
        p = *ptr--;
        if ((p & lomask) > currentBrightness)                   bits|=1;
        if ((p & himask) > currentBrightnessShifted)    bits|=2;

        while (!(SPSR & (1<<SPIF)))  { } // wait for prior bitshift to complete
        SPDR = bits;
       
       
        bits=0;
        p = *ptr--;
        if ((p & lomask) > currentBrightness)                   bits|=64;
        if ((p & himask) > currentBrightnessShifted)    bits|=128;
        p = *ptr--;
        if ((p & lomask) > currentBrightness)                   bits|=16;
        if ((p & himask) > currentBrightnessShifted)    bits|=32;
        p = *ptr--;
        if ((p & lomask) > currentBrightness)                   bits|=4;
        if ((p & himask) > currentBrightnessShifted)    bits|=8;
        p = *ptr--;
        if ((p & lomask) > currentBrightness)                   bits|=1;
        if ((p & himask) > currentBrightnessShifted)    bits|=2;

        while (!(SPSR & (1<<SPIF)))  { } // wait for prior bitshift to complete
        SPDR = bits;
       
       
        bits=0;
        p = *ptr--;
        if ((p & lomask) > currentBrightness)                   bits|=64;
        if ((p & himask) > currentBrightnessShifted)    bits|=128;
        p = *ptr--;
        if ((p & lomask) > currentBrightness)                   bits|=16;
        if ((p & himask) > currentBrightnessShifted)    bits|=32;
        p = *ptr--;
        if ((p & lomask) > currentBrightness)                   bits|=4;
        if ((p & himask) > currentBrightnessShifted)    bits|=8;
        p = *ptr--;
        if ((p & lomask) > currentBrightness)                   bits|=1;
        if ((p & himask) > currentBrightnessShifted)    bits|=2;

        while (!(SPSR & (1<<SPIF)))  { }// wait for prior bitshift to complete
        SPDR = bits;
       
        ////////////////////  Now set the row and latch the bits
       
        uint8_t portD;
       
       
        if (currentRow < 15)
                portD = currentRow+1;
        else
                portD = (currentRow -14)<<4;


        while (!(SPSR & (1<<SPIF)))  { } // wait for last bitshift to complete
       
        //if (currentBrightness == 0)
        PORTD = 0;                              // set all rows to off
        PORTB |= _BV(1);//(1<<PB1); //  latch it, values now set
        //if (currentBrightness == 0)
        PORTD = portD;     // set row
        PORTB &= ~( _BV(1));//~((1<<PB1)); // reset latch for next time
       
        // notes to self, calculations from the oscope:
        // need about minimum of 6us total to clock out all 4 bytes
        // roughly 1.5ms per byte, although some of that is
        // idle time taken between bytes.  6=7us therefore is our
        // absolute minimum time needed to refresh a row, not counting calculation time.
        // Thats just if we do nothing else when writing out SPI and toggle to another row.
        //Measured values from this routine    
        // @ 144 fps the latch is toggled every 19us with an actual 4byte clock out time of 12-13us
        // @ 70 fps the latch is toggle every 39us, with a clock out time of 13-14us
        // times do not count setup/teardown of stack frame
       
        // one byte @ 115k takes 86us (max) 78us (min) , measured time
        // one byte @ 230k takes 43us (max) 39us (min) , measured time
        // so 230k serial might barely be possible, but not with a 16mhz crystal (error rate to high)
        // 250k might just barely be possible
}

void displayInit(void)
{
        // need to set output for SPI clock, MOSI, SS and latch.  Eventhough SS is not connected,
        // it must apparently be set as output for hardware SPI to work.
        DDRB =  (1<<DDB5) | (1<<DDB3) | (1<<DDB2) | (1<<DDB1);
        // set all portd pins as output
        DDRD = 0xff;

        PORTD=0; // select no row
       
        // enable hardware SPI, set as master and clock rate of fck/2
        SPCR = (1<<SPE) | (1<<MSTR);
        SPSR = (1<<SPI2X);

        // setup the interrupt.
        TCCR0A = (1<<WGM01); // clear timer on compare match
        TCCR0B = (1<<CS01); // timer uses main system clock with 1/8 prescale
        OCR0A  = (F_CPU >> 3) / 25 / 15 / FPS; // Frames per second * 15 passes for brightness * 25 rows
        TIMSK0 = (1<<OCIE0A);   // call interrupt on output compare match


        for (uint8_t i=0; i < 4; i++)
        {
                SPDR = 0;
                while (!bit_is_set(SPSR, SPIF)) {}
        }
}




////////////////////////////////////////////////////////////////////////////////////////////
// I2C  routines
////////////////////////////////////////////////////////////////////////////////////////////

// TWI Slave Receiver staus codes, from Atmel notes
#define TWI_SRX_ADR_ACK            0x60
#define TWI_SRX_ADR_ACK_M_ARB_LOST 0x68
#define TWI_SRX_GEN_ACK            0x70
#define TWI_SRX_GEN_ACK_M_ARB_LOST 0x78
#define TWI_SRX_ADR_DATA_ACK       0x80
#define TWI_SRX_ADR_DATA_NACK      0x88
#define TWI_SRX_GEN_DATA_ACK       0x90
#define TWI_SRX_GEN_DATA_NACK      0x98
#define TWI_SRX_STOP_RESTART       0xA0
#define TWI_NO_STATE               0xF8
#define TWI_BUS_ERROR              0x00

void initTwiSlave(uint8_t addr)
{
 
    //PORTC |=  _BV(5) | _BV(4);      //(1<<PC5) | (1<<PC4); // enable pullups
    //PORTC |= (1<<PORTC5) | (1<<PORTC4);

    TWAR = (0<<TWGCE) |((uint8_t) (0xff & (addr<<1)));    // set slave address, no general call address
    TWDR = 0xff; // Default content = SDA released

    TWCR = (1<<TWINT) |   // "clear the flag"  (hate this backward terminology)
          (1<<TWEA) |     // send acks to master when getting address or data
          (0<<TWSTA) |    // not a master, cant do start
          (0<<TWSTO) |    // doc says set these to 0
          (0<<TWWC) |
          (1<<TWEN) |   // hardware TWI  enabled
          (0<<TWIE);   // do NOT generate interrupts.

    while (TWCR & (1<<TWIE)) { }

}


uint8_t getTwiByte(void)
{
        uint8_t result=0;
 
        keepListening:
 
        // wait for an state change
        while (!(TWCR & (1<<TWINT))) { } // wait for TWINT to be set
 
       
        //uint8_t sr = TWSR;
        switch (TWSR)
        {
            case TWI_SRX_ADR_DATA_ACK:  // received a byte of data data
            case TWI_SRX_GEN_DATA_ACK:      
                        result = TWDR;
                TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA);
                        break;

               
//        case TWI_SRX_GEN_ACK_M_ARB_LOST:
//        case TWI_SRX_ADR_ACK_M_ARB_LOST:
        case TWI_SRX_GEN_ACK:            
        case TWI_SRX_ADR_ACK:      // receive our address byte      
                      TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA);
                      goto keepListening;
                      break;
                       
            case TWI_SRX_STOP_RESTART:       // A STOP or repeated START condition was received
                TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA);
                    goto keepListening;      
                break;          

            case TWI_SRX_ADR_DATA_NACK:   // data received, returned nack
            case TWI_SRX_GEN_DATA_NACK:
                //result = TWDR;
                TWCR = (1<<TWEN)|(1<<TWINT); //|(1<<TWEA);
                goto keepListening;
                        break;
           
        case TWI_NO_STATE:
                goto keepListening;
                break;  
       
//          case TWI_BUS_ERROR:      
            default:                             // something bad happened. assuming a bus error, we try to recover from this
              //state = TWSR;
              //TWCR = (1<<TWEN)|(0<<TWINT)|(0<<TWEA);    // Don't ack any further requests, will stop receiving
              // alternate handling: reset state and continue
              TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO);    // ignore
              // wait for stop condition to be exectued; TWINT will not be set after a stop
                  while(TWCR & (1<<TWSTO)){ }
                  result = 0xff;
              break;
    }
        return result;
}



////////////////////////////////////////////////////////////////////////////////////////////
// MAIN LOOP: handle the input data stream  and stuff bytes into the framebuffer
////////////////////////////////////////////////////////////////////////////////////////////

void serviceInputData(void)
{
        uint8_t *ptr = frameBuffer;
        uint8_t state = 0;
        int counter = 0;
        while (1)
        {  
                uint8_t c = getTwiByte();
               
                // very simple state machine to look for 6 byte start of frame
                // marker and copy bytes that follow into buffer
                if (state  <6)
                {

                        // must get a 0xdeadbeef to start frame.
                        // I look for two more bytes after that, but
                        // they are reserved for future use.
                        // so send a 1 followed by a 0 for now.

                        if (state == 0 && c == 0xde) state++;
                        else if (state ==1 && c == 0xad) state++;
                        else if (state ==2 && c == 0xbe) state++;
                        else if (state ==3 && c == 0xef) state++;
                        else if (state ==4 && c == 0x01) state++;
                        else if (state ==5)  // dont care what 6th byte is
                        {
                                state++;
                                counter = 0;
                                ptr = frameBuffer;
                        }
                        else state = 0; // error: reset to look for start of frame
                }
                else
                {
                        // inside of a frame, so save each byte to buffer
                        *ptr++ = c;
                        counter++;
                        if (counter >= DISP_BUFFER_SIZE)
                        {
                                // buffer filled, so reset everything to wait for next frame
                                //counter = 0;
                                //ptr = frameBuffer;
                                state = 0;
                        }
                }
        }
}

 
void setup()                    // run once, when the sketch starts
{
        // Enable pullups for buttons/i2c
        //PORTB |= _BV(0);//(1<<PB0);
        //PORTC = _BV(5) |  _BV(4) |  _BV(3) | _BV(2) |  _BV(1) |  _BV(0);
                 //(1<<PC5) | (1<<PC4) | (1<<PC3) | (1<<PC2) | (1<<PC1) | (1<<PC0);

        UCSR0B =0; // turn OFF serial RX/TX, necessary if using arduino bootloader
       
        displayInit();

        initTwiSlave(TWI_SLAVE_ID);
       
        sei( );

        // clear display and set to test pattern
        // pattern should look just like the "gray test pattern" from EMS

        uint8_t v = 0;
        for (int i =0; i < DISP_BUFFER_SIZE; i++)
        {
                v = (v+2) % 16;
            // set to 0 for blank startup display
                // low order bits on the left, high order bits on the right
                        frameBuffer[i]= v + ((v+1)<<4);  
                //      frameBuffer[i]=0;
        }
       
        serviceInputData();  // never returns
}

void loop()                     // run over and over again
{
 
}





       
   
By: Anonymous: echan00 () on Friday, May 27 2011 @ 08:29 AM PDT  
Anonymous: echan00

For anyone who may be running into the same issue.. this is how i ended up fixing my problem.

1) Make sure the pull-ups are enabled only on one side (either the peggy or arduino)

2) Make sure you play around with the TWI_FREQ. It is defined as 300000 in the code. I played with a bunch of values and everything worked perfect at 100000.





       
   



 All times are PDT. The time is now 04:39 AM.
Normal Topic Normal Topic
Locked Topic Locked Topic
Sticky Topic Sticky Topic
New Post New Post
Sticky Topic W/ New Post Sticky Topic W/ New Post
Locked Topic W/ New Post Locked Topic W/ New Post
View Anonymous Posts 
Able to Post 
Filtered HTML Allowed 
Censored Content 

Evil Mad Scientist Forum Archives — Read only!

Please visit our new forums for new discussions.


DIY Hardware for Electronic Art


The Original Egg-Bot Kit


Octolively
Interactive LED kits


Meggy Jr RGB
LED matrix game
development kit.


Business-card sized
AVR target boards


Peggy 2
LED Pegboard kits

My Account






Lost your password?