<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>I.MX RT on Hoa Nguyen — Embedded Software Architect</title><link>https://hoanguyen.de/tags/i.mx-rt/</link><description>Recent content in I.MX RT on Hoa Nguyen — Embedded Software Architect</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Tue, 10 Nov 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://hoanguyen.de/tags/i.mx-rt/index.xml" rel="self" type="application/rss+xml"/><item><title>Driving an LCD Backlight: MCU PWM vs. the Display Controller's CABC_PWM</title><link>https://hoanguyen.de/posts/lcd-backlight-flexpwm-vs-cabc-pwm/</link><pubDate>Tue, 10 Nov 2026 00:00:00 +0000</pubDate><guid>https://hoanguyen.de/posts/lcd-backlight-flexpwm-vs-cabc-pwm/</guid><description>A TFT backlight can be dimmed by an MCU timer or by the display controller&amp;rsquo;s own PWM, set with the MIPI DCS brightness commands. What each option costs, what it frees up, and the traps when you move from one to the other.</description><content:encoded><![CDATA[<p>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.</p>
<p>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&rsquo;s period. Sometimes they can&rsquo;t.</p>
<p>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.</p>
<figure class="diagram">
  <div class="diagram-svg" role="img" aria-label="Two ways to drive a backlight. Path A: an MCU timer channel drives a PWM pin into the LED driver&#39;s control input; it costs one timer channel and its counter plus a pin, you choose the frequency, and a brightness change is a register write. Path B: the MCU sends DCS command 0x51 over the panel bus it already uses, the display controller&#39;s internal PWM block drives its CABC_PWM pin into the same LED driver; it costs no timer, the frequency is fixed by the controller, and a brightness change is one bus transaction"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 432">
<defs><marker id="m_green" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#10b981"/></marker></defs>
<defs><marker id="m_amber" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#f59e0b"/></marker></defs>
<text x="20" y="28" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="#f59e0b">A · MCU timer PWM</text>
<rect x="20" y="40" width="210" height="78" rx="8" fill="#f59e0b" fill-opacity="0.14" stroke="#f59e0b" stroke-width="1.5"/>
<text x="34" y="66" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">MCU</text>
<text x="34" y="84" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">timer channel, e.g. a</text>
<text x="34" y="102" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">FlexPWM submodule</text>
<path d="M232,79 L545,79 L545,128 L576,128" fill="none" stroke="#f59e0b" stroke-width="2" marker-end="url(#m_amber)"/>
<text x="388" y="71" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="middle" fill="currentColor">PWM signal on an MCU pin</text>
<text x="20" y="168" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="#10b981">B · Display controller PWM</text>
<rect x="20" y="180" width="210" height="78" rx="8" fill="#10b981" fill-opacity="0.14" stroke="#10b981" stroke-width="1.5"/>
<text x="34" y="206" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">MCU</text>
<text x="34" y="224" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">the panel bus it already</text>
<text x="34" y="242" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">drives: SPI, 8080, DSI</text>
<line x1="232" y1="219" x2="298" y2="219" stroke="#10b981" stroke-width="2" marker-end="url(#m_green)"/>
<text x="265" y="210" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="12" font-weight="bold" text-anchor="middle" fill="currentColor">DCS 0x51</text>
<text x="265" y="236" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="middle" fill="currentColor">+ value</text>
<rect x="300" y="180" width="192" height="78" rx="8" fill="#10b981" fill-opacity="0.14" stroke="#10b981" stroke-width="1.5"/>
<text x="314" y="206" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">Display controller</text>
<text x="314" y="224" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">internal PWM block</text>
<text x="314" y="242" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">drives its CABC_PWM pin</text>
<path d="M494,219 L545,219 L545,172 L576,172" fill="none" stroke="#10b981" stroke-width="2" marker-end="url(#m_green)"/>
<rect x="580" y="100" width="180" height="100" rx="8" fill="#3b82f6" fill-opacity="0.14" stroke="#3b82f6" stroke-width="2"/>
<text x="594" y="126" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">LED driver</text>
<text x="594" y="145" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">control input</text>
<text x="594" y="164" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">→ LED string</text>
<text x="594" y="183" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.7">same in both paths</text>
<rect x="20" y="290" width="360" height="128" rx="8" fill="#f59e0b" fill-opacity="0.1" stroke="#f59e0b" stroke-width="1.5"/>
<text x="36" y="316" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="#f59e0b">A costs the MCU</text>
<text x="36" y="336" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">one timer channel and its counter</text>
<text x="36" y="356" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">one pin</text>
<text x="36" y="376" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">frequency: you choose it</text>
<text x="36" y="396" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">brightness: a register write</text>
<rect x="400" y="290" width="360" height="128" rx="8" fill="#10b981" fill-opacity="0.1" stroke="#10b981" stroke-width="1.5"/>
<text x="416" y="316" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="#10b981">B costs the MCU</text>
<text x="416" y="336" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">no timer, no extra pin</text>
<text x="416" y="356" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">frequency: fixed by the controller</text>
<text x="416" y="376" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">brightness: one bus transaction,</text>
<text x="416" y="396" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">never during a frame transfer</text>
</svg>
</div>
  <figcaption>Both paths end at the same LED driver. The difference is who makes the PWM signal, and what that costs the MCU.</figcaption>
