ECE 4760 Final Project Ryan Bayne and Ben Taussig

Contents

  1. Appendix A: Course Media
  2. Appendix B: Parts
  3. Appendix C: References
  4. Appendix D: Tasks
  5. Appendix E: Complete System Code

Appendix A: Course Media

Appendix B: Parts

The total cost of the parts used in our system is $88.35. A breakdown of the parts used is shown below:

Name Link To Purchase List Price Count Description
PIC32 Microprocessor N/A $5.00 1 Microprocessor used to execute system code.
MicroStickII N/A $1.00 1 Development board used to program PIC32 Microprocessor
Big Board N/A $10.00 1 Breakout board for PIC32 microprocessor
White Board N/A $6.00 1 Breadboard used for analog low-pass filter and I/O routing
I/O Expander N/A $5.00 1 Used to enable high-speed GPIO on PIC32 Microprocessor
MyoWare Muscle Sensor https://www.sparkfun.com/products/13723 $37.95 1 EMG muscle sensor used as the game’s controller.
Muscle Sensor EMG Pads https://tinyurl.com/y6x5qqbr $4.95 2x6 Used to allow the EMG sensor to contact the user’s muscle
Battery Holder - 1x18650 (wire leads) https://www.sparkfun.com/products/12899 $1.00 1 AA Battery holder used to power the muscle sensor.
Servo - Generic Metal Gear (Micro Size) https://www.sparkfun.com/products/14760 $12.50 2 Servos used to set the pitch and yaw of the robotic leg.

Appendix C: References

Appendix D: Tasks

Appendix E: Complete Listing of System Code

    
////////////////////////////////////
// clock AND protoThreads configure!
#include "config_1_3_2.h"
// threading library
#include "pt_cornell_1_3_2.h"
//port expander
#include "port_expander_brl4.h"

////////////////////////////////////
// lock out timer interrupt during spi comm to port expander
// This is necessary if you use the SPI2 channel in an ISR
#define start_spi2_critical_section INTEnable(INT_T2, 0);
#define end_spi2_critical_section INTEnable(INT_T2, 1);
////////////////////////////////////

// note that UART input and output are threads
static struct pt pt_print, pt_time, pt_adc, pt_key;

//set period to generate wave of 50Hz
#define generate_period 12500

//generate pulse widths for 1 and 2 ms pulses, respsectively
int min_pulse = generate_period / 20;
int max_pulse = generate_period / 10;

//pulse_increment controls the speed of servo rotation
//pulse_factor represents the pulse width of servo control signal in ms
volatile float pulse_increment_goalie = .02;
volatile float pulse_factor_goalie = 1.5;

volatile float pulse_increment_player = .01;
volatile float pulse_factor_player = 1.5;

//speed multiplier for goalie and player movement
static float level = 1.0;

//game does not run unless start is 1.
static int start = 0;

//.6 ms
volatile int OC3_width = 375;
//1.5 ms
volatile int OC2_width = 938;
volatile int OC1_width = 938;

static char buffer[128];

//represents Voltage measurements from ADC
static float V;

//keypad debouncer
enum state
{
    NoPush = 0,
    MaybePush = 1,
    Pushed = 2,
    MaybeNoPush = 3
};
static enum state PushState = NoPush;

