🌱 DMA (Direct Memory Access) trong Lập Trình Nhúng: Cơ Chế và Ứng Dụng trong STM32

🌱 DMA (Direct Memory Access) trong Lập Trình Nhúng: Cơ Chế và Ứng Dụng trong STM32

    Như giới thiệu ở bài viết Các kỹ thuật thiết kế luồng xử lý trong chương trình nhúng, mình đã giới thiệu về Polling, Interrupt, và sử dụng DMA. Bài viết này mình sẽ giới thiệu kỹ hơn về kỹ thuật sử dụng DMA trong các ứng dụng nhúng, một kỹ thuật cũng rất quan trọng và được sử dụng rộng rãi trong các ứng dụng thực tế.

    ➤ Xem thêm: Các kỹ thuật thiết kế luồng xử lý trong chương trình nhúng.

👉 Cơ chế Master-Slave trong vi điều khiển

    Trước khi tìm hiểu DMA, chúng ta sẽ bắt đầu lại với cơ chế hoạt động thông thường của Core, và hiểu nó theo cơ chế Master - Slave.

DMA Master-Slave Mechanism
Hình 1: Cơ chế Master-Slave trong vi điều khiển

    Hãy nhìn hình vẽ trên, CPU sẽ điều khiển việc transfer data giữa Peripheral (UART, I2C, SPI, ...) và bộ nhớ (RAM) qua các đường bus. Cơ chế này được hiểu như cơ chế Master - Slave, với CPU đóng vai trò là Master, Peripheral và Memory đóng vai trò như các Slave. Nên việc giao tiếp giữa 2 Slave sẽ do Master điều khiển.

    Tuy nhiên với việc CPU phải làm thêm 1 công việc quan trọng khác - Fetch lệnh từ bộ nhớ (FLASH) để thực thi các lệnh của chương trình. Vì vậy, khi cần truyền dữ liệu liên tục giữa Peripheral và RAM, CPU sẽ bị chiếm dụng, và không có thời gian làm các công việc khác, hoặc có thể gây miss dữ liệu khi transfer.

    ⇨ Chính vì vậy, hệ thống hỗ trợ một Master khác để chuyên làm công việc này, đó là DMA.

👉 DMA là gì?

    DMA - Direct Memory Access, hay truy cập trực tiếp bộ nhớ, được biết đến như một thành phần trong vi điều khiển, có vai trò như một Master, dùng để điều khiển việc truy cập trực tiếp vào bộ nhớ mà không thông qua CPU. DMA có thể làm giảm tải "áp lực làm việc" cho CPU.

    💬 Một số kiến trúc Vi điều khiển không hỗ trợ DMA (8051, AVR, PIC, ...), có thể sử dụng bộ chip ngoài DMAC - DMA Controller để thay thế. Ở đây chúng ta chỉ xét đến các dòng có hỗ trợ DMA nội, cụ thể là trong Vi điều khiển STM32.

    Hoạt động cơ bản của DMA có thể được mô tả trong hình sau:

DMA Basic Operation
Hình 2: Hoạt động cơ bản của DMA

    DMA có thể điều khiển data truyền từ SRAM đến Peripheral - UART và ngược lại, mà không thông qua data bus của CPU. Bản thân CPU ARM Cortex-M hoạt động theo cơ chế Load - Store (Tức là đọc dữ liệu từ bộ nhớ về thanh ghi core - Load, có thể thực hiện tính toán, sau đó lưu trữ dữ liệu trở lại bộ nhớ - Store), vì vậy sẽ mất nhiều thời gian thực thi chương trình khi copy dữ liệu. Trong khi đó DMA thực hiện việc truy cập đọc/ghi bộ nhớ nguồn và đích một cách trực tiếp, nên tốc độ sẽ nhanh hơn so với cơ chế Load - Store.

Nhược điểm của DMA

    Có thể thấy DMA có ưu điểm to lớn, tuy nhiên, đôi khi nó mang lại một số nhược điểm. Nếu không được sử dụng đúng cách, DMA còn có thể gây tốn năng lượng hơn so với sử dụng trực tiếp CPU.

    Trong một số kiến trúc vi điều khiển sử dụng Bộ nhớ Cache, khi DMA truy cập write vào data memory, nó sẽ được ghi vào bộ nhớ Cache. Sử dụng DMA sẽ làm mất hiệu lực của bộ nhớ Cache và tốn thêm năng lượng của Vi điều khiển.

    ➤ Xem thêm: Bộ nhớ đệm - Cache.

👉 DMA trong STM32

    Mình sử dụng phần cứng là Vi điều khiển STM32F401, các bạn sử dụng chip khác có thể xem trong tài liệu Reference Manual.

    STM32F401 hỗ trợ 2 DMA Controller có 16 streams (luồng) với 8 streams cho mỗi Controller, mỗi stream dành riêng để quản lý các yêu cầu truy cập bộ nhớ từ một hoặc nhiều thiết bị ngoại vi. Mỗi Stream có thể có tối đa 8 Channel và có một cơ chế phân xử để xử lý mức độ ưu tiên giữa các Channel.