</figure>

<h2 id="1-the-two-options-side-by-side">1. The two options side by side</h2>
<p><strong>The mental model: brightness is just a duty cycle. The question is who generates it.</strong></p>
<table>
  <thead>
      <tr>
          <th>Aspect</th>
          <th>MCU timer PWM</th>
          <th>Display controller PWM (DCS)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Signal source</td>
          <td>A timer channel on the MCU</td>
          <td>The controller&rsquo;s internal PWM block, on a pin such as <code>CABC_PWM</code></td>
      </tr>
      <tr>
          <td>How you set brightness</td>
          <td>Write a duty-cycle register</td>
          <td>Send DCS command <code>0x51</code> over the panel bus</td>
      </tr>
      <tr>
          <td>MCU cost</td>
          <td>One timer channel, its counter and one pin, permanently</td>
          <td>No timer, no extra pin</td>
      </tr>
      <tr>
          <td>PWM frequency</td>
          <td>You choose it</td>
          <td>Fixed by the controller: read the datasheet</td>
      </tr>
      <tr>
          <td>Update cost</td>
          <td>A register write</td>
          <td>One bus transaction, which must not collide with a frame transfer</td>
      </tr>
      <tr>
          <td>Portability</td>
          <td>Works with any panel</td>
          <td>Needs a controller that implements the DCS brightness commands</td>
      </tr>
  </tbody>
</table>
<p>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&rsquo;s PWM gives one back.</p>
<h2 id="2-why-a-backlight-can-block-a-whole-timer">2. Why a backlight can block a whole timer</h2>
<p><strong>The mental model: on a FlexPWM submodule, the channels share one clock and one counter.</strong></p>
<p>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 (<code>VAL1</code>) and the prescaler belong to the
submodule, not to a single channel.</p>
<p>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&rsquo;t do
both, at any setting.</p>
<figure class="diagram">
  <div class="diagram-svg" role="img" aria-label="A FlexPWM submodule has one counter with one modulo VAL1 and one prescaler, shared by channels A, B, X and the input capture logic. The backlight needs a short period, so the counter wraps often; capturing a slow external signal needs a long free-running count between wraps. One counter cannot serve both, so the backlight has to move to another timer or off the MCU"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 430">
