<?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: Kaltoc</title>
    <description>The latest articles on DEV Community by Kaltoc (@kaltoc).</description>
    <link>https://dev.to/kaltoc</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%2F4195807%2F26f0be6e-7680-4b61-ab66-e41f3dc916b1.png</url>
      <title>DEV Community: Kaltoc</title>
      <link>https://dev.to/kaltoc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kaltoc"/>
    <language>en</language>
    <item>
      <title>Late Start or Long Run? Catching a Rare Missed Window</title>
      <dc:creator>Kaltoc</dc:creator>
      <pubDate>Thu, 22 Oct 2026 03:56:30 +0000</pubDate>
      <link>https://dev.to/kaltoc/late-start-or-long-run-catching-a-rare-missed-window-3dlp</link>
      <guid>https://dev.to/kaltoc/late-start-or-long-run-catching-a-rare-missed-window-3dlp</guid>
      <description>&lt;p&gt;Picture a device running smoothly for months, then suddenly missing its communication window once every few thousand cycles. The logs show no obvious errors. The question arises: was the work late, or was it slow? This article explores a technique to distinguish between these two possibilities, providing insights that logs alone cannot offer.&lt;/p&gt;

&lt;h2&gt;
  
  
  why logs and averages miss this
&lt;/h2&gt;

&lt;p&gt;Logs typically record discrete events, not the duration of tasks, making them inadequate for diagnosing timing issues. A rare spike in task duration can easily disappear when averaged over thousands of cycles. Such faults often appear after long uptimes because they require two independent activities to align in a specific way, like a burst of interrupt work coinciding with a held lock. These rare alignments need many cycles to manifest, explaining why they are often missed in shorter tests. Additionally, these faults leave a pattern indicative of timing issues rather than a stack trace, complicating direct diagnosis through logs alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  the technique
&lt;/h2&gt;

&lt;p&gt;To address this, we record the worst-case start lag and run time, instead of just the latest values, because rare faults are often outliers. Separating start lag from run time helps pinpoint whether delays occur before or during task execution. The task step looks like this:&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="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;worst_lag_us&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;uint32_t&lt;/span&gt; &lt;span class="n"&gt;worst_run_us&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;uint32_t&lt;/span&gt; &lt;span class="n"&gt;overrun_count&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;comm_task_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;scheduled_us&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="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now_us&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;start_lag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;scheduled_us&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;gpio_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEBUG_PIN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;do_communication_work&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;gpio_clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEBUG_PIN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;run_us&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now_us&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&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;start_lag&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;worst_lag_us&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;worst_lag_us&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_lag&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;run_us&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;worst_run_us&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;worst_run_us&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;run_us&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;start_lag&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;run_us&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;BUDGET_US&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;overrun_count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;trace_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EV_LATE_START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_lag&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;trace_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EV_RUN_TIME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run_us&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;This code uses unsigned subtraction for time calculations to handle timer wraparound, and both &lt;code&gt;scheduled_us&lt;/code&gt; and &lt;code&gt;now_us()&lt;/code&gt; must share the same time base. The GPIO pulse width includes preemption time, making it valuable for understanding task execution duration, though it does not capture start lag. On the oscilloscope, set a trigger for pulse widths longer than the budget, and use persistence mode to keep rare wide pulses visible.&lt;/p&gt;

&lt;p&gt;The trace buffer is a small ring:&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="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;t_us&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;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;trace_entry_t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;trace_entry_t&lt;/span&gt; &lt;span class="n"&gt;trace_buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TRACE_LEN&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;uint32_t&lt;/span&gt; &lt;span class="n"&gt;trace_head&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;trace_push&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;value&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trace_head&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TRACE_LEN&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1u&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;trace_buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;t_us&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now_us&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;trace_buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;trace_buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&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;This ring buffer keeps recent history without requiring dynamic allocation. It can be drained over a debug UART or copied to a reserved flash region for crash logs when a fault is detected. Note that &lt;code&gt;trace_head++&lt;/code&gt; is not atomic, so the function assumes a single writer; if both an ISR and a task call it, protect the index update with a short critical section. The two &lt;code&gt;trace_push&lt;/code&gt; calls create separate entries, so they should be paired by timestamp when read back.&lt;/p&gt;

&lt;h2&gt;
  
  
  how to read the result
&lt;/h2&gt;

&lt;p&gt;A wide pulse or a large &lt;code&gt;worst_run_us&lt;/code&gt; suggests either the task itself is slow or it is being preempted during execution. If the run time is consistently large or grows with workload, the issue likely lies within the task. Conversely, normal run times with rare spikes point toward mid-run preemption. A large &lt;code&gt;worst_lag_us&lt;/code&gt; with a normal pulse points toward preemption or blocking before task start, potentially due to a higher-priority task, an ISR, a held lock, or priority inversion.&lt;/p&gt;

&lt;p&gt;To test such candidates, toggle a second pin in the suspect task or ISR, view it on a second scope channel, and check if the lag spikes align with that pulse. If they never align, that candidate is ruled out. If both values are normal yet windows are missed, it suggests the issue lies outside the task's timing, possibly on the other side of the communication link.&lt;/p&gt;

&lt;h2&gt;
  
  
  what this cannot tell you
&lt;/h2&gt;

&lt;p&gt;A slow memory leak won't widen the pulse or lag until memory allocations start failing. Therefore, logging a free-heap low-water mark separately is advisable. This technique won't catch faults unrelated to this task's timing. Also, the instrumentation itself costs some time, and the trace buffer is finite, meaning very old history is overwritten. &lt;/p&gt;

&lt;p&gt;How do you differentiate between a preempted-before-start fault and a blocked-on-a-lock fault in a field device?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://stonehmi.com/hmi-guides/firmware-development/" rel="noopener noreferrer"&gt;StoneHMI&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;This post was drafted with AI assistance and reviewed by a human before publishing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
    </item>
  </channel>
</rss>
