<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ai pics</title>
    <description>The latest articles on DEV Community by ai pics (@ai_pics_6442ad429fc2ff12f).</description>
    <link>https://dev.to/ai_pics_6442ad429fc2ff12f</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3521344%2F3ec6a7fd-66f1-450a-8154-5aa0add29189.png</url>
      <title>DEV Community: ai pics</title>
      <link>https://dev.to/ai_pics_6442ad429fc2ff12f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ai_pics_6442ad429fc2ff12f"/>
    <language>en</language>
    <item>
      <title>The Quiet Failure Mode in a 12 V to 5 V Buck Converter</title>
      <dc:creator>ai pics</dc:creator>
      <pubDate>Mon, 31 Aug 2026 03:40:22 +0000</pubDate>
      <link>https://dev.to/ai_pics_6442ad429fc2ff12f/the-quiet-failure-mode-in-a-12-v-to-5-v-buck-converter-3c13</link>
      <guid>https://dev.to/ai_pics_6442ad429fc2ff12f/the-quiet-failure-mode-in-a-12-v-to-5-v-buck-converter-3c13</guid>
      <description>&lt;p&gt;A 12 V to 5 V buck converter can look perfect on the schematic and still fail during a load step. The output may dip too far, ring for several microseconds, or restart when a motor, radio, or processor wakes up. The usual instinct is to blame the regulator IC. In practice, the inductor, compensation network, current loop, and measurement setup are just as likely to be responsible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the current waveform
&lt;/h2&gt;

&lt;p&gt;The first useful measurement is not the multimeter reading. It is the input and output current waveform during the event that causes the failure. A converter rated for 3 A may be perfectly comfortable at a steady 2 A and still struggle with a 2 A-to-5 A transient if the inductor saturates or the control loop responds slowly.&lt;/p&gt;

&lt;p&gt;Record the load-step amplitude, rise time, repetition rate, and minimum output voltage. A short pulse and a continuous overload are different design cases. Also check whether the load has a large input capacitor. That capacitor can make the first edge look acceptable while the converter later runs out of current and hits thermal or cycle-by-cycle limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inductor saturation is a dynamic problem
&lt;/h2&gt;

&lt;p&gt;The inductor current is not just the DC output current. In a simplified buck converter, the peak current is approximately:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Ipeak = Iout + ΔIL / 2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;where &lt;code&gt;ΔIL&lt;/code&gt; is the inductor ripple current. The selected part must maintain enough inductance at the actual peak current and temperature. A catalog headline such as “5 A rated” is not sufficient unless the rating definition is understood.&lt;/p&gt;

&lt;p&gt;When the core begins to saturate, inductance falls. Ripple current increases, switch current rises, and the converter may become noisier precisely when the load needs clean power. Check the inductance-versus-current curve, saturation definition, copper loss, and temperature rise rather than selecting by nominal inductance alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layout decides which loop the current follows
&lt;/h2&gt;

&lt;p&gt;The high di/dt loop usually contains the input ceramic capacitor, the high-side switch, the low-side path, and the return connection. Keep this loop compact. Place the input capacitor close to the power pins, keep the switch node physically small, and route sensitive feedback traces away from the switching node and inductor.&lt;/p&gt;

&lt;p&gt;The feedback divider should return to a quiet ground reference. It should not share a long, high-current copper path with the input capacitor or power-stage return. A converter can have the right compensation values and still oscillate or show excess ripple if the feedback signal measures the wrong ground.&lt;/p&gt;

&lt;p&gt;For the first design pass, the &lt;a href="https://mozelectronics.com/semiconductor-ics/power-management-ics-pmics/" rel="noopener noreferrer"&gt;power-management IC category at MOZ Electronics&lt;/a&gt; is useful for comparing controller functions and regulator architectures. The category is only a starting point; the final choice must be checked against the inductor, switching frequency, current limit, compensation method, and thermal conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate control-loop problems from probing problems
&lt;/h2&gt;

&lt;p&gt;A long oscilloscope ground lead can add enough inductance to create ringing that is not present at the load. Measure directly across the output capacitor with a short spring ground, then repeat the measurement at the load connector. If the two waveforms differ, the board or cable impedance is part of the problem.&lt;/p&gt;

&lt;p&gt;It is also worth checking whether the ringing frequency changes when the probe position changes. If it does, improve the measurement before changing the design. If it remains stable and appears at both the converter and the load, investigate the control loop, output capacitor ESR, and layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Derate before the BOM is frozen
&lt;/h2&gt;

&lt;p&gt;A first-pass review should cover the maximum input voltage, output current, inductor peak current, switch voltage stress, capacitor ripple current, and semiconductor junction temperature. The &lt;a href="https://resicalc.com/en/tools/component-derating-calculator/" rel="noopener noreferrer"&gt;component derating calculator&lt;/a&gt; can help expose a weak voltage or power margin quickly, but it does not replace the manufacturer’s curves or a measured thermal test.&lt;/p&gt;

&lt;p&gt;Manufacturing details matter too. A small power board may use large copper pours, thermal vias, and several capacitor sizes. The &lt;a href="https://mozpcb.com/pcb-assembly/" rel="noopener noreferrer"&gt;PCB assembly guidance from MOZPCB&lt;/a&gt; is relevant when stencil openings, component orientation, reflow, and inspection coverage can change the final electrical behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  A repeatable bring-up sequence
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Power the board from a current-limited source with the load disconnected.&lt;/li&gt;
&lt;li&gt;Confirm the switching frequency, output voltage, soft-start waveform, and no-load current.&lt;/li&gt;
&lt;li&gt;Add a resistive or electronic load in small steps and record ripple and temperature.&lt;/li&gt;
&lt;li&gt;Apply the fastest realistic load transient and capture the waveform at both the converter and the load.&lt;/li&gt;
&lt;li&gt;Repeat at minimum and maximum input voltage and at the intended enclosure temperature.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is not simply to obtain 5 V on a meter. A reliable buck converter is one whose current path, control signal, thermal margin, and production process remain predictable when the load changes. For broader power-design context, Octatronics’ &lt;a href="https://octatronics.com/applications/" rel="noopener noreferrer"&gt;power and energy application resources&lt;/a&gt; are a useful way to connect the converter choice to the final product environment.&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>iot</category>
    </item>
    <item>
      <title>USB-C PD 3.1 at 240 W: The Power Number Is Only the Starting Point</title>
      <dc:creator>ai pics</dc:creator>
      <pubDate>Fri, 28 Aug 2026 08:20:32 +0000</pubDate>
      <link>https://dev.to/ai_pics_6442ad429fc2ff12f/usb-c-pd-31-at-240-w-the-power-number-is-only-the-starting-point-18hd</link>
      <guid>https://dev.to/ai_pics_6442ad429fc2ff12f/usb-c-pd-31-at-240-w-the-power-number-is-only-the-starting-point-18hd</guid>
      <description>&lt;p&gt;USB-C Power Delivery has moved far beyond phone charging. The USB-IF says PD 3.1 extends the system to 240 W using the Extended Power Range (EPR), with fixed levels up to 48 V and 5 A. That sounds like a connector upgrade, but it is really a system-level power-design problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the connector is not the design
&lt;/h2&gt;

&lt;p&gt;A 240 W port needs more than a Type-C receptacle. The source and sink must negotiate a valid PD contract over the CC pins, the cable must support the required current and voltage range, and the power path must survive abnormal conditions. The design also needs a controller, VBUS protection, current sensing, thermal monitoring, and a converter or charger that can operate across the negotiated range.&lt;/p&gt;

&lt;p&gt;The 48 V EPR level changes the protection problem. A short-to-VBUS event, hot-plug transient, or ESD strike now has a larger energy budget than a conventional 5 V USB port. A dedicated Type-C protection device can reduce the burden on the main converter, but layout still matters: keep the VBUS protection loop short, place the high-current capacitors close to the port, and separate noisy switching nodes from the CC and USB data paths.&lt;/p&gt;

