diff --git a/src/main.cpp b/src/main.cpp index 471f2f7162f696e0fe4d555afd3c27da51f9bb72..ffaa4a912d362438050cfe3ee57d258701d5eb21 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,8 +12,12 @@ using namespace helpers; // contains the verbose levels from Instr const float versionNumber = 1.1; // version displayed on boot const uint16_t serialTimeOut = 1000; // timeout in ms const long serialBaudrate = 115200; +const byte numChars = 32; //maximum length of messages parsed on receive. static uint16_t logFreqSamples = 200; // give samplecounter and summary every n samples -const uint8_t downSampler = 16; // average n number of samples before storing. 1 = no downsampling, 2 = average two samples +uint8_t downSampler = 16; // average n number of samples before storing. 1 = no downsampling, 2 = average two samples +const uint8_t maxDownSampler = 128; // max number of samples to average +const uint16_t minSampleRate = 10; // Lowest Acceptable Sample Rate +const uint16_t maxSampleRate = 8096; // Highest Acceptable Sample Rate const uint8_t channelCountSize = 1; // always fixed at 1 byte const unsigned char channelCount[channelCountSize] = {0x01}; // number of active channels send to the plot, for now this is ADC. Should be 0x02 if Batt is also send. @@ -38,7 +42,7 @@ byte sensorStatusBuffer[8]; // the task durations define the sample rate of the tasks: -const long aquisitionTaskDuration = 125; // µs per cycle //for aquisition of sensor data +long aquisitionTaskDuration = 125; // µs per cycle //for aquisition of sensor data const long communicationTaskDuration = 10000; // µs per cycle //of data transmission to client via Blueooth const uint8_t jitterMax = 10; // after this number of missed samples, the device is stopped. int jitterCounter = 0; // amount of times the sample momement was more than one sample time off. @@ -54,13 +58,13 @@ Task communicationTask(communicationTaskDuration, TASK_FOREVER, &communicationTa Scheduler runner; // tasks are added to the scheduler in the setup() sequence and the scheduler is executed in main() loop. long ADCresult; -long ADCresultArray[downSampler]; +long ADCresultArray[maxDownSampler]; float ADCvolts; byte *bADCvolts[4]; // 4 byte float for uScope format float battVolts; byte *bBattvolts[4]; // 4 byte float for uScope format -elapsedMicros elapsedMicrosTime; //timer that can easily be reset - +elapsedMicros elapsedMicrosTime; //timer for aquisition +elapsedMillis elapsedMillisTime; //timer for receiving commands namespace buffers { @@ -87,12 +91,70 @@ namespace buffers } using namespace buffers; + +//read messages received, this call is blocking for as long as new data continues to arrive +//default start and end markers are < & >, other can be defines via arguments. +//characters between the start and end marker are parsed as commands. For example "foo<start>foo" is returned as "start" +//maxReadTime is reserved for future use, to implement a timeout function if new characters keep comming in. +//based on https://forum.arduino.cc/t/serial-input-basics-updated/382007 +char *receiveSerialMessage(const char startMarker = '<', const char endMarker = '>', const int maxReadTime = 100) +{ + static char receivedChars[numChars]; // an array to store the received data + + static boolean recvInProgress = false; + boolean newData = false; + static byte index = 0; + char charactersRead; + elapsedMillisTime = 0; + + //TODO: cancel while loop if new data continues to arrive + while (Serial.available() > 0 && newData == false && elapsedMillisTime < maxReadTime) //read until no new data is available, or maxReadTime is reached + { + charactersRead = Serial.read(); + + if (recvInProgress == true) + { + if (charactersRead != endMarker) + { + receivedChars[index] = charactersRead; + index++; + if (index >= numChars) + { + index = numChars - 1; //TODO: comment FM: does not seem logical to do? + + helpers::printToLog("Received more characters via serial than the max array length! " + String(receivedChars), verboseLevels::error); + } + } + else + { + receivedChars[index] = '\0'; // terminate the string + recvInProgress = false; + index = 0; + newData = true; + } + } + + else if (charactersRead == startMarker) + { + recvInProgress = true; + } + } + if (newData == true) //print the parsed command for debug purposes. + { + helpers::printToLog("Received via Serial: " + String(receivedChars), verboseLevels::info); + } + + return receivedChars; +} + + uint32_t sampleCounter = 0; uint32_t sampleCounterOld = 0; uint32_t millisOld = 0; uint8_t millisWraps = 0; // count the number of times millis() reached 2^32. uint8_t downSampleCounter = 0; // count up each aquisition cycle + /* this taks reads the sensor data at a fixed interval. Based upon the realtime example of library TaskScheduler (https://github.com/arkhipenko/TaskScheduler) this task will be started and stopped based upon the <start> / <stop> commands received via Bluetooth. @@ -278,8 +340,95 @@ void aquisitionTask_Callback() // this task will be active right from the boot, and will never be stopped. void communicationTask_Callback() { + //read data from Serial + + // characters between the start and end marker are parsed as commands. For example "foo<start>foo" is returned as "start" + const char startMarker = '<'; + const char endMarker = '>'; + const int maxReadTime = 100; //ms + char *receivedChars = receiveSerialMessage(startMarker, endMarker, maxReadTime); + + if (strcmp(receivedChars, "start") == 0) + { + if (!aquisitionTask.isEnabled()) + { + memset(receivedChars, 0, sizeof(receivedChars) ); // clean command buffer + jitterCounter = 0; // reset the counter for a clean start + aquisitionTask.enable(); + helpers::printToLog("Start command received, enabled aquisitionTask (start sampling)", verboseLevels::info); + } + } + else if (strcmp(receivedChars, "stop") == 0) + { + if (aquisitionTask.isEnabled()) + { + memset(receivedChars, 0, sizeof(receivedChars) ); // clean command buffer + aquisitionTask.disable(); + helpers::printToLog("Stop command received: disabled aquisitionTask (stop sampling).", verboseLevels::info); + } + } + else if (strncmp(receivedChars, "SetSampleRate", 13) == 0) // command starting with "SetSampleRate", should always be followed by 4 integers, for example "SetSampleRate1000" for 1000 Hz + { + if (aquisitionTask.isEnabled()) + { + helpers::printToLog("Received Set Sample Rate command, but aquisition is active!", verboseLevels::error); + } + else + { + char argument[5]; // stores the value received from the host in ASCCI text + memcpy(argument, &receivedChars[13], 4); // copy characters from full input command + argument[4] = '\0'; // termination character + int newSampleRate = atoi(argument); + helpers::printToLog("Received SetSampleRate Command: " + String(newSampleRate), verboseLevels::info); + + if(newSampleRate < minSampleRate || newSampleRate > maxSampleRate) + { + helpers::printToLog("Received Set Sample Rate command is below " + String(minSampleRate) + " or above " + String(maxSampleRate), verboseLevels::error); + } + else if(newSampleRate / downSampler < minSampleRate) + { + helpers::printToLog("Received Set Sample Rate command is too low in combination with configured downsampler: " + String(downSampler), verboseLevels::error); + } + else + { + aquisitionTaskDuration = 1000000 / newSampleRate; // µs per cycle //for aquisition of sensor data + aquisitionTask.setInterval(aquisitionTaskDuration); + } + } + memset(receivedChars, 0, sizeof(receivedChars) ); // clean command buffer + } + else if (strncmp(receivedChars, "SetDownSampleRate", 17) == 0) // command starting with "SetDownSampleRate", should always be followed by 4 integers, for example "SetDownSampleRate0016" for 16 times downsample + { + if (aquisitionTask.isEnabled()) + { + helpers::printToLog("Received Set DownSample Rate command, but aquisition is active!", verboseLevels::error); + } + else + { + char argument[5]; // stores the value received from the host in ASCCI text + memcpy(argument, &receivedChars[17], 4); // copy characters from full input command + argument[4] = '\0'; // termination character + int newDownSampleRate = atoi(argument); + helpers::printToLog("Received SetDownSampleRate Command: " + String(newDownSampleRate), verboseLevels::info); + + if(newDownSampleRate < 1 || newDownSampleRate > maxDownSampler) + { + helpers::printToLog("Received Set Down Sample Rate command is below 1 or above " + String(maxDownSampler), verboseLevels::error); + } + else if((1000000 / aquisitionTask.getInterval()) / newDownSampleRate < minSampleRate) + { + + helpers::printToLog("Received Set Down Sample Rate command is too low in combination with configured sample rate: " + String(1000000 / aquisitionTask.getInterval()), verboseLevels::error); + } + else + { + downSampler = newDownSampleRate; //set new down sample rate + } + } + memset(receivedChars, 0, sizeof(receivedChars) ); // clean command buffer + } - // write data to Bluetooth + // write data to Serial if (btBufferA.isBufferLocked() && btBufferB.isBufferLocked()) { @@ -301,7 +450,7 @@ void communicationTask_Callback() helpers::printToLog(" try to send buffer B of size (bytes): " + String(btBufferB.getPointerPosition()) + ", data: " + String(btBufferB.buffer[10]), verboseLevels::extraBuffers); if (Serial.availableForWrite()) { - Serial.write((uint8_t *)btBufferB.buffer, btBufferB.getPointerPosition()); // write buffer until pointer position + Serial.write((uint8_t *)btBufferB.buffer, btBufferB.getPointerPosition()); // write buffer until pointer position } btBufferB.clearBuffer(); // clear buffer B & reset pointer } @@ -369,8 +518,8 @@ void setup() communicationTask.enable(); helpers::printToLog("Communication task started!", verboseLevels::info); - aquisitionTask.enable(); - helpers::printToLog("enabled aquisitionTask (start sampling)", verboseLevels::info); + //aquisitionTask.enable(); + //helpers::printToLog("enabled aquisitionTask (start sampling)", verboseLevels::info); } void loop() {