<rect x="20" y="40" width="250" height="248" rx="8" fill="#94a3b8" fill-opacity="0.06" stroke="#94a3b8" stroke-width="1.5"/>
<text x="36" y="66" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">FlexPWM submodule</text>
<rect x="36" y="80" width="218" height="58" rx="8" fill="#3b82f6" fill-opacity="0.2" stroke="#3b82f6" stroke-width="2"/>
<text x="145" y="104" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="currentColor">one counter</text>
<text x="145" y="124" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="middle" fill="currentColor">VAL1 modulo + prescaler</text>
<text x="36" y="166" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.8">shared by all of these:</text>
<rect x="36" y="178" width="106" height="36" rx="8" fill="#14b8a6" fill-opacity="0.16" stroke="#14b8a6" stroke-width="1.5"/>
<text x="89" y="201" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="middle" fill="currentColor">channel A</text>
<rect x="148" y="178" width="106" height="36" rx="8" fill="#14b8a6" fill-opacity="0.16" stroke="#14b8a6" stroke-width="1.5"/>
<text x="201" y="201" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="middle" fill="currentColor">channel B</text>
<rect x="36" y="224" width="106" height="36" rx="8" fill="#14b8a6" fill-opacity="0.16" stroke="#14b8a6" stroke-width="1.5"/>
<text x="89" y="247" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="middle" fill="currentColor">channel X</text>
<rect x="148" y="224" width="106" height="36" rx="8" fill="#8b5cf6" fill-opacity="0.16" stroke="#8b5cf6" stroke-width="1.5"/>
<text x="201" y="247" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="middle" fill="currentColor">input capture</text>
<text x="310" y="56" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="#f59e0b">Backlight PWM needs</text>
<text x="310" y="75" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor">a short period: the counter wraps often</text>
<path d="M310,130 L332,90 L332,130 L354,90 L354,130 L376,90 L376,130 L398,90 L398,130 L420,90 L420,130 L442,90 L442,130 L464,90 L464,130 L486,90 L486,130 L508,90 L508,130 L530,90 L530,130 L552,90 L552,130 L574,90 L574,130 L596,90 L596,130 L618,90 L618,130 L640,90 L640,130 L662,90 L662,130 L684,90 L684,130 L706,90 L706,130 L728,90 L728,130 L750,90 L750,130" fill="none" stroke="#f59e0b" stroke-width="1.8"/>
<text x="310" y="176" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="#8b5cf6">Capture of a slow signal needs</text>
<text x="310" y="195" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor">a long free-running count between wraps</text>
<path d="M310,262 L750,214" fill="none" stroke="#8b5cf6" stroke-width="1.8"/>
<line x1="310" y1="262" x2="750" y2="262" stroke="currentColor" stroke-width="1.6" stroke-opacity="0.35"/>
<line x1="400" y1="270" x2="400" y2="256.1818181818182" stroke="#8b5cf6" stroke-width="1.6" stroke-dasharray="3 3" stroke-opacity="0.8"/>
<circle cx="400" cy="252.1818181818182" r="4.5" fill="#8b5cf6"/>
<line x1="530" y1="270" x2="530" y2="242.0" stroke="#8b5cf6" stroke-width="1.6" stroke-dasharray="3 3" stroke-opacity="0.8"/>
<circle cx="530" cy="238.0" r="4.5" fill="#8b5cf6"/>
<line x1="660" y1="270" x2="660" y2="227.8181818181818" stroke="#8b5cf6" stroke-width="1.6" stroke-dasharray="3 3" stroke-opacity="0.8"/>
<circle cx="660" cy="223.8181818181818" r="4.5" fill="#8b5cf6"/>
<text x="750" y="286" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="end" fill="currentColor" fill-opacity="0.8">external edges, timestamped by the counter</text>
<rect x="20" y="310" width="740" height="108" rx="8" fill="#ef4444" fill-opacity="0.07" stroke="#ef4444" stroke-width="1"/>
<text x="36" y="336" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">One counter, one period. Something has to move:</text>
<text x="36" y="360" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">the capture, to another timer (for example a QTMR or GPT),</text>
<text x="36" y="380" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="normal" text-anchor="start" fill="currentColor">or the backlight PWM, to another timer,</text>
<text x="36" y="400" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="#10b981">or the backlight PWM off the MCU, to the display controller.</text>
</svg>
</div>
  <figcaption>One counter, two jobs that need different periods. Something has to move.</figcaption>