&lt;p&gt;For battery-powered equipment, a bidirectional buck-boost stage is often more flexible than a fixed buck charger. It can accept a wide input range, charge the battery, and provide power back to the USB-C port when the product is used as a source. Texas Instruments’ PMP41115 reference design demonstrates a 240 W USB PD 3.1 battery charger using a four-switch buck-boost topology and reports 97.6% full-load efficiency under its test conditions. That is a reference result, not a guarantee for every board, but it shows the architecture is practical.&lt;/p&gt;

&lt;h2&gt;
  
  
  A component-level checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;PD controller:&lt;/strong&gt; Confirm the required power roles, EPR support, firmware or configuration method, and fault reporting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VBUS protection:&lt;/strong&gt; Check the maximum working voltage, short-circuit behavior, ESD rating, and whether protection remains effective during negotiation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Power switch and magnetics:&lt;/strong&gt; Select MOSFETs, inductors, and capacitors using switching loss, RMS current, temperature rise, and derating—not headline current alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thermal path:&lt;/strong&gt; Calculate copper loss and switching loss at the worst-case contract. A compact enclosure may need more copper, airflow, or a different switching frequency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cable and compliance:&lt;/strong&gt; EPR operation depends on the cable and system implementation. Use certified components and run the required compliance tests before advertising a power level.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The power-management section at &lt;a href="https://mozelectronics.com/semiconductor-ics/power-management-ics-pmics/" rel="noopener noreferrer"&gt;MOZ Electronics&lt;/a&gt; is a useful starting point when comparing controllers and regulators by function. For the PCB, high-current paths should be reviewed together with assembly constraints; &lt;a href="https://mozpcb.com/pcb-assembly/" rel="noopener noreferrer"&gt;MOZPCB’s PCB assembly guidance&lt;/a&gt; covers stencil, reflow, polarity, inspection, and first-article risks that can affect a dense power board.&lt;/p&gt;

&lt;p&gt;Finally, do not treat derating as paperwork. A simple &lt;a href="https://resicalc.com/en/tools/component-derating-calculator/" rel="noopener noreferrer"&gt;component derating calculator&lt;/a&gt; can expose an obviously weak voltage or power margin before the BOM is frozen. At 240 W, margin is part of the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical takeaway
&lt;/h2&gt;

&lt;p&gt;USB-C PD 3.1 makes one connector capable of powering a much wider class of products, from portable power stations to tools and displays. The winning designs will not be the ones that merely expose a 240 W number. They will be the ones that negotiate power safely, control heat, protect the port, and validate the complete cable–connector–PCB–converter chain.&lt;/p&gt;

</description>
      <category>usb</category>
    </item>
    <item>
      <title>Drive 16 (or More) LEDs with Two 74HC595 Shift Registers Using Only 3 Arduino Pins</title>
      <dc:creator>ai pics</dc:creator>
      <pubDate>Fri, 10 Oct 2025 02:26:27 +0000</pubDate>
      <link>https://dev.to/ai_pics_6442ad429fc2ff12f/drive-16-or-more-leds-with-two-74hc595-shift-registers-using-only-3-arduino-pins-5af8</link>
      <guid>https://dev.to/ai_pics_6442ad429fc2ff12f/drive-16-or-more-leds-with-two-74hc595-shift-registers-using-only-3-arduino-pins-5af8</guid>
      <description>&lt;p&gt;Why this project?&lt;/p&gt;

&lt;p&gt;Arduino boards run out of GPIO pins fast when you start doing LED patterns or building a control panel. The 74HC595 serial-in/parallel-out (SIPO) shift register lets you trade a few pins (data, clock, latch) for many outputs. Each chip adds 8 outputs, and by chaining &lt;a href="https://mozelectronics.com/semiconductor-ics/" rel="noopener noreferrer"&gt;IC chips&lt;/a&gt; you can scale well beyond 16 channels—limited mainly by signal integrity, update speed, and power.&lt;/p&gt;

&lt;p&gt;This guide shows you how to:&lt;br&gt;
Wire two 74HC595s to control 16 LEDs using only three Arduino pins&lt;br&gt;
Add more chips with no code rewrite (change one number)&lt;br&gt;
Use PWM on OE for global brightness&lt;br&gt;
Initialize cleanly to avoid “mystery LEDs” turning on at power-up&lt;br&gt;
Organize code so each LED is addressed by a single channel index (0..N-1)&lt;br&gt;
Bill of Materials&lt;/p&gt;

&lt;p&gt;Required&lt;/p&gt;

&lt;p&gt;1 × Arduino Uno (or any 5 V-logic compatible board)&lt;/p&gt;

&lt;p&gt;2 × &lt;a href="https://mozelectronics.com/tutorials/74hc595-shift-register-pinout-datasheet-arduino/" rel="noopener noreferrer"&gt;74HC595 shift registers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;16 × LEDs&lt;/p&gt;

&lt;p&gt;16 × 220 Ω resistors (one per LED; 180–330 Ω is typical—see Current section)&lt;/p&gt;

&lt;p&gt;Breadboard(s) and jumper wires&lt;/p&gt;

&lt;p&gt;Optional but recommended&lt;/p&gt;

&lt;p&gt;2 × 0.1 µF &lt;a href="https://mozelectronics.com/passive-components/capacitors-and-capacitor-kits/ceramic-capacitors/" rel="noopener noreferrer"&gt;ceramic capacitors&lt;/a&gt; (one per 74HC595 between VCC and GND for decoupling)&lt;/p&gt;

&lt;p&gt;1 × External 5 V supply if you plan to light many LEDs at once&lt;/p&gt;

&lt;p&gt;1 × Potentiometer (if you insist on analog dimming in series—not recommended)&lt;/p&gt;

&lt;p&gt;1 × PWM-capable Arduino pin wired to OE for global brightness (recommended)&lt;/p&gt;

&lt;p&gt;How the 74HC595 Works (Quickly)&lt;/p&gt;

&lt;p&gt;DS (SER, pin 14): Data in (one bit per clock)&lt;/p&gt;

&lt;p&gt;SH_CP (SRCLK, pin 11): Shift clock; on rising edge the bit on DS enters the shift register&lt;/p&gt;

&lt;p&gt;ST_CP (RCLK, pin 12): Latch clock; rising edge copies the internal shift register to outputs Q0..Q7&lt;/p&gt;

&lt;p&gt;Q7′ (pin 9): Serial data out; chain this to the next chip’s DS&lt;/p&gt;

&lt;p&gt;OE (pin 13): Output enable, active low (LOW = outputs active). Tie to GND, or to a PWM pin for dimming&lt;/p&gt;

&lt;p&gt;MR (pin 10): Master reset, active low. Keep HIGH for normal use. Pulse LOW to clear all bits&lt;/p&gt;

&lt;p&gt;Data is clocked into the first chip; bits ripple toward the last chip. On latch, all outputs update together—zero flicker if you write quickly.&lt;/p&gt;

&lt;p&gt;Wiring (Two Chips → 16 LEDs)&lt;br&gt;
Common Power/Control&lt;/p&gt;

&lt;p&gt;VCC (pin 16) → 5 V&lt;/p&gt;

&lt;p&gt;GND (pin 8) → GND&lt;/p&gt;

&lt;p&gt;0.1 µF between VCC and GND on each chip (close to the IC)&lt;/p&gt;

&lt;p&gt;Control Lines (Arduino → both chips)&lt;/p&gt;

&lt;p&gt;SH_CP (pin 11) → Arduino D12 (clock)&lt;/p&gt;

&lt;p&gt;ST_CP (pin 12) → Arduino D8 (latch)&lt;/p&gt;

&lt;p&gt;OE (pin 13) → GND (or a PWM pin like D3 for global brightness)&lt;/p&gt;

&lt;p&gt;MR (pin 10) → 5 V (or a digital pin if you want software reset)&lt;/p&gt;

&lt;p&gt;Serial Data &amp;amp; Daisy-Chain&lt;/p&gt;

&lt;p&gt;Chip 1 DS (pin 14) → Arduino D11 (data)&lt;/p&gt;

&lt;p&gt;Chip 1 Q7′ (pin 9) → Chip 2 DS (pin 14)&lt;/p&gt;

&lt;p&gt;If you add a third chip: Chip 2 Q7′ → Chip 3 DS, and so on&lt;/p&gt;

