Advertisements

Archive for August 20th, 2013

Hall Effect LED Current Control: Measurements and Firmware

With that hardware in hand, a dab of firmware produces this result:

Hall Current Sense - 120 mA 25 ms 250 ms

Hall Current Sense – 120 mA 25 ms 250 ms

A detailed look at one pulse:

Hall Current Sense - 120 mA 25 ms 250 ms - detail

Hall Current Sense – 120 mA 25 ms 250 ms – detail

The top trace is the total LED current, nominally 120 mA, at 50 mA/div. The ripple (which is a nice triangle waveform at a faster sweep) comes from the 32 kHz PWM pulse train, despite passing through a 1 ms RC filter; the MOSFET runs in the linear region and makes a great amplifier.

The middle trace is the MOSFET drain voltage at 1 V/div. The on-state voltage runs around 1.6 V, so the LEDs see about 5.9 V at 120 mA, about what you’d expect, with the little bit of PWM ripple accounting for the current sawtooth in the top trace. The off-state voltage is only 3.8 V, because the LEDs soak up the rest; it’s about 1.2 V per LED.

The bottom trace is the current-sense amp output. The 1 nF cap in the op amp feedback loop rolls it off at 600 Hz, so there’s not much ripple at all in there. That goes directly to the Arduino’s ADC, where it’s further averaged over 10 samples.The LEDs take a couple of milliseconds to get up to full intensity, but it’s much faster than an incandescent filament: this thing blinks rather than flashes.

The current in each LED string runs from about 15 mA to 25 mA, with all the “old” LEDs at the low end and the “new” LED strings at the high end. Using unsorted LEDs from the same batch will probably be OK, although I’ll measure them just to see what they’re like.

The LEDs dissipate 700 mW and the MOSFET wastes 192 mW, so the efficiency is around 79%. Not too shabby for a linear regulator and it only gets better as the battery discharges. The toroid winding burns maybe 300 μW, so it’s not in the running; to be fair, a 1 Ω sense resistor would account for only 14 mW, but it would drop 120 mV instead of 3 mV, which is what matters more when the battery voltage drops.

That’s during the pulse, which should have a duty cycle under 25% or so, which means 175 mW and 48 mW on the average. Obviously, no heatsinks needed: each LED runs at 7 mW average under those conditions.

The firmware steps the gate voltage by the smallest possible increment, about 20 mV = 5 V / 256. The feedback loop adjusts the gate voltage in single steps to avoid goosing the LEDs with too much current; a binary search wouldn’t work very well at all. I think it’d be a good idea to build a table of transconductance (gate voltage to LED current) by ramping the gate voltage during startup, then fine-tune the coefficients during each pulse.

The console log tells the tale:

Hall effect Current Regulator
Ed Nisley - KE4ZNU - August 2013
Given Vcc: 5010 mV
Given VBatt divider ratio: 0.500
Bandgap reference voltage: 1105 mV
Battery voltage: 7551 mV
Nulling Hall sensor offset: 0 PWM
Final Hall sensor offset: 209 PWM
Gate voltage: 1947 mV  LED Current: 5 mA
Gate voltage: 1966 mV  LED Current: 6 mA
Gate voltage: 1986 mV  LED Current: 6 mA
Gate voltage: 2005 mV  LED Current: 7 mA
Gate voltage: 2025 mV  LED Current: 8 mA
Gate voltage: 2040 mV  LED Current: 8 mA
Gate voltage: 2064 mV  LED Current: 10 mA
Gate voltage: 2084 mV  LED Current: 11 mA
Gate voltage: 2103 mV  LED Current: 13 mA
Gate voltage: 2123 mV  LED Current: 14 mA
Gate voltage: 2142 mV  LED Current: 17 mA
Gate voltage: 2162 mV  LED Current: 19 mA
Gate voltage: 2177 mV  LED Current: 22 mA
Gate voltage: 2201 mV  LED Current: 25 mA
Gate voltage: 2221 mV  LED Current: 29 mA
Gate voltage: 2240 mV  LED Current: 33 mA
Gate voltage: 2255 mV  LED Current: 38 mA
Gate voltage: 2275 mV  LED Current: 44 mA
Gate voltage: 2294 mV  LED Current: 49 mA
Gate voltage: 2314 mV  LED Current: 56 mA
Gate voltage: 2333 mV  LED Current: 63 mA
Gate voltage: 2353 mV  LED Current: 70 mA
Gate voltage: 2372 mV  LED Current: 79 mA
Gate voltage: 2392 mV  LED Current: 89 mA
Gate voltage: 2412 mV  LED Current: 99 mA
Gate voltage: 2431 mV  LED Current: 110 mA
Gate voltage: 2451 mV  LED Current: 122 mA
Gate voltage: 2431 mV  LED Current: 110 mA
Gate voltage: 2451 mV  LED Current: 121 mA
Gate voltage: 2431 mV  LED Current: 110 mA
Gate voltage: 2451 mV  LED Current: 122 mA
Gate voltage: 2431 mV  LED Current: 110 mA
Gate voltage: 2451 mV  LED Current: 121 mA

