#include //------------------------------------------------------------------------------- // // Firmware enabling the control of the "FruitFly Arena" by Ruijsink Dynamic // Engineering. Communication to the arena is done using the Arduino Due // NOTE: Should be compiled with Arduino software newer then 1.5.5Beta. // For the moment that means Nightly build! Because of bug in analogRead() in 1.5.5 and before // // Typical use: Example here // // M.M.Span / D.H.van Rijn // // Department of Psychology // Faculty of Behavioral Sciences // University of Groningen, the Netherlands // // (C) 2013, 2014 // // //------------------------------------------------------------------------------- char version[5] = "1.0h"; // History: // // 1.0a - 1.0d: MS: Initial versions // 1.0e: HvR: Added (again) the boost auto-shut-off functionality. After issueing a boost command, the boost will stay on for at most BOOSTDURATION. // 1.0f: HvR: GetTemps now works as expected & added PID Control library to update temperature // // Problem with PID control is that our boost works way to fast to use derivatives. The old system (large increases if temperature is clearly below set // value worked a lot better. Reinstate that for 1.0g, and get rid of the PID. // // 1.0g: HvR: Get rid of PID control. Change PWM by 1 until at 100 Hz, and extreme changes if bigger temperature changes are requested. Also copied // all control code to main loop, and disabled timer. Reason for this is that the missing characters in the serial input stream seem to be caused by // the timing interrupt (i.e., it doesn't happen anymore after the timer was disabled). This does mean that the execution of *all* commands passed // over the serial loop should be executed *quickly*, as PWM and boost will not be updated during commands that need long for execution. Therefore removed // flash and demo. Added p command, which gives a human readable output of current temperatures and PWM values. // // 1.0h MS: Using native USB line (enabling higher resolution measurements) and adding LCD support. HvR: Changed order of tiles by switching pin positions. (The original code refered to the left tile as // right, and vice versa.) // uncomment this line to get human-readable messages on the com-port. //#define DEBUG // Handler for the timer that controls the interaction with the ARENA //DueTimer SampleTimer = NULL; // Pin number definitions: #define LEDPIN 13 // LEDs int Long=2; int Short=3; int matlabData=4; // #define TILESINVERSED #ifdef TILESINVERSED int setTiles[3] = {12, 11, 10}; // PinNumbers controlling the tiles (PWM) int setBoost[3] = { 7, 6, 5}; // PinNumbers controlling the boost function of the tiles int getTiles[3] = {A0, A1, A2}; // A0,A1 AND A2 #else int setTiles[3] = {10, 11, 12}; // PinNumbers controlling the tiles (PWM) int setBoost[3] = { 5, 6, 7}; // PinNumbers controlling the boost function of the tiles int getTiles[3] = {A2, A1, A0}; // A0,A1 AND A2 #endif #define setCopperBlock 9 #define getCopperBlock A3 // A3 #define getCoolingBlock A4 // A4 #define getReferenceVoltage A5 // A5 // Resolution of write/read operations (in bits) #define analogWriteResolutionBits 8 // PWM resolution #define analogReadResolutionBits 12 // PWM resolution // Variables indicating what we currently write to the ports: boolean Boost[3] = {false, false, false}; // BoostUntil contains the time until which we want to sent the boost. Time is counted in millis() unsigned long BoostUntil[3] = {0, 0, 0}; #define BOOSTDURATION 1000 // Number of miliseconds that a boost will take (Firmware controlled). The system is built based on the assumption that a boost won't last longer than ~ 100ms. // The next values will be immediately overwritten in the Sampler loop. int copperBlockPWM = 0; int tilesPWM[3] = {0, 0, 0}; unsigned long nextPWMAdjust = 0; // Variables indicating which temperature we aim for on the tiles/copperblock //int tilesDesiredTemp[3] = {25, 25, 25}; //int copperBlockDesiredTemp = 15; float tilesMeasuredTemp[3] = {0, 0, 0}; float copperBlockMeasuredTemp = 0; // Variables for input/command parsing. String inputString = ""; // a string to hold incoming data boolean stringComplete = false; // whether the string is complete // //#define MAXSAMPLES 9000 #define CHANNELS 9 #define SAMPLERATE 100 boolean isSampling = false; // are we currently storing temperature information in the mSampledData? float mSampledData[CHANNELS]; //uint32_t mTimer; uint32_t startTime=millis(); #define LCD #ifdef LCD int LCD_Update = 0; //LiquidCrystal lcd(52,53,48,49,50,51); //LiquidCrystal lcd(8,9,4,5,6,7); char TempMsg[21]; char PWMMsg[21]; float tilesAveMeasuredTemp[3] = {0, 0, 0}; LiquidCrystal lcd(52,45,48,49,50,51); // 53 doet raar. Deze setup werkt met de kabel. mms #endif // ------------------------------------------------------------- // // Start of code // // ------------------------------------------------------------- void SamplerCB() { // if (isSampling == true) { // mSampledData[0] = tilesDesiredTemp[0]; // mSampledData[1] = tilesMeasuredTemp[0]; // // Tile 2: // mSampledData[2] = tilesDesiredTemp[1]; // mSampledData[3] = tilesMeasuredTemp[1]; // Tile 3: // mSampledData[4] = tilesDesiredTemp[2]; // mSampledData[5] = tilesMeasuredTemp[2]; // Copperblock, coolingblock, PWM // mSampledData[6] = copperBlockMeasuredTemp; // mSampledData[7] = val2tempCoolingBlock(analogRead(getCoolingBlock)); // mSampledData[8] = millis()-startTime; // mTimer = millis()-startTime;; // SerialUSB.write((uint8_t*)mSampledData, ((int)CHANNELS * sizeof(float))); //SerialUSB.write((uint8_t*)&mTimer, sizeof(uint32_t)); // } } void setup() { for (int i = 0; i < 3; i++) { pinMode(setTiles[i], OUTPUT); pinMode(getTiles[i], INPUT); analogWrite(setTiles[i], 0); analogWrite(setBoost[i], 0); // start with pins LOW! } pinMode(LEDPIN, OUTPUT); analogWrite(LEDPIN, 0); pinMode(setCopperBlock, OUTPUT); pinMode(getCopperBlock, INPUT); pinMode(getCoolingBlock, INPUT); // LEDs pinMode(Long,OUTPUT); pinMode(Short,OUTPUT); digitalWrite(Long,LOW); digitalWrite(Short,LOW); SerialUSB.begin(115200); delay(250); analogWriteResolution(analogWriteResolutionBits); analogReadResolution(analogReadResolutionBits); #ifdef LCD lcd.begin(16, 2); lcd.setCursor(0, 0); lcd.clear(); lcd.print(String("Ruijsink Dynamic Engineering")); lcd.setCursor(0, 1); lcd.print(String("Univeristy of Groningen")); #endif // while (millis()-startTime < 10000) // { // lcd.scrollDisplayLeft(); // delay(300); // } #ifdef LCD lcd.setCursor(0, 0); lcd.clear(); lcd.print(String("Arena v") + version); #endif } void loop() { serialParser(); if (stringComplete) { parseCommands(inputString); inputString = ""; stringComplete = false; } if (millis() >= nextPWMAdjust) { nextPWMAdjust = millis() + 10; for (int i = 0; i <= 2; i++) { tilesMeasuredTemp[i] = val2tempTile(analogRead(getTiles[i])); tilesAveMeasuredTemp[i] += (tilesMeasuredTemp[i] / 50.0); } copperBlockMeasuredTemp = val2tempCopperBlock(analogRead(getCopperBlock)); // update the LCD temperature every 50 samples; if (LCD_Update++ >= 50) { lcd.clear(); sprintf(TempMsg, "%2.0f %2.0f %2.0f %2.0f", tilesMeasuredTemp[0],tilesMeasuredTemp[1],tilesMeasuredTemp[2],copperBlockMeasuredTemp); lcd.setCursor(0, 0); lcd.print(TempMsg); sprintf(PWMMsg, "%3.0f %3.0f %3.0f %3.0f", float(tilesPWM[0]),float(tilesPWM[1]),float(tilesPWM[2]),float(copperBlockPWM)); lcd.setCursor(0, 1); lcd.print(PWMMsg); LCD_Update = 0; tilesAveMeasuredTemp[0] = 0; tilesAveMeasuredTemp[1] = 0; tilesAveMeasuredTemp[2] = 0; //{0, 0, 0}; } // Check whether we need to turn the boosters on, or off. I don't really like to do that in this loop, // as this loop is software controlled (in contrast to the main loop). If the software-loop crashes while // a boost is on, bad things might happen... boolean ledOn = false; for (int i = 0; i <= 2; i++) { if (BoostUntil[i] > millis()) { analogWrite(setBoost[i], 255); ledOn = true; } else { analogWrite(setBoost[i], 0); Boost[i] = false; } } // Also light the led when he boost is on. if (ledOn) { analogWrite(LEDPIN, 255); } else { analogWrite(LEDPIN, 0); } analogWrite(setCopperBlock, copperBlockPWM); for (int i = 0; i <= 2; i++) { analogWrite(setTiles[i], tilesPWM[i]); } SamplerCB(); } } #define Switch(STR) String _x; _x=STR; if (false) #define Case(STR) } else if (_x==STR){ #define Default } else { void parseCommands(String Input) { String Command = getValue(Input, '=', 0); Input = getValue(Input, '=', 1); Command.toUpperCase(); Switch (Command) { // ------------------------------------------------------------------------- Case ("VERSION") #ifdef LCD lcd.setCursor(0, 0); lcd.clear(); lcd.print(String("Arena ") + version); #endif ; //break; // ------------------------------------------------------------------------- Case ("SETTILETEMP") if (getNumberOfArguments(Input, ',') != 3) { ; } else { for (int i = 0; i <= 2; i++) { tilesPWM[i] = Str2Int(getValue(Input, ',', i)); } } //break; // ------------------------------------------------------------------------- Case ("SETCOPPERTEMP") if (getNumberOfArguments(Input, ',') != 1) { ; } else { copperBlockPWM = Str2Int(Input); } //break; // ------------------------------------------------------------------------- Case ("STARTSAMPLING") #ifdef LCD lcd.setCursor(0, 0); lcd.clear(); lcd.print("Running.."); #endif startTime=millis(); isSampling = true; //break; // ------------------------------------------------------------------------- Case ("STOPSAMPLING") #ifdef LCD lcd.setCursor(0, 0); lcd.clear(); lcd.print("Stopped.."); #endif isSampling = false; //break; // ------------------------------------------------------------------------- Case ("INITIALIZE") #ifdef LCD lcd.setCursor(0, 0); lcd.clear(); lcd.print("Init.."); #endif parseCommands("SetTileTemp = 25, 25, 25"); parseCommands("Boost = 0, 0, 0"); parseCommands("StopSampling"); for (int c = 0 ; c < CHANNELS; c++) { mSampledData[c] = 0; } isSampling = false; //break; // ------------------------------------------------------------------------- //LEDs Case ("LEDS") if (getNumberOfArguments(Input, ',') != 1) { ; } else { matlabData = Str2Int(Input); if(matlabData==4){ // turn light off digitalWrite(Long,LOW); digitalWrite(Short,LOW); } else if(matlabData==1){ //Pole position digitalWrite(Long,HIGH); digitalWrite(Short,HIGH); // turn light on } else if(matlabData==2){ digitalWrite(Long,HIGH); } else if(matlabData==3){ digitalWrite(Short,HIGH); } } //break; // ------------------------------------------------------------------------- Case ("MESSAGE") #ifdef LCD lcd.setCursor(0, 0); lcd.clear(); lcd.print(Input); #else SerialUSB.println("Input"); #endif //break; // ------------------------------------------------------------------------- Case ("AREYOUTHERE") #ifdef LCD lcd.setCursor(0, 0); lcd.clear(); lcd.print("Yes I am."); #else SerialUSB.println("Yes I am."); #endif //break; // ------------------------------------------------------------------------- Default #ifdef LCD lcd.setCursor(0, 0); lcd.clear(); lcd.print("??: " + Command); #else SerialUSB.println("Unknown Command Received."); SerialUSB.println(Command); #endif } } int Str2Int(String stringValue) { // helper function to convert a sting into an integer. // no checking whatsowever. char charArg[stringValue.length() + 1]; stringValue.toCharArray(charArg, stringValue.length() + 1); return atoi(charArg); } int getNumberOfArguments(String data, char separator) { // Returns the number of arguments that are given on a commandline. // Counts the number of seperators. if (data.length() == 0) return 0; int found = 0; int maxIndex = data.length() - 1; for (int i = 0; i < maxIndex; i++) { if (data.charAt(i) == separator) { found++; } } return found + 1; } String getValue(String data, char separator, int index) { // Get the indexed value from a string. Starting with 0. // used for parsing the command line. int found = 0; int strIndex[] = {0, -1}; int maxIndex = data.length() - 1; for (int i = 0; i <= maxIndex && found <= index; i++) { if (data.charAt(i) == separator || i == maxIndex) { found++; strIndex[0] = strIndex[1] + 1; strIndex[1] = (i == maxIndex) ? i + 1 : i; } } return found > index ? data.substring(strIndex[0], strIndex[1]) : ""; } boolean isValid(char in) { // which characters are valid in the commands that can be given. // boolean retval = false; if ((in >= 'a') && (in <= 'z')) retval = true; if ((in >= 'A') && (in <= 'Z')) retval = true; if ((in >= '0') && (in <= '9')) retval = true; if ((in == '.') || (in == ',') || (in == '=')) retval = true; return retval; } void serialParser() { // This is where the communication takes place. // Commands that should be known by the board are parsed here. // Illigal commands should be 'silently ignored'. // This subroutine is called whenever serial data are available to the firmware. // Typically this is the mac mini asking the arena to do something. // // When stringComplete == true, the full command is available. In loop() this flag // is cleared and the command is parsed (calling parseCommands) // while (SerialUSB.available()) { // get the new byte: char inChar = (char)SerialUSB.read(); // if the incoming character is a newline, set a flag // so the main loop can do something about it: if (inChar == '\n') { stringComplete = true; break; } else // add it to the inputString: if (isValid(inChar)) inputString += inChar; } } float val2tempTile(int val) { float temp = (100 * val / (pow(2, analogReadResolutionBits) - 1)); return (temp); } float val2tempCopperBlock(int val) { float temp = (100 * val / (pow(2, analogReadResolutionBits) - 1)); return (temp); } float val2tempCoolingBlock(int val) { float temp = (100 * val / (pow(2, analogReadResolutionBits) - 1)); return (temp); }