/* */ //Analog input and pin assignments const byte v0Pin = A0; //Filter voltage const byte v1Pin = A1; //DUT voltage const byte v2Pin = A2; // we will use analog pin 2 to read the voltage representing the DUT current const byte v3Pin = A3; // option to attach a voltage reference for calibration const byte vOutPin = 9; // use pin 9 for pwm out //generating PWM signal const int divider = 1; //PWM: for pin 9 divider=1 sets pwm freq at 31,250Hz; =8 sets it to 3,906Hz int nMax = 255; // value is the duty cycle of the pwm ie 255 = always 1, 0 = always off. 2v5=128 int nMin = 0; int stepSize = 5; int nOut; //number to be output via PWM float mVoltsOut; float vCal; // mV out = nOut * Vcc / 255 //reading an ADC and converting to voltage or current byte nTimes = 16; //number of ADC readings to be averaged byte rDelay = 7; //msec between each reading of ADC; 16 * 7msec = 112msec to take each reading float readCal; //mV in = Vcc * 1000/1024 float Rs = 10.0; //Shunt resistor in ohms float aGain = 10.0;//gain of diff amp float iCal; // i mA = (VILoad / Rs) /aGain float iLoad; // calculated current in load float iMax = 20; //max current mA //reading "secret voltmeter" float cal = 1143775; //Vref * 1024 * 1000 - calibration constant, different for each processor void setPwmFrequency(int pin, int divisor); float readADC(int which); //averages a set of readings and prints results as CSV to serial; "which" selects which pin to read long readVcc(); //measure Vcc with "secret voltmeter" void setup() { Serial.begin(57600); analogReference(DEFAULT); setPwmFrequency(vOutPin, divider); //Serial.println("Solar panel tester: generating vOut, measuring vIn"); int rVcc = readVcc(); Serial.print("Vcc measured against internal Vref is "); Serial.print(rVcc); Serial.println(" mV \n"); readCal = rVcc / 1024.0; //calibrate ADC vCal = rVcc / 255.0; //calibrate vout PWM iCal=Rs*aGain; } void loop() { delay(10000); //time to clear the monitor display Serial.println("nOut, mVoltsOut, vFilter, vDUT, vIDUT, iDUT"); //for (nOut = nMin; nOut <= nMax; nOut += stepSize) { //for (nOut = nMax;;){ //used for calibration for (nOut = 255;;){ //used for offset null analogWrite(vOutPin, nOut); Serial.print(nOut); Serial.print(", "); mVoltsOut = nOut * vCal; Serial.print(mVoltsOut , 0); Serial.print(", "); delay(rDelay); readADC(0); //read and print voltage from RC filter vFilter readADC(1); //read and print voltage from Device Under Test vDUT iLoad = readADC(2)/iCal; //iDUT Serial.println(iLoad , 2); //2 dp //Serial.println(); if (iLoad > iMax) break; } } float readADC(int which) { float volts = 0; for (int i = 0; i < nTimes; i++) { volts += analogRead(which); delay(rDelay); } volts = volts * readCal / (float) nTimes; //find average Serial.print(volts , 0); //actually millivolts Serial.print(", "); return (volts); } long readVcc() { long result; // Read 1.1V reference against AVcc // set the reference to Vcc and the measurement to the internal 1.1V reference //choose the right register settings for the processor in use #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) ADMUX = _BV(MUX5) | _BV(MUX0); #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) ADMUX = _BV(MUX3) | _BV(MUX2); #else ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); #endif int agg = 0; // reset aggregate ready for next reading for (int count = 0; count < nTimes; count++) { delay(2); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Start conversion while (bit_is_set(ADCSRA, ADSC)); // measuring // read it a second time ADCSRA |= _BV(ADSC); // Start conversion while (bit_is_set(ADCSRA, ADSC)); // measuring uint8_t low = ADCL; // must read ADCL first - it then locks ADCH uint8_t high = ADCH; // unlocks both result = (high << 8) | low; agg = agg + result; //collect and add up nsamples readings delay(3); } // we dont find the average here, it gives better resolution to multiply by the calibration constant first result = cal * nTimes / agg; // 1115702 = 1.0896 * 1024 * 1000 - calibration constant, different for each processor return result; // Vcc in millivolts } /** Divides a given PWM pin frequency by a divisor. The resulting frequency is equal to the base frequency divided by the given divisor: - Base frequencies: o The base frequency for pins 3, 9, 10, and 11 is 31250 Hz. o The base frequency for pins 5 and 6 is 62500 Hz. - Divisors: o The divisors available on pins 5, 6, 9 and 10 are: 1, 8, 64, 256, and 1024. o The divisors available on pins 3 and 11 are: 1, 8, 32, 64, 128, 256, and 1024. PWM frequencies are tied together in pairs of pins. If one in a pair is changed, the other is also changed to match: - Pins 5 and 6 are paired on timer0 - Pins 9 and 10 are paired on timer1 - Pins 3 and 11 are paired on timer2 Note that this function will have side effects on anything else that uses timers: - Changes on pins 3, 5, 6, or 11 may cause the delay() and millis() functions to stop working. Other timing-related functions may also be affected. - Changes on pins 9 or 10 will cause the Servo library to function incorrectly. Thanks to macegr of the Arduino forums for his documentation of the PWM frequency divisors. His post can be viewed at: https://forum.arduino.cc/index.php?topic=16612#msg121031 */ void setPwmFrequency(int pin, int divisor) { byte mode; if (pin == 5 || pin == 6 || pin == 9 || pin == 10) { switch (divisor) { case 1: mode = 0x01; break; case 8: mode = 0x02; break; case 64: mode = 0x03; break; case 256: mode = 0x04; break; case 1024: mode = 0x05; break; default: return; } if (pin == 5 || pin == 6) { TCCR0B = TCCR0B & 0b11111000 | mode; } else { TCCR1B = TCCR1B & 0b11111000 | mode; } } else if (pin == 3 || pin == 11) { switch (divisor) { case 1: mode = 0x01; break; case 8: mode = 0x02; break; case 32: mode = 0x03; break; case 64: mode = 0x04; break; case 128: mode = 0x05; break; case 256: mode = 0x06; break; case 1024: mode = 0x07; break; default: return; } TCCR2B = TCCR2B & 0b11111000 | mode; } }