I started this project some time ago when there was a lot of hype over what there is to gain from measuring bar speed during the lifts. The term is VBT (velocity based training) and there is a lot of research out there about these. There is the Tendo unit and GymAware but cost in the thousands. So, I set out to build my own and here is what I got so far.

I started with an Arduino for quick prototyping but will most likely settle on the TI Launchpad when it's all done. With the Arduino base I am using the LSI7366R chip for quadrature decoding to offload that part of the processing to hardware rather than software. With the TI Launchpad I won't need the decoder chip which is one of the reasons to ditch the Arduino amongst many other reasons. The encoder is a Dynapar in a rotary cable transducer or a draw wire encoder setup, there's many different names for these. It has 2048 counts of resolution per revolution so in quadrature mode it's 8192 counts. This makes it have a linear resolution of .001". I am sending the position information back to the PC over bluetooth and will build the APP with Python and PYQT. For testing I am just sending the data back to CoolTerm then saving the results. The measurements are taken at 50Hz. Another option is to store the raw data to a SD card then review it later. Currently using a Nokia 5110 display for testing. Not sure what I will settle on for display though.

Here is the Arduino based board I am using.

Then the encoder board I set up

Back side with all the wiring

Here is the full setup with bluetooth and encoder plugged in

Another full setup powered up

And another full setup showing the current measurement

This is the TI and proto board I am working on now

With the TI I will use bluetooth first but I might work on using this WIFI IOT board as well and setup a local server

Here's a screen shot of the raw data plotted in Excel to do some analysis versus measurement results and see if they correlate

Here is some of the code so far. Seems it is pretty accurate past 5 m/s. It's hard to pull it any faster.

    /*
 * VBT.c
 *
 * Created: 11/26/2015 3:16:38 AM
 *  Author: Carlton
 */

// timer1 ranges
// prescale 1 = 245 - 16000000
// prescale 8 = 30 - 2000000 // 50hz = 39999, 100hz = 19999, 200hz = 9999, 400hz = 4999
// prescale 64 = 4 - 250000 // 50hz = 4999, 100hz = 2499, 200hz = 1249, 400hz = 624
// prescale 256 = 1 - 62500
// prescale 1024 = .24 - 15265

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <string.h>

#include "spi.h"
#include "ls7366.h"
#include "uart.h"
#include "nokia5110.h"

void add_measurement(void);
void timer_init(void);
void printNumber(int32_t n);

#define F_CPU 16000000UL
//#include <util/delay.h> // need this after defining F_CPU

#define USART_BAUDRATE 115200
#define BAUD_PRESCALE F_CPU/8/USART_BAUDRATE - 1

// global data
volatile uint8_t flag;
volatile int32_t cntr;
int32_t prev_cntr;
int32_t start_cntr;
uint8_t prev_dir;
float period = 0.02; // 50Hz
uint32_t velocity_sum;
uint32_t velocity_temp;
//uint32_t bar_weight;
//uint8_t num_rep;

typedef struct s_rep_data {
    uint16_t num_measurements;
    uint16_t peak_vel_pos;
    uint16_t peak_vel_pct;
    uint32_t peak_vel;
    uint32_t avg_vel;
    uint32_t diff_vel;
    //uint32_t accel;
    //uint32_t power;
    //int32_t start_cntr;
    //int32_t end_cntr; // should equal prev_cntr
} rep_data;

rep_data rep;
//rep_data reps[85];  // 90 = 1956 bytes 95.5% // 85 = 1866 bytes 91.1%

int main(void)
{
    cli();  // disable interrupts

    spi_init(); // used for ls7366 and nokia5110

    ls7366_init(); // initialize decoder chip

    LCDInit(); // initialize nokia 5110 display

    timer_init(); // initialize timer 1 for periodic decoder reads

    uart_init(UART_BAUD_SELECT(USART_BAUDRATE,F_CPU)); // initialize uart for debugging and bluetooth

    sei();  //enable interrupts

    LCDClear();
    LCDBitmap(Longhorn);

    while(1)
    {
        if (flag) {
            add_measurement(); // add measurement if good and detect if rep
            printNumber(cntr); // send out all raw data for analysis and desktop live graphing
            flag = 0;
        }
   }
}

