<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Reference Archives | Teach Me Microcontrollers!</title>
	<atom:link href="https://www.teachmemicro.com/category/reference/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.teachmemicro.com/category/reference/</link>
	<description>Microcontroller Tutorials and Resources</description>
	<lastBuildDate>Fri, 28 Aug 2026 22:58:58 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1</generator>

<image>
	<url>https://www.teachmemicro.com/wp-content/uploads/2019/04/blue-icon-65x65.png</url>
	<title>Reference Archives | Teach Me Microcontrollers!</title>
	<link>https://www.teachmemicro.com/category/reference/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>ESP32-S3 Pinout Diagram &#124; ESP32-DevKitC-1</title>
		<link>https://www.teachmemicro.com/esp32-s3-pinout/</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Fri, 28 Aug 2026 12:07:07 +0000</pubDate>
				<category><![CDATA[Reference]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=12795</guid>

					<description><![CDATA[<p>The ESP32-S3 is one of Espressif's most capable microcontrollers, combining Wi-Fi, Bluetooth Low Energy, USB support, a large number of GPIO pins, and hardware intended for signal-processing and AI workloads. However, taking full advantage of the ESP32-S3 requires understanding which GPIO pins are available and which pins have special functions. This guide provides a complete &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/esp32-s3-pinout/">ESP32-S3 Pinout Diagram | ESP32-DevKitC-1</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>The <strong>ESP32-S3</strong> is one of Espressif's most capable microcontrollers, combining Wi-Fi, Bluetooth Low Energy, USB support, a large number of GPIO pins, and hardware intended for signal-processing and AI workloads. However, taking full advantage of the ESP32-S3 requires understanding which GPIO pins are available and which pins have special functions.</p>
<p>This guide provides a complete <strong>ESP32-S3 pinout reference</strong>, including the GPIO pins used for ADC, touch sensing, PWM, UART, SPI, I2C, USB, JTAG, and boot configuration. We will use the popular <strong>ESP32-S3-DevKitC-1</strong> as our reference development board, although most of the GPIO information also applies to other ESP32-S3 boards. If you're new to the board, see our <a href="https://www.teachmemicro.com/esp32-s3-getting-started-guide-pinout-arduino-ide-usb-and-first-project/"><strong data-start="316" data-end="350">ESP32-S3 Getting Started Guide</strong></a> for Arduino IDE setup, board configuration, uploading your first sketch, and basic testing.</p>
<p>One important difference from the original ESP32 is that the ESP32-S3 has a much larger number of available GPIO pins. It also includes native USB support and as many as 14 capacitive-touch inputs.</p>
<p><img data-dominant-color="dddcdc" data-has-transparency="false" style="--dominant-color: #dddcdc;" decoding="async" class="aligncenter size-full wp-image-12798 not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2026/08/ESP32-S3-pinout-diagram.avif" alt="ESP32-S3 Pinout Diagram" width="1619" height="1114" srcset="https://www.teachmemicro.com/wp-content/uploads/2026/08/ESP32-S3-pinout-diagram.avif 1619w, https://www.teachmemicro.com/wp-content/uploads/2026/08/ESP32-S3-pinout-diagram-300x206.avif 300w, https://www.teachmemicro.com/wp-content/uploads/2026/08/ESP32-S3-pinout-diagram-1024x705.avif 1024w, https://www.teachmemicro.com/wp-content/uploads/2026/08/ESP32-S3-pinout-diagram-768x528.avif 768w, https://www.teachmemicro.com/wp-content/uploads/2026/08/ESP32-S3-pinout-diagram-1536x1057.avif 1536w, https://www.teachmemicro.com/wp-content/uploads/2026/08/ESP32-S3-pinout-diagram-110x75.avif 110w" sizes="(max-width: 1619px) 100vw, 1619px" /></p>
<p><em>Suggested diagram labels: Digital GPIO, ADC1, ADC2, Touch, USB, UART, JTAG, strapping pins, flash/PSRAM-related pins, power, and onboard RGB LED.</em></p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 Pinout Overview</strong></h2>
<p>The ESP32-S3 chip has <strong>45 physical GPIO pins</strong>, numbered GPIO0 through GPIO21 and GPIO26 through GPIO48. Notice that GPIO22 through GPIO25 do not exist on the ESP32-S3. Not all 45 GPIOs are necessarily available on a development board. Some GPIO pins are connected internally to the SPI flash and PSRAM, while others may be assigned to onboard components.</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>ESP32-S3</th>
</tr>
</thead>
<tbody>
<tr>
<td>GPIO</td>
<td>45 physical GPIOs</td>
</tr>
<tr>
<td>ADC</td>
<td>2 × 12-bit SAR ADCs, 20 channels</td>
</tr>
<tr>
<td>Touch</td>
<td>14 capacitive touch GPIOs</td>
</tr>
<tr>
<td>UART</td>
<td>3 controllers</td>
</tr>
<tr>
<td>I2C</td>
<td>2 controllers</td>
</tr>
<tr>
<td>SPI</td>
<td>Multiple SPI interfaces</td>
</tr>
<tr>
<td>PWM</td>
<td>8 LEDC PWM channels</td>
</tr>
<tr>
<td>Native USB</td>
<td>GPIO19 and GPIO20</td>
</tr>
<tr>
<td>Strapping Pins</td>
<td>GPIO0, GPIO3, GPIO45, GPIO46</td>
</tr>
</tbody>
</table>
<p>The ESP32 architecture also uses a flexible <strong>GPIO Matrix</strong>. This means peripherals such as UART, I2C, PWM, and many SPI signals can be routed to different GPIO pins instead of having a single permanently assigned pin. This flexibility is useful when designing an ESP32-S3 project, but it also means that a pinout diagram alone does not always tell you which pins you should actually use.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Interactive ESP32-S3 Pinout Diagram</strong></h2>
<div align="center"><iframe loading="lazy" style="border: 1px solid #e2e8f0; border-radius: 8px;" src="https://micropinouts.com/embed/board/esp32-s3-devkitc-1" width="800" height="600" frameborder="0"></iframe></div>
<h2><strong>ESP32-S3 GPIO Pin Reference</strong></h2>
<p>The following table summarizes the most important functions and restrictions of the ESP32-S3 GPIO pins.</p>
<table>
<thead>
<tr>
<th>GPIO</th>
<th>Main Functions</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td>GPIO0</td>
<td>RTC GPIO</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Strapping/BOOT pin</td>
</tr>
<tr>
<td>GPIO1</td>
<td>ADC1_CH0, TOUCH1, RTC</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO2</td>
<td>ADC1_CH1, TOUCH2, RTC</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO3</td>
<td>ADC1_CH2, TOUCH3, RTC</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Strapping pin</td>
</tr>
<tr>
<td>GPIO4</td>
<td>ADC1_CH3, TOUCH4, RTC</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO5</td>
<td>ADC1_CH4, TOUCH5, RTC</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO6</td>
<td>ADC1_CH5, TOUCH6, RTC</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO7</td>
<td>ADC1_CH6, TOUCH7, RTC</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO8</td>
<td>ADC1_CH7, TOUCH8, RTC</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO9</td>
<td>ADC1_CH8, TOUCH9, RTC, FSPI</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO10</td>
<td>ADC1_CH9, TOUCH10, RTC, FSPI</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO11</td>
<td>ADC2_CH0, TOUCH11, RTC, FSPI</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO12</td>
<td>ADC2_CH1, TOUCH12, RTC, FSPI</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO13</td>
<td>ADC2_CH2, TOUCH13, RTC, FSPI</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO14</td>
<td>ADC2_CH3, TOUCH14, RTC, FSPI</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO15</td>
<td>ADC2_CH4, RTC, U0RTS</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO16</td>
<td>ADC2_CH5, RTC, U0CTS</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO17</td>
<td>ADC2_CH6, RTC, U1TXD</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO18</td>
<td>ADC2_CH7, RTC, U1RXD</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO19</td>
<td>ADC2_CH8, USB D-, RTC</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Native USB</td>
</tr>
<tr>
<td>GPIO20</td>
<td>ADC2_CH9, USB D+, RTC</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Native USB</td>
</tr>
<tr>
<td>GPIO21</td>
<td>RTC GPIO</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO26–32</td>
<td>SPI flash/PSRAM</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Normally reserved for memory</td>
</tr>
<tr>
<td>GPIO33–37</td>
<td>SPI flash/PSRAM functions</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Availability depends on memory configuration</td>
</tr>
<tr>
<td>GPIO38</td>
<td>General GPIO</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> RGB LED on DevKitC-1 v1.1</td>
</tr>
<tr>
<td>GPIO39</td>
<td>MTCK / JTAG</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> JTAG pin</td>
</tr>
<tr>
<td>GPIO40</td>
<td>MTDO / JTAG</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> JTAG pin</td>
</tr>
<tr>
<td>GPIO41</td>
<td>MTDI / JTAG</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> JTAG pin</td>
</tr>
<tr>
<td>GPIO42</td>
<td>MTMS / JTAG</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> JTAG pin</td>
</tr>
<tr>
<td>GPIO43</td>
<td>UART0 TX</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Serial/programming output</td>
</tr>
<tr>
<td>GPIO44</td>
<td>UART0 RX</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Serial/programming input</td>
</tr>
<tr>
<td>GPIO45</td>
<td>General GPIO</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Strapping pin</td>
</tr>
<tr>
<td>GPIO46</td>
<td>General GPIO</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Strapping pin</td>
</tr>
<tr>
<td>GPIO47</td>
<td>General GPIO</td>
<td>Generally available</td>
</tr>
<tr>
<td>GPIO48</td>
<td>General GPIO</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> RGB LED on original DevKitC-1</td>
</tr>
</tbody>
</table>
<p>Keep in mind that the exact availability of GPIO33 through GPIO37 depends on the ESP32-S3 module and its flash/PSRAM configuration. Always check the module fitted to your development board before using these pins.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Best ESP32-S3 GPIO Pins to Use</strong></h2>
<p>If you simply need GPIO pins for LEDs, buttons, sensors, relays, and similar devices, it is generally best to avoid pins involved in boot configuration, flash/PSRAM, USB, and debugging. For a typical ESP32-S3 development board, the following pins are among the easier GPIOs to work with:</p>
<ul>
<li><strong>GPIO1</strong></li>
<li><strong>GPIO2</strong></li>
<li><strong>GPIO4 to GPIO18</strong></li>
<li><strong>GPIO21</strong></li>
<li><strong>GPIO47</strong></li>
</ul>
<p>These are not the only GPIO pins you can use. They are simply good starting choices because they normally do not interfere with the ESP32-S3 boot process or its USB and JTAG interfaces. GPIO38 and GPIO48 can also be usable depending on the board revision. On the ESP32-S3-DevKitC-1, however, one of these pins is connected to the onboard addressable RGB LED.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 Pins to Avoid or Use With Caution</strong></h2>
<p>Some ESP32-S3 pins can still be used as GPIOs but have important secondary functions.</p>
<table>
<thead>
<tr>
<th>Pins</th>
<th>Reason for Caution</th>
</tr>
</thead>
<tbody>
<tr>
<td>GPIO0, GPIO3, GPIO45, GPIO46</td>
<td>Strapping pins sampled during reset</td>
</tr>
<tr>
<td>GPIO19, GPIO20</td>
<td>Native USB D- and D+</td>
</tr>
<tr>
<td>GPIO26–32</td>
<td>Normally connected to SPI flash/PSRAM</td>
</tr>
<tr>
<td>GPIO33–37</td>
<td>May be used by Octal flash/PSRAM</td>
</tr>
<tr>
<td>GPIO39–42</td>
<td>JTAG debugging interface</td>
</tr>
<tr>
<td>GPIO43, GPIO44</td>
<td>UART0 serial interface</td>
</tr>
</tbody>
</table>
<p>Using one of these pins does not necessarily cause a problem. The important point is to understand what functionality you may lose or what boot behavior your external circuit might affect.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 Strapping Pins</strong></h2>
<p>The ESP32-S3 has four <strong>strapping pins</strong>:</p>
<ul>
<li>GPIO0</li>
<li>GPIO3</li>
<li>GPIO45</li>
<li>GPIO46</li>
</ul>
<p>Strapping pins are read by the ESP32-S3 during reset to determine certain startup settings. After the chip has booted, they can generally function as normal GPIO pins. The most familiar example is <strong>GPIO0</strong>. Holding GPIO0 at the appropriate level during reset can place the ESP32-S3 into its firmware download mode. This is what the BOOT button on many ESP32-S3 boards controls. Because the state of a strapping pin matters during reset, avoid connecting external circuits that force these pins to an inappropriate logic level during startup.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 ADC Pins</strong></h2>
<p>The ESP32-S3 contains <strong>two 12-bit SAR ADC peripherals</strong> providing a total of 20 analog input channels.</p>
<h3>ADC1 Pins</h3>
<table>
<thead>
<tr>
<th>GPIO</th>
<th>ADC Channel</th>
</tr>
</thead>
<tbody>
<tr>
<td>GPIO1</td>
<td>ADC1_CH0</td>
</tr>
<tr>
<td>GPIO2</td>
<td>ADC1_CH1</td>
</tr>
<tr>
<td>GPIO3</td>
<td>ADC1_CH2</td>
</tr>
<tr>
<td>GPIO4</td>
<td>ADC1_CH3</td>
</tr>
<tr>
<td>GPIO5</td>
<td>ADC1_CH4</td>
</tr>
<tr>
<td>GPIO6</td>
<td>ADC1_CH5</td>
</tr>
<tr>
<td>GPIO7</td>
<td>ADC1_CH6</td>
</tr>
<tr>
<td>GPIO8</td>
<td>ADC1_CH7</td>
</tr>
<tr>
<td>GPIO9</td>
<td>ADC1_CH8</td>
</tr>
<tr>
<td>GPIO10</td>
<td>ADC1_CH9</td>
</tr>
</tbody>
</table>
<h3>ADC2 Pins</h3>
<table>
<thead>
<tr>
<th>GPIO</th>
<th>ADC Channel</th>
</tr>
</thead>
<tbody>
<tr>
<td>GPIO11</td>
<td>ADC2_CH0</td>
</tr>
<tr>
<td>GPIO12</td>
<td>ADC2_CH1</td>
</tr>
<tr>
<td>GPIO13</td>
<td>ADC2_CH2</td>
</tr>
<tr>
<td>GPIO14</td>
<td>ADC2_CH3</td>
</tr>
<tr>
<td>GPIO15</td>
<td>ADC2_CH4</td>
</tr>
<tr>
<td>GPIO16</td>
<td>ADC2_CH5</td>
</tr>
<tr>
<td>GPIO17</td>
<td>ADC2_CH6</td>
</tr>
<tr>
<td>GPIO18</td>
<td>ADC2_CH7</td>
</tr>
<tr>
<td>GPIO19</td>
<td>ADC2_CH8</td>
</tr>
<tr>
<td>GPIO20</td>
<td>ADC2_CH9</td>
</tr>
</tbody>
</table>
<p>For new designs, <strong>ADC1 is generally the better choice</strong> when you have free pins available. Also remember that the ESP32-S3 GPIO voltage is 3.3 V. Never apply a 5 V analog signal directly to an ADC pin. Use a voltage divider or appropriate signal-conditioning circuit when measuring higher voltages. If accuracy is important, Espressif also recommends filtering the ADC input. A small capacitor close to the ADC input can reduce noise, and software calibration can further improve measurement accuracy.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 Touch Pins</strong></h2>
<p>The ESP32-S3 provides <strong>14 capacitive-touch GPIO pins</strong>. These can detect changes in capacitance caused by a finger approaching or touching an electrode.</p>
<table>
<thead>
<tr>
<th>Touch Channel</th>
<th>GPIO</th>
</tr>
</thead>
<tbody>
<tr>
<td>TOUCH1</td>
<td>GPIO1</td>
</tr>
<tr>
<td>TOUCH2</td>
<td>GPIO2</td>
</tr>
<tr>
<td>TOUCH3</td>
<td>GPIO3</td>
</tr>
<tr>
<td>TOUCH4</td>
<td>GPIO4</td>
</tr>
<tr>
<td>TOUCH5</td>
<td>GPIO5</td>
</tr>
<tr>
<td>TOUCH6</td>
<td>GPIO6</td>
</tr>
<tr>
<td>TOUCH7</td>
<td>GPIO7</td>
</tr>
<tr>
<td>TOUCH8</td>
<td>GPIO8</td>
</tr>
<tr>
<td>TOUCH9</td>
<td>GPIO9</td>
</tr>
<tr>
<td>TOUCH10</td>
<td>GPIO10</td>
</tr>
<tr>
<td>TOUCH11</td>
<td>GPIO11</td>
</tr>
<tr>
<td>TOUCH12</td>
<td>GPIO12</td>
</tr>
<tr>
<td>TOUCH13</td>
<td>GPIO13</td>
</tr>
<tr>
<td>TOUCH14</td>
<td>GPIO14</td>
</tr>
</tbody>
</table>
<p>Touch inputs are useful for projects such as touch buttons, sliders, control panels, and proximity sensors without requiring a mechanical switch.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 PWM Pins</strong></h2>
<p>PWM on the ESP32-S3 is much more flexible than on traditional Arduino boards. Instead of having a small number of pins permanently designated as PWM pins, the ESP32-S3 can route the <strong>LEDC PWM peripheral</strong> to suitable GPIO pins through the GPIO Matrix.</p>
<p>The ESP32-S3 provides <strong>8 LEDC channels</strong>. These can be used for applications including:</p>
<ul>
<li>LED brightness control</li>
<li>RGB LEDs</li>
<li>Motor speed control</li>
<li>Servo control</li>
<li>Generating periodic digital signals</li>
</ul>
<p>This means there is no simple list of dedicated "PWM pins" like there is on an Arduino Uno. Instead, choose a suitable output-capable GPIO that is not required for another important function.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 I2C Pins</strong></h2>
<p>The ESP32-S3 provides two I2C controllers. Thanks to the GPIO Matrix, the SDA and SCL signals can be assigned to different GPIO pins. Therefore, the ESP32-S3 does not have only one mandatory pair of I2C pins. For example, you could connect an I2C sensor using GPIO8 for SDA and GPIO9 for SCL:</p>
<pre><pre><code class="language-cpp">#include &lt;Wire.h&gt;

#define SDA_PIN 8
#define SCL_PIN 9

void setup() {
  Wire.begin(SDA_PIN, SCL_PIN);
}

void loop() {
}</code></pre></pre>
<p>If a development board or Arduino board definition specifies default I2C pins, calling <pre><code class="language-cpp">Wire.begin()</code></pre> without arguments may use those defaults. Explicitly specifying SDA and SCL makes your intended pin assignment clear.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 SPI Pins</strong></h2>
<p>The ESP32-S3 contains multiple SPI interfaces. Some SPI interfaces are used internally for flash and PSRAM, while others can be used to connect external devices such as:</p>
<ul>
<li>TFT displays</li>
<li>SD cards</li>
<li>Ethernet controllers</li>
<li>ADC and DAC modules</li>
<li>Radio transceivers</li>
</ul>
<p>SPI signals can often be routed through the GPIO Matrix, so external SPI devices do not always need to use a single fixed pin configuration. However, avoid blindly selecting GPIO26 through GPIO32 because these pins are normally associated with the ESP32-S3's flash and PSRAM interfaces.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 UART Pins</strong></h2>
<p>The ESP32-S3 has three UART controllers. UART signals can normally be routed to different GPIOs, making it possible to choose convenient pins for serial devices such as GPS modules, fingerprint sensors, GSM modules, and other microcontrollers. Two pins have particularly important default UART0 functions:</p>
<table>
<thead>
<tr>
<th>GPIO</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td>GPIO43</td>
<td>U0TXD</td>
</tr>
<tr>
<td>GPIO44</td>
<td>U0RXD</td>
</tr>
</tbody>
</table>
<p>GPIO43 and GPIO44 can be reused, but doing so may interfere with serial console output or other UART0 functions depending on how the board and firmware are configured.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 Native USB Pins</strong></h2>
<p>Unlike the original ESP32, the ESP32-S3 includes native USB functionality.</p>
<table>
<thead>
<tr>
<th>GPIO</th>
<th>USB Function</th>
</tr>
</thead>
<tbody>
<tr>
<td>GPIO19</td>
<td>USB D-</td>
</tr>
<tr>
<td>GPIO20</td>
<td>USB D+</td>
</tr>
</tbody>
</table>
<p>These pins support the ESP32-S3's native USB functionality, including USB Serial/JTAG and USB OTG applications. GPIO19 and GPIO20 can be configured as normal GPIOs, but doing so prevents them from simultaneously serving as the native USB data pins. If your board uses the ESP32-S3 USB connector for programming, debugging, or USB device functionality, leave these GPIOs assigned to USB.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 JTAG Pins</strong></h2>
<p>The traditional JTAG interface is available through GPIO39 to GPIO42:</p>
<table>
<thead>
<tr>
<th>GPIO</th>
<th>JTAG Signal</th>
</tr>
</thead>
<tbody>
<tr>
<td>GPIO39</td>
<td>MTCK</td>
</tr>
<tr>
<td>GPIO40</td>
<td>MTDO</td>
</tr>
<tr>
<td>GPIO41</td>
<td>MTDI</td>
</tr>
<tr>
<td>GPIO42</td>
<td>MTMS</td>
</tr>
</tbody>
</table>
<p>If you are not using external JTAG debugging, these pins may be available for other GPIO functions. However, keeping them free can be useful during firmware development and debugging. The ESP32-S3 also supports USB Serial/JTAG through GPIO19 and GPIO20, so debugging does not always require the traditional four-pin JTAG connection.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 Flash and PSRAM Pins</strong></h2>
<p>Flash and PSRAM are where ESP32-S3 pin selection becomes particularly board-dependent. GPIO26 through GPIO32 are normally used for communication with SPI flash and PSRAM and should <strong>not be treated as general-purpose GPIOs</strong>.</p>
<p>GPIO33 through GPIO37 may also be required when the ESP32-S3 uses Octal flash or Octal PSRAM. The exact GPIO availability therefore depends on the ESP32-S3 chip or module installed on your board. For example, some ESP32-S3-WROOM configurations use GPIO35, GPIO36, and GPIO37 internally. Those pins cannot then be used to connect external peripherals even if they appear in a generic ESP32-S3 GPIO table. This is why you should check both the <strong>ESP32-S3 chip pinout</strong> and the documentation for your exact ESP32-S3 module or development board.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3-DevKitC-1 RGB LED Pin</strong></h2>
<p>The ESP32-S3-DevKitC-1 includes an onboard addressable RGB LED, but there is an important difference between board revisions.</p>
<table>
<thead>
<tr>
<th>DevKitC-1 Version</th>
<th>RGB LED GPIO</th>
</tr>
</thead>
<tbody>
<tr>
<td>Original revision</td>
<td>GPIO48</td>
</tr>
<tr>
<td>v1.1</td>
<td>GPIO38</td>
</tr>
</tbody>
</table>
<p>If an RGB LED example does not work despite using the correct ESP32-S3 board definition, check which DevKitC-1 revision you own. This distinction is also worth remembering when connecting another peripheral to GPIO38 or GPIO48.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 Power Pins</strong></h2>
<p>On the ESP32-S3-DevKitC-1 you will also find several power-related pins:</p>
<table>
<thead>
<tr>
<th>Pin</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>3V3</td>
<td>3.3 V supply rail</td>
</tr>
<tr>
<td>5V</td>
<td>5 V supply rail</td>
</tr>
<tr>
<td>G / GND</td>
<td>Ground</td>
</tr>
<tr>
<td>RST / EN</td>
<td>ESP32-S3 enable/reset</td>
</tr>
</tbody>
</table>
<p>The ESP32-S3 GPIOs themselves use <strong>3.3 V logic</strong>. Do not connect a 5 V logic output directly to an ESP32-S3 GPIO unless the signal is converted to a safe voltage using a voltage divider, level shifter, or another appropriate circuit.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 GPIO Example with Arduino</strong></h2>
<p>GPIO programming on the ESP32-S3 works much like other Arduino-compatible microcontrollers. For example, the following sketch toggles an LED connected to GPIO4:</p>
<pre><pre><code class="language-cpp">#define LED_PIN 4

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_PIN, HIGH);
  delay(500);

  digitalWrite(LED_PIN, LOW);
  delay(500);
}</code></pre></pre>
<p>GPIO4 is a convenient example because it is a general-purpose pin while also supporting ADC and capacitive-touch functionality. The same physical pin can perform different functions depending on how the peripheral registers or Arduino libraries configure it.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>ESP32-S3 vs ESP32 Pinout</strong></h2>
<p>If you are moving from the original ESP32 to the ESP32-S3, do not assume that existing pin assignments can be copied directly. The two chips have substantially different GPIO layouts. The ESP32-S3 also introduces native USB on GPIO19 and GPIO20 and has different ADC, touch, flash, PSRAM, and strapping-pin assignments. At the same time, the ESP32-S3 provides considerably more usable GPIO resources, making it better suited for projects requiring many sensors, displays, buttons, or other peripherals. Always use an ESP32-S3-specific pinout when designing an ESP32-S3 circuit rather than referring to a standard ESP32 pinout diagram.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Frequently Asked Questions</strong></h2>
<h3>How many GPIO pins does the ESP32-S3 have?</h3>
<p>The ESP32-S3 chip has 45 physical GPIO pins: GPIO0 through GPIO21 and GPIO26 through GPIO48. The exact number available on a development board is smaller because some GPIOs may be connected to flash, PSRAM, USB, or onboard peripherals.</p>
<h3>What are the best GPIO pins to use on the ESP32-S3?</h3>
<p>For ordinary digital peripherals, GPIO1, GPIO2, GPIO4 through GPIO18, GPIO21, and GPIO47 are generally convenient choices. Always check the exact development board because onboard hardware can reserve additional pins.</p>
<h3>Which ESP32-S3 pins should I avoid?</h3>
<p>GPIO26 through GPIO32 should normally be avoided because they are associated with flash/PSRAM. GPIO0, GPIO3, GPIO45, and GPIO46 are strapping pins. GPIO19 and GPIO20 are used for native USB, while GPIO39 through GPIO42 are associated with JTAG.</p>
<h3>Which ESP32-S3 pins support ADC?</h3>
<p>GPIO1 through GPIO10 belong to ADC1, while GPIO11 through GPIO20 belong to ADC2. Together they provide 20 ADC channels.</p>
<h3>Which ESP32-S3 pins support touch?</h3>
<p>GPIO1 through GPIO14 provide the ESP32-S3's capacitive-touch inputs.</p>
<h3>Which ESP32-S3 pins are used for USB?</h3>
<p>GPIO19 is USB D- and GPIO20 is USB D+. Reassigning these pins as ordinary GPIOs disables their use for the native USB interface.</p>
<h3>Does the ESP32-S3 have fixed I2C pins?</h3>
<p>No. The ESP32-S3 GPIO Matrix allows I2C SDA and SCL to be assigned to suitable GPIO pins. Development-board definitions may provide default pins, but they can usually be changed in software.</p>
<h3>Can any ESP32-S3 GPIO generate PWM?</h3>
<p>PWM signals from the LEDC peripheral can be routed to suitable output-capable GPIOs. The ESP32-S3 has eight LEDC PWM channels rather than a fixed set of Arduino-style PWM pins.</p>
<h3>Why are GPIO22 through GPIO25 missing?</h3>
<p>The ESP32-S3 GPIO numbering is not continuous. Its physical GPIO ranges are GPIO0–GPIO21 and GPIO26–GPIO48, so GPIO22, GPIO23, GPIO24, and GPIO25 simply do not exist on the chip.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Conclusion</strong></h2>
<p>The ESP32-S3 provides a flexible GPIO system with plenty of pins for sensors, displays, communications interfaces, and other peripherals. Its GPIO Matrix makes interfaces such as I2C, UART, SPI, and PWM much less dependent on fixed pin assignments than on many conventional microcontrollers.</p>
<p>The main pins to pay attention to are the <strong>GPIO0, GPIO3, GPIO45, and GPIO46 strapping pins</strong>, <strong>GPIO19 and GPIO20 USB pins</strong>, <strong>GPIO39 through GPIO42 JTAG pins</strong>, and the GPIOs reserved for <strong>flash and PSRAM</strong>. Once these restrictions are understood, choosing pins for an ESP32-S3 project becomes relatively straightforward.</p>
<p>If you are working with a specific ESP32-S3 development board, remember that the board itself may reserve additional GPIOs for LEDs, displays, cameras, PSRAM, or other onboard hardware. Always combine this ESP32-S3 GPIO reference with the schematic or pinout of your exact board.</p>
<p>The post <a href="https://www.teachmemicro.com/esp32-s3-pinout/">ESP32-S3 Pinout Diagram | ESP32-DevKitC-1</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>PID Tuning Calculator: Calculate Kp, Ki and Kd</title>
		<link>https://www.teachmemicro.com/pid-tuning-calculator/</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Thu, 27 Aug 2026 22:36:25 +0000</pubDate>
				<category><![CDATA[Tools]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=12775</guid>

					<description><![CDATA[<p>PID tuning can be one of the most difficult parts of building a feedback control system. Even when your PID code is correct, the controller will not perform well until the proportional, integral, and derivative gains are properly adjusted. This PID tuning calculator uses the classic Ziegler-Nichols closed-loop method to calculate starting values for Kp, &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/pid-tuning-calculator/">PID Tuning Calculator: Calculate Kp, Ki and Kd</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>PID tuning can be one of the most difficult parts of building a feedback control system. Even when your PID code is correct, the controller will not perform well until the proportional, integral, and derivative gains are properly adjusted.</p>
<p>This <strong>PID tuning calculator</strong> uses the classic Ziegler-Nichols closed-loop method to calculate starting values for <strong>Kp, Ki, and Kd</strong>. Enter the ultimate gain and oscillation period of your system, choose the controller type, and the calculator will generate the corresponding tuning constants.</p>
<p><span id="more-12775"></span></p>

    <div class="tmm-pid-calculator">

        <div class="pid-intro">
            <strong>PID Tuning Calculator</strong>
            <p>
                Enter the ultimate gain (<strong>Ku</strong>) and oscillation period
                (<strong>Tu</strong>) measured from your system. The calculator uses
                the classic Ziegler–Nichols closed-loop tuning method to estimate
                starting values for Kp, Ki and Kd.
            </p>
        </div>

        <div class="pid-grid">

            <div class="pid-field">
                <label>Controller Type</label>
                <select class="pid-controller-type">
                    <option value="pid">PID</option>
                    <option value="pi">PI</option>
                    <option value="p">P</option>
                </select>
            </div>

            <div class="pid-field">
                <label>
                    Ultimate Gain (Ku)
                    <span class="pid-help" title="The proportional gain at which sustained oscillation begins.">?</span>
                </label>
                <input
                    type="number"
                    class="pid-ku"
                    min="0"
                    step="any"
                    placeholder="Example: 4.5"
                >
            </div>

            <div class="pid-field">
                <label>
                    Oscillation Period (Tu)
                    <span class="pid-help" title="Time for one complete sustained oscillation.">?</span>
                </label>

                <div class="pid-input-unit">
                    <input
                        type="number"
                        class="pid-tu"
                        min="0"
                        step="any"
                        placeholder="Example: 2.0"
                    >
                    <select class="pid-tu-unit">
                        <option value="s">seconds</option>
                        <option value="ms">milliseconds</option>
                    </select>
                </div>
            </div>

        </div>

        <button type="button" class="pid-calculate-btn">
            Calculate PID Values
        </button>

        <div class="pid-error" hidden></div>

        <div class="pid-results" hidden>

            <h3>Calculated PID Values</h3>

            <div class="pid-result-grid">

                <div class="pid-result-card">
                    <span>Kp</span>
                    <strong class="pid-result-kp">—</strong>
                    <small>Proportional gain</small>
                </div>

                <div class="pid-result-card">
                    <span>Ki</span>
                    <strong class="pid-result-ki">—</strong>
                    <small>Integral gain</small>
                </div>

                <div class="pid-result-card">
                    <span>Kd</span>
                    <strong class="pid-result-kd">—</strong>
                    <small>Derivative gain</small>
                </div>

            </div>

            <p class="pid-warning">
                These values are starting points only. Actual PID constants should
                be tested and adjusted on your physical system.
            </p>

            <h3>Arduino PID_v1 Code</h3>

            <div class="pid-code-wrap">
                <pre><code class="pid-code"></code></pre>
                <button type="button" class="pid-copy-btn">
                    Copy Code
                </button>
            </div>

        </div>


        <div class="pid-manual-helper">

            <h3>PID Tuning Helper</h3>

            <p>
                Already have Kp, Ki and Kd values? Select the behavior you're seeing
                for a suggested adjustment.
            </p>

            <div class="pid-field">
                <label>What is your system doing?</label>

                <select class="pid-symptom">
                    <option value="">Choose a symptom...</option>
                    <option value="slow">Very slow response</option>
                    <option value="steady">Does not quite reach the setpoint</option>
                    <option value="overshoot">Large overshoot</option>
                    <option value="fastosc">Rapid continuous oscillation</option>
                    <option value="slowosc">Slow repeated oscillation</option>
                    <option value="noise">Noisy or jittery output</option>
                    <option value="windup">Output stays saturated too long</option>
                    <option value="wrongway">Moves away from setpoint</option>
                </select>
            </div>

            <div class="pid-suggestion" hidden></div>

        </div>

    </div>

    
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>How to Use the PID Tuning Calculator</strong></h2>
<p>The calculator requires two values measured from your system:</p>
<ul>
<li><strong>Ku (Ultimate Gain)</strong> — the proportional gain at which the system produces sustained oscillation.</li>
<li><strong>Tu (Ultimate Period)</strong> — the time required for one complete oscillation at the ultimate gain.</li>
</ul>
<p>You can enter the oscillation period in either seconds or milliseconds.</p>
<p>Then select whether you want a <strong>P, PI, or PID controller</strong> and click <strong>Calculate PID Values</strong>.</p>
<p>The calculator returns the recommended starting values for:</p>
<ul>
<li><strong>Kp</strong> — proportional gain</li>
<li><strong>Ki</strong> — integral gain</li>
<li><strong>Kd</strong> — derivative gain</li>
</ul>
<p>For Arduino users, the tool also generates the corresponding constants for the popular <em>PID_v1</em> library.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Finding Ku and Tu</strong></h2>
<p>To use the Ziegler-Nichols closed-loop tuning method, first disable the integral and derivative terms:</p>
<pre><pre><code class="language-cpp">Ki = 0;
Kd = 0;</code></pre></pre>
<p>Start with a low Kp value and gradually increase it while observing the system response.</p>
<p>At some gain, the output may begin oscillating around the setpoint without the oscillation continuously growing or disappearing. This gain is called the <strong>ultimate gain, Ku</strong>.</p>
<p>Measure the time between two consecutive peaks of the oscillation. This is the <strong>ultimate period, Tu</strong>.</p>
<p>For example, suppose sustained oscillation occurs when:</p>
<pre><pre><code class="language-cpp">Ku = 4.0
Tu = 2.0 seconds</code></pre></pre>
<p>For a PID controller, the classic Ziegler-Nichols equations produce approximately:</p>
<pre><pre><code class="language-cpp">Kp = 2.4
Ki = 2.4
Kd = 0.6</code></pre></pre>
<p>These values can then be used as the starting point for further tuning.</p>
<p><strong>[IMAGE PLACEHOLDER: Graph showing sustained oscillation with Ku and Tu labeled]</strong></p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Ziegler-Nichols PID Formulas</strong></h2>
<p>The calculator uses the classic closed-loop Ziegler-Nichols rules.</p>
<p>For a proportional controller:</p>
<p style="text-align: center;"><span class='MathJax_Preview'><img src='https://www.teachmemicro.com/wp-content/plugins/latex/cache/tex_a449cc90f489689cad33c93652a777c1.gif' style='vertical-align: middle; border: none; padding-bottom:2px;' class='tex' alt="K_p = 0.5K_u" /></span><script type='math/tex'>K_p = 0.5K_u</script></p>
<p>For a PI controller:</p>
<p style="text-align: center;"><span class='MathJax_Preview'><img src='https://www.teachmemicro.com/wp-content/plugins/latex/cache/tex_c8920b77165d656c2d3272059b0ef332.gif' style='vertical-align: middle; border: none; padding-bottom:2px;' class='tex' alt="K_p = 0.45K_u" /></span><script type='math/tex'>K_p = 0.45K_u</script></p>
<p style="text-align: center;"><span class='MathJax_Preview'><img src='https://www.teachmemicro.com/wp-content/plugins/latex/cache/tex_ce03a37ceb377934ddc3889adcc12342.gif' style='vertical-align: middle; border: none; padding-bottom:2px;' class='tex' alt="K_i = \frac{K_p}{T_u/1.2}" /></span><script type='math/tex'>K_i = \frac{K_p}{T_u/1.2}</script></p>
<p>For a PID controller:</p>
<p style="text-align: center;"><span class='MathJax_Preview'><img src='https://www.teachmemicro.com/wp-content/plugins/latex/cache/tex_f2f58084d5a2c7261e1480bd74447505.gif' style='vertical-align: middle; border: none; padding-bottom:2px;' class='tex' alt="K_p = 0.6K_u" /></span><script type='math/tex'>K_p = 0.6K_u</script></p>
<p style="text-align: center;"><span class='MathJax_Preview'><img src='https://www.teachmemicro.com/wp-content/plugins/latex/cache/tex_9bd20e78afc5963b4980f558019ddfcd.gif' style='vertical-align: middle; border: none; padding-bottom:2px;' class='tex' alt="K_i = \frac{K_p}{T_u/2}" /></span><script type='math/tex'>K_i = \frac{K_p}{T_u/2}</script></p>
<p style="text-align: center;"><span class='MathJax_Preview'><img src='https://www.teachmemicro.com/wp-content/plugins/latex/cache/tex_1dde097abd3e6dde9744a57115f48041.gif' style='vertical-align: middle; border: none; padding-bottom:2px;' class='tex' alt="K_d = K_p\frac{T_u}{8}" /></span><script type='math/tex'>K_d = K_p\frac{T_u}{8}</script></p>
<p>These equations provide a systematic way to obtain initial PID parameters instead of choosing arbitrary Kp, Ki, and Kd values.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Using the Results with Arduino</strong></h2>
<p>If you are using Brett Beauregard's Arduino PID library, the calculated values can be inserted directly into your sketch:</p>
<pre><pre><code class="language-cpp">double Kp = 2.4;
double Ki = 2.4;
double Kd = 0.6;

PID myPID(&amp;Input, &amp;Output, &amp;Setpoint,
          Kp, Ki, Kd, DIRECT);</code></pre></pre>
<p>The calculator automatically generates a similar code snippet using your calculated values.</p>
<p>For a complete explanation of Arduino PID control, including manual PID implementation, the PID_v1 library, integral windup, sample time, and practical tuning, see my <a href="https://www.teachmemicro.com/arduino-pid-control-tutorial/">Arduino PID Controller Tutorial</a>.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>The Calculated Values Are Starting Points</strong></h2>
<p>Ziegler-Nichols tuning does not guarantee the best possible response for every system.</p>
<p>The method tends to produce relatively aggressive tuning and may result in noticeable overshoot. Some systems also should not be deliberately driven into sustained oscillation because doing so could damage a motor, heater, mechanical assembly, or other hardware.</p>
<p>After calculating Kp, Ki, and Kd, observe how your actual system responds.</p>
<p>Typical adjustments include:</p>
<table>
<thead>
<tr>
<th>Behavior</th>
<th>Possible Adjustment</th>
</tr>
</thead>
<tbody>
<tr>
<td>Response is too slow</td>
<td>Increase Kp gradually</td>
</tr>
<tr>
<td>Steady-state error remains</td>
<td>Increase Ki slightly</td>
</tr>
<tr>
<td>Large overshoot</td>
<td>Reduce Kp or Ki</td>
</tr>
<tr>
<td>Continuous oscillation</td>
<td>Reduce Kp</td>
</tr>
<tr>
<td>Noisy or jittery output</td>
<td>Reduce Kd or filter the sensor input</td>
</tr>
</tbody>
</table>
<p>The goal of this PID calculator is therefore not to produce a universally perfect controller, but to give you a useful set of <strong>initial PID tuning values</strong> that you can refine on your actual hardware.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>PID Tuning Calculator Summary</strong></h2>
<p>To use the calculator:</p>
<ol>
<li>Set Ki and Kd to zero.</li>
<li>Increase Kp until sustained oscillation occurs.</li>
<li>Record this gain as <strong>Ku</strong>.</li>
<li>Measure the oscillation period <strong>Tu</strong>.</li>
<li>Enter Ku and Tu into the calculator.</li>
<li>Select P, PI, or PID control.</li>
<li>Use the calculated values as your starting point.</li>
<li>Fine-tune the controller while observing the real system response.</li>
</ol>
<p>PID tuning is ultimately an experimental process. The calculator gives you a more systematic place to start, while testing on the actual system determines the final Kp, Ki, and Kd values.</p>
<p>The post <a href="https://www.teachmemicro.com/pid-tuning-calculator/">PID Tuning Calculator: Calculate Kp, Ki and Kd</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Raspberry Pi Pico 2 Pinout</title>
		<link>https://www.teachmemicro.com/raspberry-pi-pico-2-pinout/</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Wed, 26 Aug 2026 23:10:40 +0000</pubDate>
				<category><![CDATA[Reference]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=12754</guid>

					<description><![CDATA[<p>The Raspberry Pi Pico 2 is a microcontroller development board and the successor to the original Raspberry Pi Pico. It features the RP2350, a 32-bit dual-core microcontroller that can run either Arm Cortex-M33 or Hazard3 RISC-V processors at speeds up to 150 MHz. The Raspberry Pi Pico 2 has a 40-pin header with 26 exposed &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/raspberry-pi-pico-2-pinout/">Raspberry Pi Pico 2 Pinout</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>The <a href="https://www.raspberrypi.com/products/raspberry-pi-pico-2/"><strong>Raspberry Pi Pico 2</strong></a> is a microcontroller development board and the successor to the original Raspberry Pi Pico. It features the <em>RP2350</em>, a 32-bit dual-core microcontroller that can run either Arm Cortex-M33 or Hazard3 RISC-V processors at speeds up to 150 MHz.</p>
<p>The Raspberry Pi Pico 2 has a 40-pin header with <strong>26 exposed GPIO pins</strong>, including three analog inputs. It also includes 520 KB of SRAM and 4 MB of onboard flash memory. Despite the upgraded microcontroller, the Pico 2 retains the same physical pinout as the original Pico, making it largely compatible with existing Pico hardware and accessories.</p>
<p>Like the original Pico, the Pico 2 can run code using the <a href="https://www.teachmemicro.com/building-code-raspberry-pi-pico/">Pico C/C++ SDK</a>, <a href="https://www.teachmemicro.com/raspberry-pi-pico-with-arduino/">Arduino IDE</a>, or MicroPython. Its GPIO pins operate at a 3.3 V logic level.</p>
<p><span id="more-12754"></span></p>
<p><img data-dominant-color="c7cebe" data-has-transparency="false" style="--dominant-color: #c7cebe;" loading="lazy" decoding="async" class="aligncenter size-full wp-image-12755 not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2026/08/Raspberry-Pi-Pico-2-Pinout.avif" alt="" width="1181" height="798" srcset="https://www.teachmemicro.com/wp-content/uploads/2026/08/Raspberry-Pi-Pico-2-Pinout.avif 1181w, https://www.teachmemicro.com/wp-content/uploads/2026/08/Raspberry-Pi-Pico-2-Pinout-300x203.avif 300w, https://www.teachmemicro.com/wp-content/uploads/2026/08/Raspberry-Pi-Pico-2-Pinout-1024x692.avif 1024w, https://www.teachmemicro.com/wp-content/uploads/2026/08/Raspberry-Pi-Pico-2-Pinout-768x519.avif 768w, https://www.teachmemicro.com/wp-content/uploads/2026/08/Raspberry-Pi-Pico-2-Pinout-110x75.avif 110w" sizes="auto, (max-width: 1181px) 100vw, 1181px" /></p>
<h3><strong>Interactive Pinout</strong></h3>
<style id="tmm-mp-embed-css">\n.tmm-mp-embed{margin:18px 0 24px}.tmm-mp-title{margin:0 0 8px;font-weight:700}.tmm-mp-desc{margin:0 0 12px}.tmm-mp-actions{display:flex;flex-wrap:wrap;gap:10px;margin:0 0 14px}.tmm-mp-actions button{padding:10px 16px;min-height:44px;border:1px solid #cbd5e1;border-radius:5px;background:#f8fafc;color:#111827;cursor:pointer;font:inherit}.tmm-mp-actions button:hover{background:#eef2f7}.tmm-mp-actions button:focus{outline:2px solid currentColor;outline-offset:2px}.tmm-mp-hide[hidden]{display:none!important}.tmm-mp-frame{width:100%;margin:0 auto}.tmm-mp-frame iframe{display:block;width:100%;border:1px solid #e2e8f0;border-radius:8px;background:#fff}.tmm-mp-status{font-size:13px;margin:8px 0 0;color:#64748b}@media(max-width:600px){.tmm-mp-actions button{width:100%}}\n</style><div id="tmm-mp-1" class="tmm-mp-embed" data-mp-height="600" data-mp-title="Interactive MicroPinouts pinout"><div class="tmm-mp-actions"><button type="button" class="tmm-mp-load" data-mp-url="https://micropinouts.com/embed/board/raspberry-pi-pico-2">Open Interactive Pinout</button><button type="button" class="tmm-mp-hide" hidden>Hide Interactive Pinout</button></div><div class="tmm-mp-frame" style="max-width:800px"></div><p class="tmm-mp-status" aria-live="polite"></p></div>
<h3><strong>Raspberry Pi Pico 2 Pinout Description</strong></h3>
<p>Most of the GPIO pins on the Raspberry Pi Pico 2 can perform several different functions. In general, these functions include GPIO, <a href="https://www.teachmemicro.com/microcontroller-serial-communication/">UART</a>, <a href="https://www.teachmemicro.com/i2c-primer/">I2C</a>, ADC, PWM, PIO, and <a href="https://www.teachmemicro.com/spi-primer/">SPI</a>. The Pico 2 uses the same 40-pin layout as the original Pico. Therefore, projects that connect to GP0 through GP28, the power pins, or the RUN pin can generally use the Pico 2 without changing the wiring.</p>
<h4><strong>General Purpose Pins</strong></h4>
<p>The Pico 2 exposes <strong>26 GPIO pins</strong> on its headers. These are GP0 to GP22 and GP26 to GP28. Most GPIO pins can also serve as UART, I2C, SPI, PWM, or programmable I/O pins. The RP2350 provides three Programmable I/O or PIO blocks, compared with two on the original RP2040. Together, these provide 12 PIO state machines for implementing custom digital interfaces.</p>
<p><!-- Replace with Pico 2 GPIO pin image --></p>
<p>&nbsp;</p>
<h4><strong>Analog Input Pins</strong></h4>
<p>Three ADC inputs are available directly on the Pico 2 headers:</p>
<ul>
<li><strong>GP26</strong> – ADC0</li>
<li><strong>GP27</strong> – ADC1</li>
<li><strong>GP28</strong> – ADC2</li>
</ul>
<p>The RP2350 contains a 12-bit analog-to-digital converter. Although the RP2350A provides four ADC-capable GPIOs internally, only three are exposed as GPIO pins on the Pico 2 header.</p>
<h4><strong>Power Pins</strong></h4>
<p>The Raspberry Pi Pico 2 provides several pins for powering the board and connected peripherals:</p>
<ul>
<li><strong>VBUS</strong> – USB input voltage, normally around 5 V when USB is connected</li>
<li><strong>VSYS</strong> – Main system power input</li>
<li><strong>3V3(OUT)</strong> – Regulated 3.3 V output</li>
<li><strong>3V3_EN</strong> – Enables or disables the onboard 3.3 V regulator</li>
<li><strong>ADC_VREF</strong> – Reference voltage for the ADC</li>
<li><strong>GND / AGND</strong> – Digital and analog ground connections</li>
</ul>
<p><!-- Replace with Pico 2 power pin image --></p>
<p>&nbsp;</p>
<h4><strong>SWD Pins</strong></h4>
<p>The SWD pins are used to program and debug the Raspberry Pi Pico 2 using the Serial Wire Debug interface. The three dedicated pads provide <strong>SWCLK</strong>, <strong>GND</strong>, and <strong>SWDIO</strong>.</p>
<p><!-- Replace with Pico 2 SWD image --></p>
<p>&nbsp;</p>
<h4><strong>Raspberry Pi Pico vs. Pico 2 Pinout</strong></h4>
<p>One of the advantages of upgrading from the original Pico to the Pico 2 is that the two boards use the <strong>same physical pin layout</strong>. GP0 is still on pin 1, GP1 is on pin 2, GP26/ADC0 is on pin 31, and the power and ground pins remain in the same locations.</p>
<p>However, the RP2350 adds several capabilities that are not available on the RP2040. These include an additional PIO block, additional alternate GPIO functions, and the RP2350's high-speed HSTX peripheral. These advanced functions do not change the physical header layout.</p>
<p>This means that, for most existing Raspberry Pi Pico projects, you can replace the original Pico with a Pico 2 without changing the circuit wiring.</p>
<h4><strong>Raspberry Pi Pico 2 Schematic</strong></h4>
<p><em>(Click to view larger image)<img data-dominant-color="ecebec" data-has-transparency="false" style="--dominant-color: #ecebec;" loading="lazy" decoding="async" class="aligncenter size-full wp-image-12757 not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2026/08/Raspberry-Pi-Pico-2-Schematic.avif" alt="" width="1223" height="866" srcset="https://www.teachmemicro.com/wp-content/uploads/2026/08/Raspberry-Pi-Pico-2-Schematic.avif 1223w, https://www.teachmemicro.com/wp-content/uploads/2026/08/Raspberry-Pi-Pico-2-Schematic-300x212.avif 300w, https://www.teachmemicro.com/wp-content/uploads/2026/08/Raspberry-Pi-Pico-2-Schematic-1024x725.avif 1024w, https://www.teachmemicro.com/wp-content/uploads/2026/08/Raspberry-Pi-Pico-2-Schematic-768x544.avif 768w" sizes="auto, (max-width: 1223px) 100vw, 1223px" /></em></p>
<p><!-- Replace with Pico 2 schematic image --></p>
<p>&nbsp;</p>
<p>The post <a href="https://www.teachmemicro.com/raspberry-pi-pico-2-pinout/">Raspberry Pi Pico 2 Pinout</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Using a 2-in-1 USB to RS232/RS485 Converter for Embedded Debugging</title>
		<link>https://www.teachmemicro.com/using-a-2-in-1-usb-to-rs232-rs485-converter-for-embedded-debugging/</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Mon, 17 Aug 2026 01:53:32 +0000</pubDate>
				<category><![CDATA[Tools]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=12490</guid>

					<description><![CDATA[<p>When developing embedded systems, a USB-to-UART converter is probably one of the first tools you will add to your workbench. However, UART at 3.3 V or 5 V logic levels is not the only type of serial communication you will encounter. Industrial controllers, older equipment, PLCs, sensors, motor drives, and other devices commonly use RS232 &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/using-a-2-in-1-usb-to-rs232-rs485-converter-for-embedded-debugging/">Using a 2-in-1 USB to RS232/RS485 Converter for Embedded Debugging</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>When developing embedded systems, a USB-to-UART converter is probably one of the first tools you will add to your workbench. However, UART at 3.3 V or 5 V logic levels is not the only type of serial communication you will encounter. Industrial controllers, older equipment, PLCs, sensors, motor drives, and other devices commonly use <strong>RS232</strong> or <strong>RS485</strong> instead.</p>
<p>I recently picked up a small tool that combines both interfaces in one device: the <strong>UTS-T02 USB-to-RS232/RS485 converter</strong>. A slide switch selects between RS232 and RS485 operation, while three LEDs indicate power, transmit activity, and receive activity. The unit also uses a removable three-position screw terminal instead of permanently attached wires. The product listing identifies the USB interface chip as the <strong>CH343G</strong>.</p>
<p><!-- IMAGE: Photo of the actual UTS-T02 on a clean workbench --></p>
<p>This is a simple device, but it can be surprisingly useful when developing or troubleshooting embedded systems. In this article, we'll look at what it does, how the two modes differ, and how we can actually use it to communicate with embedded hardware.</p>
<p><span id="more-12490"></span></p>
<hr />
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>What Does the USB to RS232/RS485 Converter Do?</strong></h2>
<p>The job of the converter is simple: it gives a computer an RS232 or RS485 serial interface through USB.</p>
<p><img data-dominant-color="c6caca" data-has-transparency="false" style="--dominant-color: #c6caca;" loading="lazy" decoding="async" class="aligncenter size-full wp-image-12533 not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-comparison.avif" alt="USB RS485 tool comparison" width="761" height="1014" srcset="https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-comparison.avif 761w, https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-comparison-225x300.avif 225w" sizes="auto, (max-width: 761px) 100vw, 761px" /></p>
<p>Once connected, the computer sees the converter as a serial port. A serial terminal, Python program, test application, Modbus utility, or other PC software can then use that serial port to communicate with the external device.</p>
<p>The CH343 used inside this converter is a USB-to-UART bridge from WCH. The CH343 family supports virtual serial-port operation and baud rates from 50 bps up to 6 Mbps at the chip level. The maximum speed of the complete UTS-T02 should not automatically be assumed to be 6 Mbps, however, because the RS232/RS485 transceiver circuitry also affects the usable communication rate.</p>
<p>The interesting part of this particular adapter is that the UART signal generated internally is converted into one of two different electrical interfaces:</p>
<ul>
<li><strong>RS232</strong> for point-to-point serial communication</li>
<li><strong>RS485</strong> for differential serial communication, particularly over longer cables and in electrically noisy environments</li>
</ul>
<p>A small switch on the side selects which interface is connected to the terminal block.</p>
<p><img data-dominant-color="c6c7ca" data-has-transparency="false" style="--dominant-color: #c6c7ca;" loading="lazy" decoding="async" class="aligncenter size-large wp-image-12535 not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-switch-1024x943.avif" alt="RS232/RS485 switching" width="618" height="569" srcset="https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-switch-1024x943.avif 1024w, https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-switch-300x276.avif 300w, https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-switch-768x707.avif 768w, https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-switch.avif 1064w" sizes="auto, (max-width: 618px) 100vw, 618px" /></p>
<hr />
<p><!-- IMAGE: Close-up photograph pointing to RS485/RS232 selector --></p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Understanding the Terminals</strong></h2>
<p>There are only three terminals on the end of the converter:</p>
<table>
<thead>
<tr>
<th>Terminal</th>
<th>RS485 Mode</th>
<th>RS232 Mode</th>
</tr>
</thead>
<tbody>
<tr>
<td>GND</td>
<td>Ground/reference</td>
<td>Ground</td>
</tr>
<tr>
<td>A+ / TXD</td>
<td>A differential line</td>
<td>Transmit</td>
</tr>
<tr>
<td>B- / RXD</td>
<td>B differential line</td>
<td>Receive</td>
</tr>
</tbody>
</table>
<p>The dual labels initially look strange because the same physical terminals serve completely different purposes depending on the switch position. The seller's interface diagram describes the A+/TXD terminal as RS485 A or the connection toward the RS232 receiver, while B-/RXD becomes RS485 B or the connection toward the RS232 transmitter.</p>
<p><img data-dominant-color="d9dadd" data-has-transparency="false" style="--dominant-color: #d9dadd;" loading="lazy" decoding="async" class="aligncenter size-full wp-image-12525 not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485.avif" alt="USB RS232/RS485 dongle connector" width="940" height="1020" srcset="https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485.avif 940w, https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-276x300.avif 276w, https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-768x833.avif 768w" sizes="auto, (max-width: 940px) 100vw, 940px" /></p>
<p>For normal RS232 wiring, remember that the transmitter of one device connects to the receiver of the other:</p>
<pre><pre><code class="language-cpp">USB Converter             RS232 Device

TXD  -------------------&gt; RXD
RXD  &lt;------------------- TXD
GND  -------------------- GND</code></pre></pre>
<p>In RS485 mode, the wiring instead becomes:</p>
<pre><pre><code class="language-cpp">USB Converter             RS485 Device

A+   -------------------- A
B-   -------------------- B
GND  -------------------- GND</code></pre></pre>
<hr />
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>RS232 Is Not TTL UART</strong></h2>
<p>This is one of the most important things to understand about this tool.</p>
<p><strong>Do not connect the RS232 terminals directly to an ESP32, Arduino, Raspberry Pi Pico, STM32, or similar microcontroller UART.</strong></p>
<p>The UART peripheral inside a microcontroller normally operates at logic levels such as 3.3 V. RS232 uses positive and negative voltages instead. Valid RS232 receiver signals are normally greater than +3 V or less than -3 V, with transmitter levels typically extending farther in either direction.</p>
<p>If your microcontroller has a normal TTL/CMOS UART, you need an RS232 transceiver such as a <strong>MAX3232</strong> between the microcontroller and this adapter.</p>
<pre><pre><code class="language-cpp">Computer
   |
  USB
   |
UTS-T02
(RS232)
   |
 RS232
   |
MAX3232
   |
3.3 V UART
   |
 ESP32</code></pre></pre>
<p>This also means that the UTS-T02 does <em>not</em> replace the common CH340, CP2102, or FT232 USB-to-TTL adapters used when programming or debugging development boards. It serves a different purpose.</p>
<hr />
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>What's Inside the UTS-T02?</strong></h2>
<p>Since this adapter combines USB, RS232, and RS485 in such a small enclosure, I decided to open it and see what hardware is actually doing the conversion.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-large wp-image-12529" src="https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-circuit-board-labeled.jpg" alt="UTS-T02 internals" width="1" height="1" /></p>
<p>There are two main ICs on the PCB. The larger chip is a <strong>WCH CH343</strong>, which handles the USB-to-UART conversion. The smaller chip is an <strong>SP485EE</strong>, a half-duplex RS485 transceiver responsible for converting the UART signals into the differential A and B signals used by RS485.</p>
<p><!-- IMAGE: Open UTS-T02 showing the PCB, with CH343 and SP485EE labeled --></p>
<p>The basic RS485 signal path is therefore:</p>
<pre><pre><code class="language-cpp">PC USB
   |
   v
CH343
USB-to-UART
   |
   v
SP485EE
RS485 Transceiver
   |
   +---- A+
   |
   +---- B-</code></pre></pre>
<p>The RS485 side is straightforward, but the RS232 side was more interesting. There is no obvious MAX232 or MAX3232-style RS232 transceiver on the board. At first glance, this raises the possibility that the terminal marked RS232 might simply expose logic-level UART signals from the CH343.</p>
<h3>Is the RS232 Output Really RS232?</h3>
<p>To find out, I switched the adapter to RS232 mode and measured the TXD terminal relative to GND.</p>
<p>With the serial port idle, I measured approximately <strong>-5 V</strong> at TXD. I then repeatedly transmitted the character <em>U</em> from the PC and observed the TXD signal using an oscilloscope. The waveform switched between positive and negative voltages rather than between 0 V and a positive logic voltage.</p>
<p>This is important because a normal TTL or CMOS UART would typically switch between 0 V and 3.3 V or 5 V. RS232 instead uses bipolar signaling, where the idle or mark state is represented by a negative voltage.</p>
<pre><pre><code class="language-cpp">TTL UART:

Logic high  ---- +3.3 V or +5 V
Logic low   ---- 0 V


UTS-T02 RS232 TX:

Idle        ---- approximately -5 V
Data        ---- switches between
                positive and negative voltages</code></pre></pre>
<p>The character <em>U</em> is particularly useful for viewing a serial waveform because its hexadecimal value is <strong>0x55</strong>, or <strong>01010101</strong> in binary. This creates frequent transitions in the transmitted data and makes the signal easy to observe on an oscilloscope.</p>
<p>Based on these measurements, the UTS-T02's RS232 setting is <strong>not simply exposing the CH343's logic-level UART</strong>. The board contains additional circuitry that converts the UART signal into bipolar RS232 voltage levels, even though this circuitry is not implemented using an obvious dedicated MAX232-family IC.</p>
<p>&nbsp;</p>
<p>The internal architecture therefore appears to be approximately:</p>
<pre><pre><code class="language-cpp">+---- RS232 level conversion ---- TXD/RXD
                         |
USB ---- CH343 ---- UART-+
                         |
                         +---- SP485EE ------------------- A+/B-
                                  RS485</code></pre></pre>
<p>I haven't reverse-engineered every component of the RS232 level-conversion circuit, so I won't claim exactly how the positive and negative voltages are generated. However, the measured -5 V idle level and bipolar transmit waveform confirm that the output behaves electrically as an RS232 interface rather than an ordinary TTL UART.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Testing RS232 Mode</strong></h2>
<p>The easiest first test for the RS232 side is a loopback test.</p>
<p>Set the switch to <strong>RS232</strong> and connect the TXD and RXD terminals together. Connect the converter to the computer and determine which COM port has been assigned to it.</p>
<p><img data-dominant-color="b8b9bb" data-has-transparency="false" style="--dominant-color: #b8b9bb;" loading="lazy" decoding="async" class="aligncenter size-full wp-image-12532 not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-loopback.avif" alt="RS232 loopback" width="758" height="606" srcset="https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-loopback.avif 758w, https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-loopback-300x240.avif 300w" sizes="auto, (max-width: 758px) 100vw, 758px" /></p>
<p><!-- IMAGE: Photo showing TXD and RXD shorted for loopback --></p>
<p>Open your preferred serial terminal and configure a reasonable baud rate such as:</p>
<ul>
<li>9600 baud</li>
<li>8 data bits</li>
<li>No parity</li>
<li>1 stop bit</li>
<li>No flow control</li>
</ul>
<p>Characters transmitted by the PC should return through the RXD terminal and appear in the terminal program.</p>
<p><img data-dominant-color="212121" data-has-transparency="false" style="--dominant-color: #212121;" loading="lazy" decoding="async" class="aligncenter size-full wp-image-12526 not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2026/08/putty_loopback_output.avif" alt="" width="461" height="307" srcset="https://www.teachmemicro.com/wp-content/uploads/2026/08/putty_loopback_output.avif 461w, https://www.teachmemicro.com/wp-content/uploads/2026/08/putty_loopback_output-300x200.avif 300w, https://www.teachmemicro.com/wp-content/uploads/2026/08/putty_loopback_output-310x205.avif 310w" sizes="auto, (max-width: 461px) 100vw, 461px" /></p>
<p>This is a useful first check because it verifies the USB interface, serial driver, transmit circuit, receive circuit, and terminal connection without requiring another device.</p>
<hr />
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Using RS232 with a Microcontroller</strong></h2>
<p>To test this converter against a microcontroller, we can put a MAX3232 between the adapter and the MCU.</p>
<pre><pre><code class="language-cpp">UTS-T02 TXD ---- MAX3232 ---- MCU RX
UTS-T02 RXD ---- MAX3232 ---- MCU TX
UTS-T02 GND ----------------- MCU GND</code></pre></pre>
<p>You can then run a simple UART echo program on the microcontroller. Anything typed into the PC terminal is sent through USB, converted to RS232, converted back to logic-level UART by the MAX3232, and finally received by the microcontroller.</p>
<p>The response travels through exactly the opposite path.</p>
<p>This setup can be useful when developing equipment that must eventually communicate with an existing RS232 system. Instead of requiring the actual equipment during every firmware test, the PC and converter can act as the other end of the serial connection.</p>
<hr />
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>RS485 Mode</strong></h2>
<p>RS485 is where this converter becomes particularly useful for embedded development.</p>
<p>Unlike the single-ended RS232 interface, RS485 uses differential signaling. Information is represented by the voltage difference between two wires rather than the voltage of a single signal relative to ground. This makes RS485 much better suited to long cables and electrically noisy environments. RS485 also supports multiple nodes on the same bus.</p>
<p>The UTS-T02 is designed for <strong>two-wire half-duplex RS485</strong>. Half-duplex means the same pair of wires is used for both transmitting and receiving, so only one device should drive the bus at a time. Two-wire half-duplex is the most common RS485 configuration.</p>
<pre><pre><code class="language-cpp">RS485 BUS

UTS-T02     Device 1       Device 2
  A ----------- A ------------ A
  B ----------- B ------------ B
 GND ---------- GND ---------- GND</code></pre></pre>
<p>Unlike an RS485 transceiver attached directly to a microcontroller, the converter does not expose separate Driver Enable and Receiver Enable pins. From the user's perspective, direction switching is therefore handled inside the adapter when operating in RS485 mode.</p>
<hr />
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Testing RS485 with an ESP32</strong></h2>
<p>For a practical test, we can connect the UTS-T02 to an ESP32 through a 3.3 V-compatible RS485 transceiver such as the MAX3485.</p>
<p><!-- IMAGE: ESP32 + MAX3485 + UTS-T02 wiring --></p>
<table>
<thead>
<tr>
<th>UTS-T02</th>
<th>MAX3485</th>
</tr>
</thead>
<tbody>
<tr>
<td>A+</td>
<td>A</td>
</tr>
<tr>
<td>B-</td>
<td>B</td>
</tr>
<tr>
<td>GND</td>
<td>GND</td>
</tr>
</tbody>
</table>
<p>On the microcontroller side:</p>
<table>
<thead>
<tr>
<th>MAX3485</th>
<th>ESP32</th>
</tr>
</thead>
<tbody>
<tr>
<td>RO</td>
<td>GPIO 16 / RX</td>
</tr>
<tr>
<td>DI</td>
<td>GPIO 17 / TX</td>
</tr>
<tr>
<td>DE + RE</td>
<td>GPIO 4</td>
</tr>
<tr>
<td>VCC</td>
<td>3.3 V</td>
</tr>
<tr>
<td>GND</td>
<td>GND</td>
</tr>
</tbody>
</table>
<p>The following Arduino sketch receives a line from the computer and sends a response back over RS485:</p>
<pre><pre><code class="language-cpp">#define RS485_RX     16
#define RS485_TX     17
#define RS485_ENABLE 4

HardwareSerial RS485(2);

void setup() {
  Serial.begin(115200);

  RS485.begin(9600, SERIAL_8N1, RS485_RX, RS485_TX);

  pinMode(RS485_ENABLE, OUTPUT);

  // Start in receive mode
  digitalWrite(RS485_ENABLE, LOW);

  Serial.println(&quot;RS485 test ready&quot;);
}

void sendRS485(String message) {
  // Enable transmitter
  digitalWrite(RS485_ENABLE, HIGH);
  delayMicroseconds(50);

  RS485.println(message);
  RS485.flush();

  delayMicroseconds(50);

  // Return to receive mode
  digitalWrite(RS485_ENABLE, LOW);
}

void loop() {
  if (RS485.available()) {
    String message = RS485.readStringUntil(&#039;\n&#039;);
    message.trim();

    Serial.print(&quot;Received: &quot;);
    Serial.println(message);

    sendRS485(&quot;ESP32 received: &quot; + message);
  }
}</code></pre></pre>
<p>After uploading the sketch, set the UTS-T02 switch to RS485 mode. Open the converter's COM port using a serial terminal configured for 9600 baud.</p>
<p>Send:</p>
<pre><pre><code class="language-cpp">Hello ESP32</code></pre></pre>
<p>The response should be:</p>
<pre><pre><code class="language-cpp">ESP32 received: Hello ESP32</code></pre></pre>
<p>This is already enough to make the converter useful for developing custom RS485 protocols. It can also be used with protocols such as Modbus RTU, where the converter provides the physical RS485 interface while software on the PC handles the protocol.</p>
<hr />
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>What About the Advertised 1200-Meter RS485 Range?</strong></h2>
<p>The product material advertises RS485 communication distances of up to <strong>1.2 kilometers</strong>.</p>
<p>That shouldn't be interpreted as meaning that every setup will communicate reliably over a 1200-meter cable. RS485 cable length and data rate are related: higher communication speeds generally require shorter cables. Cable type, topology, termination, electrical noise, transceiver characteristics, and other factors also affect the maximum usable distance. Texas Instruments similarly lists 1200 meters as a theoretical RS485 cable length while emphasizing the relationship between signaling rate and cable length.</p>
<p>For workbench debugging, this normally doesn't matter. But if the converter is going to be used to diagnose an actual long-distance RS485 installation, proper cabling and termination become important.</p>
<hr />
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>RS485 Termination</strong></h2>
<p>Long RS485 buses generally require termination at the ends of the transmission line. The terminating resistance is selected to match the characteristic impedance of the cable; around 120 ohms is common for typical twisted-pair RS485 wiring. Proper termination reduces signal reflections on the bus.</p>
<p>I don't see a separate externally accessible termination-resistor switch on this particular converter. Therefore, if termination is required, I would plan on providing it externally unless measurement or inspection of the actual unit confirms that suitable termination is already installed.</p>
<hr />
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<p><!-- OPTIONAL IMAGE: 120-ohm resistor connected between A and B --></p>
<h2><strong>TX and RX LEDs Are More Useful Than They Look</strong></h2>
<p>The converter has three indicator LEDs:</p>
<ul>
<li><strong>PWR</strong> — power</li>
<li><strong>TXD</strong> — transmit activity</li>
<li><strong>RXD</strong> — receive activity</li>
</ul>
<p>These LEDs are useful during debugging because they quickly tell you whether communication is happening at all.</p>
<p>For example, if the TXD LED flashes every time your PC software sends a command but RXD never flashes, you immediately know that the PC is transmitting but nothing is coming back.</p>
<p>That narrows the problem considerably. You can then check the baud rate, wiring, RS232/RS485 selector, device configuration, or A/B polarity rather than wondering whether the application transmitted anything in the first place.</p>
<hr />
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Where This Tool Fits on an Embedded Workbench</strong></h2>
<p>I wouldn't consider this a replacement for a normal USB-to-TTL UART adapter. Instead, I see them as complementary tools.</p>
<table>
<thead>
<tr>
<th>Tool</th>
<th>Typical Use</th>
</tr>
</thead>
<tbody>
<tr>
<td>USB-to-TTL UART</td>
<td>Direct MCU UART debugging and programming</td>
</tr>
<tr>
<td>USB-to-RS232</td>
<td>Older equipment, controllers and RS232 devices</td>
</tr>
<tr>
<td>USB-to-RS485</td>
<td>Industrial devices, Modbus, sensors, PLCs and long serial buses</td>
</tr>
<tr>
<td>UTS-T02</td>
<td>RS232 and RS485 in one adapter</td>
</tr>
</tbody>
</table>
<p>If you regularly work with industrial controllers or embedded devices that communicate with external equipment, having both interfaces available in a single small adapter is convenient.</p>
<hr />
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Things I Would Check Before Using It in the Field</strong></h2>
<p>The UTS-T02 works well as a compact bench adapter, but one limitation becomes apparent after opening the enclosure: there is no obvious galvanic isolation between the USB interface and the serial side.</p>
<p>The PCB contains a CH343 USB-to-UART bridge and an SP485EE RS485 transceiver, along with the circuitry used for the RS232 interface. I did not find an obvious optocoupler, digital isolator, isolated DC-DC converter, or isolation barrier on the PCB.</p>
<p>This shouldn't be a problem for most short-distance bench tests where the PC and target hardware share a reasonably common ground. However, it becomes more important when connecting to RS485 equipment installed over long cable runs or powered from different electrical systems.</p>
<p>Differences in ground potential can cause current to flow through the communication ground or even through the PC's USB connection. For installations where this is a concern, an isolated USB-to-RS485 adapter would be the safer choice.</p>
<p>So I would treat the UTS-T02 primarily as a convenient development and debugging tool rather than assume it provides the electrical isolation expected from some industrial RS485 interfaces.</p>
<hr />
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>What I Like About the UTS-T02</strong></h2>
<p style="padding-left: 40px;"><div class="thumbup tie-list-shortcode">
<ul>
<li>RS232 and RS485 are available in one small adapter.</li>
<li>A physical switch makes changing interfaces straightforward.</li>
<li>TX and RX indicators are useful when debugging communication.</li>
<li>The removable screw terminal works well with temporary test wiring.</li>
<li>No separate external power supply is needed.</li>
<li>It is small enough to keep with other programming and debugging adapters.</li>
</ul>
<p style="padding-left: 40px;"></div>
<h2><strong>Limitations</strong></h2>
<p style="padding-left: 40px;"><div class="thumbdown tie-list-shortcode">
<ul>
<li>It does not provide TTL-level UART output.</li>
<li>The RS485 interface is two-wire half-duplex rather than four-wire full-duplex.</li>
<li>The shared A+/TXD and B-/RXD labels can initially be confusing.</li>
<li>There is no obvious external control for RS485 termination.</li>
<li>Galvanic isolation should not be assumed without verifying the hardware.</li>
</ul>
<p style="padding-left: 40px;"></div>
<hr />
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Would I Keep It on My Workbench?</strong></h2>
<p>Yes. A USB-to-RS485 adapter by itself is already a useful embedded-development tool, especially when working with Modbus devices, industrial sensors, motor controllers, PLCs, and custom RS485 hardware. Adding RS232 support makes this adapter even more useful without taking up additional space.</p>
<p>More importantly, tools like this let the PC become part of the debugging environment. Instead of waiting for another controller to test an embedded device, you can send commands manually, capture responses, test error conditions, and write PC software that simulates the equipment that will eventually communicate with your board.</p>
<p>That's what makes a simple serial converter more than just another USB dongle. For embedded development, it becomes a convenient way of looking into — and interacting with — the communication interface of the device you're building.</p>
<aside class="tmm-product-card tmm-product-card--align-center tmm-product-card--layout-default" style="--tmm-product-card-title-lines:3;--tmm-product-card-desc-lines:5;" aria-label="Product recommendation: USB to RS485/RS232 Dongle">
							<div class="tmm-product-card__media">
					<img width="150" height="150" src="https://www.teachmemicro.com/wp-content/uploads/2026/08/usb-rs232-rs485-switch-150x150.avif" class="tmm-product-card__image not-transparent wp-post-image" alt="RS232/RS485 switching" loading="lazy" decoding="async" data-has-transparency="false" data-dominant-color="c6c7ca" style="--dominant-color: #c6c7ca;" />				</div>
			
			<div class="tmm-product-card__content">
				<div class="tmm-product-card__title">USB to RS485/RS232 Dongle</div>

									<p class="tmm-product-card__description">USB to RS485/RS232 Bidirectional Half Duplex Serial Port Line Converter Industrial Grade Converter CH343G</p>
							</div>

							<div class="tmm-product-card__actions">
					<a class="button tmm-product-card__button" href="https://s.click.aliexpress.com/e/_c2xAOYxd" target="_blank" rel="noopener" aria-label="Check Prices for USB to RS485/RS232 Dongle">
						Check Prices					</a>
				</div>
					</aside>
<p>The post <a href="https://www.teachmemicro.com/using-a-2-in-1-usb-to-rs232-rs485-converter-for-embedded-debugging/">Using a 2-in-1 USB to RS232/RS485 Converter for Embedded Debugging</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Linked List Visualizer for Embedded Systems</title>
		<link>https://www.teachmemicro.com/linked-list-visualizer-for-embedded-systems/</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Tue, 21 Jul 2026 20:34:46 +0000</pubDate>
				<category><![CDATA[Tools]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=12317</guid>

					<description><![CDATA[<p>A linked list in embedded systems is a collection of data elements connected using pointers. Unlike an array, the elements do not need to occupy consecutive locations in memory. Each element stores its own data and a pointer that identifies the next element in the list. Linked lists are common in general-purpose software, but using &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/linked-list-visualizer-for-embedded-systems/">Linked List Visualizer for Embedded Systems</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>A <strong>linked list in embedded systems</strong> is a collection of data elements connected using pointers. Unlike an array, the elements do not need to occupy consecutive locations in memory. Each element stores its own data and a pointer that identifies the next element in the list.</p>
<p>Linked lists are common in general-purpose software, but using one in a microcontroller requires extra care. Dynamic memory allocation, memory fragmentation, pointer errors, and unpredictable execution time can all become important concerns when RAM is limited.</p>
<p>In this tutorial, I will explain how a linked list works, show how nodes are connected, and build a practical singly linked list in embedded C. You can also use the visualizer below to add, remove, and traverse nodes while watching the pointers change.</p>
<p><span id="more-12317"></span></p>
<h2><strong>Interactive Linked List Visualizer</strong></h2>
<p>Use the controls below to add nodes, remove nodes, and move through the list. Each box represents one node. The arrow leaving a node represents its <em>next</em> pointer.</p>
<p>Pay particular attention to the first node, called the <em>head</em>, and to the final node whose <em>next</em> pointer is <em>NULL</em>.</p>
        <div
            id="tmm-llv-2"
            class="tmm-llv-app"
            data-llv-instance="tmm-llv-2"
            data-llv-max-nodes="6"
            data-llv-initial-values=""
        >
            <section class="tmm-llv-card" aria-labelledby="tmm-llv-2-title">
                <noscript>
                    <div class="tmm-llv-noscript">
                        JavaScript is required for the interactive linked list visualizer.                    </div>
                </noscript>

                <div class="tmm-llv-topline">
                    <h3 id="tmm-llv-2-title">
                        Linked List                    </h3>
                    <p>
                        Singly linked nodes with head, tail, and next pointers.                    </p>
                </div>

                <div class="tmm-llv-stage">
                    <div
                        class="tmm-llv-list"
                        data-llv-el="list"
                        role="img"
                        aria-label="Singly linked list nodes with next pointers"
                    ></div>
                </div>

                <div class="tmm-llv-legend" aria-label="Pointer legend">
                    <span><strong class="tmm-llv-head-text">HEAD</strong>: first node</span>
                    <span><strong class="tmm-llv-tail-text">TAIL</strong>: last node</span>
                    <span><strong>next</strong>: pointer to the following node or NULL</span>
                </div>

                <div class="tmm-llv-controls">
                    <label class="tmm-llv-value-field" for="tmm-llv-2-value">
                        <span>Node value</span>
                        <input
                            id="tmm-llv-2-value"
                            data-llv-el="value-input"
                            type="text"
                            inputmode="text"
                            autocomplete="off"
                            maxlength="6"
                            placeholder="A or 0x41"
                        >
                    </label>
                    <button class="tmm-llv-button" type="button" data-llv-action="insert-head">
                        Insert Head                    </button>
                    <button class="tmm-llv-button" type="button" data-llv-action="insert-tail">
                        Insert Tail                    </button>
                    <button class="tmm-llv-button tmm-llv-button--secondary" type="button" data-llv-action="delete-head">
                        Delete Head                    </button>
                    <button class="tmm-llv-button tmm-llv-button--secondary" type="button" data-llv-action="delete-value">
                        Delete Value                    </button>
                    <button class="tmm-llv-button tmm-llv-button--secondary" type="button" data-llv-action="find">
                        Find                    </button>
                    <button class="tmm-llv-button tmm-llv-button--muted" type="button" data-llv-action="reset">
                        Reset                    </button>
                </div>

                <div class="tmm-llv-status" id="tmm-llv-2-status" data-llv-el="status">
                    <span>Head: <strong data-llv-el="head">NULL</strong></span>
                    <span>Tail: <strong data-llv-el="tail">NULL</strong></span>
                    <span>Nodes: <strong data-llv-el="count">0 / 6</strong></span>
                    <span>State: <strong data-llv-el="state">EMPTY</strong></span>
                </div>

                <p
                    class="tmm-llv-message"
                    id="tmm-llv-2-message"
                    data-llv-el="message"
                    aria-live="polite"
                >
                    List is empty. Insert a node to begin.                </p>

                <details class="tmm-llv-code" aria-labelledby="tmm-llv-2-code-title">
                    <summary id="tmm-llv-2-code-title">
                        Equivalent Embedded C                    </summary>
                    <button class="tmm-llv-button tmm-llv-button--copy" type="button" data-llv-action="copy-code">
                        Copy Code                    </button>
                    <pre><code data-llv-el="code">#define LIST_MAX_NODES 6

typedef struct Node
{
    uint8_t value;
    struct Node *next;
} Node;

typedef struct
{
    Node *head;
    Node *tail;
    uint8_t count;
} LinkedList;

bool list_push_front(LinkedList *list, Node *node, uint8_t value)
{
    if (list-&gt;count &gt;= LIST_MAX_NODES)
    {
        return false;
    }

    node-&gt;value = value;
    node-&gt;next = list-&gt;head;
    list-&gt;head = node;

    if (list-&gt;tail == NULL)
    {
        list-&gt;tail = node;
    }

    list-&gt;count++;
    return true;
}

bool list_push_back(LinkedList *list, Node *node, uint8_t value)
{
    if (list-&gt;count &gt;= LIST_MAX_NODES)
    {
        return false;
    }

    node-&gt;value = value;
    node-&gt;next = NULL;

    if (list-&gt;tail != NULL)
    {
        list-&gt;tail-&gt;next = node;
    }
    else
    {
        list-&gt;head = node;
    }

    list-&gt;tail = node;
    list-&gt;count++;
    return true;
}

bool list_pop_front(LinkedList *list, uint8_t *value)
{
    Node *old_head;

    if (list-&gt;head == NULL)
    {
        return false;
    }

    old_head = list-&gt;head;
    *value = old_head-&gt;value;
    list-&gt;head = old_head-&gt;next;

    if (list-&gt;head == NULL)
    {
        list-&gt;tail = NULL;
    }

    list-&gt;count--;
    return true;
}</code></pre>
                </details>
            </section>
        </div>
        
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>What Is a Linked List?</strong></h2>
<p>A linked list is a data structure made from individual elements called <strong>nodes</strong>. Each node normally contains two parts:</p>
<ul>
<li>The data stored by the node</li>
<li>A pointer to another node</li>
</ul>
<p>A basic singly linked-list node in C may look like this:</p>
<pre><pre><code class="language-cpp">typedef struct Node
{
    uint8_t value;
    struct Node *next;
} Node;</code></pre></pre>
<p>The <em>value</em> member stores the actual information. The <em>next</em> member stores the address of the following node.</p>
<p>Suppose three nodes contain the values 10, 20, and 30. Their relationship can be represented as:</p>
<pre><pre><code class="language-cpp">head
  |
  v
[10 | next] -&gt; [20 | next] -&gt; [30 | NULL]</code></pre></pre>
<p>The program starts at <em>head</em>, follows the first node’s <em>next</em> pointer, and continues until it reaches <em>NULL</em>.</p>
<p>The final node points to <em>NULL</em> because there is no node after it.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Linked List Versus Array</strong></h2>
<p>An array stores its elements next to one another in memory.</p>
<pre><pre><code class="language-cpp">uint8_t values[4] = {10, 20, 30, 40};</code></pre></pre>
<p>The elements occupy consecutive positions:</p>
<pre><pre><code class="language-cpp">Address:  0x2000  0x2001  0x2002  0x2003
Value:       10      20      30      40</code></pre></pre>
<p>This makes accessing a specific array element fast. The processor can calculate the address of <em>values[2]</em> directly.</p>
<p>Linked-list nodes may be located in different parts of memory:</p>
<pre><pre><code class="language-cpp">Node 1 at 0x2000
Node 2 at 0x2140
Node 3 at 0x2088</code></pre></pre>
<p>The nodes remain connected because each one stores the address of the next node.</p>
<div style="overflow-x: auto;">
<table>
<thead>
<tr>
<th>Feature</th>
<th>Array</th>
<th>Linked List</th>
</tr>
</thead>
<tbody>
<tr>
<td>Memory layout</td>
<td>Contiguous</td>
<td>Nodes may be separated</td>
</tr>
<tr>
<td>Direct element access</td>
<td>Fast by index</td>
<td>Requires traversal</td>
</tr>
<tr>
<td>Insert at beginning</td>
<td>May require shifting</td>
<td>Pointer update only</td>
</tr>
<tr>
<td>Remove from beginning</td>
<td>May require shifting</td>
<td>Pointer update only</td>
</tr>
<tr>
<td>Memory overhead</td>
<td>Data only</td>
<td>Each node requires a pointer</td>
</tr>
<tr>
<td>Cache locality</td>
<td>Usually good</td>
<td>Usually worse</td>
</tr>
<tr>
<td>Maximum size</td>
<td>Often fixed</td>
<td>Depends on node allocation</td>
</tr>
</tbody>
</table>
</div>
<p>A linked list is useful when elements must frequently be inserted or removed without shifting the remaining data. An array is usually better when fast indexed access and compact memory usage are more important.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>The Head Pointer</strong></h2>
<p>The <em>head</em> pointer identifies the first node in the list.</p>
<pre><pre><code class="language-cpp">Node *head = NULL;</code></pre></pre>
<p>When <em>head</em> is <em>NULL</em>, the list is empty.</p>
<p>After adding a node:</p>
<pre><pre><code class="language-cpp">Node first_node;

first_node.value = 10;
first_node.next = NULL;

head = &amp;first_node;</code></pre></pre>
<p>The head now stores the address of <em>first_node</em>.</p>
<pre><pre><code class="language-cpp">head -&gt; [10 | NULL]</code></pre></pre>
<p>The head is not a node itself. It is a pointer that tells the program where the list begins.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>How Linked-List Traversal Works</strong></h2>
<p>Unlike an array, a linked list cannot directly jump to an arbitrary element by index. The program must begin at the head and follow each <em>next</em> pointer.</p>
<pre><pre><code class="language-cpp">void linked_list_print(const Node *head)
{
    const Node *current = head;

    while (current != NULL)
    {
        printf(&quot;%u\n&quot;, current-&gt;value);
        current = current-&gt;next;
    }
}</code></pre></pre>
<p>The temporary pointer <em>current</em> begins at the first node. After processing that node, it becomes the address stored in <em>current-&gt;next</em>.</p>
<p>The traversal ends when <em>current</em> becomes <em>NULL</em>.</p>
<p>For a list containing four nodes, reaching the final node may require following three pointers. This means finding an element becomes slower as the list grows.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Creating a Basic Singly Linked List</strong></h2>
<p>A singly linked list allows movement in one direction only. Each node points to the node after it.</p>
<p>We can begin with this data structure:</p>
<pre><pre><code class="language-cpp">#include &lt;stdbool.h&gt;
#include &lt;stddef.h&gt;
#include &lt;stdint.h&gt;

typedef struct Node
{
    uint8_t value;
    struct Node *next;
} Node;

typedef struct
{
    Node *head;
    uint16_t count;
} LinkedList;</code></pre></pre>
<p>The <em>LinkedList</em> structure stores:</p>
<ul>
<li>A pointer to the first node</li>
<li>The number of nodes currently in the list</li>
</ul>
<h3><strong>Initializing the List</strong></h3>
<pre><pre><code class="language-cpp">void linked_list_init(LinkedList *list)
{
    list-&gt;head = NULL;
    list-&gt;count = 0U;
}</code></pre></pre>
<p>An initialized list contains no nodes, so the head is set to <em>NULL</em>.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Adding a Node at the Beginning</strong></h2>
<p>Adding a node at the beginning is one of the simplest linked-list operations.</p>
<p>Suppose the current list is:</p>
<pre><pre><code class="language-cpp">head -&gt; [20] -&gt; [30] -&gt; NULL</code></pre></pre>
<p>To insert a node containing 10:</p>
<ol>
<li>Make the new node point to the current head.</li>
<li>Change the head so it points to the new node.</li>
</ol>
<pre><pre><code class="language-cpp">bool linked_list_push_front(
    LinkedList *list,
    Node *node,
    uint8_t value)
{
    if ((list == NULL) || (node == NULL))
    {
        return false;
    }

    node-&gt;value = value;
    node-&gt;next = list-&gt;head;

    list-&gt;head = node;
    list-&gt;count++;

    return true;
}</code></pre></pre>
<p>After the operation:</p>
<pre><pre><code class="language-cpp">head -&gt; [10] -&gt; [20] -&gt; [30] -&gt; NULL</code></pre></pre>
<p>The existing nodes do not move in memory. Only two pointer values change.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Adding a Node at the End</strong></h2>
<p>Adding at the end requires finding the final node unless the list also stores a tail pointer.</p>
<pre><pre><code class="language-cpp">bool linked_list_push_back(
    LinkedList *list,
    Node *node,
    uint8_t value)
{
    Node *current;

    if ((list == NULL) || (node == NULL))
    {
        return false;
    }

    node-&gt;value = value;
    node-&gt;next = NULL;

    if (list-&gt;head == NULL)
    {
        list-&gt;head = node;
        list-&gt;count++;
        return true;
    }

    current = list-&gt;head;

    while (current-&gt;next != NULL)
    {
        current = current-&gt;next;
    }

    current-&gt;next = node;
    list-&gt;count++;

    return true;
}</code></pre></pre>
<p>If the list is empty, the new node becomes the head. Otherwise, the function traverses the list until it finds the node whose <em>next</em> pointer is <em>NULL</em>.</p>
<p>This operation takes longer as the list grows. Storing a separate tail pointer can make insertion at the end much faster.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Removing the First Node</strong></h2>
<p>To remove the first node:</p>
<ol>
<li>Save a pointer to the current head.</li>
<li>Move the head to the second node.</li>
<li>Disconnect the removed node.</li>
</ol>
<pre><pre><code class="language-cpp">Node *linked_list_pop_front(LinkedList *list)
{
    Node *removed;

    if ((list == NULL) || (list-&gt;head == NULL))
    {
        return NULL;
    }

    removed = list-&gt;head;
    list-&gt;head = removed-&gt;next;

    removed-&gt;next = NULL;
    list-&gt;count--;

    return removed;
}</code></pre></pre>
<p>If the original list is:</p>
<pre><pre><code class="language-cpp">head -&gt; [10] -&gt; [20] -&gt; [30] -&gt; NULL</code></pre></pre>
<p>the result becomes:</p>
<pre><pre><code class="language-cpp">head -&gt; [20] -&gt; [30] -&gt; NULL

removed -&gt; [10] -&gt; NULL</code></pre></pre>
<p>The function returns the removed node so the application can reuse it or release it through the appropriate memory-management method.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Removing a Node by Value</strong></h2>
<p>Removing a node from the middle requires keeping track of both the current node and the node before it.</p>
<pre><pre><code class="language-cpp">Node *linked_list_remove(
    LinkedList *list,
    uint8_t value)
{
    Node *current;
    Node *previous = NULL;

    if (list == NULL)
    {
        return NULL;
    }

    current = list-&gt;head;

    while (current != NULL)
    {
        if (current-&gt;value == value)
        {
            if (previous == NULL)
            {
                list-&gt;head = current-&gt;next;
            }
            else
            {
                previous-&gt;next = current-&gt;next;
            }

            current-&gt;next = NULL;
            list-&gt;count--;

            return current;
        }

        previous = current;
        current = current-&gt;next;
    }

    return NULL;
}</code></pre></pre>
<p>If the first node matches, the head changes. If a middle node matches, the previous node is connected directly to the node after the removed one.</p>
<p>For example:</p>
<pre><pre><code class="language-cpp">Before:

[10] -&gt; [20] -&gt; [30] -&gt; NULL

Remove 20:

[10] ---------&gt; [30] -&gt; NULL</code></pre></pre>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Finding a Value</strong></h2>
<pre><pre><code class="language-cpp">Node *linked_list_find(
    LinkedList *list,
    uint8_t value)
{
    Node *current;

    if (list == NULL)
    {
        return NULL;
    }

    current = list-&gt;head;

    while (current != NULL)
    {
        if (current-&gt;value == value)
        {
            return current;
        }

        current = current-&gt;next;
    }

    return NULL;
}</code></pre></pre>
<p>The function returns a pointer to the matching node or <em>NULL</em> when the value is not present.</p>
<p>Searching a linked list requires checking nodes one at a time. For large collections requiring frequent searches, another data structure may be more suitable.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Complete Dynamic Allocation Example</strong></h2>
<p>On a desktop computer, linked-list nodes are commonly created using dynamic allocation.</p>
<pre><pre><code class="language-cpp">#include &lt;stdbool.h&gt;
#include &lt;stdint.h&gt;
#include &lt;stdlib.h&gt;

typedef struct Node
{
    uint8_t value;
    struct Node *next;
} Node;

typedef struct
{
    Node *head;
    uint16_t count;
} LinkedList;

bool linked_list_add(
    LinkedList *list,
    uint8_t value)
{
    Node *node;

    if (list == NULL)
    {
        return false;
    }

    node = malloc(sizeof(Node));

    if (node == NULL)
    {
        return false;
    }

    node-&gt;value = value;
    node-&gt;next = list-&gt;head;

    list-&gt;head = node;
    list-&gt;count++;

    return true;
}

bool linked_list_remove_first(
    LinkedList *list,
    uint8_t *value)
{
    Node *removed;

    if ((list == NULL) ||
        (list-&gt;head == NULL))
    {
        return false;
    }

    removed = list-&gt;head;
    list-&gt;head = removed-&gt;next;

    if (value != NULL)
    {
        *value = removed-&gt;value;
    }

    free(removed);
    list-&gt;count--;

    return true;
}</code></pre></pre>
<p>This code works, but using <em>malloc()</em> and <em>free()</em> may not be the best option for every embedded system.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Why Dynamic Allocation Can Be a Problem</strong></h2>
<p>Microcontrollers often have limited RAM and may need to operate continuously for months or years. Repeated allocation and release can divide the available heap into small separated areas.</p>
<p>This condition is called <strong>memory fragmentation</strong>.</p>
<p>Suppose a device has several free memory regions:</p>
<pre><pre><code class="language-cpp">Free blocks: 16 bytes, 12 bytes, 20 bytes</code></pre></pre>
<p>The total free memory is 48 bytes, but an attempt to allocate one 32-byte block may fail because no individual free region is large enough.</p>
<p>Dynamic allocation can introduce other concerns:</p>
<ul>
<li>Allocation may fail at runtime.</li>
<li>Execution time may be difficult to predict.</li>
<li>Memory leaks can slowly consume the heap.</li>
<li>Double-free errors can corrupt memory.</li>
<li>Using freed nodes can cause unpredictable behavior.</li>
<li>Debugging heap corruption on a microcontroller can be difficult.</li>
</ul>
<p>Dynamic memory is not automatically forbidden in embedded software. It can be acceptable when allocation is controlled, occurs only during initialization, or runs on a system with enough memory and a suitable allocator.</p>
<p>However, a fixed node pool is often a safer choice for small microcontrollers and long-running firmware.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Using a Static Node Pool</strong></h2>
<p>A static pool reserves all possible nodes at compile time. The application takes nodes from the pool and returns them when they are no longer needed.</p>
<pre><pre><code class="language-cpp">#define NODE_POOL_SIZE 8U

typedef struct Node
{
    uint8_t value;
    struct Node *next;
} Node;

typedef struct
{
    Node nodes[NODE_POOL_SIZE];
    Node *free_list;
} NodePool;</code></pre></pre>
<p>The pool can use a second linked list to track available nodes.</p>
<h3><strong>Initializing the Pool</strong></h3>
<pre><pre><code class="language-cpp">void node_pool_init(NodePool *pool)
{
    uint8_t index;

    if (pool == NULL)
    {
        return;
    }

    for (index = 0U;
         index &lt; NODE_POOL_SIZE - 1U;
         index++)
    {
        pool-&gt;nodes[index].next =
            &amp;pool-&gt;nodes[index + 1U];
    }

    pool-&gt;nodes[NODE_POOL_SIZE - 1U].next =
        NULL;

    pool-&gt;free_list = &amp;pool-&gt;nodes[0];
}</code></pre></pre>
<p>After initialization, every available node is connected through the pool’s <em>free_list</em>.</p>
<h3><strong>Allocating a Node from the Pool</strong></h3>
<pre><pre><code class="language-cpp">Node *node_pool_allocate(NodePool *pool)
{
    Node *node;

    if ((pool == NULL) ||
        (pool-&gt;free_list == NULL))
    {
        return NULL;
    }

    node = pool-&gt;free_list;
    pool-&gt;free_list = node-&gt;next;

    node-&gt;next = NULL;

    return node;
}</code></pre></pre>
<p>The first available node is removed from the free list and returned to the caller.</p>
<h3><strong>Returning a Node to the Pool</strong></h3>
<pre><pre><code class="language-cpp">void node_pool_release(
    NodePool *pool,
    Node *node)
{
    if ((pool == NULL) || (node == NULL))
    {
        return;
    }

    node-&gt;next = pool-&gt;free_list;
    pool-&gt;free_list = node;
}</code></pre></pre>
<p>The released node is placed back at the beginning of the free list.</p>
<p>This approach has several useful properties:</p>
<ul>
<li>The maximum memory usage is known at compile time.</li>
<li>Allocation does not depend on the general-purpose heap.</li>
<li>No external fragmentation occurs inside the pool.</li>
<li>Allocation and release are fast and predictable.</li>
<li>Pool exhaustion can be detected when no free nodes remain.</li>
</ul>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Complete Static Linked List Example</strong></h2>
<p>The following example combines a linked list with a fixed node pool.</p>
<pre><pre><code class="language-cpp">#include &lt;stdbool.h&gt;
#include &lt;stddef.h&gt;
#include &lt;stdint.h&gt;

#define NODE_POOL_SIZE 8U

typedef struct Node
{
    uint8_t value;
    struct Node *next;
} Node;

typedef struct
{
    Node *head;
    uint16_t count;
} LinkedList;

typedef struct
{
    Node nodes[NODE_POOL_SIZE];
    Node *free_list;
} NodePool;

void node_pool_init(NodePool *pool)
{
    uint8_t index;

    for (index = 0U;
         index &lt; NODE_POOL_SIZE - 1U;
         index++)
    {
        pool-&gt;nodes[index].next =
            &amp;pool-&gt;nodes[index + 1U];
    }

    pool-&gt;nodes[NODE_POOL_SIZE - 1U].next =
        NULL;

    pool-&gt;free_list = &amp;pool-&gt;nodes[0];
}

Node *node_pool_allocate(NodePool *pool)
{
    Node *node;

    if (pool-&gt;free_list == NULL)
    {
        return NULL;
    }

    node = pool-&gt;free_list;
    pool-&gt;free_list = node-&gt;next;

    node-&gt;next = NULL;

    return node;
}

void node_pool_release(
    NodePool *pool,
    Node *node)
{
    node-&gt;next = pool-&gt;free_list;
    pool-&gt;free_list = node;
}

void linked_list_init(LinkedList *list)
{
    list-&gt;head = NULL;
    list-&gt;count = 0U;
}

bool linked_list_add(
    LinkedList *list,
    NodePool *pool,
    uint8_t value)
{
    Node *node;

    node = node_pool_allocate(pool);

    if (node == NULL)
    {
        return false;
    }

    node-&gt;value = value;
    node-&gt;next = list-&gt;head;

    list-&gt;head = node;
    list-&gt;count++;

    return true;
}

bool linked_list_remove_first(
    LinkedList *list,
    NodePool *pool,
    uint8_t *value)
{
    Node *removed;

    if (list-&gt;head == NULL)
    {
        return false;
    }

    removed = list-&gt;head;
    list-&gt;head = removed-&gt;next;

    if (value != NULL)
    {
        *value = removed-&gt;value;
    }

    list-&gt;count--;
    node_pool_release(pool, removed);

    return true;
}</code></pre></pre>
<p>The maximum number of active nodes is limited to <em>NODE_POOL_SIZE</em>. When all nodes are in use, <em>linked_list_add()</em> returns <em>false</em>.</p>
<p>This fixed limit is often desirable in embedded firmware because the system’s worst-case memory requirement is known before the program runs.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Example Application</strong></h2>
<pre><pre><code class="language-cpp">LinkedList event_list;
NodePool event_pool;
uint8_t event_value;

int main(void)
{
    node_pool_init(&amp;event_pool);
    linked_list_init(&amp;event_list);

    linked_list_add(
        &amp;event_list,
        &amp;event_pool,
        10U);

    linked_list_add(
        &amp;event_list,
        &amp;event_pool,
        20U);

    linked_list_add(
        &amp;event_list,
        &amp;event_pool,
        30U);

    while (1)
    {
        if (linked_list_remove_first(
                &amp;event_list,
                &amp;event_pool,
                &amp;event_value))
        {
            process_event(event_value);
        }

        run_other_tasks();
    }
}</code></pre></pre>
<p>Because nodes are added at the beginning, the values in this example are removed in reverse order: 30, 20, then 10.</p>
<p>This behavior is a stack, not a FIFO queue. To preserve insertion order, add new nodes at the end or store a tail pointer.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Linked List with Head and Tail Pointers</strong></h2>
<p>Adding a tail pointer makes it possible to insert at the end without traversing the entire list.</p>
<pre><pre><code class="language-cpp">typedef struct
{
    Node *head;
    Node *tail;
    uint16_t count;
} LinkedList;</code></pre></pre>
<p>Appending a node then becomes:</p>
<pre><pre><code class="language-cpp">bool linked_list_push_back(
    LinkedList *list,
    Node *node,
    uint8_t value)
{
    if ((list == NULL) || (node == NULL))
    {
        return false;
    }

    node-&gt;value = value;
    node-&gt;next = NULL;

    if (list-&gt;tail == NULL)
    {
        list-&gt;head = node;
        list-&gt;tail = node;
    }
    else
    {
        list-&gt;tail-&gt;next = node;
        list-&gt;tail = node;
    }

    list-&gt;count++;

    return true;
}</code></pre></pre>
<p>When removing the first node, remember to clear the tail when the final element is removed.</p>
<pre><pre><code class="language-cpp">Node *linked_list_pop_front(
    LinkedList *list)
{
    Node *removed;

    if ((list == NULL) ||
        (list-&gt;head == NULL))
    {
        return NULL;
    }

    removed = list-&gt;head;
    list-&gt;head = removed-&gt;next;

    if (list-&gt;head == NULL)
    {
        list-&gt;tail = NULL;
    }

    removed-&gt;next = NULL;
    list-&gt;count--;

    return removed;
}</code></pre></pre>
<p>This head-and-tail arrangement can be used to build a FIFO event queue.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Where Linked Lists Are Used in Embedded Systems</strong></h2>
<p>Linked lists can be useful when the number or ordering of elements changes while the program is running.</p>
<h3><strong>Event Queues</strong></h3>
<p>Each node can represent an event waiting to be processed.</p>
<pre><pre><code class="language-cpp">typedef struct Event
{
    uint16_t event_id;
    uint32_t timestamp;
    struct Event *next;
} Event;</code></pre></pre>
<h3><strong>Software Timers</strong></h3>
<p>Timer nodes may be sorted by expiration time. The application only needs to check the first timer to find the next event that will expire.</p>
<h3><strong>Network Packet Lists</strong></h3>
<p>Network stacks may connect packet buffers without copying all packet data into one large contiguous block.</p>
<h3><strong>Device and Driver Registration</strong></h3>
<p>Drivers, callbacks, or peripheral objects can be registered in a list during system initialization.</p>
<h3><strong>Memory Free Lists</strong></h3>
<p>A memory allocator can link unused blocks together. The static node pool shown earlier is itself an example of a free list.</p>
<h3><strong>RTOS Kernel Objects</strong></h3>
<p>Real-time operating systems commonly use linked lists internally for task scheduling, blocked-task lists, timer lists, and queues.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>When a Linked List Is Not the Best Choice</strong></h2>
<p>A linked list is not automatically better because elements can be inserted and removed easily.</p>
<p>Consider using an array, ring buffer, or another fixed structure when:</p>
<ul>
<li>The maximum number of elements is known.</li>
<li>Fast indexed access is required.</li>
<li>Memory overhead must be minimized.</li>
<li>Cache locality matters.</li>
<li>The data is processed in strict FIFO order.</li>
<li>Deterministic memory usage is a priority.</li>
</ul>
<p>For UART bytes, a ring buffer is normally more appropriate than a linked list. Every linked-list node would require a pointer in addition to the byte itself, creating unnecessary memory overhead.</p>
<p>A linked list becomes more attractive when elements are larger, insertions occur at different positions, or nodes already exist as application objects.</p>
<h2><strong>Memory Overhead in a Linked List</strong></h2>
<p>Each node requires space for at least one pointer.</p>
<p>Consider this structure on a 32-bit microcontroller:</p>
<pre><pre><code class="language-cpp">typedef struct Node
{
    uint8_t value;
    struct Node *next;
} Node;</code></pre></pre>
<p>The data uses only one byte, but the pointer requires four bytes. Alignment and padding may increase the total node size to eight bytes.</p>
<p>As a result, storing eight one-byte values may require approximately 64 bytes instead of eight bytes.</p>
<p>This is why linked lists are rarely a good choice for individual UART characters or tiny sensor readings.</p>
<p>They become more memory-efficient when each node contains a larger payload or when the node is embedded inside an existing object.</p>
<h2><strong>Intrusive Linked Lists</strong></h2>
<p>An intrusive linked list places the pointer directly inside the application object.</p>
<pre><pre><code class="language-cpp">typedef struct Sensor
{
    uint8_t address;
    uint16_t sample_rate;
    struct Sensor *next;
} Sensor;</code></pre></pre>
<p>The sensor object itself becomes a list node. No separate wrapper node is required.</p>
<p>This approach is common in embedded systems because it avoids additional allocation and gives the programmer direct control over memory.</p>
<p>The tradeoff is that the object becomes coupled to the list. A single <em>next</em> pointer also allows the object to belong to only one such list at a time unless more link fields are added.</p>
<h2><strong>Linked Lists and Interrupts</strong></h2>
<p>Modifying a linked list from an interrupt while the main program also modifies it can corrupt the list.</p>
<p>Consider adding a node at the head:</p>
<pre><pre><code class="language-cpp">node-&gt;next = list-&gt;head;
list-&gt;head = node;</code></pre></pre>
<p>This operation involves more than one memory access. If another execution context changes the list between those two statements, a node may be lost or connected incorrectly.</p>
<p>Depending on the system, shared-list operations may require:</p>
<ul>
<li>A short critical section</li>
<li>Temporarily disabling the relevant interrupt</li>
<li>An RTOS mutex</li>
<li>An RTOS queue</li>
<li>A carefully designed lock-free algorithm</li>
</ul>
<p>Adding <em>volatile</em> to a pointer does not make the complete linked-list operation atomic.</p>
<p>For communication between a UART interrupt and a main loop, a single-producer, single-consumer ring buffer is normally simpler and safer than a shared linked list.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Common Linked-List Mistakes</strong></h2>
<h3><strong>Losing the Head Pointer</strong></h3>
<p>Changing the head before saving the old value may make the original node unreachable.</p>
<h3><strong>Forgetting to Update the Previous Node</strong></h3>
<p>When removing a middle node, the previous node must point to the removed node’s successor.</p>
<h3><strong>Dereferencing NULL</strong></h3>
<p>Always check whether a pointer is <em>NULL</em> before accessing its members.</p>
<pre><pre><code class="language-cpp">if (current != NULL)
{
    value = current-&gt;value;
}</code></pre></pre>
<h3><strong>Using a Node After It Has Been Freed</strong></h3>
<p>A pointer does not automatically become invalid-looking after <em>free()</em>. It may still contain the old address even though that memory no longer belongs to the node.</p>
<pre><pre><code class="language-cpp">free(node);
node = NULL;</code></pre></pre>
<p>Setting the local pointer to <em>NULL</em> can help prevent accidental reuse, although other copies of the same pointer must also be handled correctly.</p>
<h3><strong>Creating a Cycle Accidentally</strong></h3>
<p>If a node points to an earlier node instead of eventually reaching <em>NULL</em>, traversal may never terminate.</p>
<pre><pre><code class="language-cpp">[10] -&gt; [20] -&gt; [30]
          ^         |
          |_________|</code></pre></pre>
<h3><strong>Returning a Node to the Pool Twice</strong></h3>
<p>Releasing the same node twice can corrupt the pool’s free list. The application must clearly define who owns each node.</p>
<h3><strong>Using Dynamic Allocation Without Handling Failure</strong></h3>
<p>Every allocation can fail. The return value of <em>malloc()</em> or a node-pool allocation function must be checked.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Singly Versus Doubly Linked Lists</strong></h2>
<p>A singly linked list stores only a pointer to the next node.</p>
<pre><pre><code class="language-cpp">typedef struct Node
{
    uint8_t value;
    struct Node *next;
} Node;</code></pre></pre>
<p>A doubly linked list stores both next and previous pointers.</p>
<pre><pre><code class="language-cpp">typedef struct Node
{
    uint8_t value;
    struct Node *next;
    struct Node *previous;
} Node;</code></pre></pre>
<p>A doubly linked list allows traversal in both directions and makes removal easier when the node address is already known. However, it requires another pointer in every node and more pointer updates during insertion and removal.</p>
<div style="overflow-x: auto;">
<table>
<thead>
<tr>
<th>Feature</th>
<th>Singly Linked List</th>
<th>Doubly Linked List</th>
</tr>
</thead>
<tbody>
<tr>
<td>Pointers per node</td>
<td>One</td>
<td>Two</td>
</tr>
<tr>
<td>Forward traversal</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<td>Backward traversal</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>Memory usage</td>
<td>Lower</td>
<td>Higher</td>
</tr>
<tr>
<td>Removal with node pointer</td>
<td>May need previous node</td>
<td>Direct pointer updates</td>
</tr>
<tr>
<td>Implementation complexity</td>
<td>Lower</td>
<td>Higher</td>
</tr>
</tbody>
</table>
</div>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Frequently Asked Questions</strong></h2>
<h3><strong>Are linked lists commonly used in embedded systems?</strong></h3>
<p>Yes, especially in operating systems, protocol stacks, event systems, driver registration, and memory managers. However, small bare-metal applications often prefer fixed arrays or ring buffers because they use less memory and are easier to analyze.</p>
<h3><strong>Should I use malloc for an embedded linked list?</strong></h3>
<p>It depends on the project. Dynamic allocation may be acceptable during initialization or on larger systems. For small or safety-sensitive microcontrollers, a fixed node pool usually provides more predictable behavior.</p>
<h3><strong>Is a linked list faster than an array?</strong></h3>
<p>Not in every operation. Inserting at the head of a linked list is fast, but finding the tenth element requires following the previous nine nodes. Arrays provide much faster indexed access and usually better memory locality.</p>
<h3><strong>Can linked-list nodes store structures?</strong></h3>
<p>Yes. A node can store sensor records, messages, tasks, timers, network packets, or other application-specific data.</p>
<h3><strong>What happens when a static node pool is full?</strong></h3>
<p>The allocation function returns <em>NULL</em>. The application must decide whether to reject the new item, report an error, reuse an older node, or increase the configured pool size.</p>
<h3><strong>Can I use a linked list inside an interrupt?</strong></h3>
<p>It is possible, but shared access must be designed carefully. Pointer updates can be interrupted halfway through, so critical sections or another synchronization method may be required.</p>
<h3><strong>Should UART data use a linked list?</strong></h3>
<p>Usually not. A fixed ring buffer is more compact and efficient for a stream of bytes. A linked list may be useful when each received item is a larger variable-size message rather than an individual byte.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Conclusion</strong></h2>
<p>A linked list connects separate data objects using pointers. The head identifies the first node, each node points to the next one, and the final node points to <em>NULL</em>.</p>
<p>The main linked-list operations are:</p>
<ul>
<li>Adding a node</li>
<li>Removing a node</li>
<li>Traversing the list</li>
<li>Searching for a value</li>
<li>Returning unused nodes to their allocator or static pool</li>
</ul>
<p>For embedded systems, the pointer logic is only part of the design. You must also consider RAM overhead, allocation failure, fragmentation, interrupt safety, and worst-case execution time.</p>
<p>The visualizer at the beginning of this tutorial shows the logical connections between nodes. In an actual microcontroller, those nodes may be stored at completely different memory addresses. The pointers are what preserve their order.</p>
<p>For small and predictable firmware, I recommend starting with a singly linked list backed by a fixed node pool. It keeps the flexibility of linked nodes while avoiding the uncertainty of repeated heap allocation.</p>
<p>The post <a href="https://www.teachmemicro.com/linked-list-visualizer-for-embedded-systems/">Linked List Visualizer for Embedded Systems</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Ring Buffer Visualizer for Embedded Systems</title>
		<link>https://www.teachmemicro.com/ring-buffer-visualizer-for-embedded-systems/</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Tue, 21 Jul 2026 03:15:47 +0000</pubDate>
				<category><![CDATA[Tools]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=12313</guid>

					<description><![CDATA[<p>A ring buffer in embedded systems is a fixed-size memory area used to temporarily store data. You will often find one between a fast peripheral and a slower part of the program. A UART interrupt, for example, can place received bytes into a ring buffer while the main application processes those bytes whenever it has &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/ring-buffer-visualizer-for-embedded-systems/">Ring Buffer Visualizer for Embedded Systems</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>A <strong>ring buffer in embedded systems</strong> is a fixed-size memory area used to temporarily store data. You will often find one between a fast peripheral and a slower part of the program. A UART interrupt, for example, can place received bytes into a ring buffer while the main application processes those bytes whenever it has time.</p>
<p>A ring buffer is also called a <strong>circular buffer</strong>. The memory itself is still an ordinary C array. What makes it circular is the way its indexes return to zero after reaching the final array element.</p>
<p>This tutorial explains how an embedded ring buffer works, how its <em>head</em> and <em>tail</em> pointers move, and how you can implement one in C. You can also use the interactive visualizer below to see each operation happen.</p>
<p><span id="more-12313"></span></p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Interactive Embedded Ring Buffer Visualizer</strong></h2>
<p>Enter a character, decimal byte, or hexadecimal value and click <strong>Write Byte</strong>. The value is stored at the current head position. Click <strong>Read Byte</strong> to remove the oldest value from the tail position.</p>
<p>Watch what happens when either pointer reaches the final slot. Instead of moving outside the array, it wraps around to index zero.</p>
        <div
            id="tmm-rbv-3"
            class="tmm-rbv-app"
            data-rbv-instance="tmm-rbv-3"
            data-rbv-buffer-size="8"
        >
            <section class="tmm-rbv-card" aria-labelledby="tmm-rbv-3-buffer-title">
                <noscript>
                    <div class="tmm-rbv-noscript">
                        JavaScript is required for the interactive ring buffer visualizer.                    </div>
                </noscript>

                <div class="tmm-rbv-topline">
                    <h3 id="tmm-rbv-3-buffer-title">
                        Ring Buffer                    </h3>
                    <p>
                        Count-based FIFO storage for embedded UART bytes.                    </p>
                </div>

                <div class="tmm-rbv-buffer-area">
                    <div
                        class="tmm-rbv-buffer"
                        data-rbv-el="buffer"
                        role="img"
                        aria-label="Circular ring buffer slots with head and tail indicators"
                    ></div>
                    <div class="tmm-rbv-legend" aria-label="Pointer legend">
                        <span><strong class="tmm-rbv-head-text">HEAD</strong>: next write position</span>
                        <span><strong class="tmm-rbv-tail-text">TAIL</strong>: next read position</span>
                    </div>
                </div>

                <div class="tmm-rbv-controls">
                    <label class="tmm-rbv-value-field" for="tmm-rbv-3-value">
                        <span>Byte or character</span>
                        <input
                            id="tmm-rbv-3-value"
                            data-rbv-el="value-input"
                            type="text"
                            inputmode="text"
                            autocomplete="off"
                            maxlength="6"
                            placeholder="A or 0x41"
                        >
                    </label>
                    <button class="tmm-rbv-button" type="button" data-rbv-action="write">
                        Write Byte                    </button>
                    <button class="tmm-rbv-button tmm-rbv-button--secondary" type="button" data-rbv-action="read">
                        Read Byte                    </button>
                    <button class="tmm-rbv-button tmm-rbv-button--muted" type="button" data-rbv-action="reset">
                        Reset                    </button>
                </div>

                <div class="tmm-rbv-uart-row">
                    <label class="tmm-rbv-toggle" for="tmm-rbv-3-uart-toggle">
                        <input id="tmm-rbv-3-uart-toggle" data-rbv-el="uart-toggle" type="checkbox">
                        <span>Simulate UART RX</span>
                    </label>
                    <button class="tmm-rbv-button tmm-rbv-button--secondary" type="button" data-rbv-action="uart-start" disabled>
                        Start UART                    </button>
                    <button class="tmm-rbv-button tmm-rbv-button--muted" type="button" data-rbv-action="uart-stop" disabled>
                        Stop UART                    </button>
                    <span class="tmm-rbv-uart-label" data-rbv-el="uart-label">
                        UART RX idle.                    </span>
                </div>

                <div class="tmm-rbv-status" id="tmm-rbv-3-status" data-rbv-el="status">
                    <span>Head: <strong data-rbv-el="head">0</strong></span>
                    <span>Tail: <strong data-rbv-el="tail">0</strong></span>
                    <span>Used: <strong data-rbv-el="used">0 / 8</strong></span>
                    <span>State: <strong data-rbv-el="state">EMPTY</strong></span>
                </div>

                <p
                    class="tmm-rbv-message"
                    id="tmm-rbv-3-message"
                    data-rbv-el="message"
                    aria-live="polite"
                >
                    Buffer is empty. Write a byte to begin.                </p>

                <details class="tmm-rbv-code" aria-labelledby="tmm-rbv-3-code-title">
                    <summary id="tmm-rbv-3-code-title">
                        Equivalent Embedded C                    </summary>
                    <button class="tmm-rbv-button tmm-rbv-button--copy" type="button" data-rbv-action="copy-code">
                        Copy Code                    </button>
                    <pre><code data-rbv-el="code">#define BUFFER_SIZE 8

typedef struct
{
    uint8_t data[BUFFER_SIZE];
    volatile uint8_t head;
    volatile uint8_t tail;
    volatile uint8_t count;
} RingBuffer;

bool ring_buffer_write(RingBuffer *rb, uint8_t value)
{
    if (rb-&gt;count &gt;= BUFFER_SIZE)
    {
        return false;
    }

    rb-&gt;data[rb-&gt;head] = value;
    rb-&gt;head = (rb-&gt;head + 1U) % BUFFER_SIZE;
    rb-&gt;count++;

    return true;
}

bool ring_buffer_read(RingBuffer *rb, uint8_t *value)
{
    if (rb-&gt;count == 0U)
    {
        return false;
    }

    *value = rb-&gt;data[rb-&gt;tail];
    rb-&gt;tail = (rb-&gt;tail + 1U) % BUFFER_SIZE;
    rb-&gt;count--;

    return true;
}</code></pre>
                </details>
            </section>
        </div>
        
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>What Is a Ring Buffer in Embedded Systems?</strong></h2>
<p>A ring buffer is a <strong>first-in, first-out</strong> or FIFO data structure built using a fixed-size array. The first value written into the buffer is normally the first value read from it.</p>
<p>Consider this eight-byte array:</p>
<pre><pre><code class="language-cpp">uint8_t buffer[8];</code></pre></pre>
<p>A normal queue implementation might remove the first value and then shift every remaining value toward index zero. That approach works, but repeatedly moving data wastes processor time.</p>
<p>A ring buffer does not shift its contents. Instead, it keeps track of two indexes:</p>
<ul>
<li><strong>Head</strong> — the position where the next value will be written</li>
<li><strong>Tail</strong> — the position where the next value will be read</li>
</ul>
<p>When a value is written, the head advances. When a value is read, the tail advances. The data remains in the same physical array slots until those slots are reused.</p>
<p>This makes ring-buffer writes and reads fast and predictable. Both operations normally take constant time because no existing elements have to be moved.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Why Ring Buffers Are Useful in Embedded Applications</strong></h2>
<p>Microcontrollers often deal with events that do not happen at exactly the same speed as the main application.</p>
<p>For example, a UART peripheral may receive a byte while the processor is:</p>
<ul>
<li>Updating a display</li>
<li>Reading a sensor</li>
<li>Writing data to flash memory</li>
<li>Processing a communication packet</li>
<li>Executing another part of the main loop</li>
</ul>
<p>The UART receive interrupt should not perform a large amount of processing. It should normally retrieve the received byte quickly, store it somewhere safe, and then return.</p>
<p>A ring buffer provides that temporary storage:</p>
<pre><pre><code class="language-cpp">UART peripheral → RX interrupt → Ring buffer → Main application</code></pre></pre>
<p>The interrupt acts as the <strong>producer</strong> because it adds bytes. The main application acts as the <strong>consumer</strong> because it removes and processes them.</p>
<p>The same idea can be used for:</p>
<ul>
<li>UART receive and transmit queues</li>
<li>ADC sample storage</li>
<li>Sensor-data collection</li>
<li>Logging systems</li>
<li>CAN and SPI message queues</li>
<li>Audio samples</li>
<li>Keyboard or keypad input</li>
<li>Data moving between interrupt handlers and the main loop</li>
</ul>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>How the Head Pointer Works</strong></h2>
<p>The head points to the array position where the next byte will be stored.</p>
<p>Suppose the head is initially at index zero:</p>
<pre><pre><code class="language-cpp">head = 0;</code></pre></pre>
<p>Writing the character <em>'A'</em> stores it in <em>buffer[0]</em>. The head then moves to index one.</p>
<pre><pre><code class="language-cpp">buffer[head] = &#039;A&#039;;
head++;</code></pre></pre>
<p>If the next value is <em>'B'</em>, it is stored in <em>buffer[1]</em>, after which the head moves to index two.</p>
<p>The head always identifies the <em>next available write position</em>. It does not normally point to the most recently written value.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>How the Tail Pointer Works</strong></h2>
<p>The tail points to the oldest unread value.</p>
<p>If the tail is at index zero, reading from the buffer returns the value stored in <em>buffer[0]</em>. The tail then advances to index one.</p>
<pre><pre><code class="language-cpp">value = buffer[tail];
tail++;</code></pre></pre>
<p>The contents do not need to be shifted after the read. The application simply considers that slot available again.</p>
<p>Some implementations clear a slot after reading it because doing so makes debugging and visualization easier. Clearing the slot is not normally required for the ring-buffer algorithm itself.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Pointer Wraparound</strong></h2>
<p>The circular behavior appears when a pointer reaches the end of the array.</p>
<p>For an eight-element buffer, the valid indexes are zero through seven. If the head is at index seven, its next position must be index zero.</p>
<p>This can be written using the modulo operator:</p>
<pre><pre><code class="language-cpp">head = (head + 1U) % BUFFER_SIZE;</code></pre></pre>
<p>With <em>BUFFER_SIZE</em> equal to eight:</p>
<pre><pre><code class="language-cpp">(7 + 1) % 8 = 0</code></pre></pre>
<p>The same operation is used for the tail:</p>
<pre><pre><code class="language-cpp">tail = (tail + 1U) % BUFFER_SIZE;</code></pre></pre>
<p>No data is physically moved from the end of the array back to the beginning. Only the index wraps around.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Detecting an Empty or Full Ring Buffer</strong></h2>
<p>A ring buffer must be able to determine whether another read or write is allowed.</p>
<p>The interactive visualizer uses a <strong>count-based implementation</strong>. In addition to the head and tail, it stores the number of elements currently inside the buffer.</p>
<pre><pre><code class="language-cpp">uint8_t head;
uint8_t tail;
uint8_t count;</code></pre></pre>
<p>The buffer is empty when:</p>
<pre><pre><code class="language-cpp">count == 0</code></pre></pre>
<p>It is full when:</p>
<pre><pre><code class="language-cpp">count == BUFFER_SIZE</code></pre></pre>
<p>This method allows every array element to be used. An eight-element array can hold eight values.</p>
<p>Another common method avoids the count variable and reserves one array slot. In that version:</p>
<ul>
<li><em>head == tail</em> means the buffer is empty.</li>
<li>The buffer is full when advancing the head would make it equal to the tail.</li>
</ul>
<p>That approach is especially useful for a single-producer, single-consumer ring buffer because the producer can own the head while the consumer owns the tail. I will return to that version later in the tutorial.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Basic Count-Based Ring Buffer in C</strong></h2>
<p>The following implementation matches the behavior of the visualizer. It stores eight bytes and rejects new data when the buffer is full.</p>
<pre><pre><code class="language-cpp">#include &lt;stdbool.h&gt;
#include &lt;stdint.h&gt;

#define BUFFER_SIZE 8U

typedef struct
{
    uint8_t data[BUFFER_SIZE];
    uint8_t head;
    uint8_t tail;
    uint8_t count;
} RingBuffer;</code></pre></pre>
<h3><strong>Initializing the Buffer</strong></h3>
<p>Initialization places both pointers at index zero and sets the stored count to zero.</p>
<pre><pre><code class="language-cpp">void ring_buffer_init(RingBuffer *rb)
{
    rb-&gt;head = 0U;
    rb-&gt;tail = 0U;
    rb-&gt;count = 0U;
}</code></pre></pre>
<p>The actual data array does not have to be cleared because no slot is considered valid while the count is zero.</p>
<h3><strong>Writing a Byte</strong></h3>
<pre><pre><code class="language-cpp">bool ring_buffer_write(RingBuffer *rb, uint8_t value)
{
    if (rb-&gt;count &gt;= BUFFER_SIZE)
    {
        return false;
    }

    rb-&gt;data[rb-&gt;head] = value;
    rb-&gt;head = (rb-&gt;head + 1U) % BUFFER_SIZE;
    rb-&gt;count++;

    return true;
}</code></pre></pre>
<p>The function first checks whether the buffer is full. If space is available, the value is written at the head position. The head advances, and the element count increases.</p>
<p>The function returns <em>false</em> when no more data can be accepted. The caller can use this result to record an overflow, increment an error counter, or take another appropriate action.</p>
<h3><strong>Reading a Byte</strong></h3>
<pre><pre><code class="language-cpp">bool ring_buffer_read(RingBuffer *rb, uint8_t *value)
{
    if (rb-&gt;count == 0U)
    {
        return false;
    }

    *value = rb-&gt;data[rb-&gt;tail];
    rb-&gt;tail = (rb-&gt;tail + 1U) % BUFFER_SIZE;
    rb-&gt;count--;

    return true;
}</code></pre></pre>
<p>The read function checks for an empty buffer before accessing the array. It returns the oldest byte, advances the tail, and decreases the count.</p>
<h3><strong>Checking the Current State</strong></h3>
<pre><pre><code class="language-cpp">bool ring_buffer_is_empty(const RingBuffer *rb)
{
    return rb-&gt;count == 0U;
}

bool ring_buffer_is_full(const RingBuffer *rb)
{
    return rb-&gt;count == BUFFER_SIZE;
}

uint8_t ring_buffer_used(const RingBuffer *rb)
{
    return rb-&gt;count;
}

uint8_t ring_buffer_free(const RingBuffer *rb)
{
    return BUFFER_SIZE - rb-&gt;count;
}</code></pre></pre>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>A Simple Ring Buffer Example</strong></h2>
<p>Here is how the buffer can be used from a normal C program:</p>
<pre><pre><code class="language-cpp">RingBuffer rx_buffer;
uint8_t received_byte;

int main(void)
{
    ring_buffer_init(&amp;rx_buffer);

    ring_buffer_write(&amp;rx_buffer, &#039;A&#039;);
    ring_buffer_write(&amp;rx_buffer, &#039;B&#039;);
    ring_buffer_write(&amp;rx_buffer, &#039;C&#039;);

    if (ring_buffer_read(&amp;rx_buffer, &amp;received_byte))
    {
        /* received_byte contains &#039;A&#039; */
    }

    if (ring_buffer_read(&amp;rx_buffer, &amp;received_byte))
    {
        /* received_byte contains &#039;B&#039; */
    }

    while (1)
    {
        /* Main application */
    }
}</code></pre></pre>
<p>Even if the head wraps around while more values are written, the tail continues to return them in FIFO order.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Using a Ring Buffer for UART Reception</strong></h2>
<p>A UART receive ring buffer is one of the most common embedded applications.</p>
<p>The general flow is:</p>
<ol>
<li>The UART peripheral receives a byte.</li>
<li>The receive interrupt runs.</li>
<li>The interrupt reads the UART data register.</li>
<li>The byte is written into the ring buffer.</li>
<li>The interrupt returns.</li>
<li>The main loop later reads and processes the buffered byte.</li>
</ol>
<p>A simplified interrupt handler might look like this:</p>
<pre><pre><code class="language-cpp">void UART_RX_IRQHandler(void)
{
    uint8_t value = uart_read_data_register();

    if (!ring_buffer_write(&amp;rx_buffer, value))
    {
        uart_rx_overflow = true;
    }
}</code></pre></pre>
<p>The main loop can process the data without waiting inside the interrupt:</p>
<pre><pre><code class="language-cpp">int main(void)
{
    uint8_t value;

    ring_buffer_init(&amp;rx_buffer);
    uart_init();

    while (1)
    {
        while (ring_buffer_read(&amp;rx_buffer, &amp;value))
        {
            process_received_byte(value);
        }

        run_other_application_tasks();
    }
}</code></pre></pre>
<p>This arrangement lets the interrupt capture incoming data quickly while the main application performs the more time-consuming work.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>A Concurrency Warning About the Count Variable</strong></h2>
<p>The count-based implementation is easy to understand and works well for the visualizer. However, notice what happens in the UART example:</p>
<ul>
<li>The interrupt increases <em>count</em>.</li>
<li>The main loop decreases <em>count</em>.</li>
</ul>
<p>Both execution contexts modify the same variable. An interrupt occurring during a read-modify-write operation may produce a race condition, depending on the microcontroller, variable width, compiler, and generated instructions.</p>
<p>Adding <em>volatile</em> prevents the compiler from treating a shared variable as if it never changes unexpectedly. It does <strong>not</strong> automatically make an operation atomic, and it does not by itself solve every concurrency problem.</p>
<p>You can protect a count-based implementation with a short critical section. Another option is to use a single-producer, single-consumer design where the interrupt modifies only the head and the main loop modifies only the tail.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Single-Producer, Single-Consumer UART Ring Buffer</strong></h2>
<p>For a typical UART receive buffer, there is one producer and one consumer:</p>
<ul>
<li>The UART RX interrupt is the only code that changes the head.</li>
<li>The main loop is the only code that changes the tail.</li>
</ul>
<p>The implementation below reserves one array slot so that <em>head == tail</em> can unambiguously represent an empty buffer.</p>
<pre><pre><code class="language-cpp">#include &lt;stdbool.h&gt;
#include &lt;stdint.h&gt;

#define UART_BUFFER_SIZE 16U
#define UART_BUFFER_MASK (UART_BUFFER_SIZE - 1U)

typedef struct
{
    uint8_t data[UART_BUFFER_SIZE];
    volatile uint16_t head;
    volatile uint16_t tail;
} UartRingBuffer;</code></pre></pre>
<p>Because the masking optimization is used here, <em>UART_BUFFER_SIZE</em> must be a power of two.</p>
<h3><strong>Writing from the UART Interrupt</strong></h3>
<pre><pre><code class="language-cpp">bool uart_ring_buffer_write_isr(
    UartRingBuffer *rb,
    uint8_t value)
{
    uint16_t head = rb-&gt;head;
    uint16_t next =
        (head + 1U) &amp; UART_BUFFER_MASK;

    if (next == rb-&gt;tail)
    {
        return false;
    }

    rb-&gt;data[head] = value;
    rb-&gt;head = next;

    return true;
}</code></pre></pre>
<h3><strong>Reading from the Main Loop</strong></h3>
<pre><pre><code class="language-cpp">bool uart_ring_buffer_read(
    UartRingBuffer *rb,
    uint8_t *value)
{
    uint16_t tail = rb-&gt;tail;

    if (tail == rb-&gt;head)
    {
        return false;
    }

    *value = rb-&gt;data[tail];
    rb-&gt;tail =
        (tail + 1U) &amp; UART_BUFFER_MASK;

    return true;
}</code></pre></pre>
<p>This arrangement avoids a shared count variable. The producer publishes a new head only after writing the byte, while the consumer advances the tail only after retrieving it.</p>
<p>You should still check the requirements of your specific architecture. Index reads and writes should be naturally aligned and atomic for the chosen index type. Systems with multiple cores, DMA, an RTOS, cache coherency concerns, or more than one producer or consumer may require memory barriers, critical sections, or an operating-system synchronization primitive.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Modulo Versus Bit Masking</strong></h2>
<p>The general wraparound operation uses modulo:</p>
<pre><pre><code class="language-cpp">next = (index + 1U) % BUFFER_SIZE;</code></pre></pre>
<p>This works for any valid buffer size.</p>
<p>If the size is a power of two, the index can instead be wrapped using a mask:</p>
<pre><pre><code class="language-cpp">next = (index + 1U) &amp; (BUFFER_SIZE - 1U);</code></pre></pre>
<p>For example, valid indexes for a 16-byte buffer are zero through 15. The value <em>BUFFER_SIZE - 1</em> is therefore <em>0x0F</em>.</p>
<p>Modern compilers can often optimize a constant modulo operation automatically. I would therefore prioritize clear and correct code first, then inspect the generated code if performance is critical.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Ring Buffer Overflow and Underflow</strong></h2>
<h3><strong>Overflow</strong></h3>
<p>An overflow occurs when the producer attempts to write while no space is available.</p>
<p>The buffer can respond in different ways:</p>
<ul>
<li>Reject the new value</li>
<li>Overwrite the oldest unread value</li>
<li>Set an overflow flag</li>
<li>Increment a lost-byte counter</li>
<li>Apply flow control when the protocol supports it</li>
</ul>
<p>The visualizer rejects the new byte because this makes the loss of data obvious. Whether that is the correct behavior in a real project depends on the application.</p>
<p>For a command interface, losing an old or new character may invalidate the entire command. For continuous sensor history, overwriting the oldest sample may be acceptable.</p>
<h3><strong>Underflow</strong></h3>
<p>An underflow occurs when the consumer attempts to read an empty buffer.</p>
<p>A well-designed read function should detect this before accessing the array. Returning a Boolean result is useful because every possible byte value, including zero, may be valid data.</p>
<pre><pre><code class="language-cpp">uint8_t value;

if (ring_buffer_read(&amp;rx_buffer, &amp;value))
{
    process_received_byte(value);
}
else
{
    /* No data currently available */
}</code></pre></pre>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Choosing the Ring Buffer Size</strong></h2>
<p>The correct buffer size depends on how quickly data arrives and how long the consumer may be unable to process it.</p>
<p>A useful starting estimate is:</p>
<p><span class='MathJax_Preview'><img src='https://www.teachmemicro.com/wp-content/plugins/latex/cache/tex_36c3ba47aec8bea6fe7cfe96cf26d71b.gif' style='vertical-align: middle; border: none; padding-bottom:2px;' class='tex' alt="N_{\min} \ge R_{\text{bytes}}T_{\text{delay}} + M" /></span><script type='math/tex'>N_{\min} \ge R_{\text{bytes}}T_{\text{delay}} + M</script></p>
<p>Where:</p>
<ul>
<li><strong>N<sub>min</sub></strong> is the minimum number of required slots.</li>
<li><strong>R<sub>bytes</sub></strong> is the incoming data rate in bytes per second.</li>
<li><strong>T<sub>delay</sub></strong> is the longest expected consumer delay in seconds.</li>
<li><strong>M</strong> is an additional safety margin.</li>
</ul>
<p>For example, an 8-N-1 UART connection at 115200 baud uses approximately ten transmitted bits for every data byte: one start bit, eight data bits, and one stop bit.</p>
<p><span class='MathJax_Preview'><img src='https://www.teachmemicro.com/wp-content/plugins/latex/cache/tex_13803e89537e1b130570a49228941c86.gif' style='vertical-align: middle; border: none; padding-bottom:2px;' class='tex' alt="R_{\text{bytes}} \approx \frac{115200}{10} = 11520\text{ bytes/s}" /></span><script type='math/tex'>R_{\text{bytes}} \approx \frac{115200}{10} = 11520\text{ bytes/s}</script></p>
<p>If the main application can be delayed for 5 milliseconds:</p>
<p><span class='MathJax_Preview'><img src='https://www.teachmemicro.com/wp-content/plugins/latex/cache/tex_79901569c7af45e0cd758adc090dd800.gif' style='vertical-align: middle; border: none; padding-bottom:2px;' class='tex' alt="N_{\min} \ge 11520 \times 0.005 = 57.6\text{ bytes}" /></span><script type='math/tex'>N_{\min} \ge 11520 \times 0.005 = 57.6\text{ bytes}</script></p>
<p>A 64-byte buffer is the smallest convenient power-of-two choice, but it leaves almost no margin. A 128-byte buffer may be more appropriate if RAM permits and occasional longer delays are possible.</p>
<p>If the implementation reserves one slot, remember that a 64-element array stores only 63 bytes.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Ring Buffer Versus an Ordinary Linear Buffer</strong></h2>
<div style="overflow-x: auto;">
<table>
<thead>
<tr>
<th>Feature</th>
<th>Ring Buffer</th>
<th>Linear Buffer with Shifting</th>
</tr>
</thead>
<tbody>
<tr>
<td>Memory allocation</td>
<td>Fixed</td>
<td>Usually fixed</td>
</tr>
<tr>
<td>Write operation</td>
<td>Advances head</td>
<td>Adds at the end</td>
</tr>
<tr>
<td>Read operation</td>
<td>Advances tail</td>
<td>May shift remaining values</td>
</tr>
<tr>
<td>Wraparound</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr>
<td>Read and write time</td>
<td>Normally constant</td>
<td>May increase when shifting</td>
</tr>
<tr>
<td>Common embedded use</td>
<td>UART, ADC, logging and streaming</td>
<td>Small packets and simple temporary storage</td>
</tr>
</tbody>
</table>
</div>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Common Ring Buffer Mistakes</strong></h2>
<h3><strong>Confusing the Head with the Last Written Position</strong></h3>
<p>In the implementation used here, the head is the <em>next</em> write position. After writing to index three, the head normally advances to index four.</p>
<h3><strong>Failing to Distinguish Full from Empty</strong></h3>
<p>If the head and tail can be equal in both states, another piece of information is required. Use a count, reserve one slot, or store a separate full flag.</p>
<h3><strong>Using an Index That Is Too Small</strong></h3>
<p>The index type must be able to represent every valid buffer position. An eight-bit index is sufficient for buffers up to 256 elements, but larger buffers require a wider type.</p>
<h3><strong>Assuming Volatile Makes Everything Thread-Safe</strong></h3>
<p><em>volatile</em> affects compiler optimization. It does not automatically protect a multi-step update from interrupts, multiple tasks, or another processor core.</p>
<h3><strong>Doing Too Much Work Inside the Interrupt</strong></h3>
<p>The receive interrupt should normally store the byte and return. Packet parsing, command execution, display updates, and other longer operations are better handled outside the ISR.</p>
<h3><strong>Ignoring Overflow</strong></h3>
<p>Even a correctly implemented ring buffer can overflow. Always decide how the application should detect and respond to lost data.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Frequently Asked Questions</strong></h2>
<h3><strong>Is a ring buffer the same as a circular queue?</strong></h3>
<p>The terms are often used interchangeably. Both normally describe FIFO storage that reuses a fixed array by wrapping its indexes. Ring buffer is particularly common terminology in embedded systems, drivers, communications, and streaming applications.</p>
<h3><strong>Does a ring buffer dynamically allocate memory?</strong></h3>
<p>It does not have to. Embedded implementations usually use a statically allocated array so memory usage is known at compile time and no heap allocation is required.</p>
<h3><strong>Can a ring buffer store structures instead of bytes?</strong></h3>
<p>Yes. The array can contain sensor samples, CAN frames, event structures, pointers, or other fixed-size objects.</p>
<pre><pre><code class="language-cpp">typedef struct
{
    uint32_t timestamp;
    int16_t temperature;
    uint16_t pressure;
} SensorSample;

SensorSample sample_buffer[16];</code></pre></pre>
<h3><strong>Should a full ring buffer reject or overwrite data?</strong></h3>
<p>It depends on the application. Communication buffers often reject new data and report an overflow. History buffers may intentionally overwrite the oldest entry so they always contain the most recent samples.</p>
<h3><strong>Can DMA use a ring buffer?</strong></h3>
<p>Yes, although the design becomes more hardware-specific. Many microcontrollers support circular DMA modes that repeatedly fill a memory region. The application must then track which portion of the region has been written and which portion has already been processed.</p>
<h3><strong>Can I use this implementation with an RTOS?</strong></h3>
<p>You can, but a ring buffer shared by multiple tasks may require a mutex, critical section, semaphore, or RTOS queue. A lock-free single-producer, single-consumer implementation should not automatically be treated as safe for multiple producers or consumers.</p>
<div class="clear"></div><div style="margin-top:20px; margin-bottom:20px;" class="divider divider-solid"></div>
<h2><strong>Conclusion</strong></h2>
<p>A ring buffer gives embedded applications an efficient way to temporarily store streaming data without shifting array elements or allocating more memory at runtime.</p>
<p>The basic idea is simple:</p>
<ul>
<li>The head identifies the next write position.</li>
<li>The tail identifies the next read position.</li>
<li>Both pointers return to zero after reaching the end.</li>
<li>Writes must detect a full buffer.</li>
<li>Reads must detect an empty buffer.</li>
</ul>
<p>The count-based approach used by the visualizer is useful for learning because the full and empty conditions are easy to see. For a real UART interrupt and main-loop arrangement, a single-producer, single-consumer implementation with separate head and tail ownership can reduce shared-state problems.</p>
<p>Try filling the visualizer, reading a few bytes, and then writing more values. The most important moment is when the head crosses the final index and returns to zero while the unread values remain in the correct FIFO order. Once that behavior is clear, the C implementation becomes much easier to understand.</p>
<p>The post <a href="https://www.teachmemicro.com/ring-buffer-visualizer-for-embedded-systems/">Ring Buffer Visualizer for Embedded Systems</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>ESP32 Pinout Diagram &#124; ESP32-CAM</title>
		<link>https://www.teachmemicro.com/esp32-pinout-diagram-esp32-cam/</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Mon, 10 Nov 2025 01:00:42 +0000</pubDate>
				<category><![CDATA[Reference]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=9670</guid>

					<description><![CDATA[<p>The ESP32-CAM is a compact yet powerful development board that packs Wi-Fi, Bluetooth, and camera capabilities into one low-cost module. But with its limited pin count and dual-purpose GPIOs, figuring out which pins to use can be confusing—especially for beginners. In this post, we’ll break down the ESP32-CAM pinout, explain the function of each pin, &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/esp32-pinout-diagram-esp32-cam/">ESP32 Pinout Diagram | ESP32-CAM</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>The <strong>ESP32-CAM</strong> is a compact yet powerful development board that packs Wi-Fi, Bluetooth, and camera capabilities into one low-cost module. But with its limited pin count and dual-purpose GPIOs, figuring out which pins to use can be confusing—especially for beginners. In this post, we’ll break down the <strong>ESP32-CAM pinout</strong>, explain the function of each pin, and help you avoid common pitfalls when connecting sensors, peripherals, or programming the board.</p>
<p><span id="more-9670"></span></p>
<p><a href="https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM-pinout-diagram.avif"><em>(Click to view larger image)</em></a></p>
<p><a href="https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM-pinout-diagram.avif"><img data-dominant-color="dcdad7" data-has-transparency="false" style="--dominant-color: #dcdad7;" loading="lazy" decoding="async" class="aligncenter size-large wp-image-9671 not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM-pinout-diagram-1024x567.avif" alt="" width="618" height="342" srcset="https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM-pinout-diagram-1024x567.avif 1024w, https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM-pinout-diagram-300x166.avif 300w, https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM-pinout-diagram-768x425.avif 768w, https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM-pinout-diagram-1536x851.avif 1536w, https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM-pinout-diagram.avif 1768w" sizes="auto, (max-width: 618px) 100vw, 618px" /></a></p>
<p>&nbsp;</p>
<h3><strong>ESP32-CAM Pinout Description</strong></h3>
<table>
<caption><em>ESP32-CAM (AI-Thinker) — Pinout (name / description)</em></caption>
<thead>
<tr>
<th>Pin Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>5V (VCC)</td>
<td>Primary power input (recommended). Often used to power the board from USB/5V supplies.</td>
</tr>
<tr>
<td>3V3 (3.3V)</td>
<td>3.3V output from the regulator. Can power small external sensors (use with caution).</td>
</tr>
<tr>
<td>GND</td>
<td>Ground (three GND pins available on the board).</td>
</tr>
<tr>
<td>IO0 (GPIO0)</td>
<td>Boot mode select (pull LOW to enter flash mode). Also used as camera XCLK in some setups — don’t leave it strapped incorrectly.</td>
</tr>
<tr>
<td>U0T / IO1 (GPIO1)</td>
<td>UART0 TX (serial transmit). Used by the on-board serial console — avoid reassigning during programming.</td>
</tr>
<tr>
<td>U0R / IO3 (GPIO3)</td>
<td>UART0 RX (serial receive). Used by serial console and required for uploading via UART adapter.</td>
</tr>
<tr>
<td>IO2 (GPIO2)</td>
<td>Used as SD card DATA0 (also available as general I/O if SD not used). Boot-sensitive on some modules.</td>
</tr>
<tr>
<td>IO4 (GPIO4)</td>
<td>SD card DATA1 / often tied to the on-board LED — usable if SD/card not in use.</td>
</tr>
<tr>
<td>IO12 (GPIO12)</td>
<td>SD card DATA2 (also MTDI). Watch this pin’s strapping behavior — avoid if unsure.</td>
</tr>
<tr>
<td>IO13 (GPIO13)</td>
<td>SD card DATA3 (also MTCK). Commonly used by the microSD interface.</td>
</tr>
<tr>
<td>IO14 (GPIO14)</td>
<td>SD card CLK (clock line). Usually occupied when using the microSD slot.</td>
</tr>
<tr>
<td>IO15 (GPIO15)</td>
<td>SD card CMD (command line). Typically used by the onboard microSD card interface.</td>
</tr>
<tr>
<td>IO16 (GPIO16)</td>
<td>General-purpose I/O (limited availability on some board variants).</td>
</tr>
<tr>
<td>IO17 (GPIO17)</td>
<td>General-purpose I/O (limited availability on some board variants).</td>
</tr>
<tr>
<td>GPIO32 (CAM_PWR / PWDN)</td>
<td>Camera power / PWDN control on some board variants (used to enable/disable camera power).</td>
</tr>
<tr>
<td>microSD slot</td>
<td>Uses many of the above pins (IO2, IO4, IO12–IO15, IO14) — if SD is used those pins aren’t free for other peripherals.</td>
</tr>
</tbody>
</table>
<h3><strong>ESP32-CAM Schematic Diagram</strong></h3>
<p>The schematic diagram below is a useful reference, together with the pinout, as you make your project.</p>
<p><a href="https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM_SCH.avif"><em>(Click to view larger image)</em></a></p>
<p><a href="https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM_SCH.avif"><img data-dominant-color="f6f3eb" data-has-transparency="true" style="--dominant-color: #f6f3eb;" loading="lazy" decoding="async" class="aligncenter wp-image-9672 size-large has-transparency" src="https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM_SCH-1024x723.avif" alt="ESP32 CAM Schematic Diagram" width="618" height="436" srcset="https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM_SCH-1024x723.avif 1024w, https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM_SCH-300x212.avif 300w, https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM_SCH-768x543.avif 768w, https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM_SCH-1536x1085.avif 1536w, https://www.teachmemicro.com/wp-content/uploads/2025/10/ESP32-CAM_SCH.avif 2000w" sizes="auto, (max-width: 618px) 100vw, 618px" /></a></p>
<p>The post <a href="https://www.teachmemicro.com/esp32-pinout-diagram-esp32-cam/">ESP32 Pinout Diagram | ESP32-CAM</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>ESP32 Pinout Diagram &#124; ESP32-WROVER</title>
		<link>https://www.teachmemicro.com/esp32-pinout-diagram-esp32-wrover/</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Mon, 29 Sep 2025 03:18:57 +0000</pubDate>
				<category><![CDATA[Reference]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=9367</guid>

					<description><![CDATA[<p>The ESP32 has become one of the most popular microcontrollers in the maker and IoT community thanks to its power, versatility, and built-in Wi-Fi and Bluetooth features. Among its many modules, the ESP32-WROVER stands out because of its extended memory and performance, making it ideal for demanding projects like data logging, machine learning, or advanced &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/esp32-pinout-diagram-esp32-wrover/">ESP32 Pinout Diagram | ESP32-WROVER</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>The ESP32 has become one of the most popular microcontrollers in the maker and IoT community thanks to its power, versatility, and built-in Wi-Fi and Bluetooth features. Among its many modules, the ESP32-WROVER stands out because of its extended memory and performance, making it ideal for demanding projects like data logging, machine learning, or advanced wireless applications. To make the most of this board, understanding its pinout is essential. Each pin serves a specific purpose—ranging from general-purpose input/output (GPIO) to power, ground, analog, and communication interfaces like SPI, I2C, and UART. In this article, we’ll walk through the ESP32-WROVER pinout diagram in detail so you can confidently connect sensors, modules, and peripherals while avoiding common wiring mistakes.</p>
<p><a href="https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-pinout-diagram.avif"><em>(Click to view larger image)</em></a></p>
<p><a href="https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-pinout-diagram.avif" target="_blank" rel="noopener"><img data-dominant-color="dad8d7" data-has-transparency="false" style="--dominant-color: #dad8d7;" loading="lazy" decoding="async" class="aligncenter wp-image-9369 size-medium not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-pinout-diagram-300x166.avif" alt="" width="300" height="166" srcset="https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-pinout-diagram-300x166.avif 300w, https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-pinout-diagram-1024x567.avif 1024w, https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-pinout-diagram-768x425.avif 768w, https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-pinout-diagram-1536x851.avif 1536w, https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-pinout-diagram.avif 1768w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a></p>
<h3></h3>
<h3 data-start="139" data-end="157"><strong>ESP32-WROVER Module Variants</strong></h3>
<h4 data-start="162" data-end="191"><strong data-start="162" data-end="189">ESP32-WROVER / WROVER-I</strong></h4>
<ul>
<li data-start="197" data-end="295">The original module with integrated PSRAM (typically 4 MB)</li>
<li data-start="301" data-end="461"><strong data-start="301" data-end="313">WROVER-I</strong> refers to the version with a U.FL (IPEX) antenna connector (instead of or in addition to PCB trace antenna)</li>
</ul>
<h4 data-start="466" data-end="498"><strong data-start="466" data-end="496">ESP32-WROVER-B / WROVER-IB</strong></h4>
<ul>
<li data-start="504" data-end="626">These versions operate their PSRAM at 3.3 V (vs. 1.8 V in earlier WROVER versions)</li>
<li data-start="632" data-end="757">The “IB” suffix again implies the variant with an IPEX connector for external antenna</li>
</ul>
<h4 data-start="762" data-end="794"><strong data-start="762" data-end="792">ESP32-WROVER-E / WROVER-IE</strong></h4>
<ul>
<li data-start="800" data-end="931">A newer revision, often supporting larger flash or PSRAM variants (for example, 8 MB PSRAM)</li>
<li data-start="937" data-end="1043">The “IE” version includes the IPEX connector for external antennas</li>
</ul>
<h4 data-start="1048" data-end="1089"><strong data-start="1048" data-end="1087">Custom Flash / PSRAM configurations</strong></h4>
<ul>
<li data-start="1095" data-end="1267">Within each of these variants, you will often find options for different flash memory sizes (e.g. 4 MB, 8 MB, 16 MB) and PSRAM sizes</li>
<li data-start="1273" data-end="1426">Also, some modules may include or exclude onboard antenna or use external antenna connectors according to design.</li>
</ul>
<h3><strong>ESP32-WROVER Pinout Description</strong></h3>
<p>The ESP32-WROVER has the same pinout as the ESP32-WROOM. The pins are categorized into digital pins, analog pins, and power pins. Refer to the table below for details on pins with secondary functions. Moreover, these secondary pins often serve communication purposes, such as I2C and SPI or as ADC channels.</p>
<table>
<thead>
<tr>
<th class="tg-c6xy">Pin Name</th>
<th class="tg-c6xy">Description</th>
<th class="tg-c6xy">Pin Name</th>
<th class="tg-c6xy">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-1vgr">3V3</td>
<td class="tg-32oo">3.3   V power supply</td>
<td class="tg-1vgr">GND</td>
<td class="tg-32oo">Ground</td>
</tr>
<tr>
<td class="tg-bgrj">EN</td>
<td class="tg-a9jm">CHIP_PU,   Reset</td>
<td class="tg-bgrj">IO23</td>
<td class="tg-a9jm">GPIO23</td>
</tr>
<tr>
<td class="tg-1vgr">VP</td>
<td class="tg-32oo">GPIO36, ADC1_CH0, S_VP</td>
<td class="tg-1vgr">IO22</td>
<td class="tg-32oo">GPIO22</td>
</tr>
<tr>
<td class="tg-bgrj">VN</td>
<td class="tg-a9jm">GPIO39, ADC1_CH3, S_VN</td>
<td class="tg-bgrj">TX</td>
<td class="tg-a9jm">GPIO1, U0TXD</td>
</tr>
<tr>
<td class="tg-1vgr">IO34</td>
<td class="tg-32oo">GPIO34, ADC1_CH6, VDET_1</td>
<td class="tg-1vgr">RX</td>
<td class="tg-32oo">GPIO3, U0RXD</td>
</tr>
<tr>
<td class="tg-bgrj">IO35</td>
<td class="tg-a9jm">GPIO35, ADC1_CH7, VDET_2</td>
<td class="tg-bgrj">IO21</td>
<td class="tg-a9jm">GPIO21</td>
</tr>
<tr>
<td class="tg-1vgr">IO32</td>
<td class="tg-32oo">GPIO32, ADC1_CH4, TOUCH_CH9, XTAL_32K_P</td>
<td class="tg-1vgr">GND</td>
<td class="tg-32oo">Ground</td>
</tr>
<tr>
<td class="tg-bgrj">IO33</td>
<td class="tg-a9jm">GPIO33, ADC1_CH5, TOUCH_CH8, XTAL_32K_N</td>
<td class="tg-bgrj">IO19</td>
<td class="tg-a9jm">GPIO19</td>
</tr>
<tr>
<td class="tg-1vgr">IO25</td>
<td class="tg-32oo">GPIO25,   ADC1_CH8, DAC_1</td>
<td class="tg-1vgr">IO18</td>
<td class="tg-32oo">GPIO18</td>
</tr>
<tr>
<td class="tg-bgrj">IO26</td>
<td class="tg-a9jm">GPIO26,   ADC2_CH9, DAC_2</td>
<td class="tg-bgrj">IO5</td>
<td class="tg-a9jm">GPIO5</td>
</tr>
<tr>
<td class="tg-1vgr">IO27</td>
<td class="tg-32oo">GPIO27, ADC2_CH7, TOUCH_CH7</td>
<td class="tg-1vgr">IO17</td>
<td class="tg-a8im">GPIO17 3</td>
</tr>
<tr>
<td class="tg-bgrj">IO14</td>
<td class="tg-a9jm">GPIO14, ADC2_CH6, TOUCH_CH6, MTMS</td>
<td class="tg-bgrj">IO16</td>
<td class="tg-fb48">GPIO16 3</td>
</tr>
<tr>
<td class="tg-1vgr">IO12</td>
<td class="tg-32oo">GPIO12, ADC2_CH5, TOUCH_CH5, MTDI</td>
<td class="tg-1vgr">IO4</td>
<td class="tg-32oo">GPIO4, ADC2_CH0, TOUCH_CH0</td>
</tr>
<tr>
<td class="tg-bgrj">GND</td>
<td class="tg-a9jm">Ground</td>
<td class="tg-bgrj">IO0</td>
<td class="tg-a9jm">GPIO0, ADC2_CH1, TOUCH_CH1, Boot</td>
</tr>
<tr>
<td class="tg-1vgr">IO13</td>
<td class="tg-32oo">GPIO13, ADC2_CH4, TOUCH_CH4, MTCK</td>
<td class="tg-1vgr">IO2</td>
<td class="tg-32oo">GPIO2, ADC2_CH2, TOUCH_CH2</td>
</tr>
<tr>
<td class="tg-bgrj">D2</td>
<td class="tg-fb48">GPIO9, D2 2</td>
<td class="tg-bgrj">IO15</td>
<td class="tg-a9jm">GPIO15, ADC2_CH3, TOUCH_CH3, MTDO</td>
</tr>
<tr>
<td class="tg-1vgr">D3</td>
<td class="tg-a8im">GPIO10, D3 2</td>
<td class="tg-1vgr">D1</td>
<td class="tg-a8im">GPIO8, D1 2</td>
</tr>
<tr>
<td class="tg-bgrj">CMD</td>
<td class="tg-fb48">GPIO11, CMD 2</td>
<td class="tg-bgrj">D0</td>
<td class="tg-fb48">GPIO7, D0 2</td>
</tr>
<tr>
<td class="tg-1vgr">5V</td>
<td class="tg-32oo">5   V power supply</td>
<td class="tg-1vgr">CLK</td>
<td class="tg-a8im">GPIO6, CLK 2</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<h3><strong>ESP32 WROVER Schematic Diagram</strong></h3>
<p>The schematic diagram below is a useful reference, together with the pinout as you make your project.</p>
<p><em><a href="https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-schematic.avif">(Click to view larger image)</a><br />
<a href="https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-schematic.avif"><img data-dominant-color="f6f2f5" data-has-transparency="false" style="--dominant-color: #f6f2f5;" loading="lazy" decoding="async" class="aligncenter wp-image-9370 size-large not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-schematic-1024x558.avif" alt="" width="618" height="337" srcset="https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-schematic-1024x558.avif 1024w, https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-schematic-300x163.avif 300w, https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-schematic-768x419.avif 768w, https://www.teachmemicro.com/wp-content/uploads/2025/09/ESP32-WROVER-schematic.avif 1156w" sizes="auto, (max-width: 618px) 100vw, 618px" /></a><br />
</em></p>
<p>&nbsp;</p>
<p>Do you want some ESP32 project ideas? Check out the following tutorials:</p>
<ul>
<li><a href="https://www.teachmemicro.com/sensor-display-on-esp32-web-server/">Sensor Display on ESP32 Web Server</a></li>
<li><a href="https://www.teachmemicro.com/esp32-turbidity-sensor/">ESP32 Turbidity Sensor</a></li>
<li><a href="https://www.teachmemicro.com/esp32-magnetometer-hmc5883l/">ESP32 Magnetometer using HMC5883L</a></li>
<li><a href="https://www.teachmemicro.com/esp32-max7219-wifi-message-board/">ESP32 MAX7219 WiFi Message Board</a></li>
<li><a href="https://www.teachmemicro.com/esp32-restful-api/">Using Restful APIs with ESP32</a></li>
<li><a href="https://www.teachmemicro.com/esp32-pressure-sensor/">ESP32 Pressure Sensor</a></li>
</ul>
<p>... and many more! See our <a href="https://www.teachmemicro.com/category/tutorials/esp32-tutorial/">ESP32 Tutorial archive</a>.</p>
<p>The post <a href="https://www.teachmemicro.com/esp32-pinout-diagram-esp32-wrover/">ESP32 Pinout Diagram | ESP32-WROVER</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>ESP12F Pinout Diagram</title>
		<link>https://www.teachmemicro.com/esp12f-pinout-diagram/</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Mon, 10 Feb 2025 12:49:24 +0000</pubDate>
				<category><![CDATA[Reference]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=8179</guid>

					<description><![CDATA[<p>The ESP-12F is an advanced version of the popular ESP8266 Wi-Fi module, offering improved RF performance and a compact design. Manufactured by Ai-Thinker, this module is ideal for IoT projects requiring wireless connectivity. Like its predecessor, the ESP-12F is built around the ESP8266EX chip, which features a 32-bit Tensilica L106 processor running at 80 MHz &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/esp12f-pinout-diagram/">ESP12F Pinout Diagram</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>The <strong>ESP-12F</strong> is an advanced version of the popular ESP8266 Wi-Fi module, offering improved RF performance and a compact design. Manufactured by Ai-Thinker, this module is ideal for IoT projects requiring wireless connectivity.</p>
<p><span id="more-8179"></span></p>
<figure id="attachment_8181" aria-describedby="caption-attachment-8181" style="width: 300px" class="wp-caption aligncenter"><a href="https://www.teachmemicro.com/wp-content/uploads/2025/02/ESP12F-pinout-diagram.avif"><img data-dominant-color="e6e3df" data-has-transparency="false" style="--dominant-color: #e6e3df;" loading="lazy" decoding="async" src="https://www.teachmemicro.com/wp-content/uploads/2025/02/ESP12F-pinout-diagram-300x216.avif" alt="ESP12F pinout diagram" width="300" height="216" class="wp-image-8181 size-medium not-transparent" srcset="https://www.teachmemicro.com/wp-content/uploads/2025/02/ESP12F-pinout-diagram-300x216.avif 300w, https://www.teachmemicro.com/wp-content/uploads/2025/02/ESP12F-pinout-diagram-1024x737.avif 1024w, https://www.teachmemicro.com/wp-content/uploads/2025/02/ESP12F-pinout-diagram-768x553.avif 768w, https://www.teachmemicro.com/wp-content/uploads/2025/02/ESP12F-pinout-diagram.avif 1360w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a><figcaption id="caption-attachment-8181" class="wp-caption-text">                     <a href="https://www.teachmemicro.com/wp-content/uploads/2025/02/ESP12F-pinout-diagram.avif">Click to view full image</a></figcaption></figure>
<p>Like its predecessor, the ESP-12F is built around the <strong>ESP8266EX</strong> chip, which features a <strong>32-bit Tensilica L106 processor</strong> running at <strong>80 MHz (up to 160 MHz overclocked)</strong> and supports <strong>Wi-Fi 2.4 GHz (802.11 b/g/n)</strong>. With <strong>4 MB (32 Mbit) of flash memory</strong>, it is well-suited for applications like home automation, smart devices, and wireless sensors.</p>
<h3>ESP-12F Pin Description</h3>
<p>The ESP-12F has <strong>22 pins</strong>, including power, GPIOs, and communication interfaces. Below is a breakdown of the key pin functions:</p>
<h4><strong>ESP-12F Pinout Table</strong></h4>
<table>
<thead>
<tr>
<th><strong>Pin</strong></th>
<th><strong>Name</strong></th>
<th><strong>Function</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>RST</td>
<td>Reset (Active Low)</td>
</tr>
<tr>
<td>2</td>
<td>ADC (A0)</td>
<td>Analog Input (0–1V)</td>
</tr>
<tr>
<td>3</td>
<td>EN (CH_PD)</td>
<td>Chip Enable (Pull High for Normal Operation)</td>
</tr>
<tr>
<td>4</td>
<td>GPIO16</td>
<td>Wake from Deep Sleep, General Purpose I/O</td>
</tr>
<tr>
<td>5</td>
<td>GPIO14</td>
<td>SPI_CLK, General Purpose I/O</td>
</tr>
<tr>
<td>6</td>
<td>GPIO12</td>
<td>SPI_MISO, General Purpose I/O</td>
</tr>
<tr>
<td>7</td>
<td>GPIO13</td>
<td>SPI_MOSI, General Purpose I/O</td>
</tr>
<tr>
<td>8</td>
<td>VCC</td>
<td>3.3V Power Supply</td>
</tr>
<tr>
<td>9</td>
<td>GND</td>
<td>Ground</td>
</tr>
<tr>
<td>10</td>
<td>GPIO15</td>
<td>SPI_CS, Boot Mode Selection (Pull Low for Normal Operation)</td>
</tr>
<tr>
<td>11</td>
<td>GPIO2</td>
<td>Boot Mode Selection, General Purpose I/O</td>
</tr>
<tr>
<td>12</td>
<td>GPIO0</td>
<td>Boot Mode Selection (Pull Low for Flash Mode)</td>
</tr>
<tr>
<td>13</td>
<td>GPIO4</td>
<td>I²C SDA, General Purpose I/O</td>
</tr>
<tr>
<td>14</td>
<td>GPIO5</td>
<td>I²C SCL, General Purpose I/O</td>
</tr>
<tr>
<td>15</td>
<td>RXD (GPIO3)</td>
<td>UART0 RX, General Purpose I/O</td>
</tr>
<tr>
<td>16</td>
<td>TXD (GPIO1)</td>
<td>UART0 TX, General Purpose I/O</td>
</tr>
</tbody>
</table>
<h3>Boot Modes</h3>
<p>The ESP-12F supports different boot modes based on the state of GPIO0, GPIO2, and GPIO15:</p>
<table>
<thead>
<tr>
<th>Mode</th>
<th>GPIO0</th>
<th>GPIO2</th>
<th>GPIO15</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Normal Boot</td>
<td>High</td>
<td>High</td>
<td>Low</td>
<td>Runs firmware from flash</td>
</tr>
<tr>
<td>Flash Mode</td>
<td>Low</td>
<td>High</td>
<td>Low</td>
<td>Enables firmware flashing</td>
</tr>
</tbody>
</table>
<h3>Power Considerations</h3>
<p>The ESP-12F requires a <strong>stable 3.3V power supply</strong> and can draw up to <strong>170 mA</strong> during Wi-Fi transmissions. It is recommended to use an <strong>LDO voltage regulator</strong> or a <strong>dedicated power supply</strong> for reliable operation.</p>
<h3>Applications</h3>
<p>Due to its compact size, Wi-Fi capability, and low power consumption, the ESP-12F is commonly used in:</p>
<ul>
<li><strong>Smart home devices</strong> (e.g., Wi-Fi switches, sensors)</li>
<li><strong>IoT projects</strong></li>
<li><strong>Wireless data logging</strong></li>
<li><strong>Home automation systems</strong></li>
</ul>
<p>The post <a href="https://www.teachmemicro.com/esp12f-pinout-diagram/">ESP12F Pinout Diagram</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>STM32 I2C Calculator</title>
		<link>https://www.teachmemicro.com/stm32-i2c-calculator/</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Wed, 05 Feb 2025 01:40:35 +0000</pubDate>
				<category><![CDATA[Tools]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=8114</guid>

					<description><![CDATA[<p>I2C (Inter-Integrated Circuit) is a widely used communication protocol in embedded systems, particularly in STM32 microcontrollers. Configuring the correct clock settings for I2C communication is crucial for reliable data transmission. However, calculating the necessary parameters, such as the Clock Control Register (CCR), the Rise Time Register (TRISE), and the Timing Register in newer STM32 versions, &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/stm32-i2c-calculator/">STM32 I2C Calculator</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p style="text-align: center;">    <div class="box shadow  aligncenter" id="i2c-calculator">
        <label for="stm32-version">Select STM32 Version:</label>
        <select id="stm32-version">
            <option value="older">Older STM32 (CCR/TRISE)</option>
            <option value="newer">Newer STM32 (Timing Register)</option>
        </select>
        <label for="pclk">Peripheral Clock (PCLK) [Hz]:</label>
        <input type="number" id="pclk" placeholder="Enter PCLK frequency" />
        <label for="target_freq">Target I2C Frequency [Hz]:</label>
        <input type="number" id="target_freq" placeholder="Enter target frequency" />
        <button id="calculate-i2c">Calculate</button>
        <div id="i2c-results">
            <p><strong>RESULTS:</strong></p>
            <p><strong>CCR Register Value:</strong> <span id="ccr-value">N/A</span></p>
            <p><strong>TRISE Register Value:</strong> <span id="trise-value">N/A</span></p>
            <p><strong>Timing Register Value:</strong> <span id="timing-value">N/A</span></p>
        </div>
    </div>
    
<p data-pm-slice="1 1 []"><span>I2C (Inter-Integrated Circuit) is a widely used communication protocol in embedded systems, particularly in STM32 microcontrollers. Configuring the correct clock settings for I2C communication is crucial for reliable data transmission. However, calculating the necessary parameters, such as the Clock Control Register (CCR), the Rise Time Register (TRISE), and the Timing Register in newer STM32 versions, can be complex.</span></p>
<p><span>To simplify this process, we’ve developed an </span><span><strong>STM32 I2C Clock Calculator</strong></span><span>, which allows users to quickly determine the required settings based on the peripheral clock (PCLK) and the target I2C frequency. </span></p>
<h2><span>Understanding I2C Clock Configuration</span></h2>
<p><span>STM32 microcontrollers implement I2C timing configuration through different registers depending on the version:</span></p>
<ol data-spread="false" start="1">
<li><span><strong>CCR (Clock Control Register):</strong></span><span> Determines the clock period based on the PCLK and the desired I2C frequency (older STM32 models).</span></li>
<li><span><strong>TRISE (Rise Time Register):</strong></span><span> Ensures that the SDA and SCL lines meet the required timing constraints for correct signal transitions.</span></li>
<li><span><strong>Timing Register:</strong></span><span> Used in newer STM32 microcontrollers (such as STM32F7, STM32H7, and STM32L4) to configure I2C timing more precisely.</span></li>
</ol>
<h3><span>I2C Modes in STM32</span></h3>
<p><span>STM32 supports two primary I2C speed modes:</span></p>
<ul data-spread="false">
<li><span><strong>Standard Mode (SM):</strong></span><span> Operates at a maximum of 100 kHz.</span></li>
<li><span><strong>Fast Mode (FM):</strong></span><span> Operates up to 400 kHz.</span></li>
</ul>
<p><span>The formulas for computing CCR and TRISE values are as follows:</span></p>
<h4><span>Standard Mode (≤ 100 kHz)</span></h4>
<ul data-spread="false">
<li>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">CCR = PCLK / (2 * Target Frequency)</code></pre></pre>
</div>
</li>
<li>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">TRISE = (PCLK / 1,000,000) + 1</code></pre></pre>
</div>
</li>
</ul>
<h4><span>Fast Mode (≤ 400 kHz)</span></h4>
<ul data-spread="false">
<li>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">CCR = PCLK / (3 * Target Frequency)</code></pre></pre>
</div>
</li>
<li>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">TRISE = (PCLK / 1,000,000) * 300 / 1000 + 1</code></pre></pre>
</div>
</li>
</ul>
<p><span>For newer STM32 models, the </span><span><strong>Timing Register</strong></span><span> is used instead, requiring a more detailed calculation based on SCL timing constraints:</span></p>
<h4><span>Timing Register Calculation (Newer STM32 Versions)</span></h4>
<p><span>The Timing Register is composed of multiple fields:</span></p>
<ul data-spread="false">
<li><span><strong>SCLL (Low period of SCL clock):</strong></span><span></span>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">(PCLK / (2 * Target Frequency)) - 1</code></pre></pre>
</div>
</li>
<li><span><strong>SCLH (High period of SCL clock):</strong></span><span></span>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">(PCLK / (2 * Target Frequency)) - 1</code></pre></pre>
</div>
</li>
<li><span><strong>SDADEL (Data hold time):</strong></span><span></span>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">(PCLK / 100,000,000) (Approximate placeholder)</code></pre></pre>
</div>
</li>
<li><span><strong>SCLDEL (Data setup time):</strong></span><span></span>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">(PCLK / 50,000,000) (Approximate placeholder)</code></pre></pre>
</div>
</li>
</ul>
<p><span>The final </span><span><strong>Timing Register Value</strong></span><span> is calculated as:</span></p>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">TIMING = (SCLDEL &lt;&lt; 20) | (SDADEL &lt;&lt; 16) | (SCLH &lt;&lt; 8) | SCLL</code></pre></pre>
</div>
<p>&nbsp;</p>
<h2 data-pm-slice="1 3 []"><span>Using the STM32 I2C Clock Calculator</span></h2>
<p><span>This tool provides a user-friendly interface where you can:</span></p>
<ol data-spread="false" start="1">
<li><span>Select whether you are using an </span><span><strong>Older STM32 (CCR/TRISE)</strong></span><span> or </span><span><strong>Newer STM32 (Timing Register)</strong></span><span>.</span></li>
<li><span>Enter the Peripheral Clock (PCLK) in Hz.</span></li>
<li><span>Specify the Target I2C Frequency in Hz.</span></li>
<li><span>Click "Calculate" to obtain the necessary timing values.</span></li>
</ol>
<p>The post <a href="https://www.teachmemicro.com/stm32-i2c-calculator/">STM32 I2C Calculator</a> appeared first on <a href="https://www.teachmemicro.com">Teach Me Microcontrollers!</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: www.teachmemicro.com @ 2026-09-04 04:00:24 by W3 Total Cache
-->