Components and supplies
1x40 Male Pin Header
ntc thermistor 10k bedrahtet
Alphacool Icicle temperature sensor
Lamptron SP105 Hub
M2.5X8mm screws
Phobya Fan Power Connector 4Pin PWM male
USB-A to USB-B cable
Arduino UNO
Fan2Click
Internal USB to Motherboard cable
ADAC Click
Jumper wires (generic)
4pin pwm extension 30 cm
Female Header 8 Position 1 Row (0.1")
Tools and machines
Solder Wire, Lead Free
Soldering iron (generic)
3D Printer (generic)
UHU Super Power superglue
Project description
Code
Python program to send GPU data to Arduino over Serial
python
This program sends the GPU data (core and vram temps) and the info if the excavator is running. Important note: check line 25 and change the COM number to the one on which your Arduino is connected! Also, if you want the program to run in the background unseen, save it with a ".pyw" extension instead of ".py".
1from pynvraw import api, NvError, get_phys_gpu 2import serial, time, psutil 3 4def checkIfProcessRunning(processName): 5 ''' 6 Check if there is any running process that contains the given name processName. 7 ''' 8 #Iterate over the all the running process 9 for proc in psutil.process_iter(): 10 try: 11 # Check if process name contains the given name string. 12 if processName.lower() in proc.name().lower(): 13 return True 14 except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): 15 pass 16 return False; 17 18def main(): 19 cuda_dev = 0 20 ser = serial.Serial() 21 ser.baudrate = 115200 22 ser.timeout = 0.1 23 ser.writeTimeout = 0.1 24 ser.setDTR(False) #THIS IS IMPORTANT - prevents Arduino from resetting 25 ser.port = 'COM8' #Put the COM number to which your Arduino is connected 26 dataBeginsInt = 32 #this is a spacebar, data follows the Space char. 27 dataBeginsChar = dataBeginsInt; 28 dataCharOffset = 33 29 outputString = " {}{}{}{}\ 30" 31 base = 94; #base for encoding temperature numbers 32 while True: 33 try: 34 gpu = get_phys_gpu(cuda_dev) 35 except ValueError: 36 break 37 try: 38 api.restore_coolers(gpu.handle) 39 except NvError as err: 40 if err.status != 'NVAPI_NOT_SUPPORTED': 41 raise 42 43 cuda_dev += 1 44 45 while True: 46 try: 47 if checkIfProcessRunning('excavator'): 48 ExcavRunning = 'Y' 49 else: 50 ExcavRunning = 'N' 51 gpuCoreTemp10xInt = int(gpu.core_temp*10); 52 coreTempCharEncodedByte2 = int(gpuCoreTemp10xInt%base+dataCharOffset) #least significant byte of the core temp encoded 53 coreTempCharEncodedByte1 = int((gpuCoreTemp10xInt-coreTempCharEncodedByte2+dataCharOffset)/base+dataCharOffset) #most significant byte of the core temp encoded 54 vramTempCharEncodedByte1 = int(gpu.vram_temp)+dataCharOffset 55 ser.open() 56 ser.write(outputString.format(chr(coreTempCharEncodedByte1),chr(coreTempCharEncodedByte2),chr(vramTempCharEncodedByte1),ExcavRunning).encode()) 57 ser.close() 58 time.sleep(0.5) 59 except: 60 time.sleep(0.10 ) 61 print('Port Busy\ 62') 63 64 65if __name__ == '__main__': 66 main()
Arduino sketch
c_cpp
The Arduino program, including the custom functions to drive the fan controller ICs over I2C, this is why it grew so big and may look intimidating. I packed everything into meaningful functions, so I hope you are able to figure it out!
1 2 3/* I2C LCD with Arduino example code. More info: https://www.makerguides.com */ 4 5// Include the libraries: 6// LiquidCrystal_I2C.h: https://github.com/johnrickman/LiquidCrystal_I2C 7#include <Wire.h> // Library for I2C communication 8#include <LiquidCrystal_I2C.h> // Library for LCD 9#include <AD5593R.h> 10#include <math.h> 11 12#define BAUD_RATE 115200 13#define LOOP_INTERVAL 1200 //ms 14 15/* FAN CONTROL CONFIGURATION DEFINITIONS */ 16#define FANCTRL_I2C_ADDR 0x50 17 18#define FANCTRL_CR1_ADDR 0x00 //control register 1 19#define FANCTRL_CR1_VAL B10011000 20 21#define FANCTRL_CR2_ADDR 0x01 //control register 2 22#define FANCTRL_CR2_VAL B00010001 23 24#define FANCTRL_CR3_ADDR 0x02 //control register 3 25#define FANCTRL_CR3_VAL B00110001 26 27#define FANCTRL_FFDC_ADDR 0x03 //fan fail duty cycle 28#define FANCTRL_FFDC_VAL 0xF2 // 95% duty cycle at failure detected 29 30#define FANCTRL_AMR_ADDR 0x04 //alert mask register 31#define FANCTRL_AMR_VAL B11111111 // ignore all alerts (for testing) 32 33#define FANCTRL_DFCS_ADDR 0x50 //direct fan control PWM setting 34 35#define FANCTRL_CPWM_ADDR 0x51 //current pwm reading 36 37#define FANCTRL_TACHH_ADDR 0x52 //high bits of tach 38#define FANCTRL_TACHL_ADDR 0x53 //low bits of tach 39 40#define FANCTRL_TEMPH_ADDR 0x58 //high bits of temp 41#define FANCTRL_TEMPL_ADDR 0x59 //low bits of temp 42 43#define FAN_PWM_MIN 72 //65 is ~25% 44#define FAN_PWM_MAX 255//110// 110 is 43% 45#define PWM_DELTA_MAX 10 46#define PWM_SLOPE 5 47 48 49#define TEMP_WATER_H T1 50#define TEMP_WATER_L T2 51#define TEMP_AIR_H T3 52 53#define WATER_PUMP_CONTROL 1 54#define FANS_CONTROL 2 55#define TEMP_DIFF_TARGET 1.7 //-0.3//, target difference between TH and TL 56//#define TEMP_TARGET 34.0 //, target TL 57 58#define BASE_CONV 94 59#define OFFS_CONV 33 60 61#define READ_TIMEOUT 500 62 63LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 20, 4); 64AD5593R AD5593R(0x10); //ADC init 65 66bool my_DACs[8] = {1,0,0,0,0,0,0,0}; 67bool my_ADCs[8] = {0,1,1,1,0,0,0,0}; 68const int buttonPin = 2; 69 70volatile bool backlightState = 1; 71volatile bool buttonPressed = 0; 72 73float v1, v2, v3, R1, R2, R3, T1, T2, T3, T4, TC, TM; // real variables: voltage, resistance and temperature 74//const char degC[3] = {4,'C',0}; // degC string for display 75const char degC[3] = {223,0}; // degC string for display 76const char Tlo[3] = {6,0}; // T_LO string for display 77const char Thi[3] = {7,0}; // T_HI string for display 78const float TempTarget[2] = {45.0,40.0};//{40.0,25.0};// 79const char TempNoData[5] = "--.-";//{165,165,'.',165,0}; 80const char modeChar[2] = {252,228}; 81const char coreChar = 4; 82const char vramChar = 5; 83 84const float vh = 2.0;//4.5; //V, high voltage for resistance measurement bias 85const float T0 = 273.15; //K, temp equal 0 86const float A = 2.5490E-4; //A parameter for recalculation of resistance to temp 87const float B = 1.0062E-3; //B parameter for recalculation of resistance to temp 88const float Rr = 20000.0;//19800.0;// kOhm 89const int N = 80; //averaging count 90//int i; 91 92unsigned long previousTime = 0; 93unsigned long currentTime = 0; 94unsigned long prevButtonPress = 0; 95unsigned long nowButtonPress = 0; 96unsigned long serialReadingBegins; 97 98String inputString; 99String inputStringBuffer; 100bool miningMode = true; 101bool freshDataAvailable = false; 102 103 104// Custom char definitions 105byte barChars0[8] = { 106 B10101, 107 B00000, 108 B10101, 109 B00000, 110 B10101, 111 B00000, 112 B10101, 113 B00000 114}; 115byte barChars1[8] = { 116 B10101, 117 B10000, 118 B10101, 119 B10000, 120 B10101, 121 B10000, 122 B10101, 123 B00000 124}; 125byte barChars2[8] = { 126 B10101, 127 B10100, 128 B10101, 129 B10100, 130 B10101, 131 B10100, 132 B10101, 133 B00000 134}; 135byte barChars3[8] = { 136 B10101, 137 B10101, 138 B10101, 139 B10101, 140 B10101, 141 B10101, 142 B10101, 143 B00000 144}; 145 146byte loSign[8] = { 147 B00000, 148 B11111, 149 B10111, 150 B10111, 151 B10001, 152 B11111, 153 B00000, 154 B00000 155}; 156byte hiSign[8] = { 157 B00000, 158 B11111, 159 B10101, 160 B10001, 161 B10101, 162 B11111, 163 B00000, 164 B00000 165}; 166byte core[8] = { 167 B11110, 168 B11111, 169 B11110, 170 B11111, 171 B11011, 172 B11011, 173 B11111, 174 B11111 175}; 176byte vram[8] = { 177 B11110, 178 B11111, 179 B11110, 180 B11011, 181 B10101, 182 B10101, 183 B11011, 184 B11111 185}; 186 187void setup() { 188 initializeI2CLCD(); 189 //Serial setup 190 Serial.begin(BAUD_RATE); 191 Serial.setTimeout(100); 192 193 //Interrupt for button to toggle LCD backlight 194 pinMode(buttonPin, INPUT_PULLUP); 195 attachInterrupt(0, pin_ISR, FALLING); 196 197 // Setup fan controllers 198 configureFan2Click(1); 199 configureFan2Click(2); 200 201 configureADCs(); 202 203 // Reserve memory for incoming strings form serial 204 inputString.reserve(20); 205 inputStringBuffer.reserve(20); 206} 207 208void loop() { 209 currentTime = millis(); 210 211 //BACKLIGHT TOGGLE BUTTON CHECK W/ DEBOUNCER 212 if ( buttonPressed ){ 213 nowButtonPress = millis(); 214 if (nowButtonPress-prevButtonPress>50){ 215 prevButtonPress = nowButtonPress; 216 backlightState = !backlightState; 217 buttonPressed = 0; 218 } 219 } 220 221 trySerialRead(); 222 223 //BACKLIGHT SET 224 if (backlightState){ 225 lcd.backlight(); 226 } 227 else{ 228 lcd.noBacklight(); 229 } 230 231 //MAIN ACITON 232 if (currentTime - previousTime >= LOOP_INTERVAL) { 233 previousTime = currentTime; // save the last executed time 234 235 getTemperatures(); 236 237 changePWM(WATER_PUMP_CONTROL, TEMP_WATER_H - TEMP_WATER_L, TEMP_DIFF_TARGET); 238 changePWM(FANS_CONTROL, TEMP_WATER_L, TempTarget[miningMode]); //mining mode bool chooses between the two array elements 239 240 displayUpdate(); 241 242 freshDataAvailable = false; //set flag to await fresh data from serial 243 } 244} 245 246void pin_ISR() { //pin interrupt for button press 247 buttonPressed = 1; 248} 249 250/////////////////////////////// 251/// FUNCTIONS FOR FAN2CLICK /// 252/////////////////////////////// 253 254void writeByteToFan2Click(int num, uint8_t addr, uint8_t value){ 255 //num - number of the fan controller, 1 or 2 256 uint8_t i2cAddress = FANCTRL_I2C_ADDR+num-1; 257 Wire.beginTransmission(i2cAddress); 258 Wire.write(addr); 259 Wire.write(value); 260 Wire.endTransmission(); 261} 262 263uint8_t readByteFromFan2Click(int num, uint8_t addr){ 264 //num - number of the fan controller, 1 or 2 265 uint8_t i2cAddress = FANCTRL_I2C_ADDR+num-1; 266 uint8_t reading = 0; 267 Wire.beginTransmission(i2cAddress); 268 Wire.write(addr); 269 Wire.endTransmission(); 270 Wire.requestFrom(i2cAddress, uint8_t(1)); 271 if (1 <= Wire.available()) { // if one byte was received 272 reading = Wire.read(); 273 } 274 return reading; 275} 276 277int read2BytesFromFan2Click(int num, uint8_t addr){ 278 //num - number of the fan controller, 1 or 2 279 int reading = readByteFromFan2Click(num, addr); 280 reading = reading<<8; 281 reading |= readByteFromFan2Click(num, addr+1); 282 return reading; 283} 284 285float readInternalTempFan2Click(int num){ 286 int reading = read2BytesFromFan2Click(num, byte(FANCTRL_TEMPH_ADDR)); 287 reading = reading>>5; 288 float Temperature = (float)reading/8; 289 return Temperature; 290} 291 292void setDirectPWMFan2Click(int num, byte pwm){ 293 writeByteToFan2Click(num, byte(FANCTRL_DFCS_ADDR), pwm); 294} 295 296byte getCurrentPWMFan2Click(int num){ 297 byte pwm_current = readByteFromFan2Click(num, byte(FANCTRL_DFCS_ADDR)); 298 return pwm_current; 299} 300 301float readTachometer(int num){ 302 int reading = read2BytesFromFan2Click(num, byte(FANCTRL_TACHH_ADDR)); 303 float Tach = 6e6/(float)reading/2; //division by 2 comes from number of tach pulses per revolution 304 //6e6 is the number of seconds in a minute times tach counter clock frequency 305 return Tach; 306} 307 308void displayTach(int num, int i, int j){ 309 //i,j - lcd cursor indexes horisontal and vertical respectively, 310 //pwm - tach float value 311 float Tach = readTachometer(num); 312 lcd.setCursor(i, j); 313 if (Tach<1000){ 314 lcd.print(" "); 315 } 316 if (Tach<0){ 317 lcd.print(" "); 318 Tach = 0; 319 } 320 lcd.print(Tach,0); 321} 322 323bool checkAndWriteByteToFan2Click(int num, uint8_t addr, uint8_t val){ 324 uint8_t val_read = readByteFromFan2Click(num, addr); 325 bool rewritten = false; 326 if(val_read != val){ 327 writeByteToFan2Click(num, addr, val); 328 rewritten = true; 329 } 330 return rewritten; 331} 332 333void configureFan2Click(int num){ 334 int rewrittenSum = 0; 335 rewrittenSum = rewrittenSum+checkAndWriteByteToFan2Click(num, byte(FANCTRL_CR1_ADDR), byte(FANCTRL_CR1_VAL) ); 336 rewrittenSum = rewrittenSum+checkAndWriteByteToFan2Click(num, byte(FANCTRL_CR2_ADDR), byte(FANCTRL_CR2_VAL) ); 337 rewrittenSum = rewrittenSum+checkAndWriteByteToFan2Click(num, byte(FANCTRL_CR3_ADDR), byte(FANCTRL_CR3_VAL) ); 338 rewrittenSum = rewrittenSum+checkAndWriteByteToFan2Click(num, byte(FANCTRL_FFDC_ADDR), byte(FANCTRL_FFDC_VAL)); 339 rewrittenSum = rewrittenSum+checkAndWriteByteToFan2Click(num, byte(FANCTRL_AMR_ADDR), byte(FANCTRL_AMR_VAL) ); 340 /* DEBUGGING DISPLAY 341 lcd.setCursor(0, 0+(num-1)*2); 342 lcd.print("Fan Controller #"); 343 lcd.print(num); 344 lcd.setCursor(0, 1+(num-1)*2); 345 lcd.print(rewrittenSum); 346 lcd.print(" settings changed."); */ 347} 348 349void changePWM(int num, float Temp, float TempSetPoint){ 350 int pwm = getCurrentPWMFan2Click(num); 351 352 //COMPUTE DELTA PWM 353 int deltaPWM = (Temp-TempSetPoint)*PWM_SLOPE; 354 if(deltaPWM>PWM_DELTA_MAX){ 355 deltaPWM = PWM_DELTA_MAX; 356 } 357 else if(deltaPWM<-PWM_DELTA_MAX){ 358 deltaPWM = -PWM_DELTA_MAX; 359 } 360 361 //CHANGE PWM BY PWM DELTA 362 pwm = pwm+deltaPWM; 363 if(pwm>FAN_PWM_MAX){ 364 pwm=FAN_PWM_MAX; 365 } 366 else if(pwm<FAN_PWM_MIN){ 367 pwm=FAN_PWM_MIN; 368 } 369 370 setDirectPWMFan2Click(num, pwm); 371} 372 373void displayPWMChart(int num, int i, int j){ 374 int pwm = getCurrentPWMFan2Click(num); 375 int bars = (pwm+7)*18/255; //18 is the total number of bars 376 int rest; 377 int divn; 378 char barChart[6] = {0,0,0,0,0,0}; 379 int k; 380 divn = bars/3; 381 rest = bars%3; 382 for (k=0;k<divn;k++){ 383 barChart[k] = 3; 384 } 385 if(rest){ 386 barChart[divn] = rest; 387 } 388 lcd.setCursor(i, j); 389// lcd.print("F"); 390// lcd.print(num); 391// lcd.print(": "); 392 if (num==WATER_PUMP_CONTROL){ 393 lcd.print("Pump"); 394 } 395 else if(num==FANS_CONTROL){ 396 lcd.print("Fans"); 397 } 398 for(k=0;k<6;k++){ 399 lcd.print(barChart[k]); 400 } 401} 402 403 404/////////////////////// 405/// OTHER FUNCTIONS /// 406/////////////////////// 407 408 409void displayTemp(int i, int j, float T, int n){ 410 //i,j - lcd cursor indexes horisontal and vertical respectively, 411 //T - temp, n - digits after decimal point 412 lcd.setCursor(i, j); 413 if(T<10 && T>=0){ 414 lcd.print("+"); 415 } 416 if(T<-50.0){ // if there is no temp data for this one, I assigned a value of -100. 417 lcd.print(TempNoData); 418 } 419 else{ 420 lcd.print(T,n); 421 } 422 lcd.print(degC); 423} 424 425void getTemperatures(){ 426 427 T1 = 0; 428 T2 = 0; 429 T3 = 0; 430 AD5593R.write_DAC(0,vh); 431 int i; 432 for(i=1;i<N+1;i++){ 433 v1 = AD5593R.read_ADC(1); 434 v2 = AD5593R.read_ADC(2); 435 v3 = AD5593R.read_ADC(3); 436 R2 = Rr*v2/(vh-v1); 437 R1 = Rr/(vh/v1-1)-R2; 438 R3 = 0.5*Rr/(vh/v3-1); 439 T1 = T1+(1/(A*log(R1)+B)-T0); 440 T2 = T2+(1/(A*log(R2)+B)-T0); 441 T3 = T3+(1/(A*log(R3)+B)-T0); 442 } 443 T1 = T1/N; 444 T2 = T2/N; 445 T3 = T3/N; 446 T4 = readInternalTempFan2Click(1); 447 AD5593R.write_DAC(0,0); 448 449 if ( freshDataAvailable ){ 450 TC = .1*((inputString.charAt(1)-OFFS_CONV)+(inputString.charAt(0)-OFFS_CONV)*BASE_CONV); 451 TM = 1.0*(inputString.charAt(2)-OFFS_CONV); 452 miningMode = (inputString.charAt(3) == 'Y'); 453 } 454 else{ 455 TC = -100.0; // values to indicate lack of data in the function that prints temps on LCD 456 TM = -100.0; 457 miningMode = true; 458 } 459} 460 461void initializeI2CLCD(){ 462 lcd.init(); 463 lcd.clear(); 464 lcd.createChar(0, barChars0); 465 lcd.createChar(1, barChars1); 466 lcd.createChar(2, barChars2); 467 lcd.createChar(3, barChars3); 468 lcd.createChar(4, core); 469 lcd.createChar(5, vram); 470 lcd.createChar(6, loSign); 471 lcd.createChar(7, hiSign); 472 backlightState = true; 473} 474 475void configureADCs(){ 476 AD5593R.enable_internal_Vref(); 477 AD5593R.set_DAC_max_1x_Vref(); 478 AD5593R.set_ADC_max_1x_Vref(); 479 AD5593R.configure_DACs(my_DACs); 480 AD5593R.configure_ADCs(my_ADCs); 481} 482 483void displayUpdate(){ 484 lcd.setCursor(0, 0); 485 lcd.print(Thi); 486 displayTemp(1, 0, TEMP_WATER_H, 1); 487 488 lcd.setCursor(0, 1); 489 lcd.print(Tlo); 490 displayTemp(1, 1, TEMP_WATER_L, 1); 491 492 displayTemp(1, 2, TEMP_WATER_H - TEMP_WATER_L, 1); 493 494 lcd.setCursor(0, 3); 495 lcd.print(char(127)); 496 displayTemp(1, 3, TEMP_AIR_H, 1); 497 498 lcd.setCursor(6, 0); 499 lcd.print(coreChar); 500 displayTemp(7, 0, TC, 1); 501 lcd.setCursor(6, 1); 502 lcd.print(vramChar); 503 displayTemp(7, 1, TM, 1); 504 505 lcd.setCursor(12, 0); 506 lcd.print("[Mode:"); 507 lcd.print(modeChar[miningMode]); 508 lcd.print("]"); 509 510 displayPWMChart(WATER_PUMP_CONTROL, 6, 2); 511 displayPWMChart(FANS_CONTROL, 6, 3); 512 513 lcd.setCursor(17, 1); 514 lcd.print("RPM"); 515 displayTach(WATER_PUMP_CONTROL, 16, 2); 516 displayTach(FANS_CONTROL, 16, 3); 517} 518 519void trySerialRead(){ 520 if ( Serial.available() ){ 521 serialReadingBegins = millis(); 522 while(Serial.read()!=' ' && (millis()-serialReadingBegins)<READ_TIMEOUT){ 523 //Do nothing 524 } 525 inputStringBuffer = Serial.readStringUntil('\ 526'); 527 if(inputStringBuffer.indexOf(char(0x09))!=-1){ //if string contains char 0x09 (tabulator) 528 backlightState = !backlightState; 529 } 530 if(inputStringBuffer.length()==4){ 531 inputString = inputStringBuffer; 532 freshDataAvailable = true; 533 } 534 } 535} 536
LCD backlight toggle Python program
python
This program sends the spacebar and tabulator to Arduino over Serial, which triggers LCD backlight toggle. Important note: check line 10 and change the COM number to the one on which your Arduino is connected! Also, if you want the program to run in the background unseen, save it with a ".pyw" extension instead of ".py".
1import serial 2import time 3 4def main(): 5 ser = serial.Serial() 6 ser.baudrate = 115200 7 ser.timeout = 0.1 8 ser.writeTimeout = 0.1 9 ser.setDTR(False) 10 ser.port = 'COM8' 11 a = " \\x09\ 12" #\\x09 is the tabulator. 13 trying = 1 14 while trying: 15 try: 16 ser.open() 17 ser.write(a.encode()) 18 ser.close() 19 trying = 0 20 except: 21 time.sleep(0.1) 22 23if __name__ == '__main__': 24 main() 25
Arduino sketch
c_cpp
The Arduino program, including the custom functions to drive the fan controller ICs over I2C, this is why it grew so big and may look intimidating. I packed everything into meaningful functions, so I hope you are able to figure it out!
1 2 3/* I2C LCD with Arduino example code. More info: https://www.makerguides.com */ 4 5// Include the libraries: 6// LiquidCrystal_I2C.h: https://github.com/johnrickman/LiquidCrystal_I2C 7#include <Wire.h> // Library for I2C communication 8#include <LiquidCrystal_I2C.h> // Library for LCD 9#include <AD5593R.h> 10#include <math.h> 11 12#define BAUD_RATE 115200 13#define LOOP_INTERVAL 1200 //ms 14 15/* FAN CONTROL CONFIGURATION DEFINITIONS */ 16#define FANCTRL_I2C_ADDR 0x50 17 18#define FANCTRL_CR1_ADDR 0x00 //control register 1 19#define FANCTRL_CR1_VAL B10011000 20 21#define FANCTRL_CR2_ADDR 0x01 //control register 2 22#define FANCTRL_CR2_VAL B00010001 23 24#define FANCTRL_CR3_ADDR 0x02 //control register 3 25#define FANCTRL_CR3_VAL B00110001 26 27#define FANCTRL_FFDC_ADDR 0x03 //fan fail duty cycle 28#define FANCTRL_FFDC_VAL 0xF2 // 95% duty cycle at failure detected 29 30#define FANCTRL_AMR_ADDR 0x04 //alert mask register 31#define FANCTRL_AMR_VAL B11111111 // ignore all alerts (for testing) 32 33#define FANCTRL_DFCS_ADDR 0x50 //direct fan control PWM setting 34 35#define FANCTRL_CPWM_ADDR 0x51 //current pwm reading 36 37#define FANCTRL_TACHH_ADDR 0x52 //high bits of tach 38#define FANCTRL_TACHL_ADDR 0x53 //low bits of tach 39 40#define FANCTRL_TEMPH_ADDR 0x58 //high bits of temp 41#define FANCTRL_TEMPL_ADDR 0x59 //low bits of temp 42 43#define FAN_PWM_MIN 72 //65 is ~25% 44#define FAN_PWM_MAX 255//110// 110 is 43% 45#define PWM_DELTA_MAX 10 46#define PWM_SLOPE 5 47 48 49#define TEMP_WATER_H T1 50#define TEMP_WATER_L T2 51#define TEMP_AIR_H T3 52 53#define WATER_PUMP_CONTROL 1 54#define FANS_CONTROL 2 55#define TEMP_DIFF_TARGET 1.7 //-0.3//, target difference between TH and TL 56//#define TEMP_TARGET 34.0 //, target TL 57 58#define BASE_CONV 94 59#define OFFS_CONV 33 60 61#define READ_TIMEOUT 500 62 63LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 20, 4); 64AD5593R AD5593R(0x10); //ADC init 65 66bool my_DACs[8] = {1,0,0,0,0,0,0,0}; 67bool my_ADCs[8] = {0,1,1,1,0,0,0,0}; 68const int buttonPin = 2; 69 70volatile bool backlightState = 1; 71volatile bool buttonPressed = 0; 72 73float v1, v2, v3, R1, R2, R3, T1, T2, T3, T4, TC, TM; // real variables: voltage, resistance and temperature 74//const char degC[3] = {4,'C',0}; // degC string for display 75const char degC[3] = {223,0}; // degC string for display 76const char Tlo[3] = {6,0}; // T_LO string for display 77const char Thi[3] = {7,0}; // T_HI string for display 78const float TempTarget[2] = {45.0,40.0};//{40.0,25.0};// 79const char TempNoData[5] = "--.-";//{165,165,'.',165,0}; 80const char modeChar[2] = {252,228}; 81const char coreChar = 4; 82const char vramChar = 5; 83 84const float vh = 2.0;//4.5; //V, high voltage for resistance measurement bias 85const float T0 = 273.15; //K, temp equal 0 86const float A = 2.5490E-4; //A parameter for recalculation of resistance to temp 87const float B = 1.0062E-3; //B parameter for recalculation of resistance to temp 88const float Rr = 20000.0;//19800.0;// kOhm 89const int N = 80; //averaging count 90//int i; 91 92unsigned long previousTime = 0; 93unsigned long currentTime = 0; 94unsigned long prevButtonPress = 0; 95unsigned long nowButtonPress = 0; 96unsigned long serialReadingBegins; 97 98String inputString; 99String inputStringBuffer; 100bool miningMode = true; 101bool freshDataAvailable = false; 102 103 104// Custom char definitions 105byte barChars0[8] = { 106 B10101, 107 B00000, 108 B10101, 109 B00000, 110 B10101, 111 B00000, 112 B10101, 113 B00000 114}; 115byte barChars1[8] = { 116 B10101, 117 B10000, 118 B10101, 119 B10000, 120 B10101, 121 B10000, 122 B10101, 123 B00000 124}; 125byte barChars2[8] = { 126 B10101, 127 B10100, 128 B10101, 129 B10100, 130 B10101, 131 B10100, 132 B10101, 133 B00000 134}; 135byte barChars3[8] = { 136 B10101, 137 B10101, 138 B10101, 139 B10101, 140 B10101, 141 B10101, 142 B10101, 143 B00000 144}; 145 146byte loSign[8] = { 147 B00000, 148 B11111, 149 B10111, 150 B10111, 151 B10001, 152 B11111, 153 B00000, 154 B00000 155}; 156byte hiSign[8] = { 157 B00000, 158 B11111, 159 B10101, 160 B10001, 161 B10101, 162 B11111, 163 B00000, 164 B00000 165}; 166byte core[8] = { 167 B11110, 168 B11111, 169 B11110, 170 B11111, 171 B11011, 172 B11011, 173 B11111, 174 B11111 175}; 176byte vram[8] = { 177 B11110, 178 B11111, 179 B11110, 180 B11011, 181 B10101, 182 B10101, 183 B11011, 184 B11111 185}; 186 187void setup() { 188 initializeI2CLCD(); 189 //Serial setup 190 Serial.begin(BAUD_RATE); 191 Serial.setTimeout(100); 192 193 //Interrupt for button to toggle LCD backlight 194 pinMode(buttonPin, INPUT_PULLUP); 195 attachInterrupt(0, pin_ISR, FALLING); 196 197 // Setup fan controllers 198 configureFan2Click(1); 199 configureFan2Click(2); 200 201 configureADCs(); 202 203 // Reserve memory for incoming strings form serial 204 inputString.reserve(20); 205 inputStringBuffer.reserve(20); 206} 207 208void loop() { 209 currentTime = millis(); 210 211 //BACKLIGHT TOGGLE BUTTON CHECK W/ DEBOUNCER 212 if ( buttonPressed ){ 213 nowButtonPress = millis(); 214 if (nowButtonPress-prevButtonPress>50){ 215 prevButtonPress = nowButtonPress; 216 backlightState = !backlightState; 217 buttonPressed = 0; 218 } 219 } 220 221 trySerialRead(); 222 223 //BACKLIGHT SET 224 if (backlightState){ 225 lcd.backlight(); 226 } 227 else{ 228 lcd.noBacklight(); 229 } 230 231 //MAIN ACITON 232 if (currentTime - previousTime >= LOOP_INTERVAL) { 233 previousTime = currentTime; // save the last executed time 234 235 getTemperatures(); 236 237 changePWM(WATER_PUMP_CONTROL, TEMP_WATER_H - TEMP_WATER_L, TEMP_DIFF_TARGET); 238 changePWM(FANS_CONTROL, TEMP_WATER_L, TempTarget[miningMode]); //mining mode bool chooses between the two array elements 239 240 displayUpdate(); 241 242 freshDataAvailable = false; //set flag to await fresh data from serial 243 } 244} 245 246void pin_ISR() { //pin interrupt for button press 247 buttonPressed = 1; 248} 249 250/////////////////////////////// 251/// FUNCTIONS FOR FAN2CLICK /// 252/////////////////////////////// 253 254void writeByteToFan2Click(int num, uint8_t addr, uint8_t value){ 255 //num - number of the fan controller, 1 or 2 256 uint8_t i2cAddress = FANCTRL_I2C_ADDR+num-1; 257 Wire.beginTransmission(i2cAddress); 258 Wire.write(addr); 259 Wire.write(value); 260 Wire.endTransmission(); 261} 262 263uint8_t readByteFromFan2Click(int num, uint8_t addr){ 264 //num - number of the fan controller, 1 or 2 265 uint8_t i2cAddress = FANCTRL_I2C_ADDR+num-1; 266 uint8_t reading = 0; 267 Wire.beginTransmission(i2cAddress); 268 Wire.write(addr); 269 Wire.endTransmission(); 270 Wire.requestFrom(i2cAddress, uint8_t(1)); 271 if (1 <= Wire.available()) { // if one byte was received 272 reading = Wire.read(); 273 } 274 return reading; 275} 276 277int read2BytesFromFan2Click(int num, uint8_t addr){ 278 //num - number of the fan controller, 1 or 2 279 int reading = readByteFromFan2Click(num, addr); 280 reading = reading<<8; 281 reading |= readByteFromFan2Click(num, addr+1); 282 return reading; 283} 284 285float readInternalTempFan2Click(int num){ 286 int reading = read2BytesFromFan2Click(num, byte(FANCTRL_TEMPH_ADDR)); 287 reading = reading>>5; 288 float Temperature = (float)reading/8; 289 return Temperature; 290} 291 292void setDirectPWMFan2Click(int num, byte pwm){ 293 writeByteToFan2Click(num, byte(FANCTRL_DFCS_ADDR), pwm); 294} 295 296byte getCurrentPWMFan2Click(int num){ 297 byte pwm_current = readByteFromFan2Click(num, byte(FANCTRL_DFCS_ADDR)); 298 return pwm_current; 299} 300 301float readTachometer(int num){ 302 int reading = read2BytesFromFan2Click(num, byte(FANCTRL_TACHH_ADDR)); 303 float Tach = 6e6/(float)reading/2; //division by 2 comes from number of tach pulses per revolution 304 //6e6 is the number of seconds in a minute times tach counter clock frequency 305 return Tach; 306} 307 308void displayTach(int num, int i, int j){ 309 //i,j - lcd cursor indexes horisontal and vertical respectively, 310 //pwm - tach float value 311 float Tach = readTachometer(num); 312 lcd.setCursor(i, j); 313 if (Tach<1000){ 314 lcd.print(" "); 315 } 316 if (Tach<0){ 317 lcd.print(" "); 318 Tach = 0; 319 } 320 lcd.print(Tach,0); 321} 322 323bool checkAndWriteByteToFan2Click(int num, uint8_t addr, uint8_t val){ 324 uint8_t val_read = readByteFromFan2Click(num, addr); 325 bool rewritten = false; 326 if(val_read != val){ 327 writeByteToFan2Click(num, addr, val); 328 rewritten = true; 329 } 330 return rewritten; 331} 332 333void configureFan2Click(int num){ 334 int rewrittenSum = 0; 335 rewrittenSum = rewrittenSum+checkAndWriteByteToFan2Click(num, byte(FANCTRL_CR1_ADDR), byte(FANCTRL_CR1_VAL) ); 336 rewrittenSum = rewrittenSum+checkAndWriteByteToFan2Click(num, byte(FANCTRL_CR2_ADDR), byte(FANCTRL_CR2_VAL) ); 337 rewrittenSum = rewrittenSum+checkAndWriteByteToFan2Click(num, byte(FANCTRL_CR3_ADDR), byte(FANCTRL_CR3_VAL) ); 338 rewrittenSum = rewrittenSum+checkAndWriteByteToFan2Click(num, byte(FANCTRL_FFDC_ADDR), byte(FANCTRL_FFDC_VAL)); 339 rewrittenSum = rewrittenSum+checkAndWriteByteToFan2Click(num, byte(FANCTRL_AMR_ADDR), byte(FANCTRL_AMR_VAL) ); 340 /* DEBUGGING DISPLAY 341 lcd.setCursor(0, 0+(num-1)*2); 342 lcd.print("Fan Controller #"); 343 lcd.print(num); 344 lcd.setCursor(0, 1+(num-1)*2); 345 lcd.print(rewrittenSum); 346 lcd.print(" settings changed."); */ 347} 348 349void changePWM(int num, float Temp, float TempSetPoint){ 350 int pwm = getCurrentPWMFan2Click(num); 351 352 //COMPUTE DELTA PWM 353 int deltaPWM = (Temp-TempSetPoint)*PWM_SLOPE; 354 if(deltaPWM>PWM_DELTA_MAX){ 355 deltaPWM = PWM_DELTA_MAX; 356 } 357 else if(deltaPWM<-PWM_DELTA_MAX){ 358 deltaPWM = -PWM_DELTA_MAX; 359 } 360 361 //CHANGE PWM BY PWM DELTA 362 pwm = pwm+deltaPWM; 363 if(pwm>FAN_PWM_MAX){ 364 pwm=FAN_PWM_MAX; 365 } 366 else if(pwm<FAN_PWM_MIN){ 367 pwm=FAN_PWM_MIN; 368 } 369 370 setDirectPWMFan2Click(num, pwm); 371} 372 373void displayPWMChart(int num, int i, int j){ 374 int pwm = getCurrentPWMFan2Click(num); 375 int bars = (pwm+7)*18/255; //18 is the total number of bars 376 int rest; 377 int divn; 378 char barChart[6] = {0,0,0,0,0,0}; 379 int k; 380 divn = bars/3; 381 rest = bars%3; 382 for (k=0;k<divn;k++){ 383 barChart[k] = 3; 384 } 385 if(rest){ 386 barChart[divn] = rest; 387 } 388 lcd.setCursor(i, j); 389// lcd.print("F"); 390// lcd.print(num); 391// lcd.print(": "); 392 if (num==WATER_PUMP_CONTROL){ 393 lcd.print("Pump"); 394 } 395 else if(num==FANS_CONTROL){ 396 lcd.print("Fans"); 397 } 398 for(k=0;k<6;k++){ 399 lcd.print(barChart[k]); 400 } 401} 402 403 404/////////////////////// 405/// OTHER FUNCTIONS /// 406/////////////////////// 407 408 409void displayTemp(int i, int j, float T, int n){ 410 //i,j - lcd cursor indexes horisontal and vertical respectively, 411 //T - temp, n - digits after decimal point 412 lcd.setCursor(i, j); 413 if(T<10 && T>=0){ 414 lcd.print("+"); 415 } 416 if(T<-50.0){ // if there is no temp data for this one, I assigned a value of -100. 417 lcd.print(TempNoData); 418 } 419 else{ 420 lcd.print(T,n); 421 } 422 lcd.print(degC); 423} 424 425void getTemperatures(){ 426 427 T1 = 0; 428 T2 = 0; 429 T3 = 0; 430 AD5593R.write_DAC(0,vh); 431 int i; 432 for(i=1;i<N+1;i++){ 433 v1 = AD5593R.read_ADC(1); 434 v2 = AD5593R.read_ADC(2); 435 v3 = AD5593R.read_ADC(3); 436 R2 = Rr*v2/(vh-v1); 437 R1 = Rr/(vh/v1-1)-R2; 438 R3 = 0.5*Rr/(vh/v3-1); 439 T1 = T1+(1/(A*log(R1)+B)-T0); 440 T2 = T2+(1/(A*log(R2)+B)-T0); 441 T3 = T3+(1/(A*log(R3)+B)-T0); 442 } 443 T1 = T1/N; 444 T2 = T2/N; 445 T3 = T3/N; 446 T4 = readInternalTempFan2Click(1); 447 AD5593R.write_DAC(0,0); 448 449 if ( freshDataAvailable ){ 450 TC = .1*((inputString.charAt(1)-OFFS_CONV)+(inputString.charAt(0)-OFFS_CONV)*BASE_CONV); 451 TM = 1.0*(inputString.charAt(2)-OFFS_CONV); 452 miningMode = (inputString.charAt(3) == 'Y'); 453 } 454 else{ 455 TC = -100.0; // values to indicate lack of data in the function that prints temps on LCD 456 TM = -100.0; 457 miningMode = true; 458 } 459} 460 461void initializeI2CLCD(){ 462 lcd.init(); 463 lcd.clear(); 464 lcd.createChar(0, barChars0); 465 lcd.createChar(1, barChars1); 466 lcd.createChar(2, barChars2); 467 lcd.createChar(3, barChars3); 468 lcd.createChar(4, core); 469 lcd.createChar(5, vram); 470 lcd.createChar(6, loSign); 471 lcd.createChar(7, hiSign); 472 backlightState = true; 473} 474 475void configureADCs(){ 476 AD5593R.enable_internal_Vref(); 477 AD5593R.set_DAC_max_1x_Vref(); 478 AD5593R.set_ADC_max_1x_Vref(); 479 AD5593R.configure_DACs(my_DACs); 480 AD5593R.configure_ADCs(my_ADCs); 481} 482 483void displayUpdate(){ 484 lcd.setCursor(0, 0); 485 lcd.print(Thi); 486 displayTemp(1, 0, TEMP_WATER_H, 1); 487 488 lcd.setCursor(0, 1); 489 lcd.print(Tlo); 490 displayTemp(1, 1, TEMP_WATER_L, 1); 491 492 displayTemp(1, 2, TEMP_WATER_H - TEMP_WATER_L, 1); 493 494 lcd.setCursor(0, 3); 495 lcd.print(char(127)); 496 displayTemp(1, 3, TEMP_AIR_H, 1); 497 498 lcd.setCursor(6, 0); 499 lcd.print(coreChar); 500 displayTemp(7, 0, TC, 1); 501 lcd.setCursor(6, 1); 502 lcd.print(vramChar); 503 displayTemp(7, 1, TM, 1); 504 505 lcd.setCursor(12, 0); 506 lcd.print("[Mode:"); 507 lcd.print(modeChar[miningMode]); 508 lcd.print("]"); 509 510 displayPWMChart(WATER_PUMP_CONTROL, 6, 2); 511 displayPWMChart(FANS_CONTROL, 6, 3); 512 513 lcd.setCursor(17, 1); 514 lcd.print("RPM"); 515 displayTach(WATER_PUMP_CONTROL, 16, 2); 516 displayTach(FANS_CONTROL, 16, 3); 517} 518 519void trySerialRead(){ 520 if ( Serial.available() ){ 521 serialReadingBegins = millis(); 522 while(Serial.read()!=' ' && (millis()-serialReadingBegins)<READ_TIMEOUT){ 523 //Do nothing 524 } 525 inputStringBuffer = Serial.readStringUntil('\n'); 526 if(inputStringBuffer.indexOf(char(0x09))!=-1){ //if string contains char 0x09 (tabulator) 527 backlightState = !backlightState; 528 } 529 if(inputStringBuffer.length()==4){ 530 inputString = inputStringBuffer; 531 freshDataAvailable = true; 532 } 533 } 534} 535
Python program to send GPU data to Arduino over Serial
python
This program sends the GPU data (core and vram temps) and the info if the excavator is running. Important note: check line 25 and change the COM number to the one on which your Arduino is connected! Also, if you want the program to run in the background unseen, save it with a ".pyw" extension instead of ".py".
1from pynvraw import api, NvError, get_phys_gpu 2import serial, time, 3 psutil 4 5def checkIfProcessRunning(processName): 6 ''' 7 Check if 8 there is any running process that contains the given name processName. 9 ''' 10 11 #Iterate over the all the running process 12 for proc in psutil.process_iter(): 13 14 try: 15 # Check if process name contains the given name string. 16 17 if processName.lower() in proc.name().lower(): 18 return 19 True 20 except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): 21 22 pass 23 return False; 24 25def main(): 26 cuda_dev = 0 27 28 ser = serial.Serial() 29 ser.baudrate = 115200 30 ser.timeout = 0.1 31 32 ser.writeTimeout = 0.1 33 ser.setDTR(False) #THIS IS IMPORTANT - prevents 34 Arduino from resetting 35 ser.port = 'COM8' #Put the COM number to which your 36 Arduino is connected 37 dataBeginsInt = 32 #this is a spacebar, data follows 38 the Space char. 39 dataBeginsChar = dataBeginsInt; 40 dataCharOffset = 33 41 42 outputString = " {}{}{}{}\ 43" 44 base = 94; #base for encoding temperature 45 numbers 46 while True: 47 try: 48 gpu = get_phys_gpu(cuda_dev) 49 50 except ValueError: 51 break 52 try: 53 api.restore_coolers(gpu.handle) 54 55 except NvError as err: 56 if err.status != 'NVAPI_NOT_SUPPORTED': 57 58 raise 59 60 cuda_dev += 1 61 62 while 63 True: 64 try: 65 if checkIfProcessRunning('excavator'): 66 67 ExcavRunning = 'Y' 68 else: 69 ExcavRunning 70 = 'N' 71 gpuCoreTemp10xInt = int(gpu.core_temp*10); 72 coreTempCharEncodedByte2 73 = int(gpuCoreTemp10xInt%base+dataCharOffset) #least significant byte of the core 74 temp encoded 75 coreTempCharEncodedByte1 = int((gpuCoreTemp10xInt-coreTempCharEncodedByte2+dataCharOffset)/base+dataCharOffset) 76 #most significant byte of the core temp encoded 77 vramTempCharEncodedByte1 78 = int(gpu.vram_temp)+dataCharOffset 79 ser.open() 80 ser.write(outputString.format(chr(coreTempCharEncodedByte1),chr(coreTempCharEncodedByte2),chr(vramTempCharEncodedByte1),ExcavRunning).encode()) 81 82 ser.close() 83 time.sleep(0.5) 84 except: 85 time.sleep(0.10 86 ) 87 print('Port Busy\ 88') 89 90 91if __name__ == '__main__': 92 93 main()
Downloadable files
Wiring diagram
Wiring diagram
Wiring diagram 2
Schematic I made and used to wire everything together on the actual 3D-printed board
Wiring diagram 2
Wiring diagram 2
Schematic I made and used to wire everything together on the actual 3D-printed board
Wiring diagram 2
Wiring diagram
Wiring diagram
Documentation
Enclosure Part 1
a simple brick to fit Arduino and the click boards
Enclosure Part 1
Case for I2C LCD - backside
Case for I2C LCD - backside
Case for I2C LCD
It worked for my model; I don't guarantee it will work with yours
Case for I2C LCD
Enclosure part 2
Enclosure part 2
Enclosure part 2
Enclosure part 2
Enclosure Part 1
a simple brick to fit Arduino and the click boards
Enclosure Part 1
Case for I2C LCD
It worked for my model; I don't guarantee it will work with yours
Case for I2C LCD
Case for I2C LCD - backside
Case for I2C LCD - backside
Comments
Only logged in users can leave comments