From b053b9d0b0cba96a50b04387df5a6755b879a7f6 Mon Sep 17 00:00:00 2001 From: Pat Thoyts Date: Tue, 26 May 2015 20:55:39 +0100 Subject: [PATCH] Arduino emulator of Linkam TMS 94 and TMS94 microscope temperature stage Useful for testing in the absence of the physical hardware as all comms are done using a serial protocol. Signed-off-by: Pat Thoyts --- Emulator.h | 210 ++++++++++++++++++++++++++++++++++++++++++++++++ TMSEmulator.ino | 49 +++++++++++ 2 files changed, 259 insertions(+) create mode 100644 Emulator.h create mode 100644 TMSEmulator.ino diff --git a/Emulator.h b/Emulator.h new file mode 100644 index 0000000..4a09aab --- /dev/null +++ b/Emulator.h @@ -0,0 +1,210 @@ +// TMS94 default COM port settings are 19200,N,8,1 +// Command end in +// All commands are acknowledged with something/nothing ending in +// +// T +// 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: +// eg: P....cr +// +// Data: D command is a data dump + +#include + +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(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 index 0000000..4a1103f --- /dev/null +++ b/TMSEmulator.ino @@ -0,0 +1,49 @@ +// Emulate a TMS94 temperature stage. -*- c++ -*- +// +// Copyright (c) 2013 Pat Thoyts +// +#include +#include +#include +#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); + } +} -- 2.23.0