Here's my attempt at regulation. I don't have time to test it on a light but I have tested the code on a PC and it appears to produce a decent ramp. It's 80 bytes on AVR as a function, probably a few less once inlined. Hopefully the comments are enough explanation but feel free to ask questions. Just don't expect too many answers before tomorrow.
The values I put in there for V_ZERO_AMP and V_FULL_PWM are placeholders, you'll need to figure out your own for a given driver and LED.
/* This requires a linear voltage reading. So if the voltage divider is after a diode, the diode drop must be added to the voltage readout (and voltage constants below).
V_ZERO_AMP is the voltage where the LED would produce 0A if the regulation curve was extended all the way to 0A. In practice, there will still be light there because the curve becomes very nonlinear at low currents. To get this value, project the part of the Vf/A curve where you want regulation to work all the way to 0A, in a straight line. Using one of djozz's awesome graphs makes this easy :) V_FULL_PWM is the voltage below which there is no more regulation (ie. PWM is left untouched). The final PWM value is in_PWM * (V_FULL_PWM - V_ZERO_AMP) / (Vcc - V_ZERO_AMP) when Vcc > V_FULL_PWM, otherwise it is unchanged (no regulation).
*/
#define V_ZERO_AMP 135
#define V_FULL_PWM 200#include <stdint.h>
/*
This does a * b / c, with the intermediate result being 16-bit to avoid
overflow. The result is still 8-bit so it could overflow if b > c.
*/
static
uint8_t u8_mul_u8_div_u8( uint8_t a, uint8_t b, uint8_t c )
{
uint16_t tmp = 0;
uint16_t wa = a;
uint8_t i = 8;
while( 1 == 1 )
{
if( (b & 1u) != 0 )
tmp += wa;
if( --i == 0 )
break;
b >>= 1;
wa <<= 1;
}
uint16_t wd = (((uint16_t)c) << 8) >> 1;
uint8_t result = 0;
i = 8;
while( 1 == 1 )
{
if( tmp >= wd )
{
tmp -= wd;
result |= 1;
}
if( --i == 0 )
break;
wd >>= 1;
result <<= 1;
}
return result;
}
/* Takes vcc reading and pwm value and returns "regulated" pwm. */
static
uint8_t regulate_fet( uint8_t vcc, uint8_t pwm )
{
if( vcc > V_FULL_PWM )
{
pwm = u8_mul_u8_div_u8(
pwm, (V_FULL_PWM - V_ZERO_AMP), (uint8_t)(vcc - V_ZERO_AMP) );
}
return pwm;
}