&lt;p&gt;LEDs&lt;/p&gt;

&lt;p&gt;For each chip:&lt;/p&gt;

&lt;p&gt;Q0..Q7 (pins 15, 1, 2, 3, 4, 5, 6, 7) → 220 Ω resistor → LED → GND&lt;br&gt;
(You can reverse LED polarity if you prefer sourcing vs sinking current; the code is the same—just invert logic if needed.)&lt;/p&gt;

&lt;p&gt;Channel Numbering (Simple Mental Model)&lt;/p&gt;

&lt;p&gt;The chip closest to Arduino is register 0: it controls channels 0..7&lt;/p&gt;

&lt;p&gt;The next chip is register 1: channels 8..15&lt;/p&gt;

&lt;p&gt;With N chips, channels go 0..(8N−1)&lt;br&gt;
The code below lets you call regWrite(channel, state) without caring which register or bit that is.&lt;/p&gt;

&lt;p&gt;Power &amp;amp; Current: What You Can Safely Drive&lt;/p&gt;

&lt;p&gt;Typical LED current with 220 Ω at 5 V is ~5–10 mA depending on LED color (VF).&lt;br&gt;
Example (red LED): (5 V − 2.0 V) / 220 Ω ≈ 13.6 mA (often too bright; many LEDs are fine at 5 mA)&lt;/p&gt;

&lt;p&gt;The 74HC595 can’t source/sink large current on all pins simultaneously. Keep per-pin current ≤ 8 mA and total per chip ≤ ~50 mA (check your datasheet).&lt;/p&gt;

&lt;p&gt;If you need to drive lots of LEDs at once at higher current, use transistor arrays (e.g., ULN2803) or MOSFETs, or multiplex with 74HC595 + 74HC138 etc.&lt;/p&gt;

&lt;p&gt;For many simultaneously-on LEDs, use a separate 5 V supply for the LED side and common GND with Arduino.&lt;/p&gt;

&lt;p&gt;Avoid “Random LEDs on at Power-Up”&lt;/p&gt;

&lt;p&gt;At startup, the internal register has undefined contents.&lt;/p&gt;

&lt;p&gt;Software fix: Immediately write zeros and latch in setup()&lt;/p&gt;

&lt;p&gt;Hardware fix: Tie MR to an Arduino pin; briefly drive LOW→HIGH after boot&lt;/p&gt;

&lt;p&gt;No-glare boot: Hold OE HIGH (outputs off), preload zeros, then set OE LOW&lt;/p&gt;

&lt;p&gt;Why PWM on OE Beats a Series Pot&lt;/p&gt;

&lt;p&gt;Putting a pot in series with all LEDs changes current and can cause color mismatch and uneven brightness. Driving OE from a PWM pin keeps per-LED resistors fixed and dims everything uniformly via duty cycle. (Remember: OE is active LOW. More PWM duty = darker unless you invert it in code.)&lt;/p&gt;

&lt;p&gt;Step-by-Step Assembly&lt;/p&gt;

&lt;p&gt;Place the two &lt;a href="https://mozelectronics.com/parts/texas-instruments-sn74hc595n-3104/" rel="noopener noreferrer"&gt;SN74HC595N&lt;/a&gt; on the breadboard with power rails connected; add 0.1 µF caps per chip.&lt;/p&gt;

&lt;p&gt;Wire Arduino D11→DS(14), D12→SH_CP(11), D8→ST_CP(12). Tie OE(13)→GND and MR(10)→5 V (or to Arduino pins as described).&lt;/p&gt;

&lt;p&gt;Connect Chip 1 Q7′(9)→Chip 2 DS(14).&lt;/p&gt;

&lt;p&gt;Add resistors from each Q pin to its LED, and LEDs back to GND.&lt;/p&gt;

&lt;p&gt;Double-check power and grounds; verify no shorts.&lt;/p&gt;

&lt;p&gt;Upload the sketch and test.&lt;/p&gt;

&lt;p&gt;The Code (drop-in, scalable)&lt;/p&gt;

&lt;p&gt;Change NUM_REGS to match how many 74HC595s you chained.&lt;/p&gt;

&lt;p&gt;Use regWrite(channel, state) to set any LED.&lt;/p&gt;

&lt;p&gt;flush() pushes the entire state array out.&lt;/p&gt;

&lt;p&gt;Optional OE_PIN for global brightness via analogWrite.&lt;/p&gt;

&lt;p&gt;// ===== User Configuration =====&lt;br&gt;
const int DATA_PIN  = 11;  // DS  -&amp;gt; 74HC595 pin 14&lt;br&gt;
const int CLOCK_PIN = 12;  // SH_CP-&amp;gt; 74HC595 pin 11&lt;br&gt;
const int LATCH_PIN = 8;   // ST_CP-&amp;gt; 74HC595 pin 12&lt;/p&gt;

&lt;p&gt;const int MR_PIN = -1;     // Tie to 5V or assign a pin (active LOW). -1 = tied HIGH.&lt;br&gt;
const int OE_PIN = -1;     // Tie to GND or assign a PWM pin (active LOW). -1 = tied LOW.&lt;/p&gt;

&lt;p&gt;const int NUM_REGS = 2;    // 2 chips = 16 channels; set to your chain length&lt;br&gt;
// ==============================&lt;/p&gt;

&lt;p&gt;byte regs[NUM_REGS]; // Each byte is Q0..Q7; bit 0 -&amp;gt; Q0, bit 7 -&amp;gt; Q7&lt;/p&gt;