</figure>

<p>When that happens, you have three ways out:</p>
<ol>
<li>Move the capture to another timer (for example a QTMR or GPT). That means a new pin
assignment, a new driver and new tests.</li>
<li>Move the backlight PWM to another timer. Same cost, if a free one exists.</li>
<li>Move the backlight PWM <strong>off the MCU</strong>, to the display controller. If the hardware allows
it, this frees the timer for good.</li>
</ol>
<div class="callout callout--tip">
  <p class="callout__title"><span class="callout__icon" aria-hidden="true">💡</span>Plan the timer budget early</p>
  <div class="callout__body">
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&rsquo;t let
it take a counter that something harder to move will need later.
  </div>
</div>

<h2 id="3-the-standard-behind-it-mipi-dcs">3. The standard behind it: MIPI DCS</h2>
<p><strong>The mental model: look in the MIPI DCS command list, not in the panel-specific chapters.</strong></p>
<p>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:</p>
<figure class="diagram">
  <div class="diagram-svg" role="img" aria-label="The three MIPI DCS brightness commands in the order you send them at init. First 0x53 WRCTRLD with value 0x2C: bit 5 BCTRL enables the brightness block, bit 3 DD enables dimming, bit 2 BL turns the backlight on. Second 0x55 WRCABC: 0x00 off, which is manual and deterministic, 0x01 user interface, 0x02 still picture, 0x03 moving image. Third 0x51 WRDISBV: one byte of brightness from 0x00 to 0xFF; after init only this command is sent"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 400">
