Every TFT display needs its backlight dimmed, and most designs do it the obvious way: a timer on the MCU makes a PWM signal, and an LED driver turns it into brightness. It works, and nobody thinks about it again.

Until a new feature needs that timer. On the i.MX RT, a FlexPWM submodule has one counter for all of its channels. If the backlight owns that counter, the other channels and the capture logic have to live with the backlight’s period. Sometimes they can’t.

There is a second option that many designs already have and never use. Most TFT display controllers have their own backlight PWM output, and a standard command to set it. In this article I compare the two options, show the three commands you need, and list the traps when you move a backlight from one to the other.

Both paths end at the same LED driver. The difference is who makes the PWM signal, and what that costs the MCU.

1. The two options side by side

The mental model: brightness is just a duty cycle. The question is who generates it.

AspectMCU timer PWMDisplay controller PWM (DCS)
Signal sourceA timer channel on the MCUThe controller’s internal PWM block, on a pin such as CABC_PWM
How you set brightnessWrite a duty-cycle registerSend DCS command 0x51 over the panel bus
MCU costOne timer channel, its counter and one pin, permanentlyNo timer, no extra pin
PWM frequencyYou choose itFixed by the controller: read the datasheet
Update costA register writeOne bus transaction, which must not collide with a frame transfer
PortabilityWorks with any panelNeeds a controller that implements the DCS brightness commands

Neither option is better in general. The timer budget decides. If the design has timers to spare, the MCU PWM is simple and fully under your control. If timers are tight, the controller’s PWM gives one back.

2. Why a backlight can block a whole timer

The mental model: on a FlexPWM submodule, the channels share one clock and one counter.

A FlexPWM submodule on the i.MX RT has one counter. Its channels (A, B and X) and its input capture all run off that counter. The modulo register (VAL1) and the prescaler belong to the submodule, not to a single channel.

A backlight PWM needs a frequency well above the flicker limit, so the counter wraps after a short period. Input capture of a slower external signal needs the counter to run for a long time between wraps. One counter can’t do both, at any setting.

One counter, two jobs that need different periods. Something has to move.

When that happens, you have three ways out:

  1. Move the capture to another timer (for example a QTMR or GPT). That means a new pin assignment, a new driver and new tests.
  2. Move the backlight PWM to another timer. Same cost, if a free one exists.
  3. Move the backlight PWM off the MCU, to the display controller. If the hardware allows it, this frees the timer for good.

Plan the timer budget early

Count every timer user before you assign channels: PWM outputs, input capture, quadrature decoders, motor control. A backlight is the easiest of them to move elsewhere, so don’t let it take a counter that something harder to move will need later.

3. The standard behind it: MIPI DCS

The mental model: look in the MIPI DCS command list, not in the panel-specific chapters.

Most TFT controllers in embedded products implement the MIPI Display Command Set (DCS). It is a standard command layer on top of whatever bus the panel uses: SPI, parallel 8080 (DBI), or MIPI DSI. Because it is a standard, three commands work the same way on almost every controller:

Three commands, sent once at start-up in this order. After that, only 0x51 changes the brightness.

Vendor datasheets often describe these commands only briefly, or just refer to MIPI DCS. That is why they are easy to miss. The parts that really differ between controllers (gamma, VCOM, power sequencing, timing) get long chapters. The brightness commands don’t.

How to recognise DCS support

If the controller’s command table has a “DCS” column, or refers to the MIPI Alliance Display Command Set, these commands carry over to the next panel you use, even from another vendor. Some controllers also offer 0x5E, a minimum brightness for the adaptive (CABC) modes.

In code, brightness becomes a bus write instead of a timer register:

c
/* At init, after the panel is enabled */
dcs_write8(0x53, 0x2C);   /* WRCTRLD: BCTRL | DD | BL */
dcs_write8(0x55, 0x00);   /* WRCABC: off, brightness stays where it is set */
dcs_write8(0x51, 0xFF);   /* WRDISBV: full brightness */

/* At run time: 0..100 % mapped to 0..255 */
dcs_write8(0x51, (uint8_t)((percent * 255u) / 100u));

dcs_write8() stands for whatever your display driver offers to send one command byte followed by one data byte.

4. The traps when you move the backlight

The mental model: the PWM moved, but every caller still expects the old behaviour.

Keep the public function the same, for example backlight_set(percent), and change only what is underneath. Then no caller changes, and the move stays small. The traps are all below that line.

Four traps, each with the symptom and the rule that avoids it.

A header is a promise about the interface, not the implementation

A display driver may declare a one-call “command plus data” function, while the bus adapter you use leaves that entry empty. Calling it then jumps through a NULL pointer. Use the plain “write command” and “write data” calls that the vendor’s own panel driver uses.

5. Checklist before you choose a panel

Decide this while you choose the display, not afterwards. Changing it later means a hardware change.

  • Does the panel module bring a CABC_PWM (or BL_PWM, LEDPWM) pin out to its connector?
  • Does the controller datasheet list 0x51, 0x53 and 0x55? A real entry in the command table is the proof, not a generic DCS list.
  • What is the controller’s fixed PWM frequency, and does it suit your LED driver, your flicker limit and your EMC requirements? You can’t change it, so get it signed off.
  • Can the LED driver’s control input be fed from either source? A pair of resistor options on the board keeps both paths open.
  • Is the brightness command sent from a context that can’t collide with a frame transfer?
  • Do you need brightness that looks even to the eye? The 0–255 value is linear, like a duty cycle, so apply a lookup table before you send it.

When you bring the panel up, test at 0 %, 50 % and 100 %, and listen and look for flicker or a whine from the LED driver. The PWM frequency is not yours to choose, so check it is acceptable on the real hardware.

Summary

  1. The backlight PWM doesn’t have to come from the MCU. If the display controller supports the DCS brightness commands, use the bus you already have and keep the timer.
  2. A timer submodule shares one counter. A backlight on one channel sets the period for the whole submodule, including its capture logic. Plan the timer budget first.
  3. Move it behind a stable function. Keep the brightness function’s signature, set the adaptive mode off for a fixed level, and send brightness from the task that owns the bus.

Sources

  • MIPI Alliance, Display Command Set (MIPI DCS): WRDISBV (0x51), WRCTRLD (0x53), WRCABC (0x55).
  • NXP, i.MX RT1060 Processor Reference Manual, eFlexPWM chapter: submodule counter, VAL1 modulo and input capture.
  • The datasheet of your display controller, for its command table and backlight PWM frequency.

💬 Questions, corrections, or your own war story? Leave a comment below.