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.
1. The two options side by side
The mental model: brightness is just a duty cycle. The question is who generates it.
| Aspect | MCU timer PWM | Display controller PWM (DCS) |
|---|---|---|
| Signal source | A timer channel on the MCU | The controller’s internal PWM block, on a pin such as CABC_PWM |
| How you set brightness | Write a duty-cycle register | Send DCS command 0x51 over the panel bus |
| MCU cost | One timer channel, its counter and one pin, permanently | No timer, no extra pin |
| PWM frequency | You choose it | Fixed by the controller: read the datasheet |
| Update cost | A register write | One bus transaction, which must not collide with a frame transfer |
| Portability | Works with any panel | Needs 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.
When that happens, you have three ways out:
- Move the capture to another timer (for example a QTMR or GPT). That means a new pin assignment, a new driver and new tests.
- Move the backlight PWM to another timer. Same cost, if a free one exists.
- 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
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:
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
0x5E, a minimum brightness for the adaptive (CABC) modes.In code, brightness becomes a bus write instead of a timer register:
/* 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.
A header is a promise about the interface, not the implementation
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(orBL_PWM,LEDPWM) pin out to its connector? - Does the controller datasheet list
0x51,0x53and0x55? 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
- 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.
- 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.
- 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,
VAL1modulo 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.
💬 Discussion
Questions, corrections, or war stories from your own board? Leave a comment below — I read and answer all of them. (Sign-in with GitHub.)