/* * Arduino BiCMOS Curve Tracer * Based on: http://www.idea2ic.com/BiCmosCurveTracer/Arduino%2520BiCmos%2520Curve%2520Tracer.html * ... but uses two PCF8591 DAC and ADC's */ #include #include #include // Peripheral addresses #define GREEN_LED 13 // Arduino onboard LED on pin 13 #define BEEPER 9 // Piezo Buzzer on pin 9 #define I2C_LCD 0x20 // LCD 16x2 driven by HD44780, connected to I2C module PCF8574T (7bit I2C address) #define I2C_PCF8574_PORT1 0x21 // I2C expander PCF8574P for keyboard and 433 MHz radio receiver (7bit I2C address) #define I2C_PCF8574_PORT2 0x38 // I2C expander PCF8574AP on expand shield, driving 8 FLUX LEDs (7bit I2C address) #define I2C_PCF8583_RTC1 0xA2 // PCF8583 RTC via I2C interface, A0 pin connected to Vdd, which will change the I2C address to 0xA2 (8bit I2C address) #define I2C_PCF8583_RTC2 0xA0 // PCF8583 RTC via I2C interface, A0 pin connected to GND #define I2C_PCF8591_DAC1 (0x90 >> 1) // PCF8591 8-bit A/D and D/A (7bit I2C address) #define I2C_PCF8591_DAC2 (0x92 >> 1) // PCF8591 8-bit A/D and D/A (7bit I2C address) /* * LCD connected via PCF8574T */ #define BACKLIGHT_PIN 3 #define En_pin 2 #define Rw_pin 1 #define Rs_pin 0 #define D4_pin 4 #define D5_pin 5 #define D6_pin 6 #define D7_pin 7 LiquidCrystal_I2C lcd(I2C_LCD, En_pin, Rw_pin, Rs_pin, D4_pin, D5_pin, D6_pin, D7_pin, BACKLIGHT_PIN, POSITIVE); /* * Two PCF8583 real time Clock/calendar's with 240 x 8-bit RAM */ PCF8583 rtc1(I2C_PCF8583_RTC1); // Used to Keeping actual datetime, and fire 'daily alarm' to interrupt0 PCF8583 rtc2(I2C_PCF8583_RTC2); // Used to fire 'timer alarm' to interrupt1 /* * Some global variables */ volatile boolean ledStatus = false; byte port2data = 0; // content of I2C expander PCF8574AP on expand shield, driving 8 FLUX LEDs // Menu support #define MENU_ITEMS 24 int currentMenuItem = 1; // Backlight auto-off support #define BACKLIGHT_TIMEOUT 25 volatile int backlightCounter = 0; volatile boolean isBacklightOn; // Flags signalized interrupts volatile bool wasInterrupt0Triggered = false; volatile bool wasInterrupt1Triggered = false; // Curve tracer support int sawtooth = 0; // Value of sawtooth wave for Uce int voltageStep = 0; // Step value for Ub const int slope = 3; int incomingByte; // read incoming serial data into /* * Setup ARDUINO and it's peripherals */ void setup() { /* * Initialize the LCD */ lcd.begin(16, 2); // 16 chars, 2 lines /* * Initialize ports */ pinMode(GREEN_LED, OUTPUT); // Initialize the onboard LED digital pin as an output. digitalWrite(GREEN_LED, HIGH); pinMode(BEEPER, OUTPUT); // Declare pin 9 to be an output analogWrite(BEEPER, 0); // 0 turns beeper off Serial.begin(9600); // initialize serial communication: /* * Backlight */ isBacklightOn = true; /* * Hello message */ sayHello(); writePort2(0); writeDAC1(0); writeDAC2(0); // Curve tracer not uses interrupts, its here only for compatibility with Home Brain attachInterrupt(0, interrupt0, FALLING); attachInterrupt(1, interrupt1, FALLING); } /* * Interrupt handler 0, from PCF8583 RTC #1 */ void interrupt0() { wasInterrupt0Triggered = true; } /* * Interrupt handler 1, from PCF8583 RTC #2 */ void interrupt1() { wasInterrupt1Triggered = true; } /* * Hello message and 3 beeps */ void sayHello() { lcd.clear(); lcd.home (); lcd.print("Arduino Uno"); lcd.setCursor(0, 1); lcd.print("Curve Tracer 0.2"); delay (500); beep(50); delay (500); beep(50); delay (500); beep(50); digitalWrite(GREEN_LED, LOW); lcd.clear(); lcd.home (); } /* * Main loop */ void loop() { /* * Scan keyboard, all 6 buttons */ int keyNumber = getKey(); if(keyNumber > 0) // key pressed ? { if(!isBacklightOn) { isBacklightOn = true; lcd.backlight(); } backlightCounter = 0; // reset counter switch(keyNumber) { case 1: // Set initial state sayHello(); break; case 3: lcd.scrollDisplayLeft(); break; case 4: // Menu printMenu(); keyNumber = getKey(); while(keyNumber != 1) // Key 1: Escape menu { switch(keyNumber) { case 2: if(currentMenuItem > 1) // Key 2: go previous menu item currentMenuItem--; else currentMenuItem = MENU_ITEMS; printMenu(); break; case 3: currentMenuItem -= 5; // Key 3: go back 5 menu items if(currentMenuItem < 1) currentMenuItem = 1; printMenu(); break; case 4: executeMenu(); // Key 4: Execute current menu item break; case 5: if(currentMenuItem < MENU_ITEMS) // Key 2: go next menu item currentMenuItem++; else currentMenuItem = 1; printMenu(); break; case 6: currentMenuItem += 5; // Key 3: go forward 5 menu items if(currentMenuItem > MENU_ITEMS) currentMenuItem = MENU_ITEMS; printMenu(); break; } delay (30); keyNumber = getKey(); } lcd.clear(); break; case 6: lcd.scrollDisplayRight(); break; default: { String num = String(keyNumber); lcd.setCursor(0, 1); lcd.print("Key " + num + " pressed "); longDelay(500); } break; } } else { digitalWrite(GREEN_LED, ledStatus); ledStatus = !ledStatus; printDateTimeShort(); // turn LCD backlight off, if alarm not occured after timeout if((isBacklightOn) && (backlightCounter++ >= BACKLIGHT_TIMEOUT)) { isBacklightOn = false; lcd.noBacklight(); backlightCounter = 0; } } /* * Handle daily interrupt, turn FLUX LED'd off if nothing to do */ if(!handleInterrupt0()) { writePort2(0x00, false); } } // loop() /* * Append spaces to rest of line to fit LCD 24 chars */ void padRight(String &line) { while(line.length() < 24) { line = line + " "; } } /* * Scan keyboard and return key number 1 - 6, if pressed * Otherwise return 0 */ int getKey() { for(byte keyCode = 0; keyCode <= 5; keyCode++) // 6 buttons are connected to inputs of 74HC151 multiplexer - scan them sequentially { byte portData = keyCode | B11111000; // I2C Port-1 bits 0-3 (B11111xxx) adresses 74HC151 multiplexer Wire.beginTransmission(I2C_PCF8574_PORT1); Wire.write(portData); Wire.endTransmission(); delay (20); Wire.requestFrom(I2C_PCF8574_PORT1, 1); if (Wire.available()) { byte c = Wire.read(); if((c & B00001000) != 0) // Test bit 4 IN, connected to output of 74HC151 multiplexer { int keyNumber = keyCode + 1; // Pressed ! beep(50); while(true) // Wait for key release { Wire.requestFrom(I2C_PCF8574_PORT1, 1); if (Wire.available()) { c = Wire.read(); // Test bit 4 IN, connected to output of 74HC151 multiplexer if((c & B00001000) == 0) { return keyNumber; // Released, return key number } // Released ? } // if (Wire.available()) } // while(true) } // Pressed ? } // if (Wire.available()) } // for 0 .. 5 return 0; // Nothing pressed } /* * Print current menu item to display */ void printMenu() { lcd.setCursor(0, 0); // Line 1 is reserved for menu item String menu = String(currentMenuItem); switch(currentMenuItem) { case 1: menu += " Start tracer"; break; case 2: menu += " DAC #1 tri++"; break; case 3: menu += " DAC #1 tri--"; break; case 4: menu += " DAC #1 tri+=10"; break; case 5: menu += " DAC #1 tri-=10"; break; case 6: menu += " DAC #2 vs++"; break; case 7: menu += " DAC #2 vs--"; break; case 8: menu += " DAC #2 vs+=10"; break; case 9: menu += " DAC #2 vs-=10"; break; } padRight(menu); lcd.print(menu); } /* * Execute current menu item */ void executeMenu() { switch(currentMenuItem) { case 1: traceCurves(); break; case 2: sawtooth++; writeDAC1(sawtooth); break; case 3: sawtooth--; writeDAC1(sawtooth); printDACs(); break; case 4: sawtooth += 10; writeDAC1(sawtooth); printDACs(); break; case 5: sawtooth -= 10; writeDAC1(sawtooth); printDACs(); break; case 6: voltageStep++; writeDAC2(voltageStep); printDACs(); break; case 7: voltageStep--; writeDAC2(voltageStep); printDACs(); break; case 8: voltageStep += 10; writeDAC2(voltageStep); printDACs(); break; case 9: voltageStep -= 10; writeDAC2(voltageStep); printDACs(); break; } } /* * Voltage step values for DAC #2 -> Ub, and corresponding string representations * Each voltage step will trace one curve on transistor characteristics chart */ const byte voltageSteps[9] = { 27, 40, 53, 66, 79, 91, 105, 130, 157 }; // Get those values by DAC2 calibration const char voltageStepsStr[9][7] = { {"0.5 V"}, {"0.75 V"}, {"1.0 V"}, {"1.25 V"}, {"1.5 V"}, {"1.75 V"}, {"2.0 V"}, {"2.5 V"}, {"3.0 V"} }; /* * Trace curves for chart */ void traceCurves() { lcd.clear(); lcd.home (); lcd.print("Waiting ... "); // Wait for signal from PC do { if(Serial.available() > 0) // see if incoming serial data { incomingByte = Serial.read(); // read oldest byte in serial buffer: } if(getKey() > 0) return; // break waiting, if key pressed } while(incomingByte != 'H'); // H (ASCII 72), printoutput // Start tracing lcd.home (); lcd.print("Starting "); port2data = B00000001; // used for progress animation for(int i = 0; i < 6; i++) // for each voltage step value - trace curve { voltageStep = voltageSteps[i]; writeDAC2(voltageStep); // set Ub voltage for this curve sawtooth = 0; // reset Sawtooth wave writePort2(false); Serial.println("@"); // Control character '@' means 'Start curve' do // In this cycle, one curve is traced: sawtooth value for Uce is inscreased by slope value { writeDAC1(sawtooth); // Set Uce delay(5); // printDACs(); // TODO: readADC1(0); readADC1(1); /* * Read and send meassured values to PC */ Serial.print(analogRead(0)); // read collector current at A0 Serial.print(" "); Serial.println(analogRead(1)); // read triangle Uc voltage at A1 //printAnalogIns(); //while(getKey() == 0); sawtooth += slope; } while(sawtooth < 254); port2data = port2data << 1; // animate progress if(port2data == 0) port2data = B00000001; // Inform PC that current curve is finished, also send Ub value String endCurveInfo = String("#"); // Control character '#' means 'End curve' endCurveInfo += voltageStepsStr[i]; Serial.println(endCurveInfo); } Serial.println("$"); // Control character '$' means 'Tracing finished' Serial.flush(); lcd.home (); lcd.print("Finished "); writePort2(0, false); writeDAC1(0); writeDAC2(0); } /////////////////////////////////////////////////////////////////////////// // // PCF8574 I2C Port 2 (FLUX LED's) support // /////////////////////////////////////////////////////////////////////////// /* * Set global variable 'port2data' to external I2C PCF8574 Port 2 */ void writePort2(boolean printIt) { Wire.beginTransmission(I2C_PCF8574_PORT2); Wire.write(port2data); Wire.endTransmission(); if(printIt) printPort2(); } /* * Set global variable 'port2data' to external I2C PCF8574 Port 2 */ void writePort2(byte _port2data, boolean printIt) { port2data = _port2data; Wire.beginTransmission(I2C_PCF8574_PORT2); Wire.write(port2data); Wire.endTransmission(); if(printIt) printPort2(); } /* * Print external I2C PCF8574 Port 2 (global variable 'port2data') */ void printPort2() { String thisString = String(port2data, BIN); while(thisString.length() < 8) { thisString = "0" + thisString; } thisString = "Port 2: " + thisString; padRight(thisString); lcd.setCursor(0, 1); lcd.print (thisString); } /////////////////////////////////////////////////////////////////////////// // // PCF8583 I2C RTC's support // /////////////////////////////////////////////////////////////////////////// const char weekday_names[7][4] = { {"Sun"}, {"Mon"}, {"Tue"}, {"Wed"}, {"Thu"}, {"Fri"}, {"Sat"} }; /* * Print date and time: DD.MM. HH.MM.SS */ void printDateTimeShort() { rtc1.getTime(); lcd.setCursor(13, 0); // Print weekday name to 1st line, right side lcd.print(weekday_names[rtc1.weekday]); char buf[25]; sprintf(buf, "%02d.%02d. %02d:%02d:%02d", rtc1.day, rtc1.month, rtc1.hour, rtc1.minute, rtc1.second); String dateTime = String(buf); padRight(dateTime); lcd.setCursor(0, 1); lcd.print(dateTime); } /////////////////////////////////////////////////////////////////////////// // // PCF8591 I2C 8-bit DAC and ADC's support // /////////////////////////////////////////////////////////////////////////// /* * Print analog inputs */ void printAnalogIns() { byte c0 = analogRead(0); byte c1 = analogRead(1); String thisString = "Ic.: " + String(c0) + ", VC: " + String(c1); padRight(thisString); lcd.setCursor(0, 1); lcd.print (thisString); } /* * Set given value to PCF8591 DAC #1 */ void writeDAC1(byte value) { Wire.beginTransmission(I2C_PCF8591_DAC1); Wire.write(0x40); // control byte - turn on DAC (binary 1000000) Wire.write(value); Wire.endTransmission(); } /* * Set given value to PCF8591 DAC #2 */ void writeDAC2(byte value) { Wire.beginTransmission(I2C_PCF8591_DAC2); Wire.write(0x40); // control byte - turn on DAC (binary 1000000) Wire.write(value); Wire.endTransmission(); } /* * Read value from given PCF8591 #1 A/D channel number */ byte readADC1(byte input) { byte controlByte = 0x40 | (input & B00000011); // Mask bits 0-2: A/D channel number Wire.beginTransmission(I2C_PCF8591_DAC1); Wire.write(controlByte); Wire.endTransmission(); Wire.requestFrom(I2C_PCF8591_DAC1, 2); byte value = Wire.read(); // PCF8591 returns the previously measured value first ... value = Wire.read(); // ... then the current byte return value; } /* * Read value from given PCF8591 #2 A/D channel number */ byte readADC2(byte input) { byte controlByte = 0x40 | (input & B00000011); // Mask bits 0-2: A/D channel number Wire.beginTransmission(I2C_PCF8591_DAC2); Wire.write(controlByte); Wire.endTransmission(); Wire.requestFrom(I2C_PCF8591_DAC2, 2); byte value = Wire.read(); // PCF8591 returns the previously measured value first ... value = Wire.read(); // ... then the current byte return value; } /* * Print both DAC values */ void printDACs() { char line[25]; sprintf(line, "Uce: %d, Ub: %d", sawtooth, voltageStep); String thisString = String(line); padRight(thisString); lcd.setCursor(0, 1); lcd.print(thisString); } /////////////////////////////////////////////////////////////////////////// // // Beeper support // /////////////////////////////////////////////////////////////////////////// /* * Single Beep. Because Beeper not have generator, send PWM wave to a pin. */ void beep(unsigned char delayms) { analogWrite(BEEPER, 128); // Almost any value can be used except 0 and 255 delay(delayms); // wait for a delayms ms analogWrite(BEEPER, 0); // 0 turns it off } /* * Multiple Beeps. */ void beeps(int count) { for(int i = 0; i < count; i++) { longDelay (500); digitalWrite(GREEN_LED, HIGH); beep(500); longDelay (500); digitalWrite(GREEN_LED, LOW); beep(500); } } /////////////////////////////////////////////////////////////////////////// // // Interrupt support // /////////////////////////////////////////////////////////////////////////// /* * Divide long delay to small chunks and handle interrupt 1 between them */ void longDelay(long ms) { long remainder = ms; const int maxDelay = 100; // threshold do { if(remainder > maxDelay) { delay(maxDelay); } else { delay(remainder); } handleInterrupt1(); remainder -= maxDelay; } while(remainder > 0); } /* * Handle interrupt 0 * * interrupt0 - fired daily from RTC1 * return true if interupt is handled, otherwise return false */ boolean handleInterrupt0() { if(wasInterrupt0Triggered) { wasInterrupt0Triggered = false; // clear flag writePort2(B11001100, false); beep(150); delay (500); writePort2(B00110011, false); beep(150); delay (500); writePort2(B11001100, false); beep(150); delay (500); writePort2(B00110011, false); beep(150); rtc1.restartClockAlarm(); return true; } return false; } /* * Handle interrupt 1 * * interrupt1 - fired periodicaly from RTC2 - flash FLUX LED's on Port2 with regards of state flags * return true if any interupt is handled, otherwise return false */ boolean handleInterrupt1() { if(wasInterrupt1Triggered) { wasInterrupt1Triggered = false; // clear flag // do nothing... rtc2.restartTimerAlarm(); return true; } return false; }