--- /dev/null
+// 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)
+};
--- /dev/null
+// 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);
+ }
+}