This commit is contained in:
Jeremy Franklin-Ross 2018-09-18 10:09:28 -07:00
Родитель e2dcbb4c82
Коммит 291d1de1a7
2 изменённых файлов: 338 добавлений и 287 удалений

Просмотреть файл

@ -1,34 +1,34 @@
/*
Tuned Mass Dampner (TMD) code using Arduino UNO and Adafruit LIS3DH breakout board
More detials on TMD: https://www.microsoft.com/en-us/education/education-workshop/seismograph.aspx
More details on LIS3DH: https://www.adafruit.com/product/2809
This code captures accelerometer data over i2c, but not relying on any external libraries, and sends it over serial.
Adi Azulay, 2017 Microsoft EDU Workshop
*/
//===-------__ Hacking STEM – HotWheels_Accel.X.X.X.ino – Arduino __-------===//
// For use with the Measuring Speed to Understand Forces & Motion lesson plan
// available from Microsoft Education Workshop at http://aka.ms/hackingSTEM
//
// Overview:
// This project times the interval in milliseconds as cars pass between
// switch gates and then measures the gforce of the car impacting the
// accelerometer at the end stop.
//
// This may be implemented with 2 to 9 switch gates. The number of switch
// gates is configured by Excel via Serial connection.
//
// This project uses an Adafruit LIS3DH breakout board, information at:
// https://www.adafruit.com/product/2809
// This project uses an Arduino UNO microcontroller board, information at:
// https://www.arduino.cc/en/main/arduinoBoardUno
//
// Comments, contributions, suggestions, bug reports, and feature requests
// are welcome! For source code and bug reports see:
// http://github.com/[TODO github path to Hacking STEM]
//
// Copyright 2018, Adi Azulay Microsoft EDU Workshop - HackingSTEM
// MIT License terms detailed in LICENSE.txt
//===----------------------------------------------------------------------===//
// Wire is a library that allows communication via i2c with devices such as
// the LIS3DH accelerometer. See https://www.arduino.cc/en/Reference/Wire
#include <Wire.h>
// List of registers used by accelerometer
#define LIS3DH_ADDRESS 0x18
#define LIS3DH_REG_STATUS1 0x07
#define LIS3DH_REG_WHOAMI 0x0F
#define LIS3DH_REG_TEMPCFG 0x1F
#define LIS3DH_REG_CTRL1 0x20
#define LIS3DH_REG_CTRL3 0x22
#define LIS3DH_REG_CTRL4 0x23
#define LIS3DH_REG_OUT_Y_L 0x2A
#define LIS3DH_REG_OUT_Y_H 0x2B
#define LIS3DH_8G_SCALE_FACTOR .00024414f
#define LIS3DH_RANGE_8_G 0b10 // +/- 8g
/*
* Digital Pins for Gate Reading
*/
// Declaring constant variables to allow us to refer to pins by name, this
// makes code easier to understand. Here are all the gate pins.
const byte gate1pin = 2;
const byte gate2pin = 3;
const byte gate3pin = 4;
@ -39,110 +39,77 @@ const byte gate7pin = 8;
const byte gate8pin = 9;
const byte gate9pin = 10;
/*
* Game Control Variables
*/
// To time each gate we declare variable to allow us to track when the
// gate is triggered.
bool gate1state = LOW; //State of gate at last read
unsigned long gate1time = 0; //Time elapsed between start and last read
bool gate1done = false; //If gate was processed during current race
bool gate1state = 0;
unsigned long gate1time = 0;
bool gate1done=0;
bool gate2state = LOW; //State of gate at last read
unsigned long gate2time = 0; //Time elapsed between start and last read
bool gate2done = false; //If gate was processed during current race
bool gate2state = 0;
unsigned long gate2time = 0;
bool gate2done=0;
bool gate3state = LOW; //State of gate at last read
unsigned long gate3time = 0; //Time elapsed between start and last read
bool gate3done = false; //If gate was processed during current race
bool gate3state = 0;
unsigned long gate3time = 0;
bool gate3done=0;
bool gate4state = LOW; //State of gate at last read
unsigned long gate4time = 0; //Time elapsed between start and last read
bool gate4done = false; //If gate was processed during current race
bool gate4state = 0;
unsigned long gate4time = 0;
bool gate4done=0;
bool gate5state = LOW; //State of gate at last read
unsigned long gate5time = 0; //Time elapsed between start and last read
bool gate5done = false; //If gate was processed during current race
bool gate5state = 0;
unsigned long gate5time = 0;
bool gate5done=0;
bool gate6state = LOW; //State of gate at last read
unsigned long gate6time = 0; //Time elapsed between start and last read
bool gate6done = false; //If gate was processed during current race
bool gate6state = 0;
unsigned long gate6time = 0;
bool gate6done=0;
bool gate7state = LOW; //State of gate at last read
unsigned long gate7time = 0; //Time elapsed between start and last read
bool gate7done = false; //If gate was processed during current race
bool gate7state = 0;
unsigned long gate7time = 0;
bool gate7done=0;
bool gate8state = LOW; //State of gate at last read
unsigned long gate8time = 0; //Time elapsed between start and last read
bool gate8done = false; //If gate was processed during current race
bool gate8state = 0;
unsigned long gate8time = 0;
bool gate8done=0;
bool gate9state = LOW; //State of gate at last read
unsigned long gate9time = 0; //Time elapsed between start and last read
bool gate9done = false; //If gate was processed during current race
bool gate9state = 0;
unsigned long gate9time = 0;
bool gate9done=0;
bool force1done = 0;
// Race variables
unsigned long startTime = 0; // Millis timestamp of race start
bool raceFinished = false; // If final gate is completed
bool forceReadDone = false; // If force sensor is read
float gforce = 0; // Accelerometer force output
int prevValue = 0;
int value = 0;
float force1time = 0;
/*
* Program control variables
*/
unsigned long startTime = 0;
byte raceFinished = 0;
/*
* Excel program control variables
*/
// Config & control variables, set via Serial (by Excel)
// to configure and trigger race
int resetTrial = 0; //resets all variables
int numberOfGates = 9; //sets the final gate variable (overridden by Excel)
int numberOfGates = 3; //sets the final gate variable (overridden by Excel)
/*
* Serial data variables
*/
const String mDELIMETER = ","; // Cordoba expects a comma delimeted string of data
String mInputString = ""; // string variable to hold incoming data
boolean mStringComplete = false; // variable to indicate the string is complete (newline found)
int mSerial_Interval = 50; // Intervel between serial writes
unsigned long mSerial_PreviousTime; // Timestamp to track interval
// Serial data variables, used when sending and receiving data
const String mDELIMETER = ","; // comma delimited values
String mInputString = ""; // incoming data
boolean mStringComplete = false; // if incoming line is complete (newline found)
int mSerial_Interval = 50; // interval between serial writes
unsigned long mSerial_PreviousTime; // timestamp to track interval
const int dataRate = 10; // frequency of serial writes, in milliseconds
const int dataRate = 10; // Change this variable to increase the frequency that data is sent to Excel
// setup() method is a special Arduino method that runs once at beginning of
// program. Typically used to initialize pins and connections
void setup(){
//Initilize LIS3DH and set to +/- 16g Scale Factor
Wire.begin();
Wire.beginTransmission(LIS3DH_ADDRESS); //Connects to LIS3DH via i2c
Wire.write (LIS3DH_REG_WHOAMI); //Check that board is connected
Wire.endTransmission(true);
Wire.requestFrom (LIS3DH_ADDRESS, 1);
uint8_t deviceID = Wire.read();
while (deviceID != 0x33){
delay(1);
}
writeRegister8 (LIS3DH_REG_CTRL1, 0x07); //Turn on all axes and set to normal mode
//set data rate
uint8_t accelDataRate = readRegister8 (LIS3DH_REG_CTRL1);
accelDataRate &= ~(0xF0);
accelDataRate |= 0b0111 << 4; //Change variable to write
writeRegister8 (LIS3DH_REG_CTRL1, accelDataRate); //Set data rate to 400 mHz, used to manage power consuption
writeRegister8 (LIS3DH_REG_CTRL4, 0x88); //Enebles High Res and BDU
writeRegister8 (LIS3DH_REG_CTRL3, 0x10); // DRDY on INT1
writeRegister8 (LIS3DH_REG_TEMPCFG, 0x80); //Activate ADC outputs
//Set read scale
uint8_t rangeControl = readRegister8 (LIS3DH_REG_CTRL4);
rangeControl &= ~(0x30);
rangeControl |= LIS3DH_RANGE_8_G << 4; //Change variable to write make sure to also update the scale factor
writeRegister8 (LIS3DH_REG_CTRL4, rangeControl);
// initialized communication with accelerometer
// note, if accelerometer has problems, program can hange here
initializeAccelerometer();
//initialize serial connection (to communicate with excel)
Serial.begin (9600);
// Set each of our gate pins to be used as input pins
pinMode(gate1pin, INPUT);
pinMode(gate2pin, INPUT);
pinMode(gate3pin, INPUT);
@ -153,20 +120,27 @@ void setup(){
pinMode(gate8pin, INPUT);
pinMode(gate9pin, INPUT);
// Reset all race variable, so we're ready to go!
resetTrialVariables();
}
// loop() method is a special Arduino function that runs over and over. This
// function executes from top to bottom and then starts again, it runs forever!
void loop(){
processIncomingSerial();
if(resetTrial==1){
processIncomingSerial(); // See if any commands have come in (from Excel)
if(resetTrial==1){ // If a race was already run, clear old data
resetTrialVariables();
}
processDigitalSensors();
processSwitches();
processForceSensor();
processOutgoingSerial();
processDigitalSensors(); // Read all gate switches
processSwitches(); // Process any changes to switch state
processForceSensor(); // Read force sensor
processOutgoingSerial(); // Write any results to Serial (to Excel)
}
// Quickly read state of each gate switch pin and write it to variables
void processDigitalSensors(){
gate1state = digitalRead(gate1pin);
gate2state = digitalRead(gate2pin);
@ -178,163 +152,177 @@ void processDigitalSensors(){
gate8state = digitalRead(gate8pin);
gate9state = digitalRead(gate9pin);
}
// Examine gate switch pine state and update timing if triggered
void processSwitches(){
// Digitial switch reads
if(raceFinished==0){
if(gate1state==HIGH && gate1done==0){
if(raceFinished==false){
// If first gate is triggered, we set startTime to current
// timestamp, in milliseconds.
if(gate1state==HIGH && gate1done==false){
gate1time = 0;
gate1done=1;
gate1done=true;
startTime = millis();
}
if(gate2state==HIGH && gate2done==0){
// When gate is triggered we calculate the elapsed time since
// the first gate was triggered
if(gate2state==HIGH && gate2done==false){
gate2time = millis() - startTime;
gate2done=1;
if(numberOfGates==2){
raceFinished = 1;
gate2done=true;
if(numberOfGates==2){ // If this is last gate, race as finished
raceFinished = true;
}
}
if(gate3state==HIGH && gate3done==0){
// When gate is triggered we calculate the elapsed time since
// the first gate was triggered
if(gate3state==HIGH && gate3done==false){
gate3time = millis() - startTime;
gate3done=1;
if(numberOfGates==3){
raceFinished = 1;
gate3done=true;
if(numberOfGates==3){ // If this is last gate, race as finished
raceFinished = true;
}
}
if(gate4state==HIGH && gate4done==0){
// When gate is triggered we calculate the elapsed time since
// the first gate was triggered
if(gate4state==HIGH && gate4done==false){
gate4time = millis() - startTime;
gate4done=1;
if(numberOfGates==4){
raceFinished = 1;
gate4done=true;
if(numberOfGates==4){ // If this is last gate, race as finished
raceFinished = true;
}
}
if(gate5state==HIGH && gate5done==0){
// When gate is triggered we calculate the elapsed time since
// the first gate was triggered
if(gate5state==HIGH && gate5done==false){
gate5time = millis() - startTime;
gate5done=1;
if(numberOfGates==5){
raceFinished = 1;
gate5done=true;
if(numberOfGates==5){ // If this is last gate, race as finished
raceFinished = true;
}
}
if(gate6state==HIGH && gate6done==0){
// When gate is triggered we calculate the elapsed time since
// the first gate was triggered
if(gate6state==HIGH && gate6done==false){
gate6time = millis() - startTime;
gate6done=1;
if(numberOfGates==6){
raceFinished = 1;
gate6done=true;
if(numberOfGates==6){ // If this is last gate, race as finished
raceFinished = true;
}
}
if(gate7state==HIGH && gate7done==0){
// When gate is triggered we calculate the elapsed time since
// the first gate was triggered
if(gate7state==HIGH && gate7done==false){
gate7time = millis() - startTime;
gate7done=1;
if(numberOfGates==7){
raceFinished = 1;
gate7done=true;
if(numberOfGates==7){ // If this is last gate, race as finished
raceFinished = true;
}
}
if(gate8state==HIGH && gate8done==0){
// When gate is triggered we calculate the elapsed time since
// the first gate was triggered
if(gate8state==HIGH && gate8done==false){
gate8time = millis() - startTime;
gate8done=1;
if(numberOfGates==8){
raceFinished = 1;
gate8done=true;
if(numberOfGates==8){ // If this is last gate, race as finished
raceFinished = true;
}
}
if(gate9state==HIGH && gate9done==0){
// When gate is triggered we calculate the elapsed time since
// the first gate was triggered
if(gate9state==HIGH && gate9done==false){
gate9time = millis() - startTime;
gate9done=1;
if(numberOfGates==9){
raceFinished = 1;
gate9done=true;
if(numberOfGates==9){ // If this is last gate, race as finished
raceFinished = true;
}
}
}
}
// If race is finished poll force sensor for a reading
void processForceSensor(){
float maxYAxis = 0;
if(raceFinished==1 && force1done==0)
{
for (int i = 0; i <= 100; i++){
Wire.beginTransmission(LIS3DH_ADDRESS);
Wire.write(LIS3DH_REG_OUT_Y_L | 0x80);
Wire.endTransmission();
Wire.requestFrom(LIS3DH_ADDRESS, 2);
while (Wire.available() < 2);
uint8_t yla = Wire.read();
uint8_t yha = Wire.read();
float yAxis = yha << 8 | yla;
yAxis = yAxis * LIS3DH_8G_SCALE_FACTOR;
if (yAxis > maxYAxis) {maxYAxis = yAxis;}
delay (1);
}
force1time = maxYAxis; //get the time interval passed during impact (for Excel)
force1done = 1;
if(raceFinished==true && forceReadDone==false) {
gforce = getMaxYAxisReadFromForceSensor(); // get force reading
forceReadDone = true;
}
}
// Resets all variables so we're ready to run another race trail
void resetTrialVariables(){
raceFinished = 0;
force1done = 0;
raceFinished = false;
forceReadDone = false;
gate1time = 999;
gate1done=0;
gate1done = false;
gate2time = 0;
gate2done=0;
gate2done = false;
gate3time = 0;
gate3done=0;
gate3done = false;
gate4time = 0;
gate4done=0;
gate4done = false;
gate5time = 0;
gate5done=0;
gate5done = false;
gate6time = 0;
gate6done=0;
gate6done = false;
gate7time = 0;
gate7done=0;
gate7done = false;
gate8time = 0;
gate8done=0;
gate8done = false;
gate9time = 0;
gate9done=0;
gate9done = false;
force1time = 0;
gforce = 0;
resetTrial = 0;
}
/*
* -------------------------------------------------------------------------------------------------------
* INCOMING SERIAL DATA FROM EXCEL PROCESSING CODE--------------------------------------------------------
* -------------------------------------------------------------------------------------------------------
*/
void processIncomingSerial()
{
// Read any lines of text, if available and parse commands and configuration
void processIncomingSerial() {
getSerialData();
parseSerialData();
}
/*
* getSerialData()
*
* Gathers bits from serial port to build mInputString
*/
void getSerialData()
// Writes data to Serial (for excel) at a maximum rate of mSerial_Interval
void processOutgoingSerial() {
// Write at intervals to prevent bogging down script
if((millis() - mSerial_PreviousTime) > mSerial_Interval)
{
mSerial_PreviousTime = millis(); // Reset interval timestamp
sendDataToSerial();
}
}
//===----------------------------------------------------------------------===//
// Note: Code below this line may be technical and somewhat confusing, the
// purpose of these functions are to do some complicated things such as
// interfacing with external devices.
//
// To communicate with LIS3DH without requiring external libraries, we had
// to implement some code in this section may seem very cryptic! Don't worry
// it's hard for us to understand as well :)
//===----------------------------------------------------------------------===//
// Looks Serial data and read it character by character into a string
void getSerialData() {
while (Serial.available()) {
char inChar = (char)Serial.read(); // get new byte
mInputString += inChar; // add it to input string
@ -344,21 +332,14 @@ void getSerialData()
}
}
/*
* parseSerialData()
*
* Parse all program control variables and data from Excel
*/
void parseSerialData()
{
if (mStringComplete) { // process data from mInputString to set program variables.
//set variables using: var = getValue(mInputString, ',', index).toInt(); // see getValue function below
// Take input string and parse commands and configuration
void parseSerialData() {
if (mStringComplete) { // process data from mInputString to set variables.
resetTrial = getValue(mInputString, ',', 0).toInt(); // Data Out column A5
String data = getValue(mInputString, ',', 1); // Data Out column B5
if(data != ""){
numberOfGates = data.toInt(); //getValue(mInputString, ',', 1); //.toInt(); // Data Out column B5
numberOfGates = data.toInt(); // convert to integer, if not empty
}
mInputString = ""; // reset mInputString
@ -366,45 +347,43 @@ void parseSerialData()
}
}
/*
* getValue()
*
* Gets value from mInputString using an index. Each comma delimited value in mInputString
* is counted and the value of the matching index is returned.
*/
String getValue(String mDataString, char separator, int index)
{
// mDataString is mInputString, separator is a comma, index is where we want to look in the data 'array'
// Gets value from mDataString using an index and separator
// Example:
// given 'alice,bob,dana' and separator ','
// index 0 returns 'alice'
// index 1 returns 'bob'
// index 2 returns 'dana'
//
// mDataString: String as read from Serial (mInputString)
// separator: Character used to separate values (a comma)
// index: where we want to look in the data 'array' for value
String getValue(String mDataString, char separator, int index) {
int matchingIndex = 0;
int strIndex[] = {0, -1};
int maxIndex = mDataString.length()-1;
for(int i=0; i<=maxIndex && matchingIndex<=index; i++){ // loop until end of array or until we find a match
if(mDataString.charAt(i)==separator || i==maxIndex){ // if we hit a comma OR we are at the end of the array
matchingIndex++; // increment matchingIndex to keep track of where we have looked
// loop until end of array or until we find a match
for(int i=0; i<=maxIndex && matchingIndex<=index; i++){
if(mDataString.charAt(i)==separator || i==maxIndex){ // if we hit a comma
// OR end of the array
matchingIndex++; // increment to track where we have looked
strIndex[0] = strIndex[1]+1; // increment first substring index
// ternary operator in objective c - [condition] ? [true expression] : [false expression]
strIndex[1] = (i == maxIndex) ? i+1 : i; // set second substring index
}
}
return matchingIndex>index ? mDataString.substring(strIndex[0], strIndex[1]) : ""; // if match return substring or ""
}
/*
* -------------------------------------------------------------------------------------------------------
* OUTGOING SERIAL DATA TO EXCEL PROCESSING CODE----------------------------------------------------------
* -------------------------------------------------------------------------------------------------------
*/
void processOutgoingSerial()
{
if((millis() - mSerial_PreviousTime) > mSerial_Interval) // Write at intervals to prevent bogging down script
{
mSerial_PreviousTime = millis(); // Reset interval timestamp
sendDataToSerial();
// if match return substring or ""
if (matchingIndex > index) {
return mDataString.substring(strIndex[0], strIndex[1]);
} else {
return "";
}
}
void sendDataToSerial()
{
// Constructs comma delimited list of gate times and force, then writes it
// to serial (for Excel).
// Writes 9 gate time stamps and one force
// example 11225,13535,18929,0,0,0,0,0,2.3223
void sendDataToSerial() {
//gate times in milliseconds
Serial.print(gate1time);
Serial.print(mDELIMETER);
@ -433,13 +412,87 @@ void sendDataToSerial()
Serial.print(gate9time);
Serial.print(mDELIMETER);
//force time in microseconds
Serial.print(force1time);
Serial.print(gforce);
Serial.print(mDELIMETER);
Serial.println(); // Line ending character
}
// List of registers used by accelerometer
#define LIS3DH_ADDRESS 0x18
#define LIS3DH_REG_STATUS1 0x07
#define LIS3DH_REG_WHOAMI 0x0F
#define LIS3DH_REG_TEMPCFG 0x1F
#define LIS3DH_REG_CTRL1 0x20
#define LIS3DH_REG_CTRL3 0x22
#define LIS3DH_REG_CTRL4 0x23
#define LIS3DH_REG_OUT_Y_L 0x2A
#define LIS3DH_REG_OUT_Y_H 0x2B
#define LIS3DH_8G_SCALE_FACTOR .00024414f
#define LIS3DH_RANGE_8_G 0b10 // +/- 8g
// Initilize LIS3DH see LIS3DH spec sheet
void initializeAccelerometer() {
Wire.begin();
Wire.beginTransmission(LIS3DH_ADDRESS); //Connects to LIS3DH via i2c
Wire.write (LIS3DH_REG_WHOAMI); //Check that board is connected
Wire.endTransmission(true);
Wire.requestFrom (LIS3DH_ADDRESS, 1);
uint8_t deviceID = Wire.read();
while (deviceID != 0x33){
delay(1); // TODO review looks like a bug... hangs forever if no LIS3DH on first Wire.read() should at least write "No LISD3H" to serial while it hangs
}
//Turn on all axes and set to normal mode
writeRegister8 (LIS3DH_REG_CTRL1, 0x07);
//set data rate
uint8_t accelDataRate = readRegister8 (LIS3DH_REG_CTRL1);
accelDataRate &= ~(0xF0);
accelDataRate |= 0b0111 << 4; //Change variable to write
//Set data rate to 400 mHz, used to manage power consumption
writeRegister8 (LIS3DH_REG_CTRL1, accelDataRate);
writeRegister8 (LIS3DH_REG_CTRL4, 0x88); //Enebles High Res and BDU
writeRegister8 (LIS3DH_REG_CTRL3, 0x10); // DRDY on INT1
writeRegister8 (LIS3DH_REG_TEMPCFG, 0x80); //Activate ADC outputs
//Set read scale
uint8_t rangeControl = readRegister8 (LIS3DH_REG_CTRL4);
rangeControl &= ~(0x30);
//Change variable to write make sure to also update the scale factor
rangeControl |= LIS3DH_RANGE_8_G << 4;
writeRegister8 (LIS3DH_REG_CTRL4, rangeControl);
}
// Reads Y axis force values from accelerometer and returns
// the highest of 100 sequential reads over a 100ms timespan
float getMaxYAxisReadFromForceSensor() {
float maxYAxis = 0;
for (int i = 0; i <= 100; i++){
Wire.beginTransmission(LIS3DH_ADDRESS);
Wire.write(LIS3DH_REG_OUT_Y_L | 0x80);
Wire.endTransmission();
Wire.requestFrom(LIS3DH_ADDRESS, 2);
while (Wire.available() < 2);
uint8_t yla = Wire.read();
uint8_t yha = Wire.read();
float yAxis = yha << 8 | yla;
yAxis = yAxis * LIS3DH_8G_SCALE_FACTOR;
if (yAxis > maxYAxis) {maxYAxis = yAxis;}
delay (1);
}
return maxYAxis;
}
// configuration of accelerometer, LIS3DH datasheet
void writeRegister8 (uint8_t reg, uint8_t val){
Wire.beginTransmission (LIS3DH_ADDRESS);
Wire.write (reg);
@ -447,6 +500,7 @@ void writeRegister8 (uint8_t reg, uint8_t val){
Wire.endTransmission();
}
// configuration of accelerometer, LIS3DH datasheet
uint8_t readRegister8 (uint8_t reg){
Wire.beginTransmission (LIS3DH_ADDRESS);
Wire.write (reg);
@ -456,5 +510,4 @@ uint8_t readRegister8 (uint8_t reg){
uint8_t val = Wire.read();
return val;
Wire.endTransmission();
}

Просмотреть файл

@ -1,20 +1,18 @@
# Introduction
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
Arduino code for Microsoft Hacking STEM Hot Wheels Speedometry Lesson Plan adapted for Arduino
# Getting Started
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
1. Installation process
2. Software dependencies
3. Latest releases
4. API references
1. Download lesson assets at http://aka.ms/hackingSTEM
1. Build your Hot Wheels track!
1. Use [Arduino IDE](https://www.arduino.cc/en/Main/Software) to the .ino to your [Arduino Uno](https://store.arduino.cc/usa/arduino-uno-rev3) microcontroller
1. Verify data interactions in Excel
1. Ready, Set, Science!
# Build and Test
TODO: Describe and show how to build your code and run the tests.
# Contributing
We gladly accept pull requests for enhancements and bug fixes! You may also submit feature requests and bugs!
https://github.com/TODO
# Contribute
TODO: Explain how other users and developers can contribute to make your code better.
# Make it your!
This project is licensed under the MIT open source license, see License.txt. The MIT license allows you to take this project and make awesome things with it! MIT is a very permissive license, but does require you include license and copyright from License.txt in any derivative work for sake of attribution.
If you want to learn more about creating good readme files then refer the following [guidelines](https://www.visualstudio.com/en-us/docs/git/create-a-readme). You can also seek inspiration from the below readme files:
- [ASP.NET Core](https://github.com/aspnet/Home)
- [Visual Studio Code](https://github.com/Microsoft/vscode)
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
Fork away! Let us know what you build!