<rect x="10" y="12" width="760" height="158" rx="8" fill="#94a3b8" fill-opacity="0.05"/>
<circle cx="36" cy="30" r="15" fill="#3b82f6"/>
<text x="36" y="35" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">1</text>
<text x="62" y="36" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="17" font-weight="bold" text-anchor="start" fill="currentColor">0x53</text>
<text x="62" y="58" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">WRCTRLD</text>
<text x="62" y="76" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.75">Write CTRL Display</text>
<text x="62" y="104" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.8">send once at init:</text>
<text x="62" y="124" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">value 0x2C</text>
<rect x="300" y="42" width="40" height="38" rx="4" fill="#94a3b8" fill-opacity="0.08" stroke="#94a3b8" stroke-width="1.5"/>
<text x="320" y="67" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="14" font-weight="normal" text-anchor="middle" fill="currentColor">0</text>
<text x="320" y="34" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.75">bit 7</text>
<rect x="344" y="42" width="40" height="38" rx="4" fill="#94a3b8" fill-opacity="0.08" stroke="#94a3b8" stroke-width="1.5"/>
<text x="364" y="67" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="14" font-weight="normal" text-anchor="middle" fill="currentColor">0</text>
<text x="364" y="34" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.75">bit 6</text>
<rect x="388" y="42" width="40" height="38" rx="4" fill="#3b82f6" fill-opacity="0.35" stroke="#3b82f6" stroke-width="1.5"/>
<text x="408" y="67" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="14" font-weight="bold" text-anchor="middle" fill="currentColor">1</text>
<text x="408" y="34" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.75">bit 5</text>
<rect x="432" y="42" width="40" height="38" rx="4" fill="#94a3b8" fill-opacity="0.08" stroke="#94a3b8" stroke-width="1.5"/>
<text x="452" y="67" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="14" font-weight="normal" text-anchor="middle" fill="currentColor">0</text>
<text x="452" y="34" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.75">bit 4</text>
<rect x="476" y="42" width="40" height="38" rx="4" fill="#3b82f6" fill-opacity="0.35" stroke="#3b82f6" stroke-width="1.5"/>
<text x="496" y="67" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="14" font-weight="bold" text-anchor="middle" fill="currentColor">1</text>
<text x="496" y="34" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.75">bit 3</text>
<rect x="520" y="42" width="40" height="38" rx="4" fill="#3b82f6" fill-opacity="0.35" stroke="#3b82f6" stroke-width="1.5"/>
<text x="540" y="67" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="14" font-weight="bold" text-anchor="middle" fill="currentColor">1</text>
<text x="540" y="34" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.75">bit 2</text>
<rect x="564" y="42" width="40" height="38" rx="4" fill="#94a3b8" fill-opacity="0.08" stroke="#94a3b8" stroke-width="1.5"/>
<text x="584" y="67" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="14" font-weight="normal" text-anchor="middle" fill="currentColor">0</text>
<text x="584" y="34" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.75">bit 1</text>
<rect x="608" y="42" width="40" height="38" rx="4" fill="#94a3b8" fill-opacity="0.08" stroke="#94a3b8" stroke-width="1.5"/>
<text x="628" y="67" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="14" font-weight="normal" text-anchor="middle" fill="currentColor">0</text>
<text x="628" y="34" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="middle" fill="currentColor" fill-opacity="0.75">bit 0</text>
<text x="300" y="106" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">bit 5 · BCTRL: brightness control block on</text>
<text x="300" y="125" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">bit 3 · DD: display dimming on</text>
<text x="300" y="144" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">bit 2 · BL: backlight on</text>
<rect x="10" y="180" width="760" height="94" rx="8" fill="#94a3b8" fill-opacity="0.05"/>
<circle cx="36" cy="198" r="15" fill="#f59e0b"/>
<text x="36" y="203" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">2</text>
<text x="62" y="204" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="17" font-weight="bold" text-anchor="start" fill="currentColor">0x55</text>
<text x="62" y="226" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">WRCABC</text>
<text x="62" y="244" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.75">content adaptive mode</text>
<rect x="300" y="190" width="110" height="46" rx="8" fill="#10b981" fill-opacity="0.25" stroke="#10b981" stroke-width="2"/>
<text x="355" y="209" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="13" font-weight="bold" text-anchor="middle" fill="currentColor">0x00</text>
<text x="355" y="227" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="middle" fill="currentColor">off</text>
<rect x="418" y="190" width="110" height="46" rx="8" fill="#94a3b8" fill-opacity="0.1" stroke="#94a3b8" stroke-width="1.5"/>
<text x="473" y="209" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="13" font-weight="bold" text-anchor="middle" fill="currentColor">0x01</text>
<text x="473" y="227" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="middle" fill="currentColor">user interface</text>
<rect x="536" y="190" width="110" height="46" rx="8" fill="#94a3b8" fill-opacity="0.1" stroke="#94a3b8" stroke-width="1.5"/>
<text x="591" y="209" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="13" font-weight="bold" text-anchor="middle" fill="currentColor">0x02</text>
<text x="591" y="227" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="middle" fill="currentColor">still picture</text>
<rect x="654" y="190" width="110" height="46" rx="8" fill="#94a3b8" fill-opacity="0.1" stroke="#94a3b8" stroke-width="1.5"/>
<text x="709" y="209" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="13" font-weight="bold" text-anchor="middle" fill="currentColor">0x03</text>
<text x="709" y="227" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="middle" fill="currentColor">moving image</text>
<text x="300" y="258" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.85">0x00 keeps a fixed level; the others follow the screen content.</text>
<rect x="10" y="292" width="760" height="96" rx="8" fill="#94a3b8" fill-opacity="0.05"/>
<circle cx="36" cy="310" r="15" fill="#10b981"/>
<text x="36" y="315" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">3</text>
<text x="62" y="316" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="17" font-weight="bold" text-anchor="start" fill="currentColor">0x51</text>
<text x="62" y="338" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13.5" font-weight="bold" text-anchor="start" fill="currentColor">WRDISBV</text>
<text x="62" y="356" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.75">Write Display Brightness</text>
<defs><linearGradient id="bl_grad" x1="0" x2="1" y1="0" y2="0"><stop offset="0" stop-color="#f59e0b" stop-opacity="0.04"/><stop offset="1" stop-color="#f59e0b" stop-opacity="0.75"/></linearGradient></defs>
<rect x="300" y="304" width="460" height="34" rx="4" fill="url(#bl_grad)" stroke="#f59e0b" stroke-width="1.5"/>
<text x="310" y="327" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor">0x00</text>
<text x="750" y="327" font-family="ui-monospace, 'SFMono-Regular', Consolas, monospace" font-size="13" font-weight="bold" text-anchor="end" fill="currentColor">0xFF</text>
<text x="530" y="327" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="middle" fill="currentColor">one byte, linear</text>
<text x="300" y="362" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12.5" font-weight="bold" text-anchor="start" fill="currentColor">After init, this is the only command you send.</text>
</svg>
</div>
  <figcaption>Three commands, sent once at start-up in this order. After that, only 0x51 changes the brightness.</figcaption>