// === Servo Control Thread ======================================================
static PT_THREAD(protothread_control(struct pt *pt))
{
    PT_BEGIN(pt);
    static int adc_9;
    static int i;
    static int counter;
    while (1)
    {
        // read the ADC AN11 (RB1))
        // read the first buffer position
        adc_9 = ReadADC10(0);
        AcquireADC10();
        // scale ADC measurement to a voltage between 0 V and 3.3 V
        V = (float)(adc_9 * 3.3 / 1023.0); // Vref*adc/1023

        //if V < 1.5, don't kick
        //if V >= 1.5 for 2 consecutive measurements, then kick
        if (V < 1.5)
        {
            //don't kick
            OC3_width = max_pulse;
            counter = 0;
        }
        else
        {
            if (counter < 1)
            {
                counter += 1;
            }
            else
            {
                //kick
                OC3_width = .6 * min_pulse;
            }
        }

        //update OC3 pulse width
        CloseOC3();
        OpenOC3(OC_ON | OC_TIMER2_SRC | OC_CONTINUE_PULSE, OC3_width, 0);

        //if goalie pulse width is too extreme, reverse direction of incrementation
        if (pulse_factor_goalie >= 2.4 || pulse_factor_goalie <= .6)
        {
            pulse_increment_goalie *= -1;
            pulse_factor_goalie += 2 * pulse_increment_goalie;
        }
        //increment goalie pulse width
        pulse_factor_goalie += start * level * pulse_increment_goalie;
        OC2_width = (int)(pulse_factor_goalie * min_pulse);

        CloseOC2();
        OpenOC2(OC_ON | OC_TIMER2_SRC | OC_CONTINUE_PULSE, OC2_width, 0);

        //if player pulse width is too extreme, reverse direction of incrementation
        if (pulse_factor_player >= 2 || pulse_factor_player <= 1)
        {
            pulse_increment_player *= -1;
            pulse_factor_player += 2 * pulse_increment_player;
        }
        //increment player pulse width
        pulse_factor_player += start * level * pulse_increment_player;
        OC1_width = (int)(pulse_factor_player * min_pulse);

        CloseOC1();
        OpenOC1(OC_ON | OC_TIMER2_SRC | OC_CONTINUE_PULSE, OC1_width, 0);

        //yield
        PT_YIELD_TIME_msec(20);

    } // END WHILE(1)

    PT_END(pt);
}
// === Keypad Thread =============================================
// Port Expander connections:
// z0 -- row 1 -- thru 300 ohm resistor -- avoid short when two buttons pushed
// z1 -- row 2 -- thru 300 ohm resistor
// z2 -- row 3 -- thru 300 ohm resistor
// z3 -- row 4 -- thru 300 ohm resistor
// z4 -- col 1 -- internal pullup resistor -- avoid open circuit input when no button pushed
// z5 -- col 2 -- internal pullup resistor
// z6 -- col 3 -- internal pullup resistor
static PT_THREAD(protothread_key(struct pt *pt))
{
    PT_BEGIN(pt);
    static int possibleKey, record, i, pattern, junk, n;
    static short keypad;
    static int keytable[12] =
        //    0     1      2    3     4     5     6      7    8     9    10-*  11-#
        {0xd7, 0xbe, 0xde, 0xee, 0xbd, 0xdd, 0xed, 0xbb, 0xdb, 0xeb, 0xb7, 0xe7};

    static float leveltable[12] =
        {0.5, 0.583, 0.666, 0.75, 0.833, 0.916, 1, 1.083, 1.166, 1.25, 1.333, 1.416};

// the read-pattern if no button is pulled down by an output
#define no_button (0x70)

    // bit pattern for each row of the keypad scan -- active LOW
    // bit zero low is first entry
    static char out_table[4] = {0b1110, 0b1101, 0b1011, 0b0111};

    // init the port expander
    start_spi2_critical_section;
    initPE();
    // PortZ on Expander ports as digital outputs
    mPortZSetPinsOut(BIT_0 | BIT_1 | BIT_2 | BIT_3); //Set port as output
    // PortZ as inputs
    mPortZSetPinsIn(BIT_4 | BIT_5 | BIT_6 | BIT_7); //Set port as input
    mPortZEnablePullUp(BIT_4 | BIT_5 | BIT_6 | BIT_7);
    end_spi2_critical_section;
    while (1)
    {
        // yield time
        PT_YIELD_TIME_msec(50);

        for (i = 0; i < 4; i++)
        {
            start_spi2_critical_section;
            // scan each row active-low
            writePE(GPIOZ, out_table[i]);
            //reading the port also reads the outputs
            keypad = readPE(GPIOZ);
            end_spi2_critical_section;

            //Break if there was a keypress
            if ((keypad & no_button) != no_button)
            {
                break;
            }
        }

        if (keypad > 0)
        { // then button is pushed
            // if (keypad != 0x70)
            for (i = 0; i < 12; i++)
            {
                if (keytable[i] == keypad)
                {
                    break;
                }
            }
            // if invalid, two button push, set to -1
            if (i == 12)
                i = -1;
        }
        else
        {
            i = -1;
        } // no button pushed

        //debouncer state machine
        switch (PushState)
        {
        case NoPush:
            if (i > -1)
            {
                PushState = MaybePush; //drop into state of MaybePush
                possibleKey = i;       //set possible to the keypad code
            }
            else
            {
                // no push means speed stays the same
                PushState = NoPush; //return to state NoPush to check again
            }
            break;
        case MaybePush:
            // if keycode still matches stored value then button being pressed
            if (i == possibleKey)
            {
                if (i == 0)
                    start = (start + 1) % 2;
                else
                    level = leveltable[i];
                PushState = Pushed;
            }
            else
                PushState = NoPush;
            break;
        case Pushed:
            if (i != possibleKey)
                PushState = MaybeNoPush;
            break;

        case MaybeNoPush:
            if (i == possibleKey)
                PushState = Pushed;
            else
                PushState = NoPush;
            break;
        }
        // end case
    }
    PT_END(pt);
} // keypad thread

// === Main  ======================================================

int main(void)
{
    SpiChnOpen(SPI_CHANNEL2, SPI_OPEN_ON | SPI_OPEN_MODE16 | SPI_OPEN_MSTEN | SPI_OPEN_CKE_REV, 4);

    // SCK2 is pin 26
    // SDO2 (MOSI) is in PPS output group 2, could be connected to RB5 which is pin 14
    PPSOutput(2, RPB5, SDO2);

    // ADC Configuration ///////////////////////////////////////

    // configure and enable the ADC
    CloseADC10(); // ensure the ADC is off before setting the configuration

    // define setup parameters for OpenADC10
    // Turn module on | ouput in integer | trigger mode auto | enable autosample

    // ADC_CLK_AUTO -- Internal counter ends sampling and starts conversion (Auto convert)
    // ADC_AUTO_SAMPLING_ON -- Sampling begins immediately after last conversion completes; SAMP bit is automatically set
    // ADC_AUTO_SAMPLING_OFF -- Sampling begins with AcquireADC10();
    #define PARAM1 ADC_FORMAT_INTG16 | ADC_CLK_AUTO | ADC_AUTO_SAMPLING_OFF //

    // define setup parameters for OpenADC10
    // ADC ref external  | disable offset test | disable scan mode | do 1 sample | use single buf | alternate mode off
    #define PARAM2 ADC_VREF_AVDD_AVSS | ADC_OFFSET_CAL_DISABLE | ADC_SCAN_OFF | ADC_SAMPLES_PER_INT_1 | ADC_ALT_BUF_OFF | ADC_ALT_INPUT_OFF

    // Define setup parameters for OpenADC10
    // use peripherial bus clock | set sample time | set ADC clock divider
    // ADC_CONV_CLK_Tcy2 means divide CLK_PB by 2 (max speed)
    // ADC_SAMPLE_TIME_5 seems to work with a source resistance < 1kohm
    #define PARAM3 ADC_CONV_CLK_PB | ADC_SAMPLE_TIME_5 | ADC_CONV_CLK_Tcy2 //ADC_SAMPLE_TIME_15| ADC_CONV_CLK_Tcy2

    // define setup parameters for OpenADC10
    // set AN11 and  as analog inputs
    #define PARAM4 ENABLE_AN11_ANA // pin 24

    // define setup parameters for OpenADC10
    // do not assign channels to scan
    #define PARAM5 SKIP_SCAN_ALL

    // use ground as neg ref for A | use AN11 for input A
    // configure to sample AN11
    SetChanADC10(ADC_CH0_NEG_SAMPLEA_NVREF | ADC_CH0_POS_SAMPLEA_AN11); // configure to sample AN11
    OpenADC10(PARAM1, PARAM2, PARAM3, PARAM4, PARAM5);                  // configure ADC using the parameters defined above

    EnableADC10(); // Enable the ADC

    // Output/Input Compare Configuration ///////////////////////////////////////

    // === Config timer and output compares to make pulses ========
    // set up timer2 to generate the wave period
    // count is Zero-based, so subtract one for # of clock ticks
    OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_64, generate_period - 1);

    // set up compare3 for double compare mode
    // first number is the time to clear, second is the time to set the pin
    // in this case, 1/20 of the timer period and the start of the timer period
    OpenOC3(OC_ON | OC_TIMER2_SRC | OC_CONTINUE_PULSE, OC3_width, 0); //
    // OC3 is PPS group 4, map to RPB9 (pin 18)
    PPSOutput(4, RPA3, OC3);

    OpenOC2(OC_ON | OC_TIMER2_SRC | OC_CONTINUE_PULSE, OC2_width, 0);
    // OC2 is PPS group 2, map to RPB5
    PPSOutput(2, RPA1, OC2);

    OpenOC1(OC_ON | OC_TIMER2_SRC | OC_CONTINUE_PULSE, OC1_width, 0);
    // OC1 is PPS group 1, map to RPB3
    PPSOutput(1, RPB3, OC1);
    // === Config timer3 free running ==========================
    // set up timer3 as a source for input capture
    // and let it overflow for continuous readings
    OpenTimer3(T3_ON | T3_SOURCE_INT | T3_PS_1_1, 0xffff);

    // Display Configuration ///////////////////////////////////////
    tft_init_hw();
    tft_begin();
    tft_fillScreen(ILI9340_BLACK);
    //240x320 vertical display
    tft_setRotation(0); // Use tft_setRotation(1) for 320x240
    tft_setCursor(0, 0);

    // === config the uart, DMA, vref, timer5 ISR ===========
    PT_setup();

    // === setup system wide interrupts  ====================
    INTEnableSystemMultiVectoredInt();

    //  pt_add(protothread_print, 1);
    //  pt_add(protothread_time, 1);
    pt_add(protothread_control, 2);
    pt_add(protothread_key, 2);

    // === initalize the scheduler ====================
    PT_INIT(&pt_sched);
    PT_INIT(&pt_adc);

    // SCHED_ROUND_ROBIN just cycles thru all defined threads
    pt_sched_method = SCHED_ROUND_ROBIN;

    // === scheduler thread =======================
    // scheduler never exits
    PT_SCHEDULE(protothread_sched(&pt_sched));
    // ============================================
} // main