void add_measurement(void) {
    if (cntr - prev_cntr > 10) { // going up more than .01"
        if (prev_dir == 0) { // changed directions
            start_cntr = prev_cntr; // or cntr
            velocity_sum = 0;
            rep.num_measurements = 0;
            rep.peak_vel_pos = 0;
            rep.peak_vel_pct = 0;
            rep.peak_vel = 0;
            rep.avg_vel = 0;
            rep.diff_vel = 0;
        }
        velocity_temp = (cntr - prev_cntr) / period;
        velocity_sum += velocity_temp;
        if (velocity_temp > rep.peak_vel) {
            rep.peak_vel = velocity_temp;
            rep.peak_vel_pos = rep.num_measurements;
        }
        prev_dir = 1;
        rep.num_measurements++;
    } else if (prev_cntr - cntr > 10) { // going down more than .01"
        if (prev_dir == 1) { // changed directions
            if (cntr - start_cntr > 10000) { // if going up more than 10" count as rep
                // add velocity, peak velocity, acceleration, and power to display
                rep.avg_vel = ((velocity_sum / rep.num_measurements) * 254) / 1000; // calculate avg velocity and convert to m/s using only integer math
                rep.diff_vel = (((prev_cntr-start_cntr) / (rep.num_measurements*period)) * 254) / 1000;
                rep.peak_vel = (rep.peak_vel * 254) / 1000;
                rep.peak_vel_pct = (rep.peak_vel_pos * 100) / rep.num_measurements;

                // pass to display current rep velocity
                LCDClear();
                LCDString("Avg");
                LCDgotoXY(0, 1);
                LCDDec(rep.avg_vel);
                LCDString(" m/s");
                //LCDgotoXY(0, 1);
                //LCDDec(rep.diff_vel);
                //LCDString(" m/s");
                LCDgotoXY(0, 2);
                LCDString("Peak");
                LCDgotoXY(0, 3);
                LCDDec(rep.peak_vel);
                LCDString(" m/s");
                LCDgotoXY(0, 4);
                LCDString("  @ ");
                LCDInt(rep.peak_vel_pct);
                LCDString(" %");

                // send out calculated data at end of rep to see if it correlates with raw data
                uart_putc('-');
                uart_putc('\r');
                printNumber(velocity_sum);
                printNumber(rep.num_measurements);
                printNumber(start_cntr);
                printNumber(prev_cntr);
                uart_putc('-');
                uart_putc('\r');
            }
        }
        prev_dir = 0;
    }
    prev_cntr = cntr;
}

// uart helper function to convert int to string
void printNumber(int32_t n) {
    char buf[8 * sizeof(int32_t) + 1]; // Assumes 8-bit chars plus zero byte.
    char *str = &buf[sizeof(buf) - 1];

    *str = '\0';

    if (n < 0) {
        uart_putc('-');
        n = -n;
    }

    do {
        uint32_t m = n;
        n /= 10;
        char c = m - 10 * n;
        *--str = c + '0';
    } while(n);

    uart_puts(str);
    uart_putc('\r'); // 0D 0A
}

void timer_init(void)
{
    // Timer1 configuration
    TCCR1A = 0;  // registers for timer 1 to 0
    TCCR1B = 0;
    OCR1A = 4999; // Target Timer Count = ((1 / Target Frequency) / (1 / Timer Clock Frequency)) - 1
    TCCR1B |= (1 << CS11) | (1 << CS10);  // prescale of 64
    TCCR1B |= (1 << WGM12); // turn on CTC mode
    TIMSK1 |= (1 << OCIE1A);  // enable TIMER1 COMPARE INTERUPT
}

ISR(TIMER1_COMPA_vect)
{
    // read from ls7366r to get encoder counts
    cntr = read_counter_signed();
    flag = 1; // set flag for loop to send data
}