Arduino emulator of Linkam TMS 94 and TMS94 microscope temperature stage
authorPat Thoyts <patthoyts@users.sourceforge.net>
Tue, 26 May 2015 19:55:39 +0000 (20:55 +0100)
committerPat Thoyts <patthoyts@users.sourceforge.net>
Tue, 26 May 2015 19:55:39 +0000 (20:55 +0100)
Useful for testing in the absence of the physical hardware as all comms
are done using a serial protocol.

Signed-off-by: Pat Thoyts <patthoyts@users.sourceforge.net>
Emulator.h [new file with mode: 0644]
TMSEmulator.ino [new file with mode: 0644]

diff --git a/Emulator.h b/Emulator.h
new file mode 100644 (file)
index 0000000..4a09aab
--- /dev/null
@@ -0,0 +1,210 @@
+// TMS94 default COM port settings are 19200,N,8,1
+// Command end in <CR>
+// All commands are acknowledged with something/nothing ending in <CR>
+//
+// T<CR>
+//   11 byte result
+//   0: status byte (SB1)
+//   1: error byte (EB1)
+//   2: pump byte (PB1)
+//   3: gen status (GS1)
+//   4: not used
+//   5: not used
+//   6-9: temp *10 as signed integer hex value [-1960..15000]
+//   11: carriage return
+// SB1: 0x01 stopped 0x10 heating 0x20 cooling 0x30 holding at limit
+//      0x40 holding the limit time 0x50 holding current temp
+// EB1: bit0 cooling rate too fast
+//      bit1 open circuit (stage not connected)
+//      bit2 power surge
+//      bit3 no exit 300 (TS1500 attempted over temperature)
+//      bit4 both stages (cannot connect TS1500 and THM)
+//      bit5 link error (serial comms error)
+//      bit6 nc
+//      bit7 1 (default value)
+//
+// Rate: sets heating/cooling in degrees/min * 100. Min is 0.1 deg/min
+//       eg: R12000cr => 20 deg/min
+//
+// Limit: sets limit temp in degrees C * 10.
+//       eg: L11250cr => 125 degrees C
+//
+// Start: begin heating at rate R1 to limit L1. When reached SB1 == 0x30
+//       eg: Scr
+//
+// Stop: stop heating or cooling
+//       eg: Ecr
+//
+// Hold: hold at current temp (SB1==0x50) or hold program (SB1==0x40)
+//       eg: Ocr
+//
+// Sampling time: send 0xe7 followed by 4 chars then cr to adjust the sampling rate
+//    as {0.3 0.6 0.9 1.5 3 6 9 15 30 60 90 150} seconds. The value should be
+//    divided by 50ms so for 0.3s we should send 6 (right padded with spaces).
+//    eg: 0.3s msg= 0xe7 0x20 0x20 0x20 0x36 0x0d  "\xe7   6\r"
+//        60s  msg= 0xe7 0x31 0x32 0x30 0x30 0x0d  "\xe71200\r"
+//
+// Reset: B command clears internal buffers
+//    eg: Bcr
+//
+// Pump: <not done>
+//       eg: P....cr
+//
+// Data: D command is a data dump
+
+#include <stdint.h>
+
+class Emulator
+{
+    enum {Status_Stopped = 0x01,
+          Status_Heating = 0x10,
+          Status_Cooling = 0x20,
+          Status_HoldingLimit = 0x30,
+          Status_HoldingTime = 0x40,
+          Status_HoldingCurrent = 0x50};
+    enum {
+        Error_CoolingRate = (1<<0),
+        Error_OpenCircuit = (1<<1),
+        Error_PowerSurge = (1<<2),
+        Error_Link = (1<<5),
+        Error_NoError = (1<<7),
+    };
+
+public:
+    Emulator()
+    {
+        mIndex = 0;
+        mStatus = Status_Stopped;
+        mErrorCode = Error_NoError;
+        mLimit = mTemp = 180;
+        mRamp = 1000;
+    }
+
+    void Add(const char *s)
+    {
+        while (s && *s)
+            Add(*s++);
+    }
+
+    void Add(char c)
+    {
+        if (c == '\r' || c=='\n')
+        {
+            mBuffer[mIndex++] = 0;
+            Parse(mBuffer);
+        }
+        else
+        {
+            if (mIndex < static_cast<uint8_t>(sizeof(mBuffer)))
+                mBuffer[mIndex++] = c;
+            else
+                Error(Error_Link, "input exceeded buffer size!");
+        }
+    }
+
+    void Parse(const char *cmd)
+    {
+        switch (cmd[0])
+        {
+            case 'T': Command_T(); break;
+            case 'S': Command_S(); break;
+            case 'O': Command_O(); break;
+            case 'E': Command_E(); break;
+            case 'L': Command_L(cmd); break;
+            case 'R': Command_R(cmd); break;
+            default:
+                Error(Error_Link, "command not handled or invalid");
+        }
+        mIndex = 0;
+        mBuffer[0] = 0;
+    }
+
+    void Command_L(const char *cmd)
+    {
+        const char *p = &cmd[2];
+        int v = 0;
+        while (p && *p) {
+            v = (v * 10) + (*p - 0x30);
+            ++p;
+        }
+        mLimit = v;
+        Serial.write('\r');
+    }
+
+    void Command_R(const char *cmd)
+    {
+        const char *p = &cmd[2];
+        int v = 0;
+        while (p && *p) {
+            v = (v * 10) + (*p - 0x30);
+            ++p;
+        }
+        mRamp = v;
+        Serial.write('\r');
+    }
+
+    void Command_T()
+    {
+        char buffer[12];
+        Serial.write(mStatus);
+        Serial.write(mErrorCode);
+        sprintf(buffer, "++++%04x", mTemp);
+        Serial.write((const uint8_t *)buffer, 8);
+        Serial.write('\r');
+    }
+    void Command_S()
+    {
+        mStatus = (mTemp > mLimit) ? Status_Cooling : Status_Heating;
+        Serial.write('\r');
+    }
+    void Command_E()
+    {
+        mStatus = Status_Stopped;
+        Serial.write('\r');
+    }
+    void Command_O()
+    {
+        if (mStatus != Status_Stopped)
+        {
+            if (mStatus == Status_Heating || mStatus == Status_Cooling)
+                mStatus = Status_HoldingCurrent;
+            else
+                mStatus = Status_HoldingTime;
+        }
+        Serial.write('\r');
+    }
+
+    // Tick is called once per second
+    void Tick()
+    {
+        if (mErrorCode == Error_NoError)
+        {
+            if (mStatus == Status_Heating) {
+                mTemp += mRamp/600;
+                if (mTemp >= mLimit)
+                    mStatus = Status_HoldingLimit;
+            } else if (mStatus == Status_Cooling) {
+                mTemp -= mRamp/600;
+                if (mTemp <= mLimit)
+                    mStatus = Status_HoldingLimit;
+            }
+        }
+    }
+
+    virtual void Error(uint8_t code, const char *message)
+    {
+        mErrorCode = code;
+    }
+
+    virtual void Emit(const char *s) = 0;
+
+protected:
+    char mBuffer[10];
+    uint8_t mIndex;
+
+    short mRamp; //< Ramp rate in 0.01 degrees per minute
+    short mLimit; //< Limit value in 0.1 degrees C
+    short mTemp; //< Current temperature in 0.1 degrees C
+    uint8_t mStatus; //< Status byte (SB1)
+    uint8_t mErrorCode; //< Error byte (EB1)
+};
diff --git a/TMSEmulator.ino b/TMSEmulator.ino
new file mode 100644 (file)
index 0000000..4a1103f
--- /dev/null
@@ -0,0 +1,49 @@
+// Emulate a TMS94 temperature stage.                              -*- c++ -*-
+//
+// Copyright (c) 2013 Pat Thoyts <patthoyts@users.sourceforge.net>
+//
+#include <Arduino.h>
+#include <Ports.h>
+#include <avr/pgmspace.h>
+#include "Emulator.h"
+
+class Emulator2 : public Emulator
+{
+public:
+    void Emit(const char *s)
+    {
+        Serial.print(s);
+        Serial.print('\r');
+    }
+    void Error(uint8_t code, const char *msg)
+    {
+        Serial.println(msg);
+    }
+};
+static Emulator2 emulator;
+static MilliTimer timer1;
+
+void setup()
+{
+    pinMode(13, OUTPUT);
+    digitalWrite(13, HIGH);
+    Serial.begin(19200, SERIAL_8N1);
+}
+
+void loop()
+{
+    int count = Serial.available();
+    if (count > 0) {
+        for (int n = 0; n < count; ++n) {
+            char c = Serial.read();
+            emulator.Add(c);
+            //Serial.print(c, HEX);
+        }
+        //Serial.println(".");
+    }
+    if (timer1.poll(1000)) {
+        emulator.Tick();
+        //Serial.println("tick");
+        PORTB ^= _BV(PINB5);
+    }
+}