/******************************************************************************** uses Adafruit SPI library for 1.8 TFT SPI display with ST7735R chip This library works with the Adafruit 1.8" TFT Breakout w/SD card ----> http://www.adafruit.com/products/358 as well as Adafruit raw 1.8" TFT display ----> http://www.adafruit.com/products/618 Credits: Adafruit GFX & ST7735 libraries. https://github.com/adafruit/Adafruit-GFX-Library https://github.com/adafruit/Adafruit-ST7735-Library RMP monitoring: Crenn from http://thebestcasescenario.com http://www.themakersworkbench.com/content/tutorial/reading-pc-fan-rpm-arduino Gauge graphics: http://www.instructables.com/id/Arduino-analogue-ring-meter-on-colour-TFT-display/ *************************************************************************************/ //LCD pins and libraries #define sclk 13 #define mosi 11 #define cs 10 #define dc 9 #define rst 8 // you can also connect this to the Arduino reset #define SD_CS 4 // Chip select line for SD card #define fanSpdPin 6// was5 //PWM out to control fan speed (blue wire on 4 pin fans) #define temp1Pin A2 //Power supply temp monitor pin #define temp2Pin A1 //RAMBO board temp monitor pin #define pushBotton 3 // LED Light control pusbutton #define LEDpin 5 // Pin to control PWM to LEDs 5 is soldered so cant be moved from LEDs #define hallsensor 2 //The pin location of the fan PWM sensor (green wire on 4 pin fans) #define beepPin 7 #define tempMin 20 // base temperature - only really for reference and calculations. The fan will always have a minimum speed #define tempMax 50 // the maximum temperature when fan is at 100% #define minFanSpeed 65 // Meter colour schemes #define RED2RED 0 #define GREEN2GREEN 1 #define BLUE2BLUE 2 #define BLUE2RED 3 #define GREEN2RED 4 #define RED2GREEN 5 #include // Core graphics library #include // Hardware-specific library #include #include #if defined(__SAM3X8E__) #undef __FlashStringHelper::F(string_literal) #define F(string_literal) string_literal #endif #define BUFFPIXEL 20 //for BmpDraw // Option 1: use any pins but a little slower //Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, mosi, sclk, rst); // Option 2: must use the hardware SPI pins // (for UNO thats sclk = 13 and sid = 11) and pin 10 must be // an output. This is much faster - also required if you want // to use the microSD card (see the image drawing example) Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, rst); //END LCD Pins and Libraries int NbTopsFan; int RPM; int temp1,temp2; int lastVal1,lastVal2 = 0; volatile byte half_revolutions; unsigned int rpm; unsigned long timeold; int lastRPM = 0; int lastPct = 0; //int RPMx = 0; //float lastVoltage = 0.0; //int pushBotton = 3; // LED Light control pusbutton //int LEDpin = 5; // Pin to control PWM to LEDs int level = 0; // LED light level selected String levelTxt = "-", lastLevelTxt = "-"; //int levelLEDGraphic = 1; int buttonState; // LED pushbutton - the current reading from the input pin int lastButtonState = LOW; // LED: the previous reading from the input pin long lastDebounceTime = 0; // LED pushbotton debounce control - the last time the output pin was toggled long debounceDelay = 150; // LED pushbotton debounce control - the debounce time; increase if the output flickers boolean fanX = false; //used to flip fan animation from X to + char buf[3]; //for drawing circles - temperature bar int avgFanSpeed; int avgCount=1; unsigned long previousMillis = 0; const long interval = 1000; void setup() { pinMode(LEDpin, OUTPUT); analogWrite(LEDpin,255); //ensure the LED is off at startup. pinMode(hallsensor, OUTPUT); digitalWrite(hallsensor, HIGH); //needs pull up enabled to be readable. pinMode(fanSpdPin, OUTPUT); pinMode(temp1Pin, INPUT); pinMode(temp2Pin, INPUT); //Serial.begin(9600);Serial.println("Starting..."); //attachInterrupt(0, rpm_fun, RISING); //for pin 2 - hall effect sensor on FAN attachInterrupt(digitalPinToInterrupt(hallsensor), rpm_fun, RISING); //for pin 2 - hall effect sensor on FAN half_revolutions = 0; rpm = 0; timeold = 0; attachInterrupt(1, monitorPushbutton, CHANGE); //for LED light control switch monitoring. Nano - D3 analogWrite(fanSpdPin, minFanSpeed); // spin the fan at a low speed by default //LCD Init // If your TFT's plastic wrap has a Black Tab, use the following: //tft.initR(INITR_BLACKTAB); // initialize a ST7735S chip, black tab // If your TFT's plastic wrap has a Red Tab, use the following: tft.initR(INITR_REDTAB); // initialize a ST7735R chip, red tab // If your TFT's plastic wrap has a Green Tab, use the following: //tft.initR(INITR_GREENTAB); // initialize a ST7735R chip, green tab //set to landscape tft.setRotation(tft.getRotation()-1); //END LCD Init //black out the screen so nothing from previous run shows. tft.fillScreen(ST7735_BLACK); //display splash logo //Serial.print("Initializing SD card..."); if (!SD.begin(SD_CS)) { //Serial.println("failed!"); return; } //Serial.println("OK!"); doSplash(); drawBackgroundGraphics(); } void loop () { unsigned long currentMillis = millis(); //Get RPM readings if (half_revolutions >= 20) { //Update RPM every 20 counts, increase this for better RPM resolution, //decrease for faster update rpm = 30*1000/(millis() - timeold)*half_revolutions; timeold = millis(); half_revolutions = 0; //Serial.println(rpm,DEC); } //Get Temperature Readings temp1 = readTemp(temp1Pin); // get the temperature temp2 = readTemp(temp2Pin); // get the temperature //Do screen updates here - this ocurrs once per interval - currently set as 1 second if (currentMillis - previousMillis >= interval) { // save the last time you blinked the LED previousMillis = currentMillis; //START Do stuff here //Fan RPM animated graphic doAnimateFan(4, 75); //show the RPM value //RPMx = map(temp1, tempMin, tempMax, 1200, 2000); printVal(46,97, 2, rpm, lastRPM, ST7735_GREEN); lastRPM = rpm; //################### print RAMBO temp //ringMeter(value, minVal, maxVal, x, y, radius, text, colour scheme ringMeter(temp1, 0, 100, 6, 25, 20, "degC", GREEN2RED); //prints the Rambo Temp value //printVal(x,y, fontSize, currenttemp, lastVal, colour); printVal(15,37, 2, temp1, lastVal1, ST7735_ORANGE); lastVal1 = temp1; //#################### print PWR supply temp //ringMeter(value, minVal, maxVal, x, y, radius, text, colour scheme ringMeter(temp2, 0, 100, 58, 25, 20, "degC", GREEN2RED); //prints the PWR suuply temp value //printVal(x,y, fontSize, currenttemp, lastVal, colour); printVal(67,37, 2, temp2, lastVal2, ST7735_ORANGE); lastVal2 = temp2; //Temperature: Manage the Fan Speed //map(temp, lowTemp, maxTemp,fanMinSpeed, FanMaxSpeed if (temp1 > tempMax) { //temp is excessive and above range, keep fan on high analogWrite(fanSpdPin, 255); beep(); //send out a warning beep to alert user that attention may be required } //keep a minimum speed else if (temp1 < tempMin) { analogWrite(fanSpdPin, minFanSpeed); } else { //auto adjust fan based on temp ranges - with this circuit the Noctual is not happy below 40. analogWrite(fanSpdPin, map(temp1, tempMin, tempMax, minFanSpeed, 255)); } int currPct = (map((float)temp1, (float)tempMin, (float)tempMax, (float)minFanSpeed, 255.00) / 255.00) * 100.00; printVal(54,118, 1, currPct, lastPct, ST7735_ORANGE); lastPct = currPct; //################### print LED level //ringMeter(value, minVal, maxVal, x, y, radius, text, colour scheme ringMeter(level, 0, 100, 110, 25, 20, "%", GREEN2RED); //print the LED level txt H M L O printValString(125,37,2, levelTxt, lastLevelTxt, ST7735_ORANGE); lastLevelTxt = levelTxt; //END Do stuff here } } //############################################################################################## int readTemp(int pin) { // get the temperature and convert it to celsius int temp; temp = analogRead(pin); return temp * 0.48828125; } void doSplash() { bmpDraw("LulzBot2.bmp", 0, 0); delay(3000); tft.fillScreen(ST7735_BLACK); } void drawBackgroundGraphics() { //Top bounding box tft.drawRoundRect(0, 1, 158, 67, 4, ST7735_WHITE); //Bottom bounding box tft.drawRoundRect(0, 67, 158, 61, 4, ST7735_WHITE); tft.drawLine(98, 67, 98, tft.height()-1, ST7735_WHITE); //Setup RAMBO Temp static graphics //draw the rambo text and rectangle tft.drawRoundRect(7, 7, 39, 13, 3, ST7735_MAGENTA); tft.setTextSize(1); tft.setTextColor(ST7735_WHITE); tft.setCursor(12, 10); tft.print("RAMBO"); //print degrees C text at bottom of graph meter tft.setCursor(24, 57); tft.print("C"); //Setup PWR supply temp tft.drawRoundRect(59, 7, 39, 13, 3, ST7735_MAGENTA); tft.setCursor(67, 10); //tft.setTextColor(ST7735_WHITE); tft.print("SUPL"); //print degrees C text at bottom of graph meter tft.setCursor(76, 57); tft.print("C"); //Setup LED static graphics tft.drawRoundRect(112, 7, 39, 13, 3, ST7735_MAGENTA); tft.setCursor(123, 10); //tft.setTextColor(ST7735_WHITE); tft.print("LED"); //print % text at bottom of graph meter tft.setCursor(128, 57); tft.print("%"); //setup Fan static graphics tft.drawRoundRect(45, 78, 51, 13, 3, ST7735_MAGENTA); //tft.setTextSize(1); tft.setCursor(50, 81); //tft.setTextColor(ST7735_WHITE); tft.print("FAN RPM"); //tft.setTextColor(ST7735_WHITE); //draw the fan power static text at the bottom tft.setCursor(5, 118); tft.print("Fan Pwr: %"); //draw the MINI text bmp.bmp, x,y bmpDraw("MINI2.bmp", 100, 80); } void dispFanSpeed(int x, int y, int temp) { int RPMx = map(temp, tempMin, tempMax, 1200, 2000); //analogWrite(fanSpdPin, map(temp, tempMin, tempMax, 40, 255)); printVal(x,y, 2, RPMx, lastRPM, ST7735_GREEN); lastRPM = RPMx; } void printVal(int xLoc, int yLoc, int txtSize, int newV, int lastV, uint16_t color) { tft.setTextSize(txtSize); tft.setCursor(xLoc, yLoc); tft.setTextColor(ST7735_BLACK); tft.print(lastV); tft.setTextColor(color); tft.setCursor(xLoc, yLoc); tft.print(newV); } void printValString(int xLoc, int yLoc, int txtSize, String newV, String lastV, uint16_t color) { tft.setTextSize(txtSize); tft.setCursor(xLoc, yLoc); tft.setTextColor(ST7735_BLACK); tft.print(lastV); tft.setTextColor(color); tft.setCursor(xLoc, yLoc); tft.print(newV); } void doAnimateFan(int x, int y) { if (fanX) { bmpDraw("Fan3.bmp", x, y); } else { bmpDraw("Fan4.bmp", x, y); } fanX = !fanX; } void monitorPushbutton() { int reading = digitalRead(pushBotton); // If the switch changed, due to noise or pressing: if (buttonState != lastButtonState) { // reset the debouncing timer lastDebounceTime = millis(); } if ((millis() - lastDebounceTime) > debounceDelay) { // whatever the reading is at, it's been there for longer // than the debounce delay, so take it as the actual current state: // if the button state has changed: if (reading != buttonState) { buttonState = reading; // only toggle the LED if the new button state is HIGH if (buttonState == HIGH) { //ledState = !ledState; changeLevel(); analogWrite(LEDpin, map(level, 0, 100, 255, 0)); //print the text in the LED ring bar //printVal(107,37, 1, level, lastVal, ST7735_WHITE); } } } delay(100); lastButtonState = reading; } void changeLevel() { switch (level) { case 0: level = 100; levelTxt = "H"; break; case 100: level = 77; levelTxt = "M"; break; case 77: level = 44; levelTxt = "L"; break; case 44: level = 0; levelTxt = "-"; break; default: level = 0; levelTxt = "-"; break; break; } } void bmpDraw(char *filename, uint8_t x, uint8_t y) { File bmpFile; int bmpWidth, bmpHeight; // W+H in pixels uint8_t bmpDepth; // Bit depth (currently must be 24) uint32_t bmpImageoffset; // Start of image data in file uint32_t rowSize; // Not always = bmpWidth; may have padding uint8_t sdbuffer[3*BUFFPIXEL]; // pixel buffer (R+G+B per pixel) uint8_t buffidx = sizeof(sdbuffer); // Current position in sdbuffer boolean goodBmp = false; // Set to true on valid header parse boolean flip = true; // BMP is stored bottom-to-top int w, h, row, col; uint8_t r, g, b; uint32_t pos = 0, startTime = millis(); if((x >= tft.width()) || (y >= tft.height())) return; //Serial.println(); //Serial.print("Loading image '"); //Serial.print(filename); //Serial.println('\''); // Open requested file on SD card if ((bmpFile = SD.open(filename)) == NULL) { //Serial.print("File not found"); return; } // Parse BMP header if(read16(bmpFile) == 0x4D42) { // BMP signature //Serial.print("File size: "); //Serial.println(read32(bmpFile)); read32(bmpFile); //needs to be done if above Serial print is commented. (void)read32(bmpFile); // Read & ignore creator bytes bmpImageoffset = read32(bmpFile); // Start of image data //Serial.print("Image Offset: "); Serial.println(bmpImageoffset, DEC); // Read DIB header //Serial.print("Header size: "); //Serial.println(read32(bmpFile)); read32(bmpFile); bmpWidth = read32(bmpFile); bmpHeight = read32(bmpFile); if(read16(bmpFile) == 1) { // # planes -- must be '1' bmpDepth = read16(bmpFile); // bits per pixel //Serial.print("Bit Depth: "); Serial.println(bmpDepth); if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed goodBmp = true; // Supported BMP format -- proceed! //Serial.print("Image size: "); //Serial.print(bmpWidth); //Serial.print('x'); //Serial.println(bmpHeight); // BMP rows are padded (if needed) to 4-byte boundary rowSize = (bmpWidth * 3 + 3) & ~3; // If bmpHeight is negative, image is in top-down order. // This is not canon but has been observed in the wild. if(bmpHeight < 0) { bmpHeight = -bmpHeight; flip = false; } // Crop area to be loaded w = bmpWidth; h = bmpHeight; if((x+w-1) >= tft.width()) w = tft.width() - x; if((y+h-1) >= tft.height()) h = tft.height() - y; // Set TFT address window to clipped image bounds tft.setAddrWindow(x, y, x+w-1, y+h-1); for (row=0; row= sizeof(sdbuffer)) { // Indeed bmpFile.read(sdbuffer, sizeof(sdbuffer)); buffidx = 0; // Set index to beginning } // Convert pixel from BMP to TFT format, push to display b = sdbuffer[buffidx++]; g = sdbuffer[buffidx++]; r = sdbuffer[buffidx++]; tft.pushColor(tft.Color565(r,g,b)); } // end pixel } // end scanline //Serial.print("Loaded in "); //Serial.print(millis() - startTime); //Serial.println(" ms"); } // end goodBmp } } bmpFile.close(); //if(!goodBmp) Serial.println("BMP format not recognized."); } // These read 16- and 32-bit types from the SD card file. // BMP data is stored little-endian, Arduino is little-endian too. // May need to reverse subscript order if porting elsewhere. uint16_t read16(File f) { uint16_t result; ((uint8_t *)&result)[0] = f.read(); // LSB ((uint8_t *)&result)[1] = f.read(); // MSB return result; } uint32_t read32(File f) { uint32_t result; ((uint8_t *)&result)[0] = f.read(); // LSB ((uint8_t *)&result)[1] = f.read(); ((uint8_t *)&result)[2] = f.read(); ((uint8_t *)&result)[3] = f.read(); // MSB return result; } // ######################################################################### // Draw the meter on the screen, returns x coord of righthand side // ######################################################################### int ringMeter(int value, int vmin, int vmax, int x, int y, int r, char *units, byte scheme) { // Minimum value of r is about 52 before value text intrudes on ring // drawing the text first is an option x += r; y += r; // Calculate coords of centre of ring int w = r / 4; // Width of outer ring is 1/4 of radius int angle = 150; // Half the sweep angle of meter (300 degrees) int text_colour = 0; // To hold the text colour int v = map(value, vmin, vmax, -angle, angle); // Map the value to an angle v byte seg = 5; // Segments are 5 degrees wide = 60 segments for 300 degrees byte inc = 15; // Draw segments every 5 degrees, increase to 10 for segmented ring // Draw colour blocks every inc degrees for (int i = -angle; i < angle; i += inc) { // Choose colour from scheme int colour = 0; switch (scheme) { case 0: colour = ST7735_RED; break; // Fixed colour case 1: colour = ST7735_GREEN; break; // Fixed colour case 2: colour = ST7735_BLUE; break; // Fixed colour case 3: colour = rainbow(map(i, -angle, angle, 0, 127)); break; // Full spectrum blue to red case 4: colour = rainbow(map(i, -angle, angle, 63, 127)); break; // Green to red (high temperature etc) case 5: colour = rainbow(map(i, -angle, angle, 127, 63)); break; // Red to green (low battery etc) default: colour = ST7735_BLUE; break; // Fixed colour } // Calculate pair of coordinates for segment start float sx = cos((i - 90) * 0.0174532925); float sy = sin((i - 90) * 0.0174532925); uint16_t x0 = sx * (r - w) + x; uint16_t y0 = sy * (r - w) + y; uint16_t x1 = sx * r + x; uint16_t y1 = sy * r + y; // Calculate pair of coordinates for segment end float sx2 = cos((i + seg - 90) * 0.0174532925); float sy2 = sin((i + seg - 90) * 0.0174532925); int x2 = sx2 * (r - w) + x; int y2 = sy2 * (r - w) + y; int x3 = sx2 * r + x; int y3 = sy2 * r + y; if (i < v) { // Fill in coloured segments with 2 triangles tft.fillTriangle(x0, y0, x1, y1, x2, y2, colour); tft.fillTriangle(x1, y1, x2, y2, x3, y3, colour); text_colour = colour; // Save the last colour drawn } else // Fill in blank segments { tft.fillTriangle(x0, y0, x1, y1, x2, y2, ST7735_DARKGRAY); tft.fillTriangle(x1, y1, x2, y2, x3, y3, ST7735_DARKGRAY); } } // Convert value to a string char buf[10]; byte len = 4; if (value > 999) len = 5; dtostrf(value, len, 0, buf); // Set the text colour to default tft.setTextColor(ST7735_WHITE, ST7735_BLACK); // Uncomment next line to set the text colour to the last segment value! // tft.setTextColor(text_colour, ST7735_BLACK); // Print value, if the meter is large then use big font 6, othewise use 4 //if (r > 84) tft.drawCentreString(buf, x - 5, y - 20, 6); // Value in middle //else tft.drawCentreString(buf, x - 5, y - 20, 4); // Value in middle // Print units, if the meter is large then use big font 4, othewise use 2 tft.setTextColor(ST7735_WHITE, ST7735_BLACK); //if (r > 84) tft.drawCentreString(units, x, y + 30, 4); // Units display //else tft.drawCentreString(units, x, y + 5, 2); // Units display // Calculate and return right hand side x coordinate return x + r; } // ######################################################################### // Return a 16 bit rainbow colour // ######################################################################### unsigned int rainbow(byte value) { // Value is expected to be in range 0-127 // The value is converted to a spectrum colour from 0 = blue through to 127 = red byte red = 0; // Red is the top 5 bits of a 16 bit colour value byte green = 0;// Green is the middle 6 bits byte blue = 0; // Blue is the bottom 5 bits byte quadrant = value / 32; if (quadrant == 0) { blue = 31; green = 2 * (value % 32); red = 0; } if (quadrant == 1) { blue = 31 - (value % 32); green = 63; red = 0; } if (quadrant == 2) { blue = 0; green = 63; red = value % 32; } if (quadrant == 3) { blue = 0; green = 63 - 2 * (value % 32); red = 31; } return (red << 11) + (green << 5) + blue; } // ######################################################################### // Return a value in range -1 to +1 for a given phase angle in degrees // ######################################################################### float sineWave(int phase) { return sin(phase * 0.0174532925); } void rpm_fun() { half_revolutions++; //Each rotation, this interrupt function is run twice } void beep() { tone(beepPin,3500, 300); //delay(500); //tone(beepPin,3000, 300); //delay(500); }