</figure>

<p>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&rsquo;t.</p>
<div class="callout callout--note">
  <p class="callout__title"><span class="callout__icon" aria-hidden="true">📝</span>How to recognise DCS support</p>
  <div class="callout__body">
If the controller&rsquo;s command table has a &ldquo;DCS&rdquo; 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 <code>0x5E</code>, a minimum brightness for the adaptive (CABC) modes.
  </div>
</div>

<p>In code, brightness becomes a bus write instead of a timer register:</p>
<div class="codeblock">
  <div class="codeblock-bar">
    <span class="codeblock-label"><span class="codeblock-lang">c</span></span><button class="codeblock-copy" type="button" hidden aria-label="Copy code to clipboard">copy</button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cm">/* At init, after the panel is enabled */</span>
</span></span><span class="line"><span class="cl"><span class="nf">dcs_write8</span><span class="p">(</span><span class="mh">0x53</span><span class="p">,</span> <span class="mh">0x2C</span><span class="p">);</span>   <span class="cm">/* WRCTRLD: BCTRL | DD | BL */</span>
</span></span><span class="line"><span class="cl"><span class="nf">dcs_write8</span><span class="p">(</span><span class="mh">0x55</span><span class="p">,</span> <span class="mh">0x00</span><span class="p">);</span>   <span class="cm">/* WRCABC: off, brightness stays where it is set */</span>
</span></span><span class="line"><span class="cl"><span class="nf">dcs_write8</span><span class="p">(</span><span class="mh">0x51</span><span class="p">,</span> <span class="mh">0xFF</span><span class="p">);</span>   <span class="cm">/* WRDISBV: full brightness */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* At run time: 0..100 % mapped to 0..255 */</span>
</span></span><span class="line"><span class="cl"><span class="nf">dcs_write8</span><span class="p">(</span><span class="mh">0x51</span><span class="p">,</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)((</span><span class="n">percent</span> <span class="o">*</span> <span class="mi">255u</span><span class="p">)</span> <span class="o">/</span> <span class="mi">100u</span><span class="p">));</span></span></span></code></pre></div>
</div>
<p><code>dcs_write8()</code> stands for whatever your display driver offers to send one command byte
followed by one data byte.</p>
<h2 id="4-the-traps-when-you-move-the-backlight">4. The traps when you move the backlight</h2>
<p><strong>The mental model: the PWM moved, but every caller still expects the old behaviour.</strong></p>
<p>Keep the public function the same, for example <code>backlight_set(percent)</code>, and change only what
is underneath. Then no caller changes, and the move stays small. The traps are all below that
line.</p>
<figure class="diagram">
  <div class="diagram-svg" role="img" aria-label="Four traps when moving the backlight from an MCU timer to DCS: the backlight is set before the display bus is ready, so check that the bus is initialised and apply the boot brightness in the display init; adaptive CABC changes brightness with screen content, so set WRCABC to 0x00 for a fixed level; a brightness command sent during a frame transfer on the same bus corrupts the display, so send it from the task that owns the bus; a convenience call declared in a driver header may not be implemented by every bus adapter, so use the plain command and data calls the vendor driver itself uses"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 420">
