title: How to interpret RS232C interface communication written using Pascal to C#
body: <p>I've a sample pascal program, provided in a technical handbook of coin counter S350/360 machine, that enables communication between the machine and a computer using RS232C cable. I've came up with the following equivalent C# code and for some reason only the procedures that only need to write data to the serial port are executing and working successfully. I can only write data to the port but cannot read any data from it. I checked this using a Serial Sniffer and it turns out it only sniffs data written to the port, using the C# program, but cannot read any data back. Could you guys help me with what's going on here?</p>
  
  <p>First here is the original Pascal code of the sample program.</p>
  
  <pre><code>uses crt;
  
  const
  { COM1: RS232 port address }
  RXTX = $3F8; { $2F8 if COM2: is used }
  ACK = 6;
  NAK = 21;
  ESC = 27;
  
  var
  dummy,
  checkSum : integer;
  key : char;
  protocol : integer;
  
  procedure InitComm;
  { Set baudrate to 9600, 8 bits, no parity, 1 stop bit }
  var i : integer;
  begin
  i := 1843200 div 9600 div 16;
  port[RXTX + 3] := $80;
  port[RXTX + 1] := hi(i);
  port[RXTX]:= lo(i);
  port[RXTX + 3] := 3;
  port[RXTX + 4] := $A;
  while odd(port[RXTX + 5]) do
  begin
  dummy := port[RXTX];
  delay(10);
  end;
  end; { InitComm }
  
  procedure Tx(data : integer);
  { Transmit a character on serial channel }
  begin
  while port[RXTX + 5] and $20 = 0 do;
  port[RXTX] := data and $FF;
  end; { Tx }
  
  function RxWait : integer;
  { Waits for a character from serial channel }
  begin
  while not odd(port[RXTX + 5]) do;
  RxWait := port[RXTX];
  end; { RxWait }
  
  procedure Tx2(data : integer);
  { Transmit a char on serial channel + Calculate check sum }
  begin
  Tx(data);
  checkSum := (checkSum + data) and $FF;
  end; { Tx2 }
  
  procedure TxCommand(c1, c2 : char;
  sendCheckSum : boolean);
  { Transmit command (no data) on serial channel }
  begin
  Tx(ESC);
  checkSum := 0;
  Tx2(ord(c1));
  Tx2(ord(c2));
  if sendCheckSum then
  begin
  Tx2(checkSum);
  dummy := RxWait;
  end;
  end; { TxCommand }
  
  function ReadNumber(n : integer) : real;
  { Read n bytes from serial channel }
  var
  number: real;
  i : integer;
  begin
  number := 0;
  checkSum := 0;
  for i := 1 to n do
  number := number * 256 + RxWait;
  dummy := RxWait;
  ReadNumber := number;
  end; { ReadNumber }
  
  procedure Revisions;
  var
  tmp : integer;
  sw,
  prot : real;
  begin
  TxCommand('P', 'R', FALSE);
  checkSum := 0;
  tmp := RxWait;
  sw := tmp + RxWait / 100.0;
  protocol := RxWait;
  prot := protocol + RxWait / 100.0;
  dummy := RxWait;
  tmp := RxWait;
  writeln('Software revision: ', sw:4:2);
  writeln('Protocol revision: ', prot:4:2);
  end; { Revisions }
  
  procedure ReadCountReg;
  begin
  TxCommand('R', 'C', FALSE);
  writeln(ReadNumber(4):11:0, ' coins counted.');
  dummy := RxWait;
  end; { ReadCountReg }
  
  procedure ReadAccReg;
  begin
  TxCommand('R', 'A', FALSE);
  writeln(ReadNumber(4):11:0, ' coins in accumulator.');
  dummy := RxWait;
  end; { ReadAccReg }
  
  procedure Setbatch(limit : longint);
  begin
  TxCommand('W', 'L', FALSE);
  case protocol of
  1 : begin
  Tx2(limit div 256);
  Tx2(limit mod 256);
  end;
  2 : begin
  Tx2( limit div 16777216);
  Tx2((limit div 65536) mod 256);
  Tx2((limit div 256) mod 256);
  Tx2( limit mod 256);
  end;
  end; { case protocol }
  Tx2(checkSum);
  dummy := RxWait;
  end; { Setbatch }
  </code></pre>
  
  <p>In the protocol descriptions two logical operators are used,
  + for arithmetic addition and ^ for logical AND.
  All messages have the following principal structure:</p>
  
  <pre><code>Computer SC 350/360
  –––––––&gt; ESC (message start)
  –––––––&gt; Command
  &lt;––––––&gt; Data (direction depends on command)
  &lt;––––––&gt; Check sum (direction depends on command)
  &lt;––––––– Receipt:
          - ACK (if check sum is correct) or
          - NAK (if check sum is incorrect)
  </code></pre>
  
  <p>If the data is a binary number, the most significant byte (character) is
  sent first. If the data is a text string, the first character of the string is
  sent first. A text string always ends with a NULL character.</p>
  
  <p>The check sum for received characters in a message is derived as
  follows:
  The addition of every hexadecimal character value, except those for
  the message start (ESC), forms the sum. The check sum is then the
  sum ^255.
  If any of the fault conditions listed below occurs, the computer
  should wait for at least 250 ms from the time that the last character
  was received before re-sending the message:</p>
  
  <ul>
  <li>The computer receives an incorrect check sum.</li>
  <li>The machine has sent NAK.</li>
  <li>The machine sends “invalid data”.</li>
  <li>The machine does not respond within 250 ms.
  The machine accepts all messages when counting except the message
  that sets a new batch quantity.</li>
  </ul>
  
  <p>For example; To start the machine 'TxCommand('M', '1', TRUE);' is issued as written in the handbook; I've implemented the pascal code for this in C#.Net and have successfully issued this command and the machine started right away. What I couldn't get working was the procedures 'ReadCountReg', 'ReadCountReg'and 'Setbatch'. </p>
  
  <p>Below is the C# code I implemented:</p>
  
  <pre><code> public class CoinCounter : IDisposable
  {
  
      private const int RXTX = 0x3F8;
      private const int ACK = 6;
      private const int NAK = 21;
      private const int ESC = 27;
  
      private int dummy, checkSum;
      private char key;
      private int protocol;
  
      public SerialPort port;
      private bool _disposed = false;
      public CoinCounter()
      {
          //Initialize Communication
          port = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
          port.Handshake = Handshake.None;
          port.Open();
          port.DtrEnable = true;
          port.RtsEnable = true;
  
      }
  
      public void ClosePort()
      {
          if (port.IsOpen)
              port.Close();
      }
  
  
      public void Dispose()
      {
          Dispose(true);
          GC.SuppressFinalize(this);
      }
  
      protected virtual void Dispose(bool disposing)
      {
          if (!_disposed)
          {
              if (disposing)
              {
                  DisposeManagedResources();
              }
              DisposeUnmanagedResources();
              _disposed = true;
          }
      }
  
      private  void DisposeManagedResources() 
      {
          port.Dispose();
      }
      private  void DisposeUnmanagedResources() { }
  
       ~CoinCounter() 
      {
          Dispose(false);
      }
  
      public void Tx(int data)
      {
  
          port.Write(new byte[] { (byte)(data &amp; 0xFF) }, 0, 1);
  
      }
  
      public int RxWait()
      {
  
          int readByte = 0;
          CommTimer timer = new CommTimer();
          timer.Start(250);
          while ((port.BytesToRead == 0) &amp;&amp; (timer.timedout == false))
          {
          }
          if (port.BytesToRead &gt; 0)
              readByte = port.ReadByte();
  
          return readByte;
  
      }
  
      public void Tx2(int data)
      {
          Tx(data);
          checkSum = (checkSum + data) &amp; 0xFF;
      }
  
      public void TxCommand(char c1, char c2, bool sendCheckSum)
      {
          Tx(ESC);
          checkSum = 0;
          Tx2((int)c1);
          Tx2((int)c2);
          if (sendCheckSum)
          {
              Tx2(checkSum);
              dummy = RxWait();
          }
      }
  
      public double ReadNumber(int n)
      {
          double number;
          int i;
          number = checkSum = 0;
          for (i = 0; i &lt; n; i++)
              number = number * 256 + RxWait();
          dummy = RxWait();
          return number;
      }
  
      public void Revisions(out double softVersion, out double protocolVersion)
      {
          int tmp;
          TxCommand('P', 'R', false);
          checkSum = 0;
          tmp = RxWait();
          softVersion = tmp + RxWait() / 100.0;
          protocol = RxWait();
          protocolVersion = protocol + RxWait() / 100.0;
          dummy = RxWait();
          tmp = RxWait();
      }
  
      public double ReadCountReg()
      {
          TxCommand('R', 'C', false);
          double coinsCounted = ReadNumber(4);
          dummy = RxWait();
          return coinsCounted;
      }
  
      public double ReadAccReg()
      {
          TxCommand('R', 'A', false);
          double coinsInAccumulator = ReadNumber(4);
          dummy = RxWait();
          return coinsInAccumulator;
      }
  
      public void SetBatch(long limit)
      {
          TxCommand('W', 'L', false);
          switch (protocol)
          {
              case 1:
                  Tx2((int)(limit / 256));
                  break;
              case 2:
                  Tx2((int)(limit / 16777216));
                  Tx2((int)(limit / 65536) % 256);
                  Tx2((int)(limit / 256) % 256);
                  Tx2((int)(limit % 256));
                  break;
          }
          Tx2(checkSum);
          dummy = RxWait();
      }
  
      public class CommTimer
      {
          public System.Timers.Timer tmrComm = new System.Timers.Timer();
          public bool timedout = false;
          public CommTimer()
          {
              timedout = false;
              tmrComm.AutoReset = false;
              tmrComm.Enabled = false;
              tmrComm.Interval = 1000; //default to 1 second
              tmrComm.Elapsed += new ElapsedEventHandler(OnTimedCommEvent);
          }
  
          public void OnTimedCommEvent(object source, ElapsedEventArgs e)
          {
              timedout = true;
              tmrComm.Stop();
          }
  
          public void Start(double timeoutperiod)
          {
              tmrComm.Interval = timeoutperiod; 
              tmrComm.Stop();
              timedout = false;
              tmrComm.Start();
          }
  
  
      }
  
  
  }
  </code></pre>
  
  <p>Note that the hand book states:
  1.  For simplicity the handshake lines are not used in this example.
  2.  No check sum implemented.</p>
  
  <p>While most of the commands, with simple checksum calculation, can successfully communicate with the machine I couldn't get, for example, 'ReadCountReg' procedure to work and see the results of counted coins. The protocol for this procedure is;</p>
  
  <pre><code>Computer SC 350/360
  –––––––&gt; ESC
  –––––––&gt; “R”
  –––––––&gt; “C”
  &lt;––––––– CountRegister (CR)
  &lt;––––––– (CR ^ FF00,000016 + CR ^ FF,000016 + CR ^ FF0016 +CR ^ FF16) ^ FF16
  &lt;––––––– ACK
  </code></pre>
  
  <p>Could you guys help me get this working as I don't have much experience with the serial port communication and could help me resolve my issue.</p>