The current feedback tweaks the gate voltage by one PWM increment on each loop, so the LED current pulses alternate between 110 and 122 mA when the loop finally reaches the setpoint. This doesn’t make any practical difference, as each LED string’s current varies by a few mA, at most, but maybe there should be a deadband of a bit more than ±1/2 PWM increment around the actual current.

The Arduino source code:

// LED Curve Tracer
// Ed Nisley - KE4ANU - August 2013

#include <stdio.h>

//----------
// Pin assignments

const byte PIN_READ_VBATT = 0;		// AI - battery voltage from divider
const byte PIN_READ_CURRENT = 1;	// AI - current sense amp
const byte PIN_READ_VGATE = 2;		// AI - actual gate voltage
const byte PIN_READ_HALL = 3;		// AI - raw Hall sensor voltage

const byte PIN_SET_BIAS = 11;		// PWM - VCC/2 bias voltage
const byte PIN_SET_VGATE = 3;		// PWM - MOSFET gate voltage

const byte PIN_HEARTBEAT = 13;		// DO - Arduino LED
const byte PIN_SYNC = 2;			// DO - scope sync output

//----------
// Constants

const float MaxLEDCurrent = 0.120;		// maximum LED current

const float Vcc = 5.01; 				// Arduino supply -- must be measured!

const float VBattRatio = 3.03/6.05;		// measured division ratio for battery divider

const float VStep = Vcc/256;			// minimum PWM voltage increment = 5 V / 256

const float IGain = 0.100;				// Hall sense voltage to LED current

const byte PWM_Settle = 10;				// PWM settling time ms

// Timer prescaler = 1:1 for 32 kHz PWM
#define TCCRxB 0x01

#define MK_UL(fl,sc) ((unsigned long)((fl)*(sc)))
#define MK_U(fl,sc) ((unsigned int)((fl)*(sc)))
#define MK_I(fl,sc) ((int)((fl)*(sc)))

//----------
// Globals

float AVRef1V1;					// 1.1 V bandgap reference - calculated from Vcc

float VBatt;					// battery voltage - calculated from divider
float VGateSense;				// actual gate voltage - measured after PWM filter
float ILEDSense;				// LED current from Hall effect sensor

float VGateDrive;				// gate drive voltage

byte PWMHallOffset;				// zero-field Hall effect sensor bias

long unsigned long MillisNow, MillisThen;	// sampled millis() value

//-- Read AI channel
//      averages several readings to improve noise performance
//		returns value in volts assuming known VCC ref voltage

#define NUM_T_SAMPLES    10

float ReadAI(byte PinNum) {

  word RawAverage;

  digitalWrite(PIN_SYNC,HIGH);					// scope sync

  RawAverage = (word)analogRead(PinNum);		// prime the averaging pump

  for (int i=2; i <= NUM_T_SAMPLES; i++) {
    RawAverage += (word)analogRead(PinNum);
  }

  digitalWrite(PIN_SYNC,LOW);

  RawAverage /= NUM_T_SAMPLES;

  return Vcc * (float)RawAverage / 1024.0;

}

//-- Set PWM output

void SetPWMVoltage(byte PinNum,float PWMVolt) {

byte PWM;

  PWM = constrain((byte)(255.0 * PWMVolt / Vcc),0,255);

  analogWrite(PinNum,PWM);
  delay(PWM_Settle);

}

//-- compute actual 1.1 V bandgap reference based on known VCC = AVcc (more or less)
//		adapted from http://code.google.com/p/tinkerit/wiki/SecretVoltmeter

float ReadBandGap(void) {

  word ADCBits;
  float VBandGap;

  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);	// select 1.1 V input
  delay(2); // Wait for Vref to settle

  ADCSRA |= _BV(ADSC);										// Convert
  while (bit_is_set(ADCSRA,ADSC));

  ADCBits = ADCL;
  ADCBits |= ADCH<<8;

  VBandGap = Vcc * (float)ADCBits / 1024.0;
  return VBandGap;
}

//-- Helper routine for printf()

int s_putc(char c, FILE *t) {
  Serial.write(c);
}

//------------------
// Set things up

void setup() {

float AVRef1V1;

  pinMode(PIN_HEARTBEAT,OUTPUT);
  digitalWrite(PIN_HEARTBEAT,LOW);	// show we arrived

  pinMode(PIN_SYNC,OUTPUT);
  digitalWrite(PIN_SYNC,LOW);		// show we arrived

  TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
  TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11

  analogWrite(PIN_SET_VGATE,0);		// force gate voltage = 0

  Serial.begin(57600);
  fdevopen(&s_putc,0);				// set up serial output for printf()

  printf("Hall effect Current Regulator\r\nEd Nisley - KE4ZNU - August 2013\r\n");

  printf("Given Vcc: %d mV\r\n",MK_I(Vcc,1000.0));
  printf("Given VBatt divider ratio: 0.%d\r\n",MK_I(VBattRatio,1000.0));

  AVRef1V1 = ReadBandGap();			// compute actual bandgap reference voltage
  printf("Bandgap reference voltage: %d mV\r\n",MK_I(AVRef1V1,1000.0));

  VBatt = ReadAI(PIN_READ_VBATT) / VBattRatio;
  printf("Battery voltage: %d mV\r\n",MK_I(VBatt,1000.0));

  SetPWMVoltage(PIN_SET_VGATE,0.0);		// zero LED current
  PWMHallOffset = 0;
  analogWrite(PIN_SET_BIAS,PWMHallOffset);
  printf("Nulling Hall sensor offset: %d PWM\r\n",PWMHallOffset);

  do {
	ILEDSense = IGain * ReadAI(PIN_READ_CURRENT);
//	printf("Current Sense: %d mA - ",MK_I(ILEDSense,1000.0));
	if (ILEDSense > 0.005) {
		PWMHallOffset += 1;
		analogWrite(PIN_SET_BIAS,PWMHallOffset);
        delay(PWM_Settle);
//		printf("Step offset: %d PWM\r\n",PWMHallOffset);
	}
  } while (ILEDSense > 0.005);
  printf("Final Hall sensor offset: %d PWM\r\n",PWMHallOffset);

  VGateDrive = 2.0;					// reasonable starting point

  MillisThen = millis();
}

//------------------
// Run the test loop

void loop() {

	if ((millis() - MillisThen) > 250) {
		MillisThen = millis();

		if (ILEDSense < MaxLEDCurrent) {
			VGateDrive += VStep;
		}
		else if (ILEDSense > MaxLEDCurrent) {
			VGateDrive -= VStep;
		}

		SetPWMVoltage(PIN_SET_VGATE,VGateDrive);

		VGateSense = ReadAI(PIN_READ_VGATE);
		printf("Gate voltage: %d mV  ",MK_I(VGateSense,1000.0));

		ILEDSense = IGain * ReadAI(PIN_READ_CURRENT);
		printf("LED Current: %d mA\r\n",MK_I(ILEDSense,1000.0));

		delay(50 - PWM_Settle - 3);
		SetPWMVoltage(PIN_SET_VGATE,0.0);

		digitalWrite(PIN_HEARTBEAT,!digitalRead(PIN_HEARTBEAT));
		digitalWrite(PIN_HEARTBEAT,!digitalRead(PIN_HEARTBEAT));
	}
}
Advertisements

1 Comment