Các đặc điểm chính của DMA - STM32

  • Kiến trúc Dual AHB master bus, một dành riêng cho truy cập bộ nhớ, một dành cho truy cập ngoại vi.
  • 8 streams cho mỗi DMA Controller, lên đến 8 channel cho mỗi stream. Mỗi Stream có một bộ đệm FIFO để tối ưu băng thông của hệ thống.
  • Hai mode: FIFO Mode và Direct Mode.
  • Mỗi Stream có thể được cấu hình bởi Hardware:
    • Channel thông thường hỗ trợ transfer giữa 2 slave (Memory hoặc Peripheral).
    • Channel Double Buffer hỗ trợ bộ đệm kép ở bộ nhớ.
  • Các Channel có thể lập trình mức độ ưu tiên (4 mức - Very high, high, medium, low) hoặc hỗ trợ bởi Hardware khi mức ưu tiên ngang nhau (Channel 0 > Channel 1).
  • Số lượng data items có thể được quản lý bởi DMA Controller hoặc Peripheral:
    • DMA Flow Controller: Số lượng items có thể lập trình được từ 1 đến 65535.
    • Peripheral Flow Controller: Số lượng items không xác định, được điều khiển bởi Source/Destination Peripheral và kết thúc việc truyền bởi Hardware.
  • Source và Destination có độ rộng dữ liệu độc lập (byte / half-word / word). Khi độ rộng của Data của Source và Destination không bằng nhau, DMA sẽ tự động đóng gói / giải nén các dữ liệu truyền đi để tối ưu hóa băng thông (Tính năng này chỉ dùng với FIFO Mode).
  • Mỗi Stream đều hỗ trợ bộ quản lý Circular Buffer.
  • Hỗ trợ 5 Event Flags (DMA Half Transfer, DMA Transfer Complete, DMA Transfer Error, DMA FIFO Error, Direct Mode Error).

💬 Block Diagram của DMA trong STM32F4

DMA Block Diagram
Hình 3: Block Diagram của DMA trong STM32F4

    Bộ điều khiển DMA thực hiện truyền/nhận bộ nhớ một cách trực tiếp: Với vai trò AHB Master, DMA có thể nắm quyền điều khiển bus matrix AHB để bắt đầu các quá trình truyền nhận - Transactions. Nó có thể thực hiện các giao dịch sau:

    Một số thông tin về độ trễ thời gian khi sử dụng DMA:

DMA Transaction Timing
Hình 4: Thời gian DMA truy cập SRAM

    Trên đây là các đặc điểm chính của DMA và DMA trong Vi điều khiển STM32. Các bài sau sẽ chia sẻ về cách lập trình các ứng dụng sử dụng DMA.

👉 Ví dụ lập trình DMA cơ bản

    Dưới đây là ví dụ cơ bản sử dụng DMA trên STM32F401RE để truyền dữ liệu từ Memory sang Peripheral (UART2):

#include "stm32f4xx.h"

uint8_t tx_buffer[] = "Hello DMA!\r\n";
volatile uint8_t transfer_complete = 0;

void DMA1_Stream6_IRQHandler(void) {
    if (DMA1->HISR & DMA_HISR_TCIF6) { // Transfer Complete
        DMA1->HIFCR |= DMA_HIFCR_CTCIF6; // Clear flag
        transfer_complete = 1;
    }
}

void DMA_Config(void) {
    // Bật clock cho DMA1 và UART2
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
    RCC->APB1ENR |= RCC_APB1ENR_USART2EN;

    // Cấu hình UART2 (PA2 - TX)
    GPIOA->MODER |= GPIO_MODER_MODER2_1; // Alternate function
    GPIOA->AFR[0] |= 0x0700; // AF7 (USART2_TX)
    USART2->BRR = 0x2D9; // Baud rate 115200 @ 16MHz APB1
    USART2->CR1 = USART_CR1_UE | USART_CR1_TE;

    // Cấu hình DMA1 Stream 6 Channel 4 (USART2_TX)
    DMA1_Stream6->CR = 0; // Disable trước khi cấu hình
    DMA1_Stream6->PAR = (uint32_t)&USART2->DR; // Peripheral address
    DMA1_Stream6->M0AR = (uint32_t)tx_buffer;  // Memory address
    DMA1_Stream6->NDTR = sizeof(tx_buffer);    // Số byte truyền
    DMA1_Stream6->CR = DMA_SxCR_CHSEL_2 |      // Channel 4
                      DMA_SxCR_DIR_0 |         // Memory to Peripheral
                      DMA_SxCR_MINC |          // Memory increment
                      DMA_SxCR_TCIE;           // Transfer complete interrupt
    NVIC_EnableIRQ(DMA1_Stream6_IRQn);         // Kích hoạt interrupt
    DMA1_Stream6->CR |= DMA_SxCR_EN;           // Enable DMA Stream

    // Kích hoạt DMA trên UART2
    USART2->CR3 |= USART_CR3_DMAT;
}

int main(void) {
    DMA_Config();
    while (!transfer_complete); // Chờ truyền hoàn tất
    while (1);
}
    

Giải thích:

  • DMA1 Stream 6 Channel 4 được dùng để truyền dữ liệu từ `tx_buffer` sang UART2 (PA2 - TX).
  • Cấu hình UART2 ở 115200 baud, sau đó bật DMA mode (`DMAT`).
  • DMA tự động tăng địa chỉ Memory (`MINC`), truyền từng byte đến `USART2➔DR`.
  • Khi hoàn tất, interrupt `DMA1_Stream6_IRQHandler` đặt cờ `transfer_complete`.
  • CPU không tham gia vào quá trình truyền, giảm tải đáng kể.

>>>>>> Follow ngay <<<<<<<

Để nhận được những bài học miễn phí mới nhất nhé 😊
Chúc các bạn học tập tốt 😊

Nguyễn Văn Nghĩa

Mình là một người thích học hỏi và chia sẻ các kiến thức về Nhúng IOT.

Đăng nhận xét

Mới hơn Cũ hơn
//