Skip to content
Snippets Groups Projects
Commit 25730e0a authored by MuijzerF's avatar MuijzerF
Browse files

Added start-stop and sample rate control via Serial Commands

parent 8fd19208
No related branches found
No related tags found
No related merge requests found
......@@ -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()
{
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment