OK, thanks to all your comments I think I’m back on track.
The previous version of this code didn’t “parse and write” in the serial port event handler. I have a byte array queue (thanks to @ taylorza). When the event handler got the CR, it dumped the message onto the queue and the queue processor took care of calling the right parser for each serial port message. I do lock the call to enqueue a message since it is possible in my program for a timer event handler, which I’m pretty sure does run it it’s own thread right?, to also enqueue a message.
After a message is parsed, the queue processor calls the SD write method and it is the only place where writing the SD card happens. I went down the “no queue” track after I’d gotten the parsers to be much, much simpler and neglected the fact that writing to SD is pretty slow. I’m back to using the queue and everything seems to be working normally, at least for now.
I do have a question for @ RoSchmi if you don’t mind. I’ve relearned the lesson that we’re supposed to do the bare minimum in serial port event handlers but I still look for the start of text character (LF) and end of text character (CR) in my event handler before passing the message to the queue. I looked hard at your code and can see that you pass every byte received at the event handler to your SerialBuffer without really looking at. Where do you process the SerialBuffer? I could do this but I’d have a SerialBuffer for every port which is do-able but kind of awkward. Do you think the relative small additional steps I do in the event handler will be an issue?
I’ve posted my code below if anyone is interested in the gory details. Any comments will still be greatly appreciated.
Thanks for all the help
Here is the SBE41 class that reads the data from the instrument. This gizmo is kind of old and requires some carefully timed hardware handshaking before it spits up data over it’s serial port. I do that with a timer and I think the timer event handler may have been part of my previous problem.
using System;
using Microsoft.SPOT;
using System.Text;
using System.Threading;
using System.IO.Ports;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.IO;
using GHI.Hardware.EMX;
namespace miniCPF
{
class SBE41
{
private static SerialPort SBE41Port = new SerialPort("com4", 9600, Parity.None, 8, StopBits.One);
private static Timer SBE41Timer = null;
private static OutputPort DR = new OutputPort(Pin.IO15, false);
private static OutputPort SPM = new OutputPort(Pin.IO46, true);
private static OutputPort serialTX;
private static double pressure = Double.NaN;
private static byte[] pressureBytes = new byte[16];
private static byte[] portReadBytes = new byte[1024];
private static byte[] portInBytes = new byte[240];
private static byte[] header = UTF8Encoding.UTF8.GetBytes("CTD ");
private static byte[] message = new byte[256];
private static byte space = 32;
private static byte CR = 13;
private static byte LF = 10;
private static byte comma = 44;
private static byte STX = LF;
private static byte ETX = CR;
private static byte token = space;
private static int pressureBytesIndex = 0;
private static int portBytesToRead;
private static int portInBytesIndex = -1;
private static int messageNum;
private static int timerState = 0;
private static int parserI = 0;
private static int inArrayLength = 0;
private static StringBuilder sbTemp = new StringBuilder(256);
private static Object SBE41Lock = new Object();
public static void Init()
{
SBE41Port.DataReceived += new SerialDataReceivedEventHandler(portDataReceivedHandler);
//Initialize();
SBE41Port.ReadTimeout = 5000;
SBE41Port.WriteTimeout = 20;
SBE41Port.Open();
//ReleaseWakeUp();
DR.Write(false);
SPM.Write(true);
SBE41Timer = new Timer(new TimerCallback(SBE41TimerCallback), null, 500, 2000);
//Stop the timer by set SBE41Timer = null
Array.Copy(header, message, header.Length);
}
private static void SBE41TimerCallback(object state)
{
switch (timerState)
{
case 0:
SBE41Port.Close();
serialTX = new OutputPort(Pin.IO6, true);
DR.Write(true);
SPM.Write(false);
// Debug.Print(DateTime.Now + "." + DateTime.Now.TimeOfDay.Milliseconds.ToString() + " SBE41 State 0 DR = true SM = false TX = true");
timerState = 1;
SBE41Timer.Change(0, 50);
break;
case 1:
DR.Write(false);
// Debug.Print(DateTime.Now + "." + DateTime.Now.TimeOfDay.Milliseconds.ToString() + " SBE41 State 1 DR = false SM = false TX = true");
timerState = 2;
SBE41Timer.Change(0, 150);
break;
case 2:
serialTX.Write(false);
// Debug.Print(DateTime.Now + "." + DateTime.Now.TimeOfDay.Milliseconds.ToString() + " SBE41 State 2 DR = false SM = false TX = false");
timerState = 3;
SBE41Timer.Change(0, 150);
break;
case 3:
SPM.Write(true);
serialTX.Write(true);
//Debug.Print(DateTime.Now + "." + DateTime.Now.TimeOfDay.Milliseconds.ToString() + " SBE41 State 3 DR = false SM = true TX = true");
timerState = 4;
SBE41Timer.Change(0, 50);
break;
case 4:
serialTX.Dispose();
SBE41Port.Open();
//Debug.Print(DateTime.Now + "." + DateTime.Now.TimeOfDay.Milliseconds.ToString() + " SBE41 State 3 DR = false SM = true TX = true");
timerState = 0;
SBE41Timer.Change(0, 1600);
break;
}
}
private static void portDataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
portBytesToRead = SBE41Port.BytesToRead;
try
{
SBE41Port.Read(portReadBytes, 0, portBytesToRead);
}
catch
{
Debug.Print("SBE41 Read Exception"); //TODO do something smarter here
}
for (int i = 0; i < portBytesToRead; i++)
{
if (portInBytesIndex == -1) //Check to make sure first character is = LF
{
if (portReadBytes[i] == STX) //only move on to byte index 1 after we get the STX character
{
portInBytesIndex = portInBytesIndex + 1;
}
}
else
{
portInBytes[portInBytesIndex] = portReadBytes[i];
if (portInBytes[portInBytesIndex] == ETX) //process the message when we get the ETX character
{
messageNum = messageNum + 1;
Array.Copy(portInBytes, 1, message, header.Length, portInBytesIndex);
lock (SBE41Lock) { Program.MessageQueue.Enqueue(message); }
Array.Clear(portReadBytes, 0, portReadBytes.Length);
Array.Clear(portInBytes, 0, portInBytes.Length);
portInBytesIndex = -1;
}
else
{
portInBytesIndex = portInBytesIndex + 1;
}
}
}
}
public static double parse(byte[] inArray)
{
inArrayLength = Array.IndexOf(inArray, 0);
parserI = Array.IndexOf(inArray, token);
while ((inArray[parserI] != CR) && (inArray[parserI] != comma) && (parserI < inArrayLength))
{
pressureBytes[pressureBytesIndex] = inArray[parserI];
pressureBytesIndex = pressureBytesIndex + 1;
parserI = parserI + 1;
}
sbTemp.Clear();
sbTemp.Append(UTF8Encoding.UTF8.GetChars(pressureBytes));
pressure = double.Parse(sbTemp.ToString());
Array.Clear(pressureBytes, 0, pressureBytes.Length);
pressureBytesIndex = 0;
return (pressure);
}
}
}
Here is my ByteQueue class
// Modified from @ taylorza on GHI Electronics Forum
// https://www.ghielectronics.com/community/forum/topic?id=8083&page=2#msg79627
using System;
using Microsoft.SPOT;
using System.Text;
namespace miniCPF
{
public class ByteQueue
{
private static byte[][] buffer;
private static byte[] returnArray;
private static int head;
private static int tail;
private static int count;
private static int capacity;
private static int i;
private const int defaultCapacity = 512;
private const int defaultMessageLength = 256;
public ByteQueue()
{
buffer = new byte[defaultCapacity][];
for (i = 0; i < defaultCapacity; i++)
{
buffer[i] = new byte[defaultMessageLength];
}
returnArray = new byte[defaultMessageLength];
capacity = defaultCapacity;
head = 0;
tail = 0;
count = 0;
}
public int Count
{
get { return count; }
}
public void Clear()
{
count = 0;
tail = head;
}
public void Enqueue(byte[] byteArray)
{
//GM I've taken out the ability to grow since if the queue does have to grow, something is wrong
//if (count == capacity)
//{
// Grow();
//}
//buffer[head] = value;
if (count >= capacity)
{
throw new InvalidOperationException("Queue is full");
}
else
{
Array.Copy(byteArray, buffer[head], byteArray.Length);
head = (head + 1) % capacity;
count++;
}
}
public byte[] Dequeue()
{
Array.Clear(returnArray, 0, returnArray.Length);
if (count == 0)
{
throw new InvalidOperationException("Queue is empty");
}
else
{
// byte value = buffer[tail];
Array.Copy(buffer[tail], returnArray, buffer[tail].Length);
tail = (tail + 1) % capacity;
count--;
return returnArray;
}
}
//public int Dequeue(byte[] bytes)
//{
// int maxBytes = System.Math.Min(count, bytes.Length);
// for (i = 0; i < maxBytes; i++)
// {
// bytes[i] = Dequeue();
// }
// return maxBytes;
//}
//private void Grow()
//{
// int newCapacity = capacity << 1;
// byte[] newBuffer = new byte[newCapacity];
// if (tail < head)
// {
// Array.Copy(buffer, tail, newBuffer, 0, count);
// }
// else
// {
// Array.Copy(buffer, tail, newBuffer, 0, capacity - tail);
// Array.Copy(buffer, 0, newBuffer, capacity - tail, head);
// }
// buffer = newBuffer;
// head = count;
// tail = 0;
// capacity = newCapacity;
//}
}
}
And here is a snippet from my Main that processes the queue
private static void processMessageQueue()
{
do
{
Array.Clear(dequeueMessage, 0, dequeueMessage.Length);
lock (programLock) { dequeueMessage = Program.MessageQueue.Dequeue(); }
if ((dequeueMessage[0] == (byte)'C') && (dequeueMessage[1] == (byte)'T') && (dequeueMessage[2] == (byte)'D')) //TODO Probably need a more robust way to do this
{
pressure = SBE41.parse(dequeueMessage);
Logger.write(Logger.ColumnNums.comment, "SBE 41 Message", Logger.ColumnNums.pressure, pressure);
}
else
{
sbTemp.Clear();
sbTemp.Append("Generic Queue Message");
sbTemp.Append(dequeueMessage);
Logger.write(Logger.ColumnNums.comment, sbTemp);
}
} while (Program.MessageQueue.Count > 0);
}