The group approves this report for inclusion on the course website.
The group approves the video for inclusion on the course youtube channel.
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. |
Both group members wrote the system code in a peer-programming fashion.
Ryan performed all mechanical design, laser cutting and assembly
Ben designed and implemented the project website.
Both group members collaborated to write the content used in the project website.
////////////////////////////////////
// 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