225 lines
5.0 KiB
C++
225 lines
5.0 KiB
C++
/*
|
|
* SimulatorFans
|
|
* Copyright (c) 2017 Mark van Renswoude
|
|
* https://git.x2software.net/pub/SimulatorFans
|
|
*
|
|
*
|
|
* Accepts serial commands to control one or more fans using PWM.
|
|
*
|
|
* All commands are terminated with a #10 character and are case sensitive.
|
|
* Values are passed as ASCII text. This makes it easy to test it using the
|
|
* Arduino's Serial Monitor.
|
|
*
|
|
* Baud rate is 19200 by default.
|
|
*
|
|
*
|
|
* Commands:
|
|
* >Info
|
|
* Used for validating that the device is actually a SimulatorFans device.
|
|
* Returns the number of connected fans.
|
|
*
|
|
* Example response:
|
|
* <Info:Fans=2
|
|
*
|
|
* >GetFans
|
|
* Returns the currently set values for all fans.
|
|
*
|
|
* Example response:
|
|
* <GetFans:0,255
|
|
*
|
|
* >SetFans:v1,v2,v...
|
|
* Updates the fan values. Each value ranges from 0 to 255.
|
|
*
|
|
* Example response:
|
|
* <SetFans
|
|
*/
|
|
|
|
|
|
/*
|
|
* Configuration
|
|
*/
|
|
|
|
// The number of fans connected. Each fan must connect to it's own
|
|
// PWM-enabled pin through a transistor (or preferably MOSFET).
|
|
#define FanCount 2
|
|
|
|
// The pin on which each of the fans is connected. The number of
|
|
// values must be equal to FanCount (not sure why the compiler
|
|
// doesn't enforce this if FanCount is bigger). The order is
|
|
// assumed to be left to right. In a setup of more than 180 degrees,
|
|
// start counting behind the point of view and in a clockwise manner.
|
|
//
|
|
// Possibly the PC software could support remapping at some point in the
|
|
// future as that's easier than recompiling for the Arduino, but I
|
|
// wanted to keep it simple until then.
|
|
const byte FanPin[FanCount] =
|
|
{
|
|
5,
|
|
6
|
|
};
|
|
|
|
// When a fan goes from completely off to a value below full, this
|
|
// determines how long the fan will run on full power before changing
|
|
// to the actual value, to give it a chance to start up.
|
|
#define StartingFansTime 200
|
|
|
|
|
|
/*
|
|
* Actual code. Lasciate ogne speranza, voi ch'intrate.
|
|
*/
|
|
typedef struct
|
|
{
|
|
byte value;
|
|
unsigned long startTime;
|
|
} FanStatus;
|
|
|
|
unsigned long currentTime = 0;
|
|
FanStatus fanStatus[FanCount];
|
|
|
|
|
|
// Forward declarations
|
|
void handleInfoCommand();
|
|
void handleGetFansCommand();
|
|
void handleSetFansCommand();
|
|
void handleUnknownCommand();
|
|
|
|
void checkStartingFans();
|
|
void setFan(byte fan, byte value);
|
|
|
|
|
|
void setup()
|
|
{
|
|
memset(fanStatus, 0, sizeof(fanStatus));
|
|
|
|
for (byte fan = 0; fan < FanCount; fan++)
|
|
pinMode(FanPin[fan], OUTPUT);
|
|
|
|
// Set up serial communication (through USB or the default pins)
|
|
// 19.2k is fast enough for our purpose, and according to the ATMega's datasheet
|
|
// has a low error percentage across the common oscillator frequencies.
|
|
Serial.begin(19200);
|
|
}
|
|
|
|
|
|
char command[50];
|
|
byte commandLength;
|
|
char* token;
|
|
|
|
|
|
void loop()
|
|
{
|
|
currentTime = millis();
|
|
checkStartingFans();
|
|
|
|
if (Serial.available() > 0)
|
|
{
|
|
// Try to read a serial command
|
|
memset(command, 0, sizeof(command));
|
|
commandLength = Serial.readBytesUntil('\n', command, sizeof(command) - 1);
|
|
if (commandLength > 0 && commandLength < sizeof(command))
|
|
{
|
|
token = strtok(&command[0], ":");
|
|
if (token != NULL)
|
|
{
|
|
if (strcmp(token, ">Info") == 0)
|
|
handleInfoCommand();
|
|
else if (strcmp(token, ">GetFans") == 0)
|
|
handleGetFansCommand();
|
|
else if (strcmp(token, ">SetFans") == 0)
|
|
handleSetFansCommand();
|
|
else
|
|
handleUnknownCommand();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void handleInfoCommand()
|
|
{
|
|
Serial.write("<Info:Fans=");
|
|
Serial.print(FanCount);
|
|
Serial.write("\n");
|
|
}
|
|
|
|
|
|
void handleGetFansCommand()
|
|
{
|
|
Serial.write("<GetFans:");
|
|
|
|
for (byte fan = 0; fan < FanCount; fan++)
|
|
{
|
|
if (fan > 0)
|
|
Serial.write(",");
|
|
|
|
Serial.print(fanStatus[fan].value);
|
|
}
|
|
|
|
Serial.write("\n");
|
|
}
|
|
|
|
|
|
void handleSetFansCommand()
|
|
{
|
|
for (byte fan = 0; fan < FanCount; fan++)
|
|
{
|
|
token = strtok(NULL, ",");
|
|
if (token == NULL)
|
|
break;
|
|
|
|
int value = atoi(token);
|
|
|
|
if (value < 0) value = 0;
|
|
if (value > 255) value = 0;
|
|
|
|
setFan(fan, value);
|
|
}
|
|
|
|
Serial.write("<SetFans\n");
|
|
}
|
|
|
|
|
|
void handleUnknownCommand()
|
|
{
|
|
Serial.write("<Error:unknown command\n");
|
|
}
|
|
|
|
|
|
void checkStartingFans()
|
|
{
|
|
// Check if any of the fans are currently starting up and
|
|
// have been for at least StartingFansTime
|
|
for (byte fan = 0; fan < FanCount; fan++)
|
|
{
|
|
if ((fanStatus[fan].startTime > 0) &&
|
|
(currentTime - fanStatus[fan].startTime >= StartingFansTime))
|
|
{
|
|
fanStatus[fan].startTime = 0;
|
|
setFan(fan, fanStatus[fan].value);
|
|
}
|
|
}
|
|
}
|
|
|
|
void setFan(byte fan, byte value)
|
|
{
|
|
if ((fanStatus[fan].value == 0 || fanStatus[fan].startTime > 0) && value > 0)
|
|
{
|
|
// Fan was off or still starting up, start with full power to kick it off
|
|
analogWrite(FanPin[fan], 255);
|
|
|
|
if (fanStatus[fan].startTime == 0)
|
|
{
|
|
fanStatus[fan].startTime = currentTime;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Already running, simply change the speed and reset the start time if necessary
|
|
fanStatus[fan].startTime = 0;
|
|
analogWrite(FanPin[fan], value);
|
|
}
|
|
|
|
fanStatus[fan].value = value;
|
|
}
|
|
|