A standard C# SerialPort component is used. Care must be taken that a an USB to serial port adapter can be used since many PC of toaday do not implement the native serial port.
As stated the transmission is done asynchronously via a thread seperate from the form. First this thread is created with three buffers:
public void Create(VirtualConcentratorCallback _vcc, Int16 size, ref RDSComm _Trunk) { // size of command queue to create Trunk = _Trunk; virtualconcentrator_callback = (Object)_vcc; // Information callback for Concentrator from the virtual concentrator QueueSize = size; bPending = new Boolean[QueueSize]; CommandPending = new Int16[QueueSize]; CommandDataPending = new Object[QueueSize]; if (QueueSize >= 8) queue_lower = (Int16)(QueueSize - (QueueSize / 4)); else queue_lower = QueueSize; for (Int16 i = 0; i < QueueSize; i++) { bPending[i] = false; CommandPending[i] = 0x00; CommandDataPending[i] = null; } }
_vcc the callback to be used to handle the commands size the size of the command pile eg 16 _Trunk a reference to the RDS serial port in use bPending to flag if the command is pending execution or not. CommandPending the commands that are pending execution or have been executed. CommandDataPending data associated with the command pending if applicable.
Then comes the actual working part of the thread:
public void Run() { Int16 Keep_Alive_Counter = 0; // The real thread that runs the virtual concentrator // Inform that thread has started if (virtualconcentrator_callback != null) { cd.type = VirtualConcentrator_Callback; cd.reason = VirtualConcentrator_Started; ((VirtualConcentratorCallback)virtualconcentrator_callback)(ref cd); } else return; // vc is null!!! // Set up a timer and application event handler try { while (cd.run) { Application.DoEvents(); Thread.Sleep(2); // 2msec is the time slice Application.DoEvents(); if (cd.run) { // Look up the next command if any Keep_Alive_Counter++; if (Keep_Alive_Counter >= 50) { cd.reason = VirtualConcentrator_Check_And_Kick; Keep_Alive_Counter = 0; } else { cd.type = VirtualConcentrator_Callback; cd.reason = VirtualConcentrator_Check; ((VirtualConcentratorCallback)virtualconcentrator_callback)(ref cd); } // Check TrunkFailureCounter has not reached the limit TrunkFailureCount if (TrunkFailureCounter > TrunkFailureCount) { cd.type = VirtualConcentrator_Callback; cd.reason = VC.VirtualConcentratorTrunkFailure; ((VirtualConcentratorCallback)virtualconcentrator_callback)(ref cd); } } } // The thread exits if (virtualconcentrator_callback != null) { cd.type = VirtualConcentrator_Callback; cd.reason = VirtualConcentrator_Stop; ((VirtualConcentratorCallback)virtualconcentrator_callback)(ref cd); } } catch { if (virtualconcentrator_callback != null) { cd.type = VirtualConcentrator_Callback; cd.reason = VirtualConcentrator_Stop; ((VirtualConcentratorCallback)virtualconcentrator_callback)(ref cd); } } }
The startup is synchronised with the opening of the serial port. Every 2mSec the thread kicks a keep alive counter and checks for any commands pending by using callbacks to the main form. If there is a command pending the serial port thread is commanded to execute the command, wait for a reply by executing a callback. On the reply reception the reply is posted back to update the form.
cd the object used to hold the callback data.
In addition this thread needs functions to Add, Remove, GetNext commands from the buffers. For example:
public Boolean Add(Int16 code) { // The next free space is in next_command if ((code == VC.VirtualEquipmentCommandStart) || (code == VC.VirtualEquipmentCommandStop) || (code == VC.VirtualEquipmentCommandReset)) CommandPending[next_pending] = code; else CommandPending[next_command] = code; bCommandPending = true; // Increment next_command upto the end next_command++; queue_length++; if (next_command >= QueueSize) next_command = 0; return true; }
This function accepts a simple single command, others are required to accept command + data and command + object. An object would be used when multiple data items have to be sent.
Finally the callback. Here is the action on the PI parameter to retrieve that parameter from the RDS and place it on the form. Since the call to this function can come from both the same and different threads the possibilty of an invoke is required.
protected Boolean ConcentratorCallback(ref ConcentratorData cd) { Boolean bReply = true; switch (cd.reason) { case VC.VirtualConcentratorUserCommand: switch (cd.type) { case VC.VirtualEquipmentCommandGetRDS_PI: // Get the PI value UInt16 pi = 0; bReply = Get_RDS_PI(ref pi, 0, 0); if (bReply) { // Display PI try { if (PIBox.InvokeRequired) { UpdatePICallback dd = new UpdatePICallback(UpdatePI); this.Invoke(dd, new object[] { pi }); } else { UpdatePI(pi); } } catch { bProblem = true; } } break;
cd.reason a code to indicates a command is to be executed cd.type has the command to be executed VC.VirtualEquipmentCommandGetRDS_PI the flag for the command Get_RDS_PI the function to set up the frame to get PI from the encoder
cd.reason can be used to seperate standard UECP commands, manufacture specific command and maintenance information.