{"id":4649,"date":"2026-08-08T08:56:13","date_gmt":"2026-08-08T08:56:13","guid":{"rendered":"https:\/\/blog.embeddedexpert.io\/?p=4649"},"modified":"2026-08-08T08:56:15","modified_gmt":"2026-08-08T08:56:15","slug":"getting-started-with-stm32-low-layer-ll-inter-integrated-circuit-i2c-read-from-registers-using-dma","status":"publish","type":"post","link":"https:\/\/blog.embeddedexpert.io\/?p=4649","title":{"rendered":"Getting Started with STM32 Low Layer (LL): Inter-Integrated Circuit (I2C) Read From 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\/08\/Gemini_Generated_Image_t8xvpnt8xvpnt8xv-1024x559.png\" alt=\"\" class=\"wp-image-4650\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_t8xvpnt8xvpnt8xv-1024x559.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_t8xvpnt8xvpnt8xv-300x164.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_t8xvpnt8xvpnt8xv-768x419.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_t8xvpnt8xvpnt8xv-1150x627.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_t8xvpnt8xvpnt8xv-750x409.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_t8xvpnt8xvpnt8xv-400x218.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_t8xvpnt8xvpnt8xv-250x136.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_t8xvpnt8xvpnt8xv.png 1408w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>In this fifth part of our STM32 Low-Layer (LL) I2C series, we complete our non-blocking driver suite by leveraging Direct Memory Access (DMA) for memory read operations. We will demonstrate how to configure the DMA controller and manage the I2C Repeated START condition to automatically transfer data directly from a slave device into system RAM without CPU intervention.<\/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 firmware architecture, retrieving continuous streams of telemetry\u2014such as reading multi-axis accelerometers, sampling environmental sensors, or fetching high-speed ADC data\u2014presents a major hardware management challenge. While writing to a peripheral via I2C is a straight line,&nbsp;<strong>reading from a register requires a complex two-phase protocol<\/strong>: first sending a target register address as a Master-Transmitter, and then issuing a Repeated START condition to switch roles into a Master-Receiver.<\/p>\n\n\n\n<p>When performed using traditional blocking polling loops, this two-stage read sequence holds the CPU hostage for hundreds of microseconds per transaction. In this fifth installment of our STM32 Low-Layer (LL) I2C series, we complete our non-blocking driver suite by offloading the data reception phase to&nbsp;<strong>Direct Memory Access (DMA)<\/strong>. We will examine how the STM32 DMA controller automates reading bytes directly from the I2C Receive Data Register (<code>DR<\/code>) into system RAM with zero processor involvement.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Mechanics: Why I2C DMA Reads Are Unique<\/h3>\n\n\n\n<p>An I2C memory read is inherently more complex than a write operation because it requires a dynamic protocol role switch mid-transaction. The physical bus sequence proceeds as follows:<\/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\/08\/Gemini_Generated_Image_icn8nyicn8nyicn8-1024x559.png\" alt=\"\" class=\"wp-image-4651\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_icn8nyicn8nyicn8-1024x559.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_icn8nyicn8nyicn8-300x164.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_icn8nyicn8nyicn8-768x419.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_icn8nyicn8nyicn8-1150x627.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_icn8nyicn8nyicn8-750x409.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_icn8nyicn8nyicn8-400x218.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_icn8nyicn8nyicn8-250x136.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_icn8nyicn8nyicn8.png 1408w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>The Address Phase (CPU Handled):<\/strong>\u00a0The MCU sends the target slave address with a\u00a0<code>WRITE<\/code>\u00a0bit, followed by the register memory offset it wants to read.<\/li>\n\n\n\n<li><strong>The Role Switch (Repeated START):<\/strong>\u00a0Without releasing the bus, the MCU generates a\u00a0<strong>Repeated START<\/strong>\u00a0condition and re-issues the slave address appended with a\u00a0<code>READ<\/code>\u00a0bit ($LSB = 1$).<\/li>\n\n\n\n<li><strong>The Data Transfer Phase (DMA Handled):<\/strong>\u00a0Once the slave acknowledges the read request, data streaming begins. This is where DMA takes over completely.<\/li>\n<\/ol>\n\n\n\n<p>During the reception phase, as each individual byte arrives over the SDA line and shifts into&nbsp;<code>I2C_DR<\/code>, the hardware automatically raises a&nbsp;<strong>Receive Data Register Not Empty (<code>RXNE<\/code>)<\/strong>&nbsp;signal. Instead of forcing the CPU to sit in a polling loop waiting for&nbsp;<code>RXNE<\/code>, the signal directly triggers a&nbsp;<strong>DMA Request<\/strong>. The DMA controller intercepts this request, reads&nbsp;<code>I2C_DR<\/code>, and writes the value to designated SRAM memory, automatically incrementing the RAM destination pointer after every byte.<\/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\/08\/Gemini_Generated_Image_ff8rgmff8rgmff8r-1024x559.png\" alt=\"\" class=\"wp-image-4652\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_ff8rgmff8rgmff8r-1024x559.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_ff8rgmff8rgmff8r-300x164.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_ff8rgmff8rgmff8r-768x419.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_ff8rgmff8rgmff8r-1150x627.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_ff8rgmff8rgmff8r-750x409.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_ff8rgmff8rgmff8r-400x218.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_ff8rgmff8rgmff8r-250x136.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/Gemini_Generated_Image_ff8rgmff8rgmff8r.png 1408w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Advantages of Using DMA for I2C Read Operations<\/h3>\n\n\n\n<p>While offloading writes prevents CPU lockup during configuration routines, implementing DMA for&nbsp;<strong>read sequences<\/strong>yields even greater system performance benefits in production firmware:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. High-Frequency Telemetry Buffering<\/h3>\n\n\n\n<p>Sensors like IMUs (Inertial Measurement Units), gyroscopes, and pressure gauges often require polling rates anywhere from 100 Hz to 1 kHz. If a system reads 12 bytes of sensor data every millisecond using polling, the CPU spends a substantial percentage of its total execution time waiting for hardware flags. Offloading the read phase to DMA leaves the CPU entirely unburdened, allowing it to spend those free cycles executing complex DSP filters, sensor-fusion math, or control loops.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. Eliminating Byte Overrun Errors<\/h3>\n\n\n\n<p>In fast-paced embedded systems with heavy interrupt activity, a delay in reading&nbsp;<code>I2C_DR<\/code>&nbsp;can lead to an&nbsp;<strong>Overrun Error (<code>OVR<\/code>)<\/strong>, where new incoming data overwrites unread bytes. Because the DMA controller operates on the bus matrix at hardware speed, it clears&nbsp;<code>I2C_DR<\/code>&nbsp;within clock cycles of the&nbsp;<code>RXNE<\/code>&nbsp;flag rising. This guarantees that no bytes are dropped, even if high-priority hardware interrupts are firing concurrently on the CPU core.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3. Automatic End-of-Transfer Handling via Hardware<\/h3>\n\n\n\n<p>One of the most delicate parts of an I2C read sequence is managing the&nbsp;<strong>NACK<\/strong>&nbsp;and&nbsp;<strong>STOP<\/strong>&nbsp;signals on the final byte to inform the slave device that transmission is ending. When using STM32 LL drivers alongside DMA, hardware features (such as&nbsp;<code>LL_I2C_EnableDMADeack<\/code>&nbsp;or the I2C&nbsp;<code>LAST<\/code>&nbsp;bit) automatically configure the peripheral to automatically generate a NACK on the last byte transferred by the DMA controller.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Polling vs. Interrupts vs. DMA for Data Reception<\/h3>\n\n\n\n<p>To frame where DMA sits in a professional system design, consider how different execution strategies handle data reception:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>Strategy<\/strong><\/td><td><strong>CPU Overhead<\/strong><\/td><td><strong>Implementation Complexity<\/strong><\/td><td><strong>Latency \/ Determinism<\/strong><\/td><td><strong>Ideal Use Case<\/strong><\/td><\/tr><\/thead><tbody><tr><td><strong>Polling Mode<\/strong><\/td><td><strong>High (100%)<\/strong><\/td><td>Very Low<\/td><td>Poor (Blocks all execution)<\/td><td>Driver bring-up, simple single-byte reads during boot initialization.<\/td><\/tr><tr><td><strong>Interrupt Mode (<code>RXNE<\/code>IRQ)<\/strong><\/td><td><strong>Medium<\/strong><\/td><td>Moderate<\/td><td>Moderate (Triggers CPU IRQ per byte)<\/td><td>Low-speed byte reception where DMA channels are needed elsewhere.<\/td><\/tr><tr><td><strong>DMA Mode<\/strong><\/td><td><strong>Zero<\/strong><\/td><td>Higher<\/td><td>Excellent (Fully deterministic)<\/td><td>High-rate sensor streaming, multi-byte burst reads, display controllers.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. STM32CubeMX Setup:<\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>We shall continue from the previous guide from <a href=\"https:\/\/blog.embeddedexpert.io\/?p=4627\" data-type=\"link\" data-id=\"https:\/\/blog.embeddedexpert.io\/?p=4627\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>.<\/p>\n\n\n\n<p>From Connectivity, select I2C1, DMA settings and add I2C1_RX 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\/08\/2026-08-08_09-27-01-1024x662.jpg\" alt=\"\" class=\"wp-image-4653\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-01-1024x662.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-01-300x194.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-01-768x497.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-01-1536x993.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-01-2048x1324.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-01-1150x744.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-01-750x485.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-01-400x259.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-01-250x162.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Next, from System Core, NVIC, disable the IRQ handler for DMA1_Stream0 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\/08\/2026-08-08_09-27-39-1024x662.jpg\" alt=\"\" class=\"wp-image-4654\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-39-1024x662.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-39-300x194.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-39-768x497.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-39-1536x993.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-39-2048x1324.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-39-1150x744.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-39-750x485.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-39-400x259.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-08_09-27-39-250x162.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Thats all for the STM32CubeMX configuration.<\/p>\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><\/p>\n\n\n\n<p>Next, 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_Read_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><\/p>\n\n\n\n<p>Next, open i2c.c source file. <\/p>\n\n\n\n<p>In user  code begin 0,<\/p>\n\n\n\n<p>Modify the previous declared DMA_STREAM to the following:<\/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_STREAM_TX    \tLL_DMA_STREAM_1<\/pre><\/div>\n\n\n\n<p>Next, declare the stream for the RX 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_SREAM_RX\t\tLL_DMA_STREAM_0<\/pre><\/div>\n\n\n\n<p>Next, in user code begin 1, 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_Read_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>Within the function, we shall do some checks:<\/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 *\/<\/pre><\/div>\n\n\n\n<p>These will prevent the function to send incorrect data or overlapping the current transfer.<\/p>\n\n\n\n<p>Next, get the I2C instant and set it to 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;\n\n\/* Save I2C context for the Interrupt Handler *\/\nActive_I2Cx = I2Cx;<\/pre><\/div>\n\n\n\n<p>Next, prepare the DMA1_Stream0 and clear the pending flags if any 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;}\">\/* ---- 1. Prepare DMA1 Stream 0 ---- *\/\nLL_DMA_DisableStream(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX);\nwhile (LL_DMA_IsEnabledStream(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX)) { \/* wait *\/ }\n\n\/* Clear pending DMA flags *\/\nLL_DMA_ClearFlag_TC0(I2C_DMA_INSTANCE);\nLL_DMA_ClearFlag_HT0(I2C_DMA_INSTANCE);\nLL_DMA_ClearFlag_TE0(I2C_DMA_INSTANCE);\nLL_DMA_ClearFlag_FE0(I2C_DMA_INSTANCE);\nLL_DMA_ClearFlag_DME0(I2C_DMA_INSTANCE);<\/pre><\/div>\n\n\n\n<p>Next, set the memory address, peripheral address and number of transfers 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_SREAM_RX, (uint32_t)data);\nLL_DMA_SetPeriphAddress(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX, (uint32_t)&amp;I2Cx-&gt;DR);\nLL_DMA_SetDataLength(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX, length);<\/pre><\/div>\n\n\n\n<p>Next, enable the DMA:<\/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_SREAM_RX);<\/pre><\/div>\n\n\n\n<p>Next, send the slave address and register to be read from as follows:<\/p>\n\n\n\n<p>This will be 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;}\">    \/* ---- 2. Write phase: Send register address (Polling) ---- *\/\n\n\n    LL_I2C_GenerateStartCondition(I2Cx);\n    while(!LL_I2C_IsActiveFlag_SB(I2Cx));\n\n    LL_I2C_TransmitData8(I2Cx, (slaveAddress &amp; 0xFE)); \/\/ Ensure Write bit (0)\n    while(!LL_I2C_IsActiveFlag_ADDR(I2Cx));\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    else if (regLen == 2)\n    {\n        LL_I2C_TransmitData8(I2Cx, (uint8_t)(regAddress &gt;&gt; 8));\n        while(!LL_I2C_IsActiveFlag_TXE(I2Cx));\n\n        LL_I2C_TransmitData8(I2Cx, (uint8_t)(regAddress &amp; 0xFF));\n        while(!LL_I2C_IsActiveFlag_TXE(I2Cx));\n    }<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>Next, regenerate the start condition to start the reception process 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;}\">    \/* ---- 3. Read phase: Get data bytes via DMA ---- *\/\n    LL_I2C_GenerateStartCondition(I2Cx);\n    while(!LL_I2C_IsActiveFlag_SB(I2Cx));\n\n    LL_I2C_TransmitData8(I2Cx, (slaveAddress | 0x01)); \/\/ Ensure Read bit (1)\n\n    while(!LL_I2C_IsActiveFlag_ADDR(I2Cx));<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>If the length is mover than 1, enable NACK, if it is 1, disable NACK as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">    if(length == 1)\n    {\n        LL_I2C_AcknowledgeNextData(I2Cx, LL_I2C_NACK);\n    }\n    else\n    {\n        LL_I2C_AcknowledgeNextData(I2Cx, LL_I2C_ACK);\n    }<\/pre><\/div>\n\n\n\n<p>Finally, arm the DMA to start the reception 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;}\">LL_I2C_ClearFlag_ADDR(I2Cx);\n\n\/* ---- 4. Arm DMA &amp; I2C DMA Request ---- *\/\nLL_I2C_EnableDMAReq_RX(I2Cx);\n\n\/* Start the DMA Stream *\/\nLL_DMA_EnableStream(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX);<\/pre><\/div>\n\n\n\n<p>Once the reception is ended, DMA1_Stream0 interrupt will be fired.<\/p>\n\n\n\n<p>For the 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;}\">__weak void I2C1_DMA_Rx_Completed(void)\n{\n\n}\n\n\nvoid DMA1_Stream0_IRQHandler(void)<\/pre><\/div>\n\n\n\n<p>Within the interrupt handler:<\/p>\n\n\n\n<p>First check if it is transfer completed as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">if (LL_DMA_IsActiveFlag_TC0(I2C_DMA_INSTANCE))<\/pre><\/div>\n\n\n\n<p>If it is. clear the flag:<\/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;}\">LL_DMA_ClearFlag_TC0(I2C_DMA_INSTANCE);<\/pre><\/div>\n\n\n\n<p>Then disable TC interrupt and disable the 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;}\">\/* Disable DMA TC Interrupt and Stream *\/\nLL_DMA_DisableIT_TC(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX);\nLL_DMA_DisableStream(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX);<\/pre><\/div>\n\n\n\n<p>Generate the stop condition and disable the DMA request 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;}\">\/* Terminate I2C Transaction *\/\nLL_I2C_GenerateStopCondition(Active_I2Cx);\n\n\/* CRITICAL FIX: Disable RX DMA request *\/\nLL_I2C_DisableDMAReq_RX(Active_I2Cx);<\/pre><\/div>\n\n\n\n<p>Mark the I2C as free 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 = 0;<\/pre><\/div>\n\n\n\n<p>Finally, call I2C1_DMA_Rx completed function which defined as weak function so the user can override it in his firmware 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;}\">I2C1_DMA_Rx_Completed();<\/pre><\/div>\n\n\n\n<p>Hence, the entire block of code 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;}\">void I2C_Mem_Read_DMA(I2C_TypeDef *I2Cx, uint8_t slaveAddress, uint16_t regAddress, uint8_t regLen, uint8_t *data, uint16_t length)\n{\n    if (length == 0) return;\n    if ((regLen != 1) &amp;&amp; (regLen != 2)) return;\n    if (I2C_DMA_Busy) return; \/* Prevent overlapping transfers *\/\n\n    I2C_DMA_Busy = 1;\n\n    \/* Save I2C context for the Interrupt Handler *\/\n    Active_I2Cx = I2Cx;\n\n    \/* ---- 1. Prepare DMA1 Stream 0 ---- *\/\n    LL_DMA_DisableStream(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX);\n    while (LL_DMA_IsEnabledStream(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX)) { \/* wait *\/ }\n\n    \/* Clear pending DMA flags *\/\n    LL_DMA_ClearFlag_TC0(I2C_DMA_INSTANCE);\n    LL_DMA_ClearFlag_HT0(I2C_DMA_INSTANCE);\n    LL_DMA_ClearFlag_TE0(I2C_DMA_INSTANCE);\n    LL_DMA_ClearFlag_FE0(I2C_DMA_INSTANCE);\n    LL_DMA_ClearFlag_DME0(I2C_DMA_INSTANCE);\n\n\n\n\n    \/* Update Memory Address, peripheral address and Data Length for the new payload *\/\n    LL_DMA_SetMemoryAddress(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX, (uint32_t)data);\n    LL_DMA_SetPeriphAddress(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX, (uint32_t)&amp;I2Cx-&gt;DR);\n    LL_DMA_SetDataLength(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX, length);\n\n    \/* Enable DMA Transfer Complete Interrupt *\/\n    LL_DMA_EnableIT_TC(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX);\n\n    \/* ---- 2. Write phase: Send register address (Polling) ---- *\/\n\n\n    LL_I2C_GenerateStartCondition(I2Cx);\n    while(!LL_I2C_IsActiveFlag_SB(I2Cx));\n\n    LL_I2C_TransmitData8(I2Cx, (slaveAddress &amp; 0xFE)); \/\/ Ensure Write bit (0)\n    while(!LL_I2C_IsActiveFlag_ADDR(I2Cx));\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    else if (regLen == 2)\n    {\n        LL_I2C_TransmitData8(I2Cx, (uint8_t)(regAddress &gt;&gt; 8));\n        while(!LL_I2C_IsActiveFlag_TXE(I2Cx));\n\n        LL_I2C_TransmitData8(I2Cx, (uint8_t)(regAddress &amp; 0xFF));\n        while(!LL_I2C_IsActiveFlag_TXE(I2Cx));\n    }\n\n    \/* ---- 3. Read phase: Get data bytes via DMA ---- *\/\n    LL_I2C_GenerateStartCondition(I2Cx);\n    while(!LL_I2C_IsActiveFlag_SB(I2Cx));\n\n    LL_I2C_TransmitData8(I2Cx, (slaveAddress | 0x01)); \/\/ Ensure Read bit (1)\n\n    while(!LL_I2C_IsActiveFlag_ADDR(I2Cx));\n\n    \/* CRITICAL I2C DMA READ RULE:\n       To properly receive N bytes via DMA, NACK must be set BEFORE clearing ADDR flag.\n       If length == 1, set NACK. If length &gt; 1, set ACK, but DMA\/Peripheral will handle NACK\n       for the last byte if EVERR\/TCIE is managed.\n       Simplification for DMA: Set NACK before clearing ADDR *\/\n    if(length == 1)\n    {\n        LL_I2C_AcknowledgeNextData(I2Cx, LL_I2C_NACK);\n    }\n    else\n    {\n        LL_I2C_AcknowledgeNextData(I2Cx, LL_I2C_ACK);\n    }\n\n    LL_I2C_ClearFlag_ADDR(I2Cx);\n\n    \/* ---- 4. Arm DMA &amp; I2C DMA Request ---- *\/\n    LL_I2C_EnableDMAReq_RX(I2Cx);\n\n    \/* Start the DMA Stream *\/\n    LL_DMA_EnableStream(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX);\n}\n\n\n\n__weak void I2C1_DMA_Rx_Completed(void)\n{\n\n}\n\n\n\nvoid DMA1_Stream0_IRQHandler(void)\n{\n    if (LL_DMA_IsActiveFlag_TC0(I2C_DMA_INSTANCE))\n    {\n        LL_DMA_ClearFlag_TC0(I2C_DMA_INSTANCE);\n\n        \/* Disable DMA TC Interrupt and Stream *\/\n        LL_DMA_DisableIT_TC(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX);\n        LL_DMA_DisableStream(I2C_DMA_INSTANCE, I2C_DMA_SREAM_RX);\n\n        \/* Terminate I2C Transaction *\/\n        LL_I2C_GenerateStopCondition(Active_I2Cx);\n\n        \/* CRITICAL FIX: Disable RX DMA request *\/\n        LL_I2C_DisableDMAReq_RX(Active_I2Cx);\n\n        \/* Mark transfer as complete *\/\n        I2C_DMA_Busy = 0;\n\n        I2C1_DMA_Rx_Completed();\n    }\n}<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>Next, open main.c file.<\/p>\n\n\n\n<p>In user code begin 0, declare the following volatile:<\/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_Rx_Completed=0;<\/pre><\/div>\n\n\n\n<p>This will signal the firmware that the Rx process is completed and ready for processing.<\/p>\n\n\n\n<p>Next, declare I2C1_DMA_Rx_Completed 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;}\">void I2C1_DMA_Rx_Completed(void)\n{\n\tI2C_Rx_Completed=1;\n}<\/pre><\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>In user code begin 3 in while loop, replace the reading using polling to reading using DMA and wait for the transfer to complete 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_DMA(I2C1,ds3231_addr,0x00,1,RTCData,3);\n\nwhile(I2C_Rx_Completed==0);\nI2C_Rx_Completed=0;<\/pre><\/div>\n\n\n\n<p>The rest of the code will remain the same.<\/p>\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<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><br>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>Stay tuned.<\/p>\n\n\n\n<p>Happy coding\u00a0\ud83d\ude09<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this fifth part of our STM32 Low-Layer (LL) I2C series, we complete our non-blocking driver suite by leveraging Direct Memory Access (DMA) for memory read operations. We will demonstrate how to configure the DMA controller and manage the I2C Repeated START condition to automatically transfer data directly from a slave device into system RAM [&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-4649","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\/4649"}],"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=4649"}],"version-history":[{"count":1,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4649\/revisions"}],"predecessor-version":[{"id":4655,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4649\/revisions\/4655"}],"wp:attachment":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4649"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4649"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4649"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}