/* * SimulatorFans * Copyright (c) 2017 Mark van Renswoude * https://git.x2software.net/pub/SimulatorFans * * * Accepts serial commands to control one or more fans. * * 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: * GetFans * Returns the currently set values for all fans. * * Example response: * SetFans:v1,v2,v... * Updates the fan values. Each value ranges from 0 to 255. * If the first value is 'A', the second value is applied to each fan. * * When in Servo mode, if the first value is 'M', the Servo is * set to it's maximum pulse width, disregarding the MaxValue. * Used for ESC calibration. USE WITH CARE! * * Example response: * #endif typedef struct { byte value; unsigned long startTime; #if IsServo Servo servo; #endif } FanStatus; unsigned long currentTime = 0; FanStatus fanStatus[FanCount]; // Forward declarations void handleInfoCommand(); void handleGetFansCommand(); void handleSetFansCommand(); void handleUnknownCommand(char* command); void checkStartingFans(); void setFan(byte fan, byte value); void setup() { memset(fanStatus, 0, sizeof(fanStatus)); for (byte fan = 0; fan < FanCount; fan++) { #if IsPWM pinMode(FanPin[fan], OUTPUT); #endif #if IsServo fanStatus[fan].servo.attach(FanPin[fan]); fanStatus[fan].servo.writeMicroseconds(ServoMinValue); #endif } #if IsPWM // Set all clock select bits to clk/1024. This will result in a 30 ~ 60 Hz PWM frequency. // The default frequency causes annoying noises in the fan, the low frequency is still // audible but good enough since the game sounds should easily drown that out. // It messes up the millis() function though, so compensate for that later on. TCCR0B = TCCR0B & 0b11111000 | 0b101; TCCR1B = TCCR1B & 0b11111000 | 0b101; TCCR2B = TCCR2B & 0b11111000 | 0b101; // 64 is the default prescaler #define correctedMillis() (millis() * (1024 / 64)) #endif #if IsServo #define correctedMillis() millis() #endif // 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; byte offset; char* token; void loop() { currentTime = correctedMillis(); 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)) { // The Windows application always sends two 0xFF characters when it first connects. // I'm sure it's a setting somewhere, but I couldn't figure it out just yet. Instead, // simply look for the start of the command, thus skipping all other bytes. A bit // of a hack, but if you know the proper solution, I'd love to hear it! offset = 0; while (offset < commandLength && command[offset] != '>') offset++; token = strtok(&command[offset], ":"); 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(token); } } } } void handleInfoCommand() { Serial.write(" 0) Serial.write(","); Serial.print(fanStatus[fan].value); } Serial.write("\n"); } void handleSetFansCommand() { token = strtok(NULL, ","); if (token != NULL) { if (strcmp(token, "A") == 0) { // Set all fans to the same value token = strtok(NULL, ","); if (token != NULL) { int value = atoi(token); if (value < 0) value = 0; if (value > 255) value = 0; for (byte fan = 0; fan < FanCount; fan++) setFan(fan, value); } } #if IsServo else if (strcmp(token, "M") == 0) { for (byte fan = 0; fan < FanCount; fan++) { // Join me for a drive, maximum overdrive! fanStatus[fan].servo.writeMicroseconds(ServoMaxPulseWidth); fanStatus[fan].value = 255; } } #endif else { // Set individual fan values for (byte fan = 0; fan < FanCount; fan++) { int value = atoi(token); if (value < 0) value = 0; if (value > 255) value = 0; setFan(fan, value); token = strtok(NULL, ","); if (token == NULL) break; } } } Serial.write(" 0) && (currentTime - fanStatus[fan].startTime >= StartingFansTime)) { fanStatus[fan].startTime = 0; setFan(fan, fanStatus[fan].value); } } } #if IsPWM #define writeFanValue(fan, value) analogWrite(FanPin[fan], value) #endif #if IsServo #define writeFanValue(fan, value) fanStatus[fan].servo.writeMicroseconds(map(value, 0, 255, ServoMinValue, ServoMaxValue)) #endif void setFan(byte fan, byte value) { byte correctedValue = value; if (correctedValue < MinimumValue) correctedValue = 0; if ((fanStatus[fan].value == 0 || fanStatus[fan].startTime > 0) && correctedValue > 0) { // Fan was off or still starting up, start with full power to kick it off writeFanValue(fan, StartingValue); 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; writeFanValue(fan, correctedValue); } fanStatus[fan].value = correctedValue; }