<?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: FPVtune</title>
    <description>The latest articles on DEV Community by FPVtune (@fpvtune).</description>
    <link>https://dev.to/fpvtune</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3697421%2F9dec32a3-2c1f-4cda-a26d-13a037013aa0.jpg</url>
      <title>DEV Community: FPVtune</title>
      <link>https://dev.to/fpvtune</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fpvtune"/>
    <language>en</language>
    <item>
      <title>How I Built a Neural Network to Auto-Tune FPV Drone PIDs from Blackbox Logs</title>
      <dc:creator>FPVtune</dc:creator>
      <pubDate>Sun, 01 Mar 2026 13:55:11 +0000</pubDate>
      <link>https://dev.to/fpvtune/how-i-built-a-neural-network-to-auto-tune-fpv-drone-pids-from-blackbox-logs-1aoc</link>
      <guid>https://dev.to/fpvtune/how-i-built-a-neural-network-to-auto-tune-fpv-drone-pids-from-blackbox-logs-1aoc</guid>
      <description>&lt;p&gt;If you fly FPV drones, you know the pain: hours of test flights, tweaking PIDs by hand, crashing, reflashing, repeat. I got tired of it, so I built a tool that uses neural networks to analyze Betaflight blackbox logs and suggest optimized PID values automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Manual PID Tuning
&lt;/h2&gt;

&lt;p&gt;Betaflight's default PIDs work okay for most builds, but if you want tight, locked-in freestyle or racing performance, you need to tune. The traditional approach looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fly with default PIDs&lt;/li&gt;
&lt;li&gt;Record blackbox data&lt;/li&gt;
&lt;li&gt;Open PIDtoolbox, stare at gyro traces&lt;/li&gt;
&lt;li&gt;Guess which values to change&lt;/li&gt;
&lt;li&gt;Reflash, fly again&lt;/li&gt;
&lt;li&gt;Repeat 20+ times&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most pilots give up after step 4. The learning curve is steep, and even experienced tuners rely heavily on intuition built over years of practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Approach: Let the Data Decide
&lt;/h2&gt;

&lt;p&gt;Instead of relying on human intuition, I trained a neural network on thousands of blackbox logs paired with their "before and after" PID configurations. The model learned patterns that correlate specific gyro behaviors (oscillations, overshoot, latency, noise profiles) with the PID adjustments that fix them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Simplified version of the analysis pipeline
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;scipy.signal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;welch&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blackbox_log&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Extract frequency-domain features from gyro data.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;gyro_roll&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blackbox_log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gyroADC[0]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;gyro_pitch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blackbox_log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gyroADC[1]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;gyro_yaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blackbox_log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gyroADC[2]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;roll&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gyro_roll&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pitch&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gyro_pitch&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;yaw&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gyro_yaw&lt;/span&gt;&lt;span class="p"&gt;)]:&lt;/span&gt;
        &lt;span class="n"&gt;freqs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;psd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;welch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;blackbox_log&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sample_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_noise_floor&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;psd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_peak_freq&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;freqs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;argmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;psd&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_oscillation_score&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;psd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;psd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model analyzes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gyro noise profiles&lt;/strong&gt; across roll, pitch, and yaw&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step response characteristics&lt;/strong&gt; (overshoot, settling time)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error tracking&lt;/strong&gt; between setpoint and gyro&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter effectiveness&lt;/strong&gt; at different frequency bands&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motor noise signatures&lt;/strong&gt; and their harmonics&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What It Outputs
&lt;/h3&gt;

&lt;p&gt;For each axis, the system recommends:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;P, I, and D gains&lt;/strong&gt; optimized for your specific quad&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter settings&lt;/strong&gt; (dynamic notch, lowpass frequencies)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedforward gains&lt;/strong&gt; for sharper stick response&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D-term filtering&lt;/strong&gt; to reduce hot motors&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;I've made this available as a free web tool at &lt;a href="https://fpvtune.com" rel="noopener noreferrer"&gt;FPVtune&lt;/a&gt;. Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fly your quad with blackbox logging enabled&lt;/li&gt;
&lt;li&gt;Upload your &lt;code&gt;.bbl&lt;/code&gt; or &lt;code&gt;.bfl&lt;/code&gt; file to &lt;a href="https://fpvtune.com" rel="noopener noreferrer"&gt;fpvtune.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The neural network analyzes your flight data&lt;/li&gt;
&lt;li&gt;Get optimized PID values, filter settings, and feedforward gains&lt;/li&gt;
&lt;li&gt;Paste the suggested CLI commands into Betaflight&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No registration required. Your blackbox data is processed and not stored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results So Far
&lt;/h2&gt;

