Apologies for spamming the thread, for the content firehose, and posting such annoyingly long posts, but here's the LM4048-2.048V (or any other 2.048V shunt voltage regulator that works in the 0.2mA - 10mA range)-based circuit, with component-by component explanation.
I am posting this only because I myself wish I'd seen something like this posted a few years ago, when I started trying to drive these small display backlight circuits, in my Teensy display projects. (I like to use Teensy 4, because of its high-speed USB, to add human-readable status information to OpenWRT routers and such. Instead of asking whether a non-technical person sees LEDs flashing, I can just add a small display, that shows the key statistics –– upstream connectivity, current throughput, etc. –– in readable form, maybe with a button that lets them switch between displays, and even turning it off when not needed. If you have elderly family members living in very rural areas with occasional issues in network connections, you know what I'm trying to solve!)
Reminder: this circuit is specifically designed by a hobbyist, for us hobbyists to use; proper EEs and PCB designers can make it even better.
The components are chosen to provide just under 120mA LED current, when the LED forward voltage drop is between 3.0V and 3.4V, and LED_VIN is between 4.7V and 6V. (This will work outside that range, with either lower display current, or generating more heat at the MOSFET.) The differences in forward voltage drop between different LEDs in different devices does not really affect the current passed through those LEDs.
The BL pin yields a nearly linear current control between 0.9V (although the LEDs will start emitting a tiny bit of light at about 0.6V) and 2.5V. Voltages between 2.5V and 6V on the BL pin produce the same brightness, as they are clamped to 2.048V by the shunt voltage regulator U1. The BL pin can be PWM'd at up to tens of kHz. The current draw on the BL pin stays under 5mA even during switching; steady-high current draw is about 0.5mA.
Because the signal is such low power, and the LED current so high, the grounds for the two are drawn separately, to indicate how one should wire it to a display module. Essentially, LED_VIN and LED_GND form a completely separate, high-current circuit, that should have a single point ground connection to the signal ground at the power connector, and have conductors/traces thick/large enough for the 120mA current. (It's not
that much current, but doing it correctly will give you a much more robust and reliable system, avoiding "signal ground lift" (where the inherent resistance in the ground traces and wires causes ground currents to lift the observed potential) that can be annoying to deal with in a circuit, especially if you have any analog signals like ADCs or potentiometers.)
Q1 is the main switching N-channel enhancement mode MOSFET, a Toshiba SSM3K345R,LF. It will dissipate half/most of the excess power not consumed by the LEDs as heat. Basically, at the 120mA current, the difference between LED_VIN and the LED forward voltage at that current, is mostly dissipated by Q1. The typical range is between 50mW and 250mW, but better design for up to 400mW of power being dissipated. With some heat-sinking copper area on a standard PCB, this one should do just fine.
Q2 is an NPN BJT, a Nexperia BC847C,215. It sets the voltage drop over the current-sense resistors R1 and R2. This handles only the bias current, which is small, and doesn't need to dissipate much heat.
The current-sense resistors R1 and R2, in parallel (thus R = R1//R2 = 1/(1/18.4Ω + 1/18.4Ω) = 9.2Ω), set the LED forward current. Smaller resistances yielding higher currents. Because 120mA flowing through the 9.2Ω resistance generates (P = R I²) about 130mW of waste heat, we split the resistance into two resistors in parallel, so each one dissipates half the heat. These should be fairly precise, say 1%, and rated for 250mW or more. As increasing resistance lowers the LED current, if one resistor fails, the LED current will drop (to around 62mA), and the remaining resistor will only dissipate around 75mW or so. A single resistor will also work, but 18.4Ω resistors are better stocked than 9.2Ω, and spreading the heat a bit more is a good thing for longevity anyway.
The 2.048V shunt voltage regulator U1, one of the common LM4040 ones at 2.048V voltage, will cap the voltage seen at the gate of Q1 and collector of Q2 to 2.048V. This is the component that ensures a constant LED current at varying voltages of LED_VIN. As the LED current is linear near that voltage, it does not need to be especially precise (as in be exactly 2.048V), as long as it is stable, and does not vary too much when the voltage on its cathode is above 2.1V or so. The LM4040 series can handle currents under 15mA, dissipating the excess voltage as heat.
R3 and R4 resistors have a dual purpose. On one hand, R4 limits the current draw from the BL pin to a couple of milliamps, and R3 ensures there is at least 0.4mA (400µA) of current flowing whenever the voltage at the gate of Q1 and collector of Q2 is at or above 2.048V, allowing the shunt voltage regulator to work. (The gate of the MOSFET acts like a capacitor, and only draws current when it is switching between conducting and non-conducting. In this circuit, when PWM'ing, it draws a short spike of less than 2mA. Thus, the current draw from the BL pin is expected to stay between 0.5mA and 2.5mA whenever the shunt voltage regulator needs to cap the voltage.)
On the other hand, resistors R3 and R4 form a voltage divider of ratio 4.7kΩ/(4.7kΩ+1kΩ) ≃ 0.825. This means that a voltage of 2.5V on the BL pin yields 2.5V × 4.7/5.7 ≃ 2.1V at the voltage shunt regulator, base of Q1, and collector of Q2. Even at 3.3V, the shunt voltage regulator only needs to drop about 0.7V, so it will not generate basically any heat either (perhaps 3mW), and is well within its operational range. Because of this, the exact values of R3 and R4 do not matter if you only do PWM. Only if you do voltage control using e.g. DAC output on the BL pin (0 - 2.5V), you do want R3 and R4 to have 1% or better precision, so you'll get similar brightness at specific DAC output voltages from each unit you create. R3 and R4 will dissipate very little heat, on the order of 10mW, so they're completely ordinary signal resistors.
While overkill for most use cases, consider the alternate circuit that uses a Tiny-1 series 8-pin AVR, ATtiny412 or similar, to control the backlight PWM/DAC via SPI, reusing the BL pin as a SPI /SS. Note that Mouser sells ATtiny412 in standard 8-pin surface mount package for 0.52€ in singles, so this is not an expensive option at all; we are talking about an increase of under USD $1 in parts cost, although the helper microcontroller also does need to be programmed, so installation/integration/firmware flashing will have an added step.
The one real downside is that ATtiny412 uses the same pin for reset, UPDI programming, and SPI /SS, so programming has to be done outside the circuit (because the reset and UPDI functionality has to be disabled). Running at 16 MHz, the SPI SCK can be up to 8 MHz or so, when communicating to the ATtiny412. Of course, one could use I²C or even UART at a specific baud rate, for example when using a parallel bus between Teensy and the display module instead of SPI bus. Also, the exact maximum current is adjustable via the R1//R2 resistors, of course (within LED forward voltage drop of 2.8V to 3.5V, and maximum current between 60mA and 150mA or so). The schematic (click to embiggen):
The blue pin, SPI_MISO, for sending data back to Teensy, is really not needed. PA2 can instead be used for UART_RXD (receiving commands via UART, at a predetermined baud rate), or even for a 10k potentiometer (one end connected to the voltage reference at pin PA7, one to the ATtiny412 GND pin, and the wiper to PA2), for "analog" control of the backlight intensity. (The backlight is still fully under the control of the ATtiny412, it can just scale its maximum brightness within a specified range determined by the potentiometer, but still adjust it back to black/unlit and so on as it wishes.)
While Tiny-1 series AVRs like ATtiny412 do have an internal 2.5V voltage reference, only its lower 80% would be safe to use, so I added the same LM4040-2.0 reference to its Vref pin. PWM'ing is basically alternately writing 0 and the desired maximum value, to the DAC DATA register, for example in a timer interrupt. (AVR architecture has lots of registers, and if you reserve two for this, you can do a super-lightweight interrupt handler that just writes one of the registers to DAC DATA, and swaps the values of the two registers using three exclusive-OR operations between the two. This is just five instructions: one store, three XORs, and IRET, so the overhead is
tiny. You do need to temporarily block interrupts when changing the two levels, though, as the XOR swapping is not atomic.)
If one wants to use PWM and PWM only, then R4 can be connected to PA6 instead of VIN, and PA7 disconnected or used for e.g. UART RXD or as a GPIO pin. Then, the PWM output is clamped to 2.0V. One can allow both –– PA6 supports both DAC output and PWM via timers TCB0 and TCD0 ––, if one added an opamp like MCP6006 (MCP6006T-E/LT costs 0.27€ in singles at Mouser) to buffer the output in unity gain configuration, and connected its output to R4 instead of VIN, disconnecting PA7. The opamp would be powered using VIN ≃ 3.3V; it only needs to provide under 5mA of current. The DAC itself on these can supply very little current; load has to be 5kΩ or more, so a buffer is needed. Including the MOSFET gate current when switching, it would be marginal or not work through the resistor. Internal 2.5V reference would be used, because R3+R4 acts like a DAC voltage divider.) Thus, DAC 0V-2.5V would adjust the current (scaled to 0-2.048V), and TCB0/TCD0 PWM output would effectively have 2.048V when high, clamped by the voltage reference.
As you see, there are lots of ways to adjust the circuit for ones exact needs.
There are three reasons for using such a helper microcontroller. First is that any differences between units can be calibrated away using a light sensor in a dark room or box, as only the helper microcontroller needs to know what voltage to send to get the desired brightness. Second is that only single commands need to be sent, to change the backlight brightness level as desired. The third is that when touch display modules are used, giving feedback to the human user that the touch has been noted is
difficult. I believe that a quarter-second smoothly-ramping intensity pulse –– brightening the display for a fraction of a second when the Teensy notes a touch that invokes some action or effect –– will do that better than e.g. a beeper.