&lt;p&gt;inline void latch() {&lt;br&gt;
  digitalWrite(LATCH_PIN, LOW);&lt;br&gt;
  digitalWrite(LATCH_PIN, HIGH);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Push regs[] to the chain (send farthest chip first)&lt;br&gt;
void flush() {&lt;br&gt;
  digitalWrite(LATCH_PIN, LOW);&lt;br&gt;
  for (int i = NUM_REGS - 1; i &amp;gt;= 0; --i) {&lt;br&gt;
    shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, regs[i]);&lt;br&gt;
  }&lt;br&gt;
  digitalWrite(LATCH_PIN, HIGH);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;void clearAll() {&lt;br&gt;
  for (int i = 0; i &amp;lt; NUM_REGS; ++i) regs[i] = 0;&lt;br&gt;
  flush();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Set one channel (0..NUM_REGS*8-1), then flush&lt;br&gt;
void regWrite(int channel, bool state) {&lt;br&gt;
  if (channel &amp;lt; 0 || channel &amp;gt;= NUM_REGS * 8) return;&lt;br&gt;
  int r = channel / 8;         // which register&lt;br&gt;
  int b = channel % 8;         // which bit (0=Q0 .. 7=Q7)&lt;br&gt;
  bitWrite(regs[r], b, state);&lt;br&gt;
  flush();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Optionally set all 8-bit registers at once then flush (length must be NUM_REGS)&lt;br&gt;
void writeAll(const byte* values) {&lt;br&gt;
  for (int i = 0; i &amp;lt; NUM_REGS; ++i) regs[i] = values[i];&lt;br&gt;
  flush();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;void setup() {&lt;br&gt;
  pinMode(DATA_PIN,  OUTPUT);&lt;br&gt;
  pinMode(CLOCK_PIN, OUTPUT);&lt;br&gt;
  pinMode(LATCH_PIN, OUTPUT);&lt;/p&gt;

&lt;p&gt;if (MR_PIN &amp;gt;= 0) {&lt;br&gt;
    pinMode(MR_PIN, OUTPUT);&lt;br&gt;
    digitalWrite(MR_PIN, HIGH); // keep not-reset (LOW would clear)&lt;br&gt;
  }&lt;br&gt;
  if (OE_PIN &amp;gt;= 0) {&lt;br&gt;
    pinMode(OE_PIN, OUTPUT);&lt;br&gt;
    digitalWrite(OE_PIN, LOW);  // enable outputs (LOW = on)&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// Clean startup&lt;br&gt;
  clearAll();&lt;/p&gt;

&lt;p&gt;// If you wired MR to a pin and want to hard-reset at boot:&lt;br&gt;
  // if (MR_PIN &amp;gt;= 0) { digitalWrite(MR_PIN, LOW); delay(1); digitalWrite(MR_PIN, HIGH); }&lt;/p&gt;

&lt;p&gt;// If you wired OE to a pin and want outputs disabled during init:&lt;br&gt;
  // if (OE_PIN &amp;gt;= 0) { digitalWrite(OE_PIN, HIGH); /* preload zeros */ clearAll(); digitalWrite(OE_PIN, LOW); }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;void loop() {&lt;br&gt;
  const int N = NUM_REGS * 8;&lt;/p&gt;

&lt;p&gt;// 1) Light up one-by-one&lt;br&gt;
  for (int i = 0; i &amp;lt; N; ++i) { regWrite(i, true); delay(60); }&lt;br&gt;
  delay(200);&lt;br&gt;
  for (int i = N - 1; i &amp;gt;= 0; --i) { regWrite(i, false); delay(40); }&lt;br&gt;
  delay(200);&lt;/p&gt;

&lt;p&gt;// 2) Single "runner" back and forth&lt;br&gt;
  clearAll();&lt;br&gt;
  for (int pass = 0; pass &amp;lt; 2; ++pass) {&lt;br&gt;
    for (int i = 0; i &amp;lt; N; ++i) { clearAll(); regWrite(i, true); delay(40); }&lt;br&gt;
    for (int i = N - 1; i &amp;gt;= 0; --i) { clearAll(); regWrite(i, true); delay(40); }&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// 3) Even/odd blink pattern&lt;br&gt;
  byte evenMask = 0b01010101; // Q0,2,4,6&lt;br&gt;
  byte oddMask  = 0b10101010; // Q1,3,5,7&lt;br&gt;
  for (int k = 0; k &amp;lt; 6; ++k) {&lt;br&gt;
    for (int r = 0; r &amp;lt; NUM_REGS; ++r) regs[r] = (k % 2 == 0) ? evenMask : oddMask;&lt;br&gt;
    flush();&lt;br&gt;
    delay(180);&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// 4) Global brightness sweep via OE (if connected to PWM pin)&lt;br&gt;
  // NOTE: OE is active LOW. We invert the duty with (255 - d).&lt;br&gt;
  /*&lt;br&gt;
  if (OE_PIN &amp;gt;= 0) {&lt;br&gt;
    for (int r = 0; r &amp;lt; NUM_REGS; ++r) regs[r] = 0xFF; // all on&lt;br&gt;
    flush();&lt;br&gt;
    for (int d = 0; d &amp;lt;= 255; d += 5) { analogWrite(OE_PIN, 255 - d); delay(8); }&lt;br&gt;
    for (int d = 255; d &amp;gt;= 0; d -= 5) { analogWrite(OE_PIN, 255 - d); delay(8); }&lt;br&gt;
  }&lt;br&gt;
  */&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Scaling to More LEDs&lt;/p&gt;

&lt;p&gt;Hardware: Chain Q7′ of the last chip to DS of the new chip; share clock, latch, OE, MR, VCC, GND.&lt;/p&gt;

&lt;p&gt;Software: Set NUM_REGS to your new count. Your channel numbers keep increasing linearly (e.g., with 3 chips, channels 0–23).&lt;/p&gt;

&lt;p&gt;Troubleshooting&lt;/p&gt;

&lt;p&gt;Some LEDs randomly on at power-up&lt;br&gt;
Initialize with clearAll() in setup(); optionally wire MR and/or OE to Arduino pins as explained.&lt;/p&gt;

&lt;p&gt;Nothing lights&lt;br&gt;
Check VCC/GND, verify latch wiring (ST_CP). Make sure you call flush() or use regWrite() which calls it for you.&lt;/p&gt;

&lt;p&gt;Only first 8 work&lt;br&gt;
Q7′(pin 9) of chip 1 must go to DS(pin 14) of chip 2. Also confirm you’re shifting MSBFIRST and sending the last register first in the loop.&lt;/p&gt;

&lt;p&gt;Flicker or unreliable updates with many chips&lt;br&gt;
Lower clock rate (use shiftOut as is, or add small delays), keep wires short, add decoupling capacitors, ensure solid ground. Consider buffering if chain gets long.&lt;/p&gt;

&lt;p&gt;Uneven brightness&lt;br&gt;
Use individual current-limiting resistors per LED and dim via OE PWM, not a shared series potentiometer.&lt;/p&gt;

&lt;p&gt;Frequently Asked (Useful) Variations&lt;/p&gt;

&lt;p&gt;Can I multiplex instead to reduce current and chips?&lt;br&gt;
Yes. For matrixes (e.g., 8×8), pair 74HC595 with a row/column driver (like ULN2803, TPIC6B595, or a 74HC138) and scan rows. Code is different but very scalable.&lt;/p&gt;

&lt;p&gt;What about SPI for speed?&lt;br&gt;
You can wire DS→MOSI, SH_CP→SCK, and manually toggle ST_CP as latch. Then use SPI.transfer() for much faster updates than shiftOut().&lt;/p&gt;

&lt;p&gt;Can I use 3.3 V boards?&lt;br&gt;
74HC595 typically works at 3.3–5 V. Check your particular HC family and ensure LED current and logic thresholds are respected.&lt;/p&gt;

&lt;p&gt;Summary&lt;/p&gt;

&lt;p&gt;Two 74HC595s = 16 LED channels using just 3 Arduino pins&lt;/p&gt;

&lt;p&gt;Global brightness via OE on a PWM pin is cleaner than a series potentiometer&lt;/p&gt;

&lt;p&gt;Add more chips: wire Q7′→DS, change NUM_REGS, done&lt;/p&gt;

&lt;p&gt;The provided code presents a universal channel interface and example effects you can extend&lt;/p&gt;

&lt;p&gt;If you want, I can also provide a version wrapped as a small C++ class (with non-blocking timers for smooth patterns) or an SPI-accelerated variant for longer chains.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>arduino</category>
    </item>
    <item>
      <title>STM32 Internal Temperature Sensor Reading (With DMA + Timer Trigger) — Complete Guide &amp; Example Code</title>
      <dc:creator>ai pics</dc:creator>
      <pubDate>Thu, 25 Sep 2025 02:48:23 +0000</pubDate>
      <link>https://dev.to/ai_pics_6442ad429fc2ff12f/stm32-internal-temperature-sensor-reading-with-dma-timer-trigger-complete-guide-example-code-cjk</link>
      <guid>https://dev.to/ai_pics_6442ad429fc2ff12f/stm32-internal-temperature-sensor-reading-with-dma-timer-trigger-complete-guide-example-code-cjk</guid>
      <description>&lt;p&gt;STM32 &lt;a href="https://mozelectronics.com/semiconductor-ics/embedded-processors-and-controllers/microcontrollers-mcus/" rel="noopener noreferrer"&gt;microcontrollers&lt;/a&gt; include an internal temperature sensor connected to a dedicated ADC channel. The sensor is intended primarily for monitoring the temperature of the silicon die rather than measuring ambient temperature. It is therefore best suited to thermal trend detection, system health monitoring, thermal throttling, and failsafe logic.&lt;/p&gt;

&lt;p&gt;With an adequate ADC sampling time, compensation for supply-voltage changes, deterministic triggering, and basic calibration, the internal sensor can still provide stable and repeatable readings for many embedded applications.&lt;/p&gt;

&lt;p&gt;This tutorial shows how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable the internal temperature sensor and VREFINT channels&lt;/li&gt;
&lt;li&gt;Trigger ADC conversions at 50 Hz using TIM3 TRGO&lt;/li&gt;
&lt;li&gt;Transfer two ADC channels through DMA in circular mode&lt;/li&gt;
&lt;li&gt;Compensate for VDD variation using VREFINT&lt;/li&gt;
&lt;li&gt;Convert the measured sensor voltage into degrees Celsius&lt;/li&gt;
&lt;li&gt;Calibrate and stabilize the result for a real product&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Always check the datasheet and reference manual for your exact STM32 part number. Temperature-sensor constants, factory calibration data, channel names, ADC resolution, trigger routing, and initialization details vary between STM32 families and sometimes between device revisions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What the Internal Temperature Sensor Measures&lt;/li&gt;
&lt;li&gt;Measurement Flow and Conversion Equation&lt;/li&gt;
&lt;li&gt;Project Architecture: A 50 Hz Sampling Pipeline&lt;/li&gt;
&lt;li&gt;STM32CubeMX Configuration&lt;/li&gt;
&lt;li&gt;HAL Example Code&lt;/li&gt;
&lt;li&gt;Calibration and Accuracy Tips&lt;/li&gt;
&lt;li&gt;PCB Implementation Considerations&lt;/li&gt;
&lt;li&gt;Troubleshooting FAQ&lt;/li&gt;
&lt;li&gt;Wrap-Up&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. What the Internal Temperature Sensor Measures
&lt;/h2&gt;

&lt;p&gt;The STM32 temperature sensor produces a voltage, normally identified as &lt;strong&gt;VSENSE&lt;/strong&gt;, that changes with die temperature. This voltage is routed internally to an ADC channel; no external sensor pin is required.&lt;/p&gt;

&lt;p&gt;A second internal ADC channel, &lt;strong&gt;VREFINT&lt;/strong&gt;, exposes the MCU's internal bandgap reference. By measuring VREFINT, the firmware can estimate the actual analog supply voltage and compensate the VSENSE calculation for VDD variation.&lt;/p&gt;

&lt;p&gt;This distinction matters because the ADC result is a ratio between the input voltage and the ADC reference. If the firmware assumes a fixed 3.3 V supply while the real VDD changes, the calculated temperature will drift even if the die temperature remains constant.&lt;/p&gt;

&lt;p&gt;The internal sensor is useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detecting rising or falling die temperature&lt;/li&gt;
&lt;li&gt;Initiating thermal throttling or a controlled shutdown&lt;/li&gt;
&lt;li&gt;Recording thermal behavior during system tests&lt;/li&gt;
&lt;li&gt;Comparing operating modes or firmware workloads&lt;/li&gt;
&lt;li&gt;Detecting abnormal self-heating&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not the right choice when the application requires precise room, enclosure, battery, surface, or remote-object temperature measurement. Those tasks normally require a properly positioned external sensor.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Measurement Flow and Conversion Equation
&lt;/h2&gt;

&lt;p&gt;The basic measurement sequence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable the internal temperature-sensor and VREFINT paths.&lt;/li&gt;
&lt;li&gt;Configure a sampling time that meets the datasheet minimum.&lt;/li&gt;
&lt;li&gt;Trigger a two-channel ADC sequence at a fixed rate.&lt;/li&gt;
&lt;li&gt;Read VREFINT and VSENSE.&lt;/li&gt;
&lt;li&gt;Estimate the actual VDD from VREFINT.&lt;/li&gt;
&lt;li&gt;Convert the raw temperature-sensor code into VSENSE.&lt;/li&gt;
&lt;li&gt;Convert VSENSE into temperature using the correct device constants.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For STM32 devices that use the typical V25 and average-slope method, the equation has this form:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Temperature (°C) = ((V25 − VSENSE) / Avg_Slope) + 25&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;V25&lt;/strong&gt; is the sensor output voltage at 25 °C, approximately 1.43 V for many STM32F1 parts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avg_Slope&lt;/strong&gt; is the average sensor slope, approximately 4.3 mV/°C for many STM32F1 parts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VSENSE&lt;/strong&gt; is calculated from the raw ADC code after VDD compensation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A 12-bit ADC has 4,096 quantization levels. At a 3.3 V reference, one ideal LSB is approximately 0.806 mV. The &lt;a href="https://resicalc.com/en/tools/adc-dac-resolution-calculator/" rel="noopener noreferrer"&gt;ADC/DAC resolution calculator&lt;/a&gt; is useful for checking LSB size, quantization limits, and the effect of reference voltage before comparing expected code changes with measured data.&lt;/p&gt;

&lt;p&gt;If the MCU provides factory-programmed temperature calibration values such as &lt;strong&gt;TS_CAL1&lt;/strong&gt; and &lt;strong&gt;TS_CAL2&lt;/strong&gt;, use them instead of generic V25 and Avg_Slope values. Two-point factory calibration usually provides a better starting point because it accounts for device-to-device variation.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Project Architecture: A 50 Hz Sampling Pipeline
&lt;/h2&gt;

&lt;p&gt;The example uses a simple deterministic pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TIM3 generates a TRGO update event every 20 ms, producing a 50 Hz trigger rate.&lt;/li&gt;
&lt;li&gt;ADC1 uses the timer event as the external trigger for its regular conversion group.&lt;/li&gt;
&lt;li&gt;Each trigger starts a two-channel sequence: VREFINT first, then TempSensor.&lt;/li&gt;
&lt;li&gt;DMA transfers both results into a two-element buffer in circular mode.&lt;/li&gt;
&lt;li&gt;The conversion-complete callback toggles a GPIO and marks the new sample as ready.&lt;/li&gt;
&lt;li&gt;The main loop estimates VDD, calculates VSENSE, converts it to temperature, and sends the result through UART.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The example target is the &lt;a href="https://octatronics.com/components/stmicroelectronics-stm32f103c8t6" rel="noopener noreferrer"&gt;STM32F103C8T6&lt;/a&gt;, a 72 MHz Arm Cortex-M3 MCU with 12-bit ADCs, timers, DMA, and common serial interfaces. The same architecture applies to many other STM32 devices, but the internal channel definitions, calibration addresses, and HAL configuration can differ.&lt;/p&gt;

&lt;p&gt;Using a timer rather than repeated software starts gives the ADC a consistent sampling interval. DMA keeps the two channel readings together and reduces timing variation caused by firmware execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. STM32CubeMX Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  MCU or Board
&lt;/h3&gt;

&lt;p&gt;The following settings are based on an STM32F103C8 device, such as a Blue Pill development board. Adapt the clock tree, ADC channels, and trigger source to the exact MCU used in your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  RCC and Clock Tree
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use HSE with the PLL to obtain a 72 MHz system clock when appropriate for the board.&lt;/li&gt;
&lt;li&gt;Keep the ADC clock within the datasheet limit. For an STM32F103 running PCLK2 at 72 MHz, a prescaler of 6 gives a 12 MHz ADC clock, below the 14 MHz maximum specified for this family.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ADC1
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Enable scan conversion mode.&lt;/li&gt;
&lt;li&gt;Set the regular sequence length to two conversions.&lt;/li&gt;
&lt;li&gt;Configure Rank 1 as VREFINT and Rank 2 as TempSensor.&lt;/li&gt;
&lt;li&gt;Disable continuous conversion; conversions will be started by the external timer trigger.&lt;/li&gt;
&lt;li&gt;Select TIM3 TRGO as the external trigger.&lt;/li&gt;
&lt;li&gt;Choose a sampling time that meets or exceeds the temperature sensor's minimum acquisition time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, with a 12 MHz ADC clock, 239.5 sampling cycles correspond to approximately 19.96 µs, which is longer than the commonly specified 17 µs minimum for STM32F1 internal channels.&lt;/p&gt;

&lt;p&gt;CubeMX normally generates the required internal-channel enable logic, but verify that the temperature-sensor/VREFINT path is enabled on your device. On STM32F1, this is controlled through the TSVREFE bit.&lt;/p&gt;

&lt;h3&gt;
  
  
  DMA
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add the ADC1 DMA channel.&lt;/li&gt;
&lt;li&gt;Select circular mode.&lt;/li&gt;
&lt;li&gt;Use halfword peripheral and memory alignment.&lt;/li&gt;
&lt;li&gt;Enable memory increment.&lt;/li&gt;
&lt;li&gt;Set the buffer length to two halfwords.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  TIM3
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Select the internal clock source.&lt;/li&gt;
&lt;li&gt;Configure the prescaler and auto-reload values for a 20 ms update interval.&lt;/li&gt;
&lt;li&gt;Set TRGO to &lt;strong&gt;Update Event&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At a 72 MHz timer clock, one valid example is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prescaler: 23&lt;/li&gt;
&lt;li&gt;Auto-reload: 59,999&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The timer update frequency is then:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;72 MHz / (23 + 1) / (59,999 + 1) = 50 Hz&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  USART1
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Configure 115200 baud, 8 data bits, no parity, and 1 stop bit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GPIO
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Configure one output, such as PB0, as a timing probe.&lt;/li&gt;
&lt;li&gt;Toggle it in the ADC conversion-complete callback.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. HAL Example Code
&lt;/h2&gt;

&lt;p&gt;This example uses V25 = 1.43 V, Avg_Slope = 4.3 mV/°C, and a typical VREFINT value of 1.20 V. These values are commonly associated with STM32F1 devices but must be checked against the datasheet for the exact part.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/*
 * Demo: STM32 internal temperature sensor
 * ADC + DMA + TIM3 TRGO at 50 Hz
 * Target style: STM32F103; adjust constants and configuration for your MCU
 */&lt;/span&gt;

&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;"main.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cm"&gt;/* Datasheet parameters: verify for the exact MCU */&lt;/span&gt;
&lt;span class="cp"&gt;#define AVG_SLOPE_mV_per_C   (4.3f)
#define V_AT_25C_V           (1.43f)
#define VREFINT_TYP_V        (1.20f)
&lt;/span&gt;
&lt;span class="n"&gt;ADC_HandleTypeDef&lt;/span&gt;   &lt;span class="n"&gt;hadc1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;DMA_HandleTypeDef&lt;/span&gt;   &lt;span class="n"&gt;hdma_adc1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;TIM_HandleTypeDef&lt;/span&gt;   &lt;span class="n"&gt;htim3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;UART_HandleTypeDef&lt;/span&gt;  &lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/* Rank 1 = VREFINT, Rank 2 = VSENSE */&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;adc_buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;new_sample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;vdd_V&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;vsense_V&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;temperature_C&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;SystemClock_Config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MX_GPIO_Init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MX_DMA_Init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MX_ADC1_Init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MX_TIM3_Init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MX_USART1_UART_Init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;HAL_Init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;SystemClock_Config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;MX_GPIO_Init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;MX_DMA_Init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;MX_ADC1_Init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;MX_TIM3_Init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;MX_USART1_UART_Init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="cm"&gt;/* Calibrate the ADC before starting conversions. */&lt;/span&gt;
  &lt;span class="n"&gt;HAL_ADCEx_Calibration_Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;hadc1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="cm"&gt;/* Arm ADC + DMA first, then start the timer trigger. */&lt;/span&gt;
  &lt;span class="n"&gt;HAL_ADC_Start_DMA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;hadc1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;adc_buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;HAL_TIM_Base_Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;htim3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(;;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_sample&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;vrefint_raw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;vsense_raw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="cm"&gt;/* Clear the flag, then take a local snapshot of the DMA buffer. */&lt;/span&gt;
      &lt;span class="n"&gt;new_sample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;vrefint_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adc_buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="n"&gt;vsense_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adc_buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vrefint_raw&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0U&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;adc_fullscale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4095&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="cm"&gt;/* Estimate VDD from the typical internal reference value. */&lt;/span&gt;
      &lt;span class="n"&gt;vdd_V&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VREFINT_TYP_V&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;adc_fullscale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;vrefint_raw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="cm"&gt;/* Convert the temperature-sensor ADC code into volts. */&lt;/span&gt;
      &lt;span class="n"&gt;vsense_V&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;vsense_raw&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;vdd_V&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;adc_fullscale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="cm"&gt;/* Convert VSENSE into degrees Celsius. */&lt;/span&gt;
      &lt;span class="n"&gt;temperature_C&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
          &lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;V_AT_25C_V&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;vsense_V&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
           &lt;span class="n"&gt;AVG_SLOPE_mV_per_C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"%.2f&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature_C&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;HAL_UART_Transmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint16_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;HAL_ADC_ConvCpltCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ADC_HandleTypeDef&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;hadc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hadc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ADC1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;HAL_GPIO_TogglePin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GPIOB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GPIO_PIN_0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;new_sample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the device provides a factory VREFINT calibration code measured at a known voltage, calculate VDD with the calibration value instead of the typical 1.20 V assumption:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VDD = (VREF_CAL_VOLTAGE × VREFINT_CAL_CODE) / ADC[VREFINT]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If TS_CAL1 and TS_CAL2 are available, scale the current ADC result to the calibration voltage and interpolate between the two temperature points. Follow the formula given in the reference manual or datasheet for that STM32 family.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Calibration and Accuracy Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use the Correct Sampling Time
&lt;/h3&gt;

&lt;p&gt;The internal temperature-sensor path has a relatively high source impedance and needs enough acquisition time to charge the ADC sample-and-hold capacitor. A sampling time below the datasheet minimum can create unstable or biased readings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compensate for VDD Variation
&lt;/h3&gt;

&lt;p&gt;Read VREFINT in the same sequence as VSENSE. Using a fixed assumed supply voltage can turn ordinary regulator tolerance, USB voltage changes, or load transients into apparent temperature changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefer Factory Calibration
&lt;/h3&gt;

&lt;p&gt;Factory-programmed VREFINT_CAL, TS_CAL1, and TS_CAL2 values are usually better than family-level typical constants because they account for some device-to-device variation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Average Sensibly
&lt;/h3&gt;

&lt;p&gt;Thermal behavior changes slowly. An 8- or 16-sample moving average is often enough to reduce visible jitter. Sampling at 10–50 Hz is normally adequate for thermal monitoring; very high sampling rates add processing and data without providing useful thermal bandwidth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calibrate in the Final Hardware
&lt;/h3&gt;

&lt;p&gt;If absolute accuracy matters, compare the reading with a known-good external sensor placed close to the MCU package. Allow the system to reach thermal equilibrium, collect measurements at several operating points, and fit an offset or two-point correction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remember What Is Being Measured
&lt;/h3&gt;

&lt;p&gt;The reading is die temperature. CPU activity, clock frequency, flash accesses, regulator losses, nearby power devices, copper area, airflow, and enclosure conditions all affect it. The value should not be expected to match room temperature.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. PCB Implementation Considerations
&lt;/h2&gt;

&lt;p&gt;Firmware compensation cannot correct every hardware problem. On a custom board, keep analog supply and reference decoupling close to the MCU, follow the manufacturer's grounding guidance, and prevent fast digital or switching-current return paths from disturbing the ADC reference network.&lt;/p&gt;

&lt;p&gt;Board layout also changes the thermal result. A large copper area under and around the MCU can spread heat, while a nearby regulator, MOSFET, or high-current trace can warm the package. Test points for VDD, VDDA, the timing-probe GPIO, and relevant power rails make bring-up much easier.&lt;/p&gt;

&lt;p&gt;When moving from a development board to a custom design, a small &lt;a href="https://mozpcb.com/prototype-pcb/" rel="noopener noreferrer"&gt;prototype PCB build&lt;/a&gt; gives you an opportunity to validate ADC noise, decoupling, trigger timing, thermal coupling, and sensor calibration before committing to production quantities.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Troubleshooting FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why is the temperature reading noisy or unstable?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Increase the ADC sampling time.&lt;/li&gt;
&lt;li&gt;Average several readings.&lt;/li&gt;
&lt;li&gt;Confirm that TIM3 TRGO is actually selected as the ADC trigger.&lt;/li&gt;
&lt;li&gt;Check VDDA decoupling and the analog supply layout.&lt;/li&gt;
&lt;li&gt;Verify that the DMA buffer order matches the configured ADC ranks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why does the reading drift when VDD changes?
&lt;/h3&gt;

&lt;p&gt;The calculation is probably using a fixed reference voltage. Measure VREFINT during every sequence and use it to estimate the actual VDD.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do I get an obviously wrong result, such as −20 °C at room temperature?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Check whether VREFINT and TempSensor are reversed in the DMA buffer.&lt;/li&gt;
&lt;li&gt;Verify that V25, Avg_Slope, and any calibration addresses match the exact MCU.&lt;/li&gt;
&lt;li&gt;Confirm the ADC clock, resolution, and channel sampling time.&lt;/li&gt;
&lt;li&gt;Make sure the internal sensor/VREFINT path is enabled.&lt;/li&gt;
&lt;li&gt;Check whether the formula's slope direction matches the device documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How do I verify the 50 Hz sampling rate?
&lt;/h3&gt;

&lt;p&gt;Toggle a GPIO in &lt;code&gt;HAL_ADC_ConvCpltCallback()&lt;/code&gt; and inspect it with an oscilloscope or logic analyzer. The pin changes state once per completed two-channel sequence, so transitions should occur every 20 ms. Because the pin alternates high and low, the complete square-wave period is 40 ms, corresponding to a displayed waveform frequency of 25 Hz.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I read the sensor without DMA?
&lt;/h3&gt;

&lt;p&gt;Yes. Polling or interrupt-driven conversions can work, especially for a slow diagnostic measurement. DMA is useful because it keeps both channel results together, reduces CPU involvement, and supports a predictable timer-driven pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does &lt;code&gt;snprintf()&lt;/code&gt; not print the floating-point value?
&lt;/h3&gt;

&lt;p&gt;Some embedded C library configurations disable floating-point formatting to save flash. Enable float support in the linker settings or transmit a scaled integer value instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should UART transmit at every 50 Hz sample?
&lt;/h3&gt;

&lt;p&gt;It is acceptable for a short lab test at 115200 baud, but production firmware should avoid blocking communication in a time-sensitive loop. Consider decimating the log rate, buffering messages, or using DMA for UART transmission.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Wrap-Up
&lt;/h2&gt;

&lt;p&gt;With timer-triggered ADC conversions, circular DMA, and VREFINT compensation, the STM32 internal temperature &lt;a href="https://mozelectronics.com/sensors/" rel="noopener noreferrer"&gt;sensor&lt;/a&gt; becomes a practical monitor for thermal trends, system health, throttling, and safety logic.&lt;/p&gt;

&lt;p&gt;For the most reliable result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the datasheet sampling-time requirement.&lt;/li&gt;
&lt;li&gt;Trigger measurements at a consistent rate.&lt;/li&gt;
&lt;li&gt;Measure VREFINT alongside VSENSE.&lt;/li&gt;
&lt;li&gt;Prefer factory calibration data where available.&lt;/li&gt;
&lt;li&gt;Validate the result on the final PCB under realistic loads and airflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The internal sensor will not replace a precision ambient probe, but it can provide valuable information about what is happening inside the MCU and help firmware respond before thermal stress becomes a system failure.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>stm32</category>
      <category>code</category>
    </item>
    <item>
      <title>How to Optimize HLS Designs for FPGAs (A Practical, Vendor-Agnostic Playbook)</title>
      <dc:creator>ai pics</dc:creator>
      <pubDate>Mon, 22 Sep 2025 08:39:19 +0000</pubDate>
      <link>https://dev.to/ai_pics_6442ad429fc2ff12f/how-to-optimize-hls-designs-for-fpgas-a-practical-vendor-agnostic-playbook-49k0</link>
      <guid>https://dev.to/ai_pics_6442ad429fc2ff12f/how-to-optimize-hls-designs-for-fpgas-a-practical-vendor-agnostic-playbook-49k0</guid>
      <description>&lt;p&gt;Optimizing High-Level Synthesis (HLS) for &lt;a href="https://mozelectronics.com/semiconductor-ics/embedded-processors-and-controllers/fpgas/" rel="noopener noreferrer"&gt;FPGAs&lt;/a&gt; is about turning C/C++ into RTL that meets your throughput, latency, area, and power targets—without breaking correctness. Below is a concise, field-tested checklist you can apply in Vitis HLS (Xilinx), Intel HLS, Catapult, etc. Examples use Vitis HLS-style pragmas, with notes for portability.&lt;/p&gt;

&lt;p&gt;1) Know the Optimization Stack&lt;/p&gt;

&lt;p&gt;Algorithm level – choose math/data representations that minimize work.&lt;/p&gt;

&lt;p&gt;Loop &amp;amp; task level – expose parallelism (pipeline, unroll, dataflow).&lt;/p&gt;

&lt;p&gt;Memory &amp;amp; I/O – feed the beast (partition, reshape, burst, stream).&lt;/p&gt;

&lt;p&gt;Micro-architecture – bind operators/memories, balance latencies, share resources.&lt;/p&gt;

&lt;p&gt;Closure – verify (C/COSIM), analyze (util/timing/II/latency), iterate.&lt;/p&gt;

&lt;p&gt;2) Numerics &amp;amp; Code Structure&lt;br&gt;
Use bit-accurate fixed types&lt;/p&gt;

&lt;p&gt;Prefer ap_(u)int / ap_fixed (or vendor equivalents) over float/double when error budget allows.&lt;/p&gt;

&lt;p&gt;Right-size widths aggressively to cut LUTs, FFs, and DSP usage.&lt;/p&gt;

&lt;h1&gt;
  
  
  include "ap_int.h"
&lt;/h1&gt;

&lt;h1&gt;
  
  
  include "ap_fixed.h"
&lt;/h1&gt;

&lt;p&gt;using pix_t = ap_uint&amp;lt;10&amp;gt;;          // example: 10-bit pixel&lt;br&gt;
using coeff_t = ap_fixed&amp;lt;16,2&amp;gt;;     // 2 integer bits, 14 fractional&lt;/p&gt;

&lt;p&gt;Make dependencies obvious (or remove them)&lt;/p&gt;

&lt;p&gt;Keep hot loops simple; hoist conditionals outside loops when possible.&lt;/p&gt;

&lt;p&gt;Replace complex if/else trees on the critical path with tables or precomputed constants where sensible.&lt;/p&gt;

&lt;p&gt;Use const, restrict (where safe), and pass-by-reference to help the compiler infer no-aliasing and enable bursting.&lt;/p&gt;

&lt;p&gt;3) Loop-Level Optimization&lt;br&gt;
Pipeline first&lt;/p&gt;

&lt;p&gt;Goal: II=1 on the critical loop whenever feasible.&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS PIPELINE II=1
&lt;/h1&gt;

&lt;p&gt;for (int i = 0; i &amp;lt; N; i++) {&lt;br&gt;
  // body with no loop-carried true deps&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Tip: If HLS won’t reach II=1, check the synthesis log’s “stall” reason:&lt;/p&gt;

&lt;p&gt;Memory port conflicts → partition/reshape arrays or widen the data path.&lt;/p&gt;

&lt;p&gt;Loop-carried dependency (RAW/WAR/WAW) → restructure buffers or prove independence:&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS DEPENDENCE variable=buf inter false
&lt;/h1&gt;

&lt;p&gt;Unroll to trade area for throughput&lt;/p&gt;

&lt;p&gt;Partial unroll to match available memory banks/ports; full unroll only if you can feed it.&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS UNROLL factor=4
&lt;/h1&gt;

&lt;p&gt;for (int k=0; k&amp;lt;K; k++) { ... }&lt;/p&gt;

&lt;p&gt;Tile / block for locality&lt;/p&gt;

&lt;p&gt;Break large loops into tiles that fit BRAM/URAM; combine with on-chip buffers to reduce DDR traffic.&lt;/p&gt;

&lt;p&gt;for (int ii=0; ii&amp;lt;N; ii+=Ti)&lt;br&gt;
  for (int jj=0; jj&amp;lt;M; jj+=Tj)&lt;br&gt;
    compute_tile(ii, jj);&lt;/p&gt;

&lt;p&gt;Help the estimator&lt;/p&gt;

&lt;p&gt;Tripcounts improve latency reports and scheduling:&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS LOOP_TRIPCOUNT min=64 max=128
&lt;/h1&gt;

&lt;p&gt;4) Task-Level Concurrency (DATAFLOW)&lt;/p&gt;

&lt;p&gt;Use dataflow to run producer/consumer stages concurrently. Connect stages with hls::stream (or Intel channels).&lt;/p&gt;

&lt;h1&gt;
  
  
  include "hls_stream.h"
&lt;/h1&gt;

&lt;p&gt;void stageA(hls::stream&amp;amp; out);&lt;br&gt;
void stageB(hls::stream&amp;amp; in, hls::stream&amp;amp; out);&lt;br&gt;
void stageC(hls::stream&amp;amp; in);&lt;/p&gt;

&lt;p&gt;void top(hls::stream&amp;amp; in, hls::stream&amp;amp; out) {&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS DATAFLOW
&lt;/h1&gt;

&lt;p&gt;static hls::stream s1("s1"), s2("s2");&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS STREAM variable=s1 depth=64
&lt;/h1&gt;

&lt;h1&gt;
  
  
  pragma HLS STREAM variable=s2 depth=64
&lt;/h1&gt;

&lt;p&gt;stageA(s1);&lt;br&gt;
  stageB(s1, s2);&lt;br&gt;
  stageC(s2);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Tips&lt;/p&gt;

&lt;p&gt;Choose FIFO depths to absorb burstiness and meet initiation intervals across stages.&lt;/p&gt;

&lt;p&gt;Avoid reading/writing the same array from multiple tasks unless you bank/partition correctly.&lt;/p&gt;

&lt;p&gt;5) Memory &amp;amp; Interface Tuning&lt;br&gt;
Partition / reshape arrays to add ports&lt;/p&gt;

&lt;p&gt;PARTITION creates true parallel banks (good for random access).&lt;/p&gt;

&lt;p&gt;RESHAPE packs multiple elements per word (great for sequential access and burst width).&lt;/p&gt;

&lt;p&gt;// Random parallel reads&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS ARRAY_PARTITION variable=buf cyclic factor=4 dim=1
&lt;/h1&gt;

&lt;p&gt;// Wide sequential loads/stores (e.g., 512-bit DDR beats)&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS ARRAY_RESHAPE variable=line factor=16 dim=1
&lt;/h1&gt;

&lt;p&gt;Burst DDR and align widths&lt;/p&gt;

&lt;p&gt;Use m_axi (Vitis) and wide types (ap_uint&amp;lt;256/512&amp;gt;) to match DDR or NoC widths; ensure contiguous access patterns.&lt;/p&gt;

&lt;p&gt;Add offset=slave &amp;amp; proper bundle= names for multiple ports.&lt;/p&gt;

&lt;p&gt;void kernel(ap_uint&amp;lt;512&amp;gt;* in, ap_uint&amp;lt;512&amp;gt;* out, int N) {&lt;br&gt;
  #pragma HLS INTERFACE m_axi     port=in  offset=slave bundle=gmem0 depth=1024&lt;br&gt;
  #pragma HLS INTERFACE m_axi     port=out offset=slave bundle=gmem1 depth=1024&lt;br&gt;
  #pragma HLS INTERFACE s_axilite port=N   bundle=control&lt;br&gt;
  #pragma HLS INTERFACE s_axilite port=return bundle=control&lt;br&gt;
  // ...&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Stream for high throughput and low latency&lt;/p&gt;

&lt;p&gt;Use AXI4-Stream at the top and hls::stream internally for line-rate pipelines (video, radio, ML).&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS INTERFACE axis port=in_axis
&lt;/h1&gt;

&lt;h1&gt;
  
  
  pragma HLS INTERFACE axis port=out_axis
&lt;/h1&gt;

&lt;p&gt;6) Resource Binding &amp;amp; Micro-Architecture&lt;br&gt;
Bind operations and memories&lt;/p&gt;

&lt;p&gt;Map multiplies to DSPs (throughput) or LUTs (save DSPs).&lt;/p&gt;

&lt;p&gt;Choose BRAM vs URAM for large buffers; single-/dual-port appropriately.&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS RESOURCE variable=mul_op core=DSP48
&lt;/h1&gt;

&lt;h1&gt;
  
  
  pragma HLS BIND_STORAGE variable=tile type=ram_2p impl=bram
&lt;/h1&gt;

&lt;p&gt;Control sharing vs. replication&lt;/p&gt;

&lt;p&gt;Use UNROLL to replicate compute, or ALLOCATION/RESOURCE pragmas to limit operator instances for area.&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS ALLOCATION operation instances=mul limit=2
&lt;/h1&gt;

&lt;p&gt;Latency balancing&lt;/p&gt;

&lt;p&gt;For long adder trees or MAC chains, HLS will usually insert registers; you can constrain:&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS LATENCY min=1 max=6
&lt;/h1&gt;

&lt;p&gt;7) Throughput vs. Latency vs. Fmax&lt;/p&gt;

&lt;p&gt;II (Initiation Interval) controls throughput (samples/cycle).&lt;/p&gt;

&lt;p&gt;Latency is total cycles from input to output.&lt;/p&gt;

&lt;p&gt;Fmax comes from post-synthesis timing; shorten critical paths (reduce fan-out, balance trees, use DSPs).&lt;/p&gt;

&lt;p&gt;Clocking note: Set the target period in tool constraints (e.g., Vitis HLS create_clock -period 5) rather than in code; adjust until timing is clean with margin.&lt;/p&gt;

&lt;p&gt;8) Verification &amp;amp; Reporting&lt;/p&gt;

&lt;p&gt;C-sim: Prove algorithm correctness fast.&lt;/p&gt;

&lt;p&gt;C/RTL Co-sim: Validate that RTL matches C under realistic I/O.&lt;/p&gt;

&lt;p&gt;Reports: Inspect&lt;/p&gt;

&lt;p&gt;Achieved II and latency,&lt;/p&gt;

&lt;p&gt;Stall reasons (dependencies/ports),&lt;/p&gt;

&lt;p&gt;Resource map (LUT/FF/DSP/BRAM/URAM),&lt;/p&gt;

&lt;p&gt;Interface burst efficiency.&lt;/p&gt;

&lt;p&gt;Bit-exact testing for fixed-point: measure SNR/PSNR or error budgets vs. floating-point golden.&lt;/p&gt;

&lt;p&gt;9) Example: Streaming FIR with One-Sample-per-Cycle&lt;/p&gt;

&lt;p&gt;This version sustains II=1 by unrolling the tap MAC and fully partitioning coefficients and the shift register. It uses fixed-point, AXI-Stream I/O, and works nicely inside a DATAFLOW pipeline.&lt;/p&gt;

&lt;h1&gt;
  
  
  include "ap_fixed.h"
&lt;/h1&gt;

&lt;h1&gt;
  
  
  include "hls_stream.h"
&lt;/h1&gt;

&lt;p&gt;using data_t  = ap_fixed&amp;lt;16,8&amp;gt;;&lt;br&gt;
using acc_t   = ap_fixed&amp;lt;32,12&amp;gt;;   // wider accumulator&lt;br&gt;
const int N = 64;&lt;/p&gt;

&lt;p&gt;struct axis_t {&lt;br&gt;
  data_t data;&lt;br&gt;
  bool   last;&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;void fir64(hls::stream&amp;amp; in, hls::stream&amp;amp; out, const data_t coeff[N]) {&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS INTERFACE axis port=in
&lt;/h1&gt;

&lt;h1&gt;
  
  
  pragma HLS INTERFACE axis port=out
&lt;/h1&gt;

&lt;h1&gt;
  
  
  pragma HLS INTERFACE ap_ctrl_none port=return
&lt;/h1&gt;

&lt;h1&gt;
  
  
  pragma HLS ARRAY_PARTITION variable=coeff complete dim=1
&lt;/h1&gt;

&lt;p&gt;static data_t shift_reg[N];&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS ARRAY_PARTITION variable=shift_reg complete dim=1
&lt;/h1&gt;

&lt;p&gt;while (true) {&lt;/p&gt;

&lt;h1&gt;
  
  
  pragma HLS PIPELINE II=1
&lt;/h1&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;axis_t x = in.read();

// shift
for (int i = N-1; i &amp;gt; 0; --i) {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  pragma HLS UNROLL
&lt;/h1&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  shift_reg[i] = shift_reg[i-1];
}
shift_reg[0] = x.data;

// MAC
acc_t acc = 0;
for (int i = 0; i &amp;lt; N; ++i) {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  pragma HLS UNROLL
&lt;/h1&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  acc += (acc_t)shift_reg[i] * (acc_t)coeff[i];
}

axis_t y;
y.data = (data_t)acc;
y.last = x.last;
out.write(y);

if (x.last) break;  // simple frame terminator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
}&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