&lt;p&gt;I've been testing this on my own 5" freestyle builds and the results have been surprisingly good:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Oscillations reduced&lt;/strong&gt; by 60-80% compared to default PIDs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propwash handling&lt;/strong&gt; noticeably improved&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motor temps&lt;/strong&gt; dropped 5-10°C with better D-term filtering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stick feel&lt;/strong&gt; more responsive with proper feedforward tuning&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;For those curious about the implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Python (Flask) with TensorFlow for the neural network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blackbox parsing&lt;/strong&gt;: Custom parser based on the Betaflight blackbox spec&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Vanilla JS with real-time visualization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signal processing&lt;/strong&gt;: NumPy + SciPy for frequency analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;I'm working on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support for more flight controller firmware (iNav, KISS)&lt;/li&gt;
&lt;li&gt;Comparative analysis (overlay multiple flights)&lt;/li&gt;
&lt;li&gt;Community-contributed training data to improve the model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're an FPV pilot struggling with tuning, give &lt;a href="https://fpvtune.com" rel="noopener noreferrer"&gt;FPVtune&lt;/a&gt; a try and let me know how it works for your build. I'd love feedback from the community!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Python, TensorFlow, and way too many LiPo cycles. Check it out at &lt;a href="https://fpvtune.com" rel="noopener noreferrer"&gt;fpvtune.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>machinelearning</category>
      <category>showdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Finally stopped guessing my Betaflight PIDs - here's what worked</title>
      <dc:creator>FPVtune</dc:creator>
      <pubDate>Wed, 25 Feb 2026 12:05:34 +0000</pubDate>
      <link>https://dev.to/fpvtune/finally-stopped-guessing-my-betaflight-pids-heres-what-worked-369f</link>
      <guid>https://dev.to/fpvtune/finally-stopped-guessing-my-betaflight-pids-heres-what-worked-369f</guid>
      <description>&lt;p&gt;Been flying FPV drones for about 2 years now. Love the flying part, but PID tuning? That's always been the part I dread.&lt;/p&gt;

&lt;p&gt;You know the drill - spend a weekend doing test flights, recording blackbox logs, staring at PIDtoolbox graphs trying to figure out what to change. Tweak P a bit, test again. Still got prop wash? Maybe try D term filtering. Rinse and repeat until you give up or get lucky.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;PIDtoolbox is great for &lt;em&gt;analyzing&lt;/em&gt; logs, but it doesn't tell you what to actually change. You still need to understand all the theory - step response, noise floors, filter delays. Most of us just want to fly, not become control systems engineers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;So I built &lt;a href="https://fpvtune.com" rel="noopener noreferrer"&gt;FPVtune&lt;/a&gt; - a tool that reads your blackbox logs and spits out PID suggestions automatically. It uses neural networks trained on thousands of flight logs to figure out what changes would improve your tune.&lt;/p&gt;

&lt;p&gt;The workflow is dead simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload your blackbox log&lt;/li&gt;
&lt;li&gt;Wait ~30 seconds for analysis&lt;/li&gt;
&lt;li&gt;Get recommended PID values&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No graphs to interpret, no theory to understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works (for the curious)
&lt;/h2&gt;

&lt;p&gt;The core is a neural network that learned patterns from analyzing blackbox data. It looks at things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gyro noise characteristics&lt;/li&gt;
&lt;li&gt;Motor response times&lt;/li&gt;
&lt;li&gt;Current PID behavior&lt;/li&gt;
&lt;li&gt;Oscillation patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then it predicts what PID changes would reduce issues like prop wash, oscillations, and bounce-back.&lt;/p&gt;

&lt;p&gt;The whole thing is open source on GitHub: &lt;a href="https://github.com/chugzb/betaflight-pid-autotuning" rel="noopener noreferrer"&gt;github.com/chugzb/betaflight-pid-autotuning&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Tested it on my 5" freestyle build that had annoying prop wash on throttle chops. The suggested PIDs reduced it noticeably on the first try. Not perfect, but way better than my hours of manual tuning got me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The tool costs $9.90 (one-time), but I have a beta code if anyone wants to try it free:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code: &lt;code&gt;FPVTUNE-BETA-2026&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Just go to &lt;a href="https://fpvtune.com" rel="noopener noreferrer"&gt;fpvtune.com&lt;/a&gt;, upload a log, and enter the code on the payment page.&lt;/p&gt;




&lt;p&gt;Curious if anyone else has tried auto-tuning approaches? Would love to hear what's worked (or not worked) for others.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>datascience</category>
    </item>
    <item>
      <title>How I Built a Blackbox Log Analyzer to Auto-Tune FPV Drone PIDs</title>
      <dc:creator>FPVtune</dc:creator>
      <pubDate>Thu, 19 Feb 2026 03:34:17 +0000</pubDate>
      <link>https://dev.to/fpvtune/how-i-built-a-blackbox-log-analyzer-to-auto-tune-fpv-drone-pids-1e3d</link>
      <guid>https://dev.to/fpvtune/how-i-built-a-blackbox-log-analyzer-to-auto-tune-fpv-drone-pids-1e3d</guid>
      <description>&lt;p&gt;I fly FPV drones as a hobby, and if you've ever tried tuning Betaflight PIDs manually, you know the pain. Record a flight, pull the blackbox log, open PIDtoolbox, stare at gyro traces for an hour, change one number, fly again... repeat forever.&lt;/p&gt;

&lt;p&gt;So I built a tool that does it automatically. Here's the story and some of the technical bits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Betaflight's PID controller has ~15 parameters that affect how your drone flies. Most pilots either copy someone else's tune (which rarely works because every build is different) or spend days doing test flights and analyzing blackbox logs.&lt;/p&gt;

&lt;p&gt;The existing tools like PIDtoolbox are great for &lt;em&gt;visualizing&lt;/em&gt; data, but they don't tell you what to actually change. You still need to know what a noisy gyro trace means and which PID term to adjust.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Approach
&lt;/h2&gt;

&lt;p&gt;I wanted something that could:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parse Betaflight blackbox logs (&lt;code&gt;.bbl&lt;/code&gt; files)&lt;/li&gt;
&lt;li&gt;Analyze the frequency response and noise characteristics&lt;/li&gt;
&lt;li&gt;Suggest specific PID values based on the analysis&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The blackbox format is basically a compressed binary stream of sensor data — gyro, accelerometer, motor outputs, RC inputs, etc. sampled at up to 8kHz.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing Blackbox Logs
&lt;/h2&gt;

&lt;p&gt;Betaflight's blackbox logs use a custom encoding with variable-length integers and predictive coding. Here's a simplified look at how the parsing works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decode_blackbox_frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_defs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;field_defs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;signed_vlq&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_signed_vlq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unsigned_vlq&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_unsigned_vlq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tag8_8svb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_tag8_8svb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tricky part is handling the different Betaflight firmware versions — the log format changes between versions and you need to parse the header to figure out which fields are present.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequency Analysis
&lt;/h2&gt;

&lt;p&gt;Once you have the raw gyro and PID error data, the real magic happens in the frequency domain. I use FFT to identify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Noise floor&lt;/strong&gt;: How much electrical/mechanical noise your quad produces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motor resonance peaks&lt;/strong&gt;: Frequencies where your motors/props create vibrations
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PID response&lt;/strong&gt;: How well the current tune tracks setpoint changes
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyze_axis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gyro_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid_error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sample_rate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# FFT of gyro data to find noise profile
&lt;/span&gt;    &lt;span class="n"&gt;freqs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rfftfreq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gyro_data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sample_rate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;gyro_fft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rfft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gyro_data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# Find dominant noise frequencies
&lt;/span&gt;    &lt;span class="n"&gt;noise_peaks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find_peaks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gyro_fft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gyro_fft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Analyze step response from setpoint changes
&lt;/span&gt;    &lt;span class="n"&gt;step_indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find_setpoint_steps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid_error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;overshoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculate_overshoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gyro_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step_indices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;settling_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculate_settling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gyro_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step_indices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;noise_peaks&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;freqs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;noise_peaks&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;overshoot&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;overshoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;settling_time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;settling_time&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  From Analysis to PID Suggestions
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. Based on the frequency analysis, the tool adjusts PIDs following some basic control theory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High overshoot on roll/pitch&lt;/strong&gt; → reduce P gain or increase D gain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow response&lt;/strong&gt; → increase P gain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High frequency oscillation&lt;/strong&gt; → reduce D gain, check D lowpass filter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prop wash oscillation&lt;/strong&gt; (low frequency, shows up in throttle cuts) → adjust I gain and D term&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool outputs specific numbers you can paste directly into Betaflight configurator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;I turned this into a web tool called &lt;a href="https://fpvtune.com" rel="noopener noreferrer"&gt;FPVtune&lt;/a&gt; — you upload your blackbox log and it spits out PID recommendations. No software to install, works in the browser.&lt;/p&gt;

&lt;p&gt;The source code for the analysis engine is on &lt;a href="https://github.com/chugzb/betaflight-pid-autotuning" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; if you want to dig into the algorithms.&lt;/p&gt;

&lt;p&gt;It's $9.90 for the full analysis, but I have a beta code for the DEV community: &lt;strong&gt;FPVTUNE-BETA-2026&lt;/strong&gt; — just enter it on the activation page to get free access.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Building this taught me a lot about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Signal processing in Python (scipy.signal is your friend)&lt;/li&gt;
&lt;li&gt;Working with binary data formats that have zero documentation&lt;/li&gt;
&lt;li&gt;The gap between "analyzing data" and "making actionable suggestions"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you fly FPV or work with any kind of PID control systems (robotics, etc.), I'd love to hear how you approach tuning. The control theory fundamentals are the same whether you're tuning a drone or a robot arm.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Repo: &lt;a href="https://github.com/chugzb/betaflight-pid-autotuning" rel="noopener noreferrer"&gt;github.com/chugzb/betaflight-pid-autotuning&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>showdev</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I built an auto PID tuning tool for Betaflight — here's how it works under the hood</title>
      <dc:creator>FPVtune</dc:creator>
      <pubDate>Thu, 19 Feb 2026 03:18:48 +0000</pubDate>
      <link>https://dev.to/fpvtune/i-built-an-auto-pid-tuning-tool-for-betaflight-heres-how-it-works-under-the-hood-okg</link>
      <guid>https://dev.to/fpvtune/i-built-an-auto-pid-tuning-tool-for-betaflight-heres-how-it-works-under-the-hood-okg</guid>
      <description>&lt;p&gt;I fly FPV drones as a hobby, and if you've ever tried to tune Betaflight PIDs manually, you know the pain. Fly a pack, check blackbox logs, tweak one number, fly again, repeat for hours. I got tired of it and decided to build something to automate the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Betaflight doesn't have a real autotune feature (unlike iNav or Ardupilot). The closest thing we had was PIDtoolbox, which is great for &lt;em&gt;visualizing&lt;/em&gt; blackbox data but doesn't actually tell you what to change. You still need to know what you're looking at.&lt;/p&gt;

&lt;p&gt;Most pilots either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copy someone else's PIDs and hope for the best&lt;/li&gt;
&lt;li&gt;Spend weekends doing test flights and manual tuning&lt;/li&gt;
&lt;li&gt;Pay someone to tune their quad&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are great options.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://fpvtune.com" rel="noopener noreferrer"&gt;FPVtune&lt;/a&gt; — a web-based tool that reads your Betaflight blackbox logs and generates optimized PID settings using a neural network.&lt;/p&gt;

&lt;p&gt;The basic flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Record a blackbox log (just fly normally for 30 seconds)&lt;/li&gt;
&lt;li&gt;Upload the &lt;code&gt;.bbl&lt;/code&gt; or &lt;code&gt;.bfl&lt;/code&gt; file to fpvtune.com&lt;/li&gt;
&lt;li&gt;Get back a full set of PID and filter recommendations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No install needed, works in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works under the hood
&lt;/h2&gt;

&lt;p&gt;Here's the interesting part for the dev crowd.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blackbox parsing
&lt;/h3&gt;

&lt;p&gt;Betaflight blackbox logs are binary files with a custom format. I wrote a Python parser that extracts gyro data, motor outputs, setpoint, PID controller outputs (P/I/D terms), and RC commands at the logged sample rate (usually 2kHz for gyro, 500Hz-1kHz for PID).&lt;/p&gt;

&lt;p&gt;The tricky part is handling different Betaflight versions — the log format has changed across versions, and there are edge cases with corrupt logs, partial writes (battery died mid-flight), and different logging rates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signal analysis
&lt;/h3&gt;

&lt;p&gt;Once parsed, the raw data goes through several analysis steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Simplified version of the analysis pipeline
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyze_axis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gyro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid_p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid_d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;motor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;roll&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Step response analysis - how fast does the quad respond?
&lt;/span&gt;    &lt;span class="n"&gt;step_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_step_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gyro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Noise floor analysis - where is the noise?
&lt;/span&gt;    &lt;span class="n"&gt;noise_spectrum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rfft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gyro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;noise_floor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;estimate_noise_floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;noise_spectrum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Prop wash detection - oscillations after throttle changes
&lt;/span&gt;    &lt;span class="n"&gt;throttle_events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;detect_throttle_cuts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;motor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;propwash_severity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;measure_oscillation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gyro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throttle_events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# D-term noise vs effectiveness tradeoff
&lt;/span&gt;    &lt;span class="n"&gt;d_noise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;highpass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid_d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cutoff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;d_effectiveness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;correlation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid_d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gyro_error_derivative&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;AnalysisResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step_response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;noise_floor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                          &lt;span class="n"&gt;propwash_severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_noise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_effectiveness&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key metrics I extract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step response delay&lt;/strong&gt; — how many ms between stick input and quad response&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overshoot&lt;/strong&gt; — does the quad overshoot the target angle?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Noise floor by frequency&lt;/strong&gt; — where is motor/prop noise showing up?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prop wash severity&lt;/strong&gt; — how bad are the oscillations after throttle blips?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D-term noise ratio&lt;/strong&gt; — is D actually helping or just adding noise?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The neural network
&lt;/h3&gt;

&lt;p&gt;The model takes these extracted features and predicts optimal PID values. It was trained on a dataset of ~2000 blackbox logs with known "good" tunes (collected from experienced FPV pilots who shared their logs and final PID values).&lt;/p&gt;

&lt;p&gt;The architecture is pretty simple — a feed-forward network with 3 hidden layers. Nothing fancy. The hard part was building the training dataset, not the model itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input (28 features) → Dense(128, ReLU) → Dense(64, ReLU) → Dense(32, ReLU) → Output (18 PID values)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 18 outputs cover P, I, D for roll/pitch/yaw, plus filter settings (gyro lowpass, D-term lowpass, notch filters).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not just use rules?
&lt;/h3&gt;

&lt;p&gt;I actually started with a rule-based system ("if noise is high, lower D; if response is slow, raise P"). It worked okay for simple cases but fell apart with edge cases — like when you have both high noise AND slow response, which means the filters are too aggressive and you need to fix those first before touching PIDs.&lt;/p&gt;

&lt;p&gt;The neural network handles these multi-variable interactions much better than hand-written rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Python, FastAPI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: React&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blackbox parser&lt;/strong&gt;: Custom Python (no existing library handled all the edge cases)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ML&lt;/strong&gt;: PyTorch for training, ONNX for inference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: PostgreSQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing is &lt;a href="https://github.com/chugzb/betaflight-pid-autotuning" rel="noopener noreferrer"&gt;open source on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results so far
&lt;/h2&gt;

&lt;p&gt;I've been testing it on my own quads (5" freestyle, 3" cinewhoop, 7" long range) and the results have been surprisingly good. Prop wash handling improved noticeably on my freestyle build without me having to think about filter settings at all.&lt;/p&gt;

&lt;p&gt;It's not perfect — it sometimes suggests filter settings that are too aggressive on noisy builds, and it doesn't handle RPM filtering tuning yet. But for getting a solid baseline tune, it saves hours of manual work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;The tool is live at &lt;a href="https://fpvtune.com" rel="noopener noreferrer"&gt;fpvtune.com&lt;/a&gt;. It costs $9.90 for a full analysis, but I have a beta test code if you want to try it free:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code: &lt;code&gt;FPVTUNE-BETA-2026&lt;/code&gt;&lt;/strong&gt; (limited uses)&lt;/p&gt;

&lt;p&gt;Just upload your blackbox log, and on the payment page expand the "Activation Code" section and enter the code.&lt;/p&gt;

&lt;p&gt;I'd love feedback from other pilots and devs. If you fly FPV and have blackbox logs lying around, give it a shot and let me know how the suggestions compare to your current tune.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're interested in the technical details of blackbox log parsing or the training pipeline, I'm happy to write a follow-up post going deeper into either topic. Drop a comment!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>python</category>
      <category>opensource</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Betaflight Has No Autotune — So I Built One</title>
      <dc:creator>FPVtune</dc:creator>
      <pubDate>Mon, 16 Feb 2026 13:04:20 +0000</pubDate>
      <link>https://dev.to/fpvtune/betaflight-has-no-autotune-so-i-built-one-2oa4</link>
      <guid>https://dev.to/fpvtune/betaflight-has-no-autotune-so-i-built-one-2oa4</guid>
      <description>&lt;h2&gt;
  
  
  The PID Tuning Loop Every FPV Pilot Knows
&lt;/h2&gt;

&lt;p&gt;If you fly FPV, you've been through this cycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fly a pack. Notice oscillations on descents.&lt;/li&gt;
&lt;li&gt;Land. Plug in USB. Open Betaflight Configurator.&lt;/li&gt;
&lt;li&gt;Stare at P, I, D sliders. Which one do I change? By how much?&lt;/li&gt;
&lt;li&gt;Guess. Flash. Fly again.&lt;/li&gt;
&lt;li&gt;Still not right. Repeat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This loop can eat hours — sometimes days. And even experienced pilots admit that PID tuning is more art than science. You're basically doing gradient descent by hand, one flight at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Doesn't Betaflight Have Autotune?
&lt;/h2&gt;

&lt;p&gt;Pilots have been asking for years. There's a &lt;a href="https://github.com/betaflight/betaflight/issues/6857" rel="noopener noreferrer"&gt;GitHub issue (#6857)&lt;/a&gt; with hundreds of comments requesting autotune. INAV has a basic autotune feature, but Betaflight's team hasn't implemented one — the problem is genuinely hard to solve on a microcontroller with limited resources.&lt;/p&gt;

&lt;p&gt;The existing tools — PIDtoolbox and Blackbox Explorer — are great for &lt;em&gt;visualizing&lt;/em&gt; your flight data. FFT plots, step responses, gyro traces. But they show you what happened, not what to do about it. You still need to interpret the graphs and decide which values to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  What If Your Blackbox Log Could Tell You the Answer?
&lt;/h2&gt;

&lt;p&gt;That's the idea behind &lt;a href="https://fpvtune.com" rel="noopener noreferrer"&gt;FPVtune&lt;/a&gt;. Instead of showing you graphs and leaving you to figure it out, it reads your blackbox log and outputs optimized PID values, filter settings, and feedforward gains.&lt;/p&gt;

&lt;p&gt;Here's how it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blackbox Log (.bbl) → Neural Network Analysis → Optimized CLI Commands
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The neural network analyzes multiple dimensions of your flight data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gyro noise spectrum&lt;/strong&gt; — identifies motor noise frequencies and vibration patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step response&lt;/strong&gt; — measures how your quad responds to stick inputs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PID error tracking&lt;/strong&gt; — shows how well current PIDs follow commands&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter performance&lt;/strong&gt; — checks if filters add too much delay or pass too much noise&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prop wash detection&lt;/strong&gt; — identifies oscillation events during descents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then it generates Betaflight CLI commands you can paste directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generated by FPVtune&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;p_pitch &lt;span class="o"&gt;=&lt;/span&gt; 45
&lt;span class="nb"&gt;set &lt;/span&gt;i_pitch &lt;span class="o"&gt;=&lt;/span&gt; 80
&lt;span class="nb"&gt;set &lt;/span&gt;d_pitch &lt;span class="o"&gt;=&lt;/span&gt; 35
&lt;span class="nb"&gt;set &lt;/span&gt;f_pitch &lt;span class="o"&gt;=&lt;/span&gt; 120
&lt;span class="nb"&gt;set &lt;/span&gt;p_roll &lt;span class="o"&gt;=&lt;/span&gt; 42
&lt;span class="nb"&gt;set &lt;/span&gt;i_roll &lt;span class="o"&gt;=&lt;/span&gt; 75
&lt;span class="nb"&gt;set &lt;/span&gt;d_roll &lt;span class="o"&gt;=&lt;/span&gt; 30
&lt;span class="nb"&gt;set &lt;/span&gt;f_roll &lt;span class="o"&gt;=&lt;/span&gt; 110
&lt;span class="nb"&gt;set &lt;/span&gt;p_yaw &lt;span class="o"&gt;=&lt;/span&gt; 35
&lt;span class="nb"&gt;set &lt;/span&gt;i_yaw &lt;span class="o"&gt;=&lt;/span&gt; 90
&lt;span class="nb"&gt;set &lt;/span&gt;dyn_notch_count &lt;span class="o"&gt;=&lt;/span&gt; 2
&lt;span class="nb"&gt;set &lt;/span&gt;dyn_notch_q &lt;span class="o"&gt;=&lt;/span&gt; 350
save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No interpretation needed. No guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Difference from Default PIDs
&lt;/h2&gt;

&lt;p&gt;Betaflight's default PIDs are a compromise — they work "okay" on most quads but are optimized for none. Your specific frame, motors, props, weight, and vibration profile all affect what the ideal settings should be.&lt;/p&gt;

&lt;p&gt;After using FPVtune, pilots typically see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less prop wash oscillation on quick descents&lt;/li&gt;
&lt;li&gt;Tighter, more locked-in stick feel&lt;/li&gt;
&lt;li&gt;Cooler motors (properly tuned D-gain stops sending noise to motors)&lt;/li&gt;
&lt;li&gt;Smoother HD footage from reduced vibrations&lt;/li&gt;
&lt;li&gt;Better hover stability in wind&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How It Compares
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;FPVtune&lt;/th&gt;
&lt;th&gt;PIDtoolbox&lt;/th&gt;
&lt;th&gt;Blackbox Explorer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Auto PID recommendations&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web-based (no install)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ (needs MATLAB)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter tuning&lt;/td&gt;
&lt;td&gt;✅ Auto&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI export&lt;/td&gt;
&lt;td&gt;✅ One-click&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beginner friendly&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Still maintained&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ (ended May 2024)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;PIDtoolbox was an excellent tool by Brian White, but development ended in May 2024 and it requires a ~2GB MATLAB runtime download. Blackbox Explorer is the official Betaflight log viewer — great for visualization, but it doesn't generate recommendations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;If you're tired of the guess-and-fly loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable blackbox logging in Betaflight (Blackbox tab → set logging rate to 2K+)&lt;/li&gt;
&lt;li&gt;Fly a normal 2-3 minute pack&lt;/li&gt;
&lt;li&gt;Upload your &lt;code&gt;.bbl&lt;/code&gt; file at &lt;strong&gt;&lt;a href="https://fpvtune.com" rel="noopener noreferrer"&gt;fpvtune.com&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Get optimized PIDs in ~30 seconds&lt;/li&gt;
&lt;li&gt;Paste CLI commands into Betaflight and fly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No special test flight needed. No MATLAB download. No graph interpretation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://fpvtune.com" rel="noopener noreferrer"&gt;fpvtune.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;FPVtune supports Betaflight 4.3/4.4/4.5+, all common flight controllers (F4/F7/H7), and every drone type from tiny whoops to X-class. &lt;a href="https://github.com/chugzb/betaflight-pid-autotuning" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>opensource</category>
      <category>machinelearning</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