<text x="60" y="26" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="currentColor" fill-opacity="0.75">Trap</text>
<text x="252" y="26" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="#ef4444">Symptom</text>
<text x="512" y="26" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="start" fill="#10b981">Rule</text>
<rect x="10" y="40" width="760" height="84" rx="8" fill="#94a3b8" fill-opacity="0.05"/>
<circle cx="34" cy="82.0" r="13" fill="#f59e0b"/>
<text x="34" y="87.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">1</text>
<text x="60" y="79.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">Called too early</text>
<text x="60" y="98.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.75">before display init</text>
<rect x="244" y="48" width="254" height="68" rx="8" fill="#ef4444" fill-opacity="0.1" stroke="#ef4444" stroke-width="1.5"/>
<text x="256" y="69.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">NULL dereference or a lost</text>
<text x="256" y="87.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">command: the bus is not</text>
<text x="256" y="105.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">set up yet</text>
<rect x="508" y="48" width="254" height="68" rx="8" fill="#10b981" fill-opacity="0.12" stroke="#10b981" stroke-width="1.5"/>
<text x="520" y="69.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">Check the bus is ready;</text>
<text x="520" y="87.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">apply the boot brightness at</text>
<text x="520" y="105.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">the end of the display init</text>
<rect x="10" y="132" width="760" height="84" rx="8" fill="#94a3b8" fill-opacity="0.05"/>
<circle cx="34" cy="174.0" r="13" fill="#f59e0b"/>
<text x="34" y="179.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">2</text>
<text x="60" y="171.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">Adaptive mode on</text>
<text x="60" y="190.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.75">WRCABC not 0x00</text>
<rect x="244" y="140" width="254" height="68" rx="8" fill="#ef4444" fill-opacity="0.1" stroke="#ef4444" stroke-width="1.5"/>
<text x="256" y="170.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">Brightness drifts with</text>
<text x="256" y="188.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">what is on the screen</text>
<rect x="508" y="140" width="254" height="68" rx="8" fill="#10b981" fill-opacity="0.12" stroke="#10b981" stroke-width="1.5"/>
<text x="520" y="161.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">Send WRCABC 0x00, so the</text>
<text x="520" y="179.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">level stays where the</text>
<text x="520" y="197.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">operator set it</text>
<rect x="10" y="224" width="760" height="84" rx="8" fill="#94a3b8" fill-opacity="0.05"/>
<circle cx="34" cy="266.0" r="13" fill="#f59e0b"/>
<text x="34" y="271.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">3</text>
<text x="60" y="263.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">Shared bus</text>
<text x="60" y="282.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.75">command vs frame data</text>
<rect x="244" y="232" width="254" height="68" rx="8" fill="#ef4444" fill-opacity="0.1" stroke="#ef4444" stroke-width="1.5"/>
<text x="256" y="253.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">A command lands in the middle</text>
<text x="256" y="271.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">of a frame transfer: a</text>
<text x="256" y="289.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">corrupted display</text>
<rect x="508" y="232" width="254" height="68" rx="8" fill="#10b981" fill-opacity="0.12" stroke="#10b981" stroke-width="1.5"/>
<text x="520" y="253.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">Send brightness only from</text>
<text x="520" y="271.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">the task that owns the bus</text>
<text x="520" y="289.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">and the frame flush</text>
<rect x="10" y="316" width="760" height="84" rx="8" fill="#94a3b8" fill-opacity="0.05"/>
<circle cx="34" cy="358.0" r="13" fill="#f59e0b"/>
<text x="34" y="363.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="middle" fill="#fefefe">4</text>
<text x="60" y="355.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" text-anchor="start" fill="currentColor">Missing API entry</text>
<text x="60" y="374.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="12" font-weight="normal" text-anchor="start" fill="currentColor" fill-opacity="0.75">header vs bus adapter</text>
<rect x="244" y="324" width="254" height="68" rx="8" fill="#ef4444" fill-opacity="0.1" stroke="#ef4444" stroke-width="1.5"/>
<text x="256" y="345.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">A convenience call declared</text>
<text x="256" y="363.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">in the header is NULL in</text>
<text x="256" y="381.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">this adapter: a hard fault</text>
<rect x="508" y="324" width="254" height="68" rx="8" fill="#10b981" fill-opacity="0.12" stroke="#10b981" stroke-width="1.5"/>
<text x="520" y="345.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">Use plain write-command and</text>
<text x="520" y="363.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">write-data calls, as the</text>
<text x="520" y="381.0" font-family="-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" font-size="13" font-weight="normal" text-anchor="start" fill="currentColor">vendor's panel driver does</text>
</svg>
</div>
  <figcaption>Four traps, each with the symptom and the rule that avoids it.</figcaption>
