{"id":4627,"date":"2026-07-29T09:13:43","date_gmt":"2026-07-29T09:13:43","guid":{"rendered":"https:\/\/blog.embeddedexpert.io\/?p=4627"},"modified":"2026-07-29T09:13:45","modified_gmt":"2026-07-29T09:13:45","slug":"getting-started-with-stm32-low-layer-ll-inter-integrated-circuit-i2c-write-to-registers-using-dma","status":"publish","type":"post","link":"https:\/\/blog.embeddedexpert.io\/?p=4627","title":{"rendered":"Getting Started with STM32 Low Layer (LL): Inter-Integrated Circuit (I2C) Write to Registers Using DMA"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"559\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_s7rkgjs7rkgjs7rk-1024x559.png\" alt=\"\" class=\"wp-image-4628\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_s7rkgjs7rkgjs7rk-1024x559.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_s7rkgjs7rkgjs7rk-300x164.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_s7rkgjs7rkgjs7rk-768x419.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_s7rkgjs7rkgjs7rk-1150x627.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_s7rkgjs7rkgjs7rk-750x409.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_s7rkgjs7rkgjs7rk-400x218.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_s7rkgjs7rkgjs7rk-250x136.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_s7rkgjs7rkgjs7rk.png 1408w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>In the fourth part of our STM32 Low-Layer (LL) I2C series, we offload transaction overhead from the CPU by implementing Direct Memory Access (DMA) for memory write operations. We will explore how to configure the DMA controller to automate byte transfers directly from RAM to the I2C transmit register, freeing processor cycles for concurrent application logic.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>In this guide, we shall cover the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Introduction.<\/li>\n\n\n\n<li>STM32CubeMX setup.<\/li>\n\n\n\n<li>Firmware development.<\/li>\n\n\n\n<li>Results.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">1. Introduction:<\/h2>\n\n\n\n<p>In embedded system development, achieving high performance is often defined not by how fast your CPU can execute a loop, but by how effectively it delegates routine background work to specialized hardware peripherals. Up to this point in our STM32 Low-Layer (LL) I2C series, we have relied on&nbsp;<strong>blocking polling loops<\/strong>&nbsp;(<code>while(!LL_I2C_IsActiveFlag_...())<\/code>) to manage bus transactions. While polling is easy to implement and useful for quick hardware bring-up, it consumes 100% of CPU cycles during every bit sequence.<\/p>\n\n\n\n<p>In this fourth installment, we transition from blocking CPU-driven transfers to non-blocking&nbsp;<strong>Direct Memory Access (DMA)<\/strong>&nbsp;hardware offloading. We will explore the mechanics of configuring the STM32 DMA controller to pump data directly from RAM buffers to the I2C peripheral hardware without processor intervention.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Polling vs. DMA: The CPU Bottleneck<\/h3>\n\n\n\n<p>To understand why professional firmware architecture favors DMA, it helps to analyze the physical time scale of an I2C transaction compared to the execution speed of a modern ARM Cortex-M core.<\/p>\n\n\n\n<p>Consider a standard I2C bus running at&nbsp;<strong>Standard Mode (100 kHz)<\/strong>&nbsp;or&nbsp;<strong>Fast Mode (400 kHz)<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A typical 8-byte write transaction (Address frame, Register offset, and 6 payload bytes) requires approximately\u00a0<strong>80 clock cycles<\/strong>\u00a0on the physical SDA line.<\/li>\n\n\n\n<li>At a\u00a0100\u00a0kHz\u00a0SCL frequency, this transaction takes\u00a0<strong>800 microseconds<\/strong>\u00a0(0.8\u00a0ms).<\/li>\n\n\n\n<li>An STM32 microcontroller running at a modest\u00a072\u00a0MHz\u00a0core clock executes 72,000 instruction cycles every single millisecond.<\/li>\n<\/ul>\n\n\n\n<p>When using&nbsp;<strong>polling mode<\/strong>, the CPU core sits trapped inside tight&nbsp;<code>while()<\/code>&nbsp;loops waiting for status flags (<code>SB<\/code>,&nbsp;<code>ADDR<\/code>,&nbsp;<code>TXE<\/code>,&nbsp;<code>BTF<\/code>) to clear. During that&nbsp;0.8&nbsp;ms&nbsp;transmission, the CPU wastes nearly&nbsp;<strong>57,600 clock cycles<\/strong>&nbsp;doing nothing but checking hardware bits. If your firmware needs to update a display, read multi-axis sensors, process math filters, or respond to high-speed user inputs, blocking the CPU for I2C flag checks severely degrades system throughput and increases interrupt latency.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"559\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_x5xcy6x5xcy6x5xc-1024x559.png\" alt=\"\" class=\"wp-image-4629\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_x5xcy6x5xcy6x5xc-1024x559.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_x5xcy6x5xcy6x5xc-300x164.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_x5xcy6x5xcy6x5xc-768x419.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_x5xcy6x5xcy6x5xc-1150x627.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_x5xcy6x5xcy6x5xc-750x409.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_x5xcy6x5xcy6x5xc-400x218.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_x5xcy6x5xcy6x5xc-250x136.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/Gemini_Generated_Image_x5xcy6x5xcy6x5xc.png 1408w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">Why Professional Firmware Developers Favor DMA<\/h4>\n\n\n\n<p>In commercial and industrial embedded products, firmware reliability, real-time determinism, and energy efficiency are critical requirements. Professional system architects rely on DMA over polling for several key reasons:<\/p>\n\n\n\n<h6 class=\"wp-block-heading\">1. Deterministic Real-Time Responsiveness<\/h6>\n\n\n\n<p>In complex real-time applications (such as motor control, audio processing, or flight control systems), the processor must respond to high-priority external events within strict microsecond deadlines. A CPU locked inside an I2C polling loop cannot serve low-priority tasks and risks delaying time-critical software threads. By offloading payload transfers to the DMA controller, the main CPU core remains free to execute control loops, process signal streams, or service higher-priority interrupts without jitter.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">2. Maximizing Energy Efficiency (Low-Power Systems)<\/h4>\n\n\n\n<p>For battery-operated IoT nodes and wearable devices, power consumption is a primary constraint. When transmitting large data blocks (such as buffering sensor logs to an external EEPROM or driving a display controller), the CPU core does not need to stay active at full operational voltage just to feed bytes into a data register. With DMA configured, the core can enter low-power modes (such as&nbsp;<code>SLEEP<\/code>&nbsp;or&nbsp;<code>STOP<\/code>&nbsp;mode on STM32) while the autonomous DMA controller and I2C peripheral continue transferring data in the background, waking the core only when the full transfer finishes.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">3. High-Throughput Burst Transmits<\/h4>\n\n\n\n<p>When writing multi-byte payloads\u2014such as updating continuous configuration registers, streaming pixel arrays to an I2C OLED display, or loading bulk data arrays into external memory\u2014DMA handles byte-to-byte sequencing entirely in hardware. As soon as the I2C peripheral&#8217;s Transmit Data Register (<code>DR<\/code>) empties, the I2C hardware automatically generates a DMA request. The DMA controller instantly fetches the next byte from system RAM and writes it to&nbsp;<code>I2C_DR<\/code>&nbsp;via the internal AHB bus matrix without spending a single CPU instruction cycle.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">When Is Polling Mode Still Used?<\/h4>\n\n\n\n<p>Despite the overwhelming architectural benefits of DMA, polling is not obsolete. Professional developers intentionally use polling mode in specific, narrow scenarios:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Board Bring-Up &amp; Diagnostics:<\/strong>\u00a0During early hardware validation, simple polling functions ensure that hardware behavior can be debugged sequentially without configuring DMA channels, stream interrupts, or ring buffers.<\/li>\n\n\n\n<li><strong>Low-Byte Atomic Configuration:<\/strong>\u00a0Writing a single 8-bit command byte or reading a single status bit during system initialization (before the main RTOS scheduler starts) often does not justify the memory and setup overhead of a full DMA stream.<\/li>\n\n\n\n<li><strong>Fault Recovery &amp; Hard Fault Handlers:<\/strong>\u00a0If a critical error occurs and the system enters an emergency panic handler, the DMA controller or interrupt vector table might be disabled or corrupted. Synchronous polling routines allow the MCU to dump diagnostic logs or issue emergency reset commands directly to slave hardware.<\/li>\n<\/ul>\n\n\n\n<p>In the practical sections that follow, we will examine how to bridge the STM32 DMA Stream to the I2C peripheral using Low-Layer (LL) APIs\u2014configuring source memory pointers, destination register targets, transfer counts, and interrupt flags to achieve seamless, zero-CPU-overhead write transactions.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. STM32CubMX Setup:<\/h2>\n\n\n\n<p>We shall continue from the previous guide from <a href=\"https:\/\/blog.embeddedexpert.io\/?p=4606\" data-type=\"link\" data-id=\"https:\/\/blog.embeddedexpert.io\/?p=4606\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>.<\/p>\n\n\n\n<p>Open the project .ioc file using STM32CubeMX.<\/p>\n\n\n\n<p>From connectivity, select I2C1 or the one you used before and enable DMA from DMA settings as follows:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Add I2C TX to the DMA.<\/li>\n\n\n\n<li>Set the mode to normal.<\/li>\n\n\n\n<li>Keep the rest as default.<\/li>\n<\/ul>\n\n\n\n<p>Note: you may change the DMA stream if the current one is used.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"662\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_09-25-44-1024x662.jpg\" alt=\"\" class=\"wp-image-4630\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_09-25-44-1024x662.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_09-25-44-300x194.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_09-25-44-768x497.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_09-25-44-1536x993.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_09-25-44-2048x1324.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_09-25-44-1150x744.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_09-25-44-750x485.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_09-25-44-400x259.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_09-25-44-250x162.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Please take note which DMA and stream is used. In this guide, we shall use DMA Stream1.<\/p>\n\n\n\n<p>Next, from System Core, select NVIC then code generation and disable generate IRQ handler for DMA1 Stream 1 or your chosen DMA stream and click on Generate code as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"662\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_11-44-39-1024x662.jpg\" alt=\"\" class=\"wp-image-4631\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_11-44-39-1024x662.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_11-44-39-300x194.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_11-44-39-768x497.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_11-44-39-1536x993.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_11-44-39-2048x1324.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_11-44-39-1150x744.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_11-44-39-750x485.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_11-44-39-400x259.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-29_11-44-39-250x162.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. Firmware Development:<\/h2>\n\n\n\n<p>Open the project in STM32CubeIDE.<\/p>\n\n\n\n<p>Open i2c.h header file and declare the following function:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">void I2C_Mem_Write_DMA(I2C_TypeDef *I2Cx,uint8_t  slaveAddress,uint16_t regAddress,uint8_t  regLen,uint8_t *data,uint16_t length);<\/pre><\/div>\n\n\n\n<p>This function takes the same parameters as the polling one, with difference that it will use DMA rather than to transmit the data.<\/p>\n\n\n\n<p>Next, open i2c.c source file.<\/p>\n\n\n\n<p>We start by including the DMA header file:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">#include &quot;dma.h&quot;<\/pre><\/div>\n\n\n\n<p>Next, declare some define statements to hold the DMA and stream as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">#define I2C_DMA_INSTANCE  DMA1\n#define I2C_DMA_STREAM    LL_DMA_STREAM_1<\/pre><\/div>\n\n\n\n<p>Note: If you used different DMA Stream, please update it accordingly.<\/p>\n\n\n\n<p>Next, declare a flag to hold if the current I2C is busy or not as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">static volatile uint8_t I2C_DMA_Busy = 0;<\/pre><\/div>\n\n\n\n<p>Next, declare a variable to hold which I2C is used as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">static I2C_TypeDef *Active_I2Cx = NULL;<\/pre><\/div>\n\n\n\n<p>This will be useful when dealing with interrupt later in this guide.<\/p>\n\n\n\n<p>Next, in user code begin 1, declare this function:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">void I2C_Mem_Write(I2C_TypeDef *I2Cx,uint8_t slaveAddress, uint16_t regAddress,uint8_t regLen, uint8_t *data, uint16_t length)<\/pre><\/div>\n\n\n\n<p>Within the function, we start by start by determining some conditions:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">if (length == 0) return;\nif ((regLen != 1) &amp;&amp; (regLen != 2)) return;\nif (I2C_DMA_Busy) return; \/* Prevent overlapping transfers *\/\n<\/pre><\/div>\n\n\n\n<ul class=\"wp-block-list\">\n<li> If the length is zero, return.<\/li>\n\n\n\n<li>If register length is not 1 nor 2, return.<\/li>\n\n\n\n<li>If the current I2C is busy, return.<\/li>\n<\/ul>\n\n\n\n<p>Next, set the flag for i2c to be busy as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">I2C_DMA_Busy = 1;<\/pre><\/div>\n\n\n\n<p>Get the current I2C being used as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">Active_I2Cx = I2Cx;<\/pre><\/div>\n\n\n\n<p>Next, prepare DMA1 Stream1 by disabling it first and waiting for the stream to be disabled a follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">\/* ---- 2. Prepare DMA1 Stream 1 ---- *\/\nLL_DMA_DisableStream(I2C_DMA_INSTANCE, I2C_DMA_STREAM);\nwhile (LL_DMA_IsEnabledStream(I2C_DMA_INSTANCE, I2C_DMA_STREAM)) { \/* wait *\/ }<\/pre><\/div>\n\n\n\n<p>Clear any pending flags:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">\/* Clear pending DMA flags *\/\nLL_DMA_ClearFlag_TC1\t(I2C_DMA_INSTANCE) ;\nLL_DMA_ClearFlag_HT1  \t(I2C_DMA_INSTANCE);\nLL_DMA_ClearFlag_TE1  \t(I2C_DMA_INSTANCE);\nLL_DMA_ClearFlag_FE1  \t(I2C_DMA_INSTANCE);\nLL_DMA_ClearFlag_DME1 \t(I2C_DMA_INSTANCE);<\/pre><\/div>\n\n\n\n<p>Note, lets say you are using Stream2, you need to change the number from 1 to 2. For example, LL_DMA_ClearFlag_TC1 to LL_DMA_ClearFlag_TC2.<\/p>\n\n\n\n<p>Update the memory address, peripheral address and length of the data as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">\/* Update Memory Address, peripheral address and Data Length for the new payload *\/\nLL_DMA_SetMemoryAddress(I2C_DMA_INSTANCE, I2C_DMA_STREAM, (uint32_t)data);\nLL_DMA_SetPeriphAddress(I2C_DMA_INSTANCE, I2C_DMA_STREAM,  (uint32_t)&amp;I2Cx-&gt;DR);\nLL_DMA_SetDataLength   (I2C_DMA_INSTANCE, I2C_DMA_STREAM, length);<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>Enable DMA1 Stream1 transfer complete interrupt as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">\/* Enable DMA Transfer Complete Interrupt *\/\nLL_DMA_EnableIT_TC(I2C_DMA_INSTANCE, I2C_DMA_STREAM);<\/pre><\/div>\n\n\n\n<p>Next, send the slave address and the register address in polling mode:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">\/* ---- 3. Manual I2C sequence: START + Slave Address + Register Address ---- *\/\n    LL_I2C_GenerateStartCondition(I2Cx);\n    while (!LL_I2C_IsActiveFlag_SB(I2Cx)) { \/* wait for START *\/ }\n\n    LL_I2C_TransmitData8(I2Cx, (uint8_t)(slaveAddress));\n    while (!LL_I2C_IsActiveFlag_ADDR(I2Cx)) { \/* wait for ACK *\/ }\n    LL_I2C_ClearFlag_ADDR(I2Cx);\n\n    if (regLen == 1)\n          {\n              LL_I2C_TransmitData8(I2Cx, regAddress &amp; 0xFF);\n              while(!LL_I2C_IsActiveFlag_TXE(I2Cx));\n          }\n\n\t  else if (regLen == 2)\n\t  {\n\t\t  \/\/ Send regAddress high byte\n\t\t  LL_I2C_TransmitData8(I2Cx, (uint8_t)(regAddress &gt;&gt; 8));\n\t\t  while(!LL_I2C_IsActiveFlag_TXE(I2Cx));\n\n\t\t  \/\/ Send regAddress low byte\n\t\t  LL_I2C_TransmitData8(I2Cx, (uint8_t)(regAddress &amp; 0xFF));\n\t\t  while(!LL_I2C_IsActiveFlag_TXE(I2Cx));\n\t  }\n\n    \/* Wait until register address is completely emptied from the DR *\/\n    while (!LL_I2C_IsActiveFlag_TXE(I2Cx)) { }<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>Finally, arm the DMA to handle the sending request:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\"> \/* ---- 4. Arm DMA &amp; I2C DMA Request ---- *\/\nLL_I2C_EnableDMAReq_TX(I2Cx);\nLL_DMA_EnableStream(I2C_DMA_INSTANCE, I2C_DMA_STREAM);<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>Next, declare this weak function to be overwritten by the user as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">__weak void I2C1_DMA_Tx_Completed(void)\n{\n\n}<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>Next, for DMA1 Stream1 interrupt handler:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">void DMA1_Stream1_IRQHandler(void)\n{\n\n  \n    \/* Check if Transfer Complete flag is set *\/\n    if (LL_DMA_IsActiveFlag_TC1(I2C_DMA_INSTANCE))\n    {\n        LL_DMA_ClearFlag_TC1(I2C_DMA_INSTANCE);\n\n        \/* Disable DMA TC Interrupt and Stream *\/\n        LL_DMA_DisableIT_TC(I2C_DMA_INSTANCE,I2C_DMA_STREAM);\n        LL_DMA_DisableStream(I2C_DMA_INSTANCE,I2C_DMA_STREAM);\n\n        \/* Wait for I2C Byte Transfer Finished (last byte physically shifted out) *\/\n        while (!LL_I2C_IsActiveFlag_BTF(Active_I2Cx)) { }\n\n        \/* Terminate I2C Transaction *\/\n        LL_I2C_GenerateStopCondition(Active_I2Cx);\n        LL_I2C_DisableDMAReq_TX(Active_I2Cx);\n\n        \/* Mark transfer as complete *\/\n        I2C_DMA_Busy = 0;\n\n        I2C1_DMA_Tx_Completed();\n    }\n}<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>If the source interrupt is Transfer completed.<\/li>\n\n\n\n<li>Clear the pending flag.<\/li>\n\n\n\n<li>Disable the DMA Stream.<\/li>\n\n\n\n<li>Wait for the last byte to be transferred.<\/li>\n\n\n\n<li>Generate the stop condition.<\/li>\n\n\n\n<li>Clear DMA busy flag.<\/li>\n\n\n\n<li>Call I2C1_DMA_Tx_Completed function.<\/li>\n<\/ol>\n\n\n\n<p>That all for the source file.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Next, in main.c file, declare the following volatile variable:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">volatile uint8_t I2C_Tx_Completed=0;<\/pre><\/div>\n\n\n\n<p>This will flag our firmware if the transfer has completed.<\/p>\n\n\n\n<p>In user code begin 0:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">void I2C1_DMA_Tx_Completed(void)\n{\n\tI2C_Tx_Completed=1;\n}<\/pre><\/div>\n\n\n\n<p>This function shall be called when the I2C finishes the transfer.<\/p>\n\n\n\n<p>In while 1 loop, replace the polling mode to DMA write as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">I2C_Mem_Read(I2C1,ds3231_addr,0x00,1,RTCData,3);\n\nif((bcdToDec(RTCData[0]) % 5) == 0)\n{\n  RTCDataWrite[0]=DEC_to_BCD(1);\n  RTCDataWrite[1]=DEC_to_BCD(random()%10);\n  RTCDataWrite[2]=DEC_to_BCD(random()%10);\n  printf(&quot;New value set\\r\\n&quot;);\n\n  \/\/I2C_Mem_Write(I2C1,ds3231_addr,0x00,1,RTCDataWrite,3);\n  I2C_Mem_Write_DMA(I2C1,ds3231_addr,0x00,1,RTCDataWrite,3);\n  while(I2C_Tx_Completed==0);\n  I2C_Tx_Completed=0;\n\n}\n\nprintf(&quot;DS3231 register values:\\r\\n&quot;);\n\nprintf(&quot;Seconds=%d\\r\\n&quot;,bcdToDec(RTCData[0]));\nprintf(&quot;Minutes=%d\\r\\n&quot;,bcdToDec(RTCData[1]));\nprintf(&quot;Hours=%d\\r\\n&quot;,bcdToDec(RTCData[2]));\n\nfor (volatile int32_t i=0;i&lt;1000000;i++)\n{\n  \/\/DO Nothing\n}<\/pre><\/div>\n\n\n\n<p>Thats all for the firmware.<\/p>\n\n\n\n<p>Save, build the project and run it as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"34\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-1024x34.png\" alt=\"\" class=\"wp-image-4349\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-1024x34.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-300x10.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-768x26.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-1536x51.png 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-1150x38.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-750x25.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-400x13.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1-250x8.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/04\/image-1.png 1986w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>You may download the project file from&nbsp;<a href=\"https:\/\/github.com\/hussamaldean\/Embedded-Expert-Post-Projects\/tree\/main\/Projects\/LL_I2C\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. Results:<\/h2>\n\n\n\n<p>Open your serial terminal and you should something like this:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"1024\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-22_09-39-26-1024x1024.jpg\" alt=\"\" class=\"wp-image-4610\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-22_09-39-26-1024x1024.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-22_09-39-26-300x300.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-22_09-39-26-150x150.jpg 150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-22_09-39-26-768x768.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-22_09-39-26-1536x1536.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-22_09-39-26-1150x1150.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-22_09-39-26-750x750.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-22_09-39-26-400x400.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-22_09-39-26-250x250.jpg 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/2026-07-22_09-39-26.jpg 1890w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Here is the bus state during the writing:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"630\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/RigolDS19.png\" alt=\"\" class=\"wp-image-4611\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/RigolDS19.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/RigolDS19-300x185.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/RigolDS19-768x473.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/RigolDS19-750x461.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/RigolDS19-400x246.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/07\/RigolDS19-250x154.png 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Stay tuned.<\/p>\n\n\n\n<p>Happy coding \ud83d\ude09 <\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the fourth part of our STM32 Low-Layer (LL) I2C series, we offload transaction overhead from the CPU by implementing Direct Memory Access (DMA) for memory write operations. We will explore how to configure the DMA controller to automate byte transfers directly from RAM to the I2C transmit register, freeing processor cycles for concurrent application [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,11,12],"tags":[],"class_list":["post-4627","post","type-post","status-publish","format-standard","hentry","category-embedded-systems","category-peripheral-drivers","category-stm32"],"_links":{"self":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4627"}],"collection":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4627"}],"version-history":[{"count":1,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4627\/revisions"}],"predecessor-version":[{"id":4632,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4627\/revisions\/4632"}],"wp:attachment":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4627"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4627"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4627"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}