{"id":4679,"date":"2026-08-20T09:16:55","date_gmt":"2026-08-20T09:16:55","guid":{"rendered":"https:\/\/blog.embeddedexpert.io\/?p=4679"},"modified":"2026-08-20T09:26:46","modified_gmt":"2026-08-20T09:26:46","slug":"zero-flicker-magic-mastering-embedded-display-animations-with-u8g2","status":"publish","type":"post","link":"https:\/\/blog.embeddedexpert.io\/?p=4679","title":{"rendered":"Zero-Flicker Magic: Mastering Embedded Display Animations with U8g2"},"content":{"rendered":"<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"585\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/oled_spectrum_visualizer-1024x585.jpg\" alt=\"\" class=\"wp-image-4680\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/oled_spectrum_visualizer-1024x585.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/oled_spectrum_visualizer-300x171.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/oled_spectrum_visualizer-768x439.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/oled_spectrum_visualizer-1150x657.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/oled_spectrum_visualizer-750x429.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/oled_spectrum_visualizer-400x229.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/oled_spectrum_visualizer-250x143.jpg 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/oled_spectrum_visualizer.jpg 1344w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure><\/div>\n\n\n<p>Adding fluid, dynamic animations to embedded displays doesn&#8217;t require a high-end GPU or a complex graphics framework. In this guide, you will learn how to use the U8g2 graphics library to build a zero-flicker, high-frame-rate audio visualizer on a standard 128&#215;64 OLED using nothing but standard C math and smart buffer management.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>In this guide, we shall cover the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Introduction.<\/li>\n\n\n\n<li>Firmware Development.<\/li>\n\n\n\n<li>Results.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">1. Introduction:<\/h2>\n\n\n\n<p>Adding dynamic, fluid animations to embedded displays often feels like a dark art reserved only for high-end microcontrollers running embedded Linux and pushing raw RGB data to bulky TFT screens. We have all been conditioned to believe that if you want an interface to feel &#8220;alive&#8221;\u2014to have sweeping gauges, bouncing bars, or smooth transitions\u2014you need a graphics processing unit, a dedicated frame buffer in external SDRAM, and a complex GUI framework like TouchGFX or LVGL.<\/p>\n\n\n\n<p>But the truth is, you don&#8217;t need 65,000 colors or a massive parallel bus to create a visually stunning user experience. A classic, inexpensive $2 monochrome 128&#215;64 OLED screen is more than capable of delivering high-frame-rate, fluid animations\u2014provided you pair it with the right software architecture and a bit of mathematical creativity. Monochrome displays offer incredible contrast, perfectly crisp pixel edges, and lightning-fast refresh rates, making them a staple in everything from professional audio gear to custom mechanical keyboards and industrial instruments.<\/p>\n\n\n\n<p>The secret to unlocking this potential on a tiny 1-bit display lies entirely in how you manage memory and draw to the screen. Sending data byte-by-byte directly to the display controller might seem intuitive, but it is a recipe for disastrous screen tearing and flickering. In this guide, we are going to prove that constraints breed creativity by building a high-tech, simulated Audio Spectrum Visualizer from scratch using the <strong>U8g2<\/strong> graphics library.<\/p>\n\n\n\n<p>We will walk step-by-step through the essential techniques for embedded animation. You will learn how to use a full-screen RAM frame buffer to achieve a zero-flicker &#8220;double-buffered&#8221; redraw, how to simulate complex physical properties like gravity and acceleration using standard C math, and how to structure a non-blocking main loop so your animations run at a buttery 30 frames per second without freezing your microcontroller. Whether you are building a synthesizer, a benchtop power supply, or a custom smart home remote, the principles in this guide will change the way you approach monochrome UI design. Let&#8217;s make those pixels move.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. Firmware Development:<\/h2>\n\n\n\n<p>We shall use the OLED setup from this guide <a href=\"https:\/\/blog.embeddedexpert.io\/?p=3700\" data-type=\"link\" data-id=\"https:\/\/blog.embeddedexpert.io\/?p=3700\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>.<\/p>\n\n\n\n<p>Once you have setup the firmware, open main.c <\/p>\n\n\n\n<p>Within the main.c, we start by including the following header files:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">#include &quot;stdio.h&quot;\n#include &quot;math.h&quot;\n#include &quot;stdlib.h&quot;<\/pre><\/div>\n\n\n\n<p>These will allow us to get access to certain functions that needed later.<\/p>\n\n\n\n<p>Next, define the following parameters:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">#define NUM_BARS 16\n#define MAX_BAR_HEIGHT 40<\/pre><\/div>\n\n\n\n<p>The UI shall have 16 bars and maximum hight of 40pixel.<\/p>\n\n\n\n<p>In user code begin 0, declare the following function:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">void render_audio_visualizer(void) <\/pre><\/div>\n\n\n\n<p>This function shall be called to render the animated UI.<\/p>\n\n\n\n<p>Within the function:<\/p>\n\n\n\n<p>Declare the following variables:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">static uint32_t last_update = 0;\nstatic float time_counter = 0.0f;\n\n\/* Persistent peak markers that slowly fall down *\/\nstatic uint8_t peak_heights[NUM_BARS] = {0};\nstatic float peak_fall_speed[NUM_BARS] = {0};<\/pre><\/div>\n\n\n\n<p>Next, get the current ticks as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">uint32_t now = HAL_GetTick();<\/pre><\/div>\n\n\n\n<p>Next, we shall update the UI each 33ms in none blocking mode as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">if (now - last_update &gt; 33) {<\/pre><\/div>\n\n\n\n<p>Store the current last_update to match now value and increment time_counter by 0.15 as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">last_update = now;\ntime_counter += 0.15f;<\/pre><\/div>\n\n\n\n<p>Next, clear the buffer:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">u8g2_ClearBuffer(&amp;myDisplay);<\/pre><\/div>\n\n\n\n<p>Next, draw the UI header:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">\/* 1. Draw UI Header *\/\nu8g2_SetFont(&amp;myDisplay, u8g2_font_5x7_tr);\nu8g2_DrawStr(&amp;myDisplay, 0, 7, &quot;AUDIO SCAN&quot;);<\/pre><\/div>\n\n\n\n<p>Next, the sweeping needle that update the graphs:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">\/* Sweeping needle that triggers the bars *\/\nuint8_t needle_x = (uint8_t)((sinf(time_counter * 0.5f) + 1.0f) * 60.0f);\nu8g2_DrawLine(&amp;myDisplay, needle_x, 2, needle_x + 4, 7);\nu8g2_DrawLine(&amp;myDisplay, needle_x, 2, needle_x - 4, 7);\nu8g2_DrawVLine(&amp;myDisplay, needle_x, 0, 8);<\/pre><\/div>\n\n\n\n<p>Right aligned dB value:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">\/* Right aligned dB text *\/\nu8g2_DrawStr(&amp;myDisplay, 100, 7, &quot;-12.4dB&quot;);<\/pre><\/div>\n\n\n\n<p>Draw a separation line as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">u8g2_DrawHLine(&amp;myDisplay, 0, 9, 128);<\/pre><\/div>\n\n\n\n<p>Next, draw the 16 spectrum bars:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\"> \/* 2. Draw the 16 Spectrum Bars *\/\nfor (int i = 0; i &lt; NUM_BARS; i++) {\n  \/* Simulate an audio curve (lower freqs have higher amplitude, falls off to right) *\/\n  float base_amp = (1.0f - (float)i \/ NUM_BARS) * 0.6f + 0.1f;\n\n  \/* Create dynamic waveforms with slightly different sine frequencies *\/\n  float wave1 = sinf(time_counter + i * 0.3f);\n  float wave2 = sinf(time_counter * 1.3f + i * 0.1f);\n  float combined_wave = (wave1 + wave2) \/ 2.0f; \/* Range: -1.0 to 1.0 *\/\n\n  uint8_t bar_val = (uint8_t)((combined_wave + 1.0f) * base_amp * (MAX_BAR_HEIGHT \/ 2.0f));\n\n  \/* Force trigger specific bars when the needle passes over them *\/\n  if (abs(needle_x - (i * 8 + 4)) &lt; 6) {\n    bar_val = MAX_BAR_HEIGHT - (rand() % 10);\n  }\n\n  if (bar_val &gt; MAX_BAR_HEIGHT) bar_val = MAX_BAR_HEIGHT;\n\n  \/* Update persistent peaks *\/\n  if (bar_val &gt; peak_heights[i]) {\n    peak_heights[i] = bar_val;\n    peak_fall_speed[i] = 0;\n  } else {\n    peak_fall_speed[i] += 0.2f; \/* Gravity accelerating *\/\n    peak_heights[i] -= (uint8_t)peak_fall_speed[i];\n    if (peak_heights[i] &gt; MAX_BAR_HEIGHT) peak_heights[i] = 0; \/* Underflow check *\/\n  }\n\n  \/* Draw the main bar (Solid) *\/\n  uint8_t bar_x = i * 8;\n  uint8_t bar_y = 10 + (MAX_BAR_HEIGHT - bar_val);\n  u8g2_DrawBox(&amp;myDisplay, bar_x + 1, bar_y, 6, bar_val);\n\n  \/* Draw the peak marker (line on top) *\/\n  uint8_t peak_y = 10 + (MAX_BAR_HEIGHT - peak_heights[i]);\n  u8g2_DrawHLine(&amp;myDisplay, bar_x + 1, peak_y, 6);\n\n  \/* Draw a centered baseline shadow *\/\n  uint8_t mid_y = 10 + (MAX_BAR_HEIGHT \/ 2);\n  u8g2_DrawHLine(&amp;myDisplay, bar_x + 1, mid_y, 6);\n}<\/pre><\/div>\n\n\n\n<p>Next, draw the sub bass :<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\"> \/* 3. Draw the Sub-bass bouncing bar at the bottom *\/\nfloat sub_wave = (sinf(time_counter * 0.8f) + 1.0f) \/ 2.0f; \/* 0.0 to 1.0 *\/\nuint8_t sub_h = (uint8_t)(sub_wave * 10.0f);\nu8g2_DrawBox(&amp;myDisplay, 0, 64 - sub_h, 128, sub_h);<\/pre><\/div>\n\n\n\n<p>Draw the moving tick:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">\/* 4. Draw moving tick marks over the sub bar *\/\nfor (int i = 0; i &lt; 8; i++) {\n  int tick_x = ((int)(time_counter * 10) + i * 20) % 140 - 10;\n  u8g2_DrawVLine(&amp;myDisplay, tick_x, 60, 4);\n}<\/pre><\/div>\n\n\n\n<p>Finally, update the display:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">u8g2_SendBuffer(&amp;myDisplay);<\/pre><\/div>\n\n\n\n<p>Hence, the function as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">#define NUM_BARS 16\n#define MAX_BAR_HEIGHT 40\n\nvoid render_audio_visualizer(void) \n{\n    static uint32_t last_update = 0;\n    static float time_counter = 0.0f;\n\n    \/* Persistent peak markers that slowly fall down *\/\n    static uint8_t peak_heights[NUM_BARS] = {0};\n    static float peak_fall_speed[NUM_BARS] = {0};\n\n    uint32_t now = HAL_GetTick();\n\n    \/* Update ~30 frames per second (non-blocking) *\/\n    if (now - last_update &gt; 33) {\n        \n\n        u8g2_ClearBuffer(&amp;myDisplay);\n\n        \/* 1. Draw UI Header *\/\n        u8g2_SetFont(&amp;myDisplay, u8g2_font_5x7_tr);\n        u8g2_DrawStr(&amp;myDisplay, 0, 7, &quot;AUDIO SCAN&quot;);\n\n        \/* Sweeping needle that triggers the bars *\/\n        uint8_t needle_x = (uint8_t)((sinf(time_counter * 0.5f) + 1.0f) * 60.0f);\n        u8g2_DrawLine(&amp;myDisplay, needle_x, 2, needle_x + 4, 7);\n        u8g2_DrawLine(&amp;myDisplay, needle_x, 2, needle_x - 4, 7);\n        u8g2_DrawVLine(&amp;myDisplay, needle_x, 0, 8);\n\n        \/* Right aligned dB text *\/\n        u8g2_DrawStr(&amp;myDisplay, 100, 7, &quot;-12.4dB&quot;);\n\n        u8g2_DrawHLine(&amp;myDisplay, 0, 9, 128);\n\n        \/* 2. Draw the 16 Spectrum Bars *\/\n        for (int i = 0; i &lt; NUM_BARS; i++) {\n            \/* Simulate an audio curve (lower freqs have higher amplitude, falls off to right) *\/\n            float base_amp = (1.0f - (float)i \/ NUM_BARS) * 0.6f + 0.1f;\n\n            \/* Create dynamic waveforms with slightly different sine frequencies *\/\n            float wave1 = sinf(time_counter + i * 0.3f);\n            float wave2 = sinf(time_counter * 1.3f + i * 0.1f);\n            float combined_wave = (wave1 + wave2) \/ 2.0f; \/* Range: -1.0 to 1.0 *\/\n\n            uint8_t bar_val = (uint8_t)((combined_wave + 1.0f) * base_amp * (MAX_BAR_HEIGHT \/ 2.0f));\n\n            \/* Force trigger specific bars when the needle passes over them *\/\n            if (abs(needle_x - (i * 8 + 4)) &lt; 6) {\n                bar_val = MAX_BAR_HEIGHT - (rand() % 10);\n            }\n\n            if (bar_val &gt; MAX_BAR_HEIGHT) bar_val = MAX_BAR_HEIGHT;\n\n            \/* Update persistent peaks *\/\n            if (bar_val &gt; peak_heights[i]) {\n                peak_heights[i] = bar_val;\n                peak_fall_speed[i] = 0;\n            } else {\n                peak_fall_speed[i] += 0.2f; \/* Gravity accelerating *\/\n                peak_heights[i] -= (uint8_t)peak_fall_speed[i];\n                if (peak_heights[i] &gt; MAX_BAR_HEIGHT) peak_heights[i] = 0; \/* Underflow check *\/\n            }\n\n            \/* Draw the main bar (Solid) *\/\n            uint8_t bar_x = i * 8;\n            uint8_t bar_y = 10 + (MAX_BAR_HEIGHT - bar_val);\n            u8g2_DrawBox(&amp;myDisplay, bar_x + 1, bar_y, 6, bar_val);\n\n            \/* Draw the peak marker (line on top) *\/\n            uint8_t peak_y = 10 + (MAX_BAR_HEIGHT - peak_heights[i]);\n            u8g2_DrawHLine(&amp;myDisplay, bar_x + 1, peak_y, 6);\n\n            \/* Draw a centered baseline shadow *\/\n            uint8_t mid_y = 10 + (MAX_BAR_HEIGHT \/ 2);\n            u8g2_DrawHLine(&amp;myDisplay, bar_x + 1, mid_y, 6);\n        }\n\n        \/* 3. Draw the Sub-bass bouncing bar at the bottom *\/\n        float sub_wave = (sinf(time_counter * 0.8f) + 1.0f) \/ 2.0f; \/* 0.0 to 1.0 *\/\n        uint8_t sub_h = (uint8_t)(sub_wave * 10.0f);\n        u8g2_DrawBox(&amp;myDisplay, 0, 64 - sub_h, 128, sub_h);\n\n        \/* 4. Draw moving tick marks over the sub bar *\/\n        for (int i = 0; i &lt; 8; i++) {\n            int tick_x = ((int)(time_counter * 10) + i * 20) % 140 - 10;\n            u8g2_DrawVLine(&amp;myDisplay, tick_x, 60, 4);\n        }\n\n        u8g2_SendBuffer(&amp;myDisplay);\n    }\n}\n\n<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>Finally, in while 1 loop, in user code begin 3, call the function:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">render_audio_visualizer();<\/pre><\/div>\n\n\n\n<p>hats all for the firmware. Save the project and run it on your MCU as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"34\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/07\/2025-07-03_17-48-35-1024x34.jpg\" alt=\"\" class=\"wp-image-3598\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/07\/2025-07-03_17-48-35-1024x34.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/07\/2025-07-03_17-48-35-300x10.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/07\/2025-07-03_17-48-35-768x25.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/07\/2025-07-03_17-48-35-1536x51.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/07\/2025-07-03_17-48-35-2048x68.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/07\/2025-07-03_17-48-35-1150x38.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/07\/2025-07-03_17-48-35-750x25.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/07\/2025-07-03_17-48-35-400x13.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2025\/07\/2025-07-03_17-48-35-250x8.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>You may download the project from <a href=\"https:\/\/github.com\/hussamaldean\/Embedded-Expert-Post-Projects\/tree\/main\/Projects\/U8G2_UI\" data-type=\"link\" data-id=\"https:\/\/github.com\/hussamaldean\/Embedded-Expert-Post-Projects\/tree\/main\/Projects\/U8G2_UI\">here<\/a>.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. Results:<\/h2>\n\n\n\n<p>You should get something like this<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Animation Using U8G2 with STM32\" width=\"1170\" height=\"658\" src=\"https:\/\/www.youtube.com\/embed\/kwCnUPKT3hY?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>Next, we shall use encoder and push button to create multiple page UI.<\/p>\n\n\n\n<p>Stay tuned.<\/p>\n\n\n\n<p>Happy coding \ud83d\ude09<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Adding fluid, dynamic animations to embedded displays doesn&#8217;t require a high-end GPU or a complex graphics framework. In this guide, you will learn how to use the U8g2 graphics library to build a zero-flicker, high-frame-rate audio visualizer on a standard 128&#215;64 OLED using nothing but standard C math and smart buffer management. In this guide, [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,19,11,12],"tags":[],"class_list":["post-4679","post","type-post","status-publish","format-standard","hentry","category-embedded-systems","category-lcd","category-peripheral-drivers","category-stm32"],"_links":{"self":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4679"}],"collection":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4679"}],"version-history":[{"count":2,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4679\/revisions"}],"predecessor-version":[{"id":4683,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4679\/revisions\/4683"}],"wp:attachment":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4679"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4679"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4679"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}