</figure>

<div class="callout callout--danger">
  <p class="callout__title"><span class="callout__icon" aria-hidden="true">🐛</span>A header is a promise about the interface, not the implementation</p>
  <div class="callout__body">
A display driver may declare a one-call &ldquo;command plus data&rdquo; function, while the bus adapter
you use leaves that entry empty. Calling it then jumps through a NULL pointer. Use the plain
&ldquo;write command&rdquo; and &ldquo;write data&rdquo; calls that the vendor&rsquo;s own panel driver uses.
  </div>
</div>

<h2 id="5-checklist-before-you-choose-a-panel">5. Checklist before you choose a panel</h2>
<p>Decide this while you choose the display, not afterwards. Changing it later means a hardware
change.</p>
<ul>
<li><input disabled="" type="checkbox"> Does the panel module bring a <code>CABC_PWM</code> (or <code>BL_PWM</code>, <code>LEDPWM</code>) pin out to its connector?</li>
<li><input disabled="" type="checkbox"> Does the controller datasheet list <code>0x51</code>, <code>0x53</code> and <code>0x55</code>? A real entry in the command
table is the proof, not a generic DCS list.</li>
<li><input disabled="" type="checkbox"> What is the controller&rsquo;s fixed PWM frequency, and does it suit your LED driver, your
flicker limit and your EMC requirements? You can&rsquo;t change it, so get it signed off.</li>
<li><input disabled="" type="checkbox"> Can the LED driver&rsquo;s control input be fed from either source? A pair of resistor
options on the board keeps both paths open.</li>
<li><input disabled="" type="checkbox"> Is the brightness command sent from a context that can&rsquo;t collide with a frame transfer?</li>
<li><input disabled="" type="checkbox"> 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.</li>
</ul>
<p>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.</p>
<h2 id="summary">Summary</h2>
<ol>
<li><strong>The backlight PWM doesn&rsquo;t have to come from the MCU.</strong> If the display controller
supports the DCS brightness commands, use the bus you already have and keep the timer.</li>
<li><strong>A timer submodule shares one counter.</strong> A backlight on one channel sets the period for
the whole submodule, including its capture logic. Plan the timer budget first.</li>
<li><strong>Move it behind a stable function.</strong> Keep the brightness function&rsquo;s signature, set the
adaptive mode off for a fixed level, and send brightness from the task that owns the bus.</li>
</ol>
<div class="callout callout--note">
  <p class="callout__title"><span class="callout__icon" aria-hidden="true">📝</span>Sources</p>
  <div class="callout__body">
<ul>
<li>MIPI Alliance, <em>Display Command Set (MIPI DCS)</em>: <code>WRDISBV</code> (0x51), <code>WRCTRLD</code> (0x53),
<code>WRCABC</code> (0x55).</li>
<li>NXP, <em>i.MX RT1060 Processor Reference Manual</em>, eFlexPWM chapter: submodule counter,
<code>VAL1</code> modulo and input capture.</li>
<li>The datasheet of your display controller, for its command table and backlight PWM frequency.</li>
</ul>

  </div>
</div>

<p>💬 Questions, corrections, or your own war story? Leave a comment below.</p>
]]></content:encoded></item></channel></rss>