Project 4.07 Section 4 ⚡ Embedded Relevance: Critical 2D Arrays Row-Major DMA Transfers Stride Display Buffers

4.07 Row-Major Memory Contiguity, Nested Loops & Direct Memory Access (DMA)

Executive Summary: Deep dive into two-dimensional arrays in C++. We examine row-major contiguous memory layouts, why row-first iteration maximizes CPU cache hits, and how hardware Direct Memory Access (DMA) controllers stream 2D display and sensor buffers without CPU intervention.

💻 1. Annotated Source Code

#include <iostream>
using namespace std;

int main() {

	int myNums[2][3] = {
		{1, 2, 3},
		{4, 5, 6}
	};

	cout << myNums[0][2] << endl;

	myNums[1][0] = 14;

	cout << myNums[1][0] << endl;

	//for (int row = 0; row < 2; row++) {
	//	for (int col = 0; col < 3; col++) {
	//		cout << myNums[row][col] << " ";
	//	}//end inner for
	//	cout << endl;
	//}//end outer for

	for (int row = 1; row >= 0; row--) {
		for (int col = 2; col >= 0; col--) {
			cout << myNums[row][col] << " ";
		}
		cout << endl;
	}

	return 0;
}

📐 2. Architecture & UML Class Model

📐 2D Row-Major Matrix & DMA Framebuffer Model
+ Public - Private # Protected
<<struct>> Matrix2D Row-Major Memory Grid
+grid[2][3] : int32_t (6 elements, 24 contiguous bytes)
+ROWS : constexpr size_t = 2
+COLS : constexpr size_t = 3
+at(r: size_t, c: size_t) : int32_t&
+printRowMajor() : void const
+sumAllElements() : int32_t const

📚 3. Core C++ Concepts Deep-Dive

1. Row-Major Contiguous Memory Layout

In C and C++, multidimensional arrays are laid out in row-major order: the second index changes fastest. In memory, grid[2][3] is stored as a single contiguous 1D block of 6 elements.

2. Address Calculation Formula

The memory address of element grid[row][col] is calculated as:

$$\text{Address} = \text{Base} + ((\text{row} \times \text{COLS}) + \text{col}) \times \text{sizeof}(T)$$

⚡ 4. Embedded Systems & Hardware Reality

1. Cache Line Thrashing from Column-Major Access

Iterating column-first (grid[col][row]) strides through memory by COLS * sizeof(T) bytes on every step. This causes a cache miss on every single access. Always iterate row-first!

2. Direct Memory Access (DMA) Framebuffers

Because 2D arrays are contiguous in SRAM, a microcontroller DMA controller (e.g. STM32 DMA2D / Chrom-ART) can stream full display frames directly from SRAM to an SPI/I2C TFT display with 0% CPU utilization.

💡 5. Production-Ready Embedded Refactoring

Type-safe flat 2D display framebuffer wrapper with DMA compatibility:

💡 Production-Ready Refactor
#include <cstdint>
#include <array>

template <size_t Rows, size_t Cols>
class Framebuffer2D {
private:
    // Contiguous in memory; 100% DMA transfer compatible
    std::array<uint16_t, Rows * Cols> pixels_{};

public:
    constexpr void set_pixel(size_t r, size_t c, uint16_t rgb565) noexcept {
        if (r < Rows && c < Cols) {
            pixels_[r * Cols + c] = rgb565;
        }
    }

    const uint16_t* dma_buffer() const noexcept { return pixels_.data(); }
    constexpr size_t byte_size() const noexcept { return pixels_.size() * sizeof(uint16_t); }
};

📝 Knowledge Verification Quiz

Test your understanding of the C++ concepts and embedded microcontroller trade-offs covered in this guide. Click any option for instant feedback.

Q1. How are 2D arrays organized in C and C++ memory?
A Row-Major order: elements of the first row are stored contiguously, followed by elements of the second row
B Column-Major order: columns are stored sequentially
C Fragmented linked blocks across the heap
D Randomly distributed by the linker
Detailed Explanation: C and C++ use row-major ordering where consecutive elements of a row occupy adjacent memory addresses.
Q2. Why does column-major iteration over a large 2D array degrade CPU performance?
A It accesses memory with large address strides, causing frequent CPU data cache misses and memory stalls
B It triggers compiler syntax errors
C It forces the array to reallocate on the heap
D It changes the values of adjacent elements
Detailed Explanation: Striding across rows skips memory lines, causing cache misses on every access rather than reusing loaded cache lines.
Q3. What is the primary role of a Direct Memory Access (DMA) controller when managing 2D graphics buffers?
A It transfers pixel data from SRAM to peripheral hardware (e.g. SPI display) in the background without CPU intervention
B It compiles graphics shaders at runtime
C It formats the SD card file system
D It increases the microcontroller crystal clock speed
Detailed Explanation: DMA controllers transfer memory blocks directly between SRAM and peripheral hardware asynchronously, freeing the CPU to execute application logic.
Q4. For an array int grid[4][8] on a 32-bit MCU, what is the byte offset of grid[2][3] from the array base address?
A 76 bytes ( (2 * 8 + 3) * 4 bytes )
B 19 bytes
C 48 bytes
D 96 bytes
Detailed Explanation: Linear index $= (2 \times 8) + 3 = 19$. Byte offset $= 19 \times 4\text{ bytes} = 76\text{ bytes}$.