🌱 Lập Trình Thanh ghi STM32 Ngoại vi DMA: Memory to Memory
➤ Bài viết trước: DMA - Direct Memory Access
Mục lục
- Phần cứng sử dụng
- Phần mềm sử dụng
- Các bước lập trình DMA - STM32
- Các bước cấu hình DMA
- Ví dụ lập trình DMA Memory-to-Memory
- Video thực hành
👉 Phần cứng sử dụng
STM32F401RE - NUCLEO Board.
👉 Phần mềm sử dụng
STM32CubeIDE ⇒ Xem hướng dẫn sử dụng.
👉 Các bước lập trình DMA - STM32
Để lập trình sử dụng trực tiếp các thanh ghi DMA, các bạn có thể làm theo các bước sau:
-
Define các thanh ghi DMA
Phần này rất đơn giản, chỉ cần mở tài liệu Reference Manual của Vi điều khiển mà các bạn sử dụng, sau đó define địa chỉ Base của các DMA, và struct thanh ghi của DMA. Dưới đây mình Define cho Vi điều khiển STM32F401. -
Viết Hàm cấu hình DMA - DMA_Init()
Các bạn đặt tên gì tùy ý - ở đây mình đặt theo chuẩn chung thôi. Quan trọng nhất là nội dung của hàm này, đó là để cấu hình các chế độ của DMA khi sử dụng. Hàm này sẽ chủ yếu làm việc với thanh ghi DMA_SxCR - DMA Stream x Control Register. Cụ thể cần cấu hình gì mình sẽ nêu chi tiết ở bên dưới nhé! -
Viết Hàm Transfer DMA
Hàm này có tác dụng định địa chỉ truyền và địa chỉ nhận dữ liệu, kích thước dữ liệu cần truyền, và tiến hành truyền dữ liệu (Có thể viết 1 hàm riêng cũng được).
👉 Các bước cấu hình DMA
Nội dung trong hàm DMA_Init và cấu hình địa chỉ trước khi truyền Data sẽ tuân theo flow sau:
- Cấu hình Clock cho DMA sử dụng.
- Chọn Channel DMA tương ứng với Stream sử dụng, bằng các bit CHSEL trong thanh ghi CR.
- Cấu hình các ngắt DMA (Optional nếu sử dụng ngắt), sử dụng các bit TCIF, HTIF, TEIF, DMEIR... trong thanh ghi CR.
- Chọn Data Direction (Mem to Mem, Peripheral to Mem or Mem to Peripheral), sử dụng các bit DIR trong thanh ghi CR.
- Cấu hình Enable hoặc Disable Circular Mode (Bit CIRC thanh ghi CR).
- Cấu hình Enable hoặc Disable Memory/Peripheral Increment (Bit PINC/CIRC thanh ghi CR).
- Set Memory/Peripheral Data Size, sử dụng các bit MSIZE/PSIZE trong thanh ghi CR.
- Set Priority Level (Mức độ ưu tiên) cho Channel (Các bit PL thanh ghi CR).
Khi truyền Data:
- Cấu hình Data Size bằng thanh ghi NDTR.
-
Cấu hình Start Address của Source và Destination, Phần địa chỉ nguồn và đích sẽ phụ thuộc vào Data Direction mà chúng ta chọn ở trên, các bạn có thể tham khảo bảng phụ thuộc đó trong RM, như dưới đây:
- Sau đó cấu hình Start Address cho Source và Destination vào các thanh ghi như trên. Ở đây mình truyền Mem-to-Mem nên sẽ dùng 2 thanh ghi là DMA_SxPAR và thanh ghi DMA_SxM0AR.
Bước cuối cùng sau khi đảm bảo mọi thứ đã được cấu hình xong, chúng ta có thể bắt đầu transfer bằng cách Enable DMA bằng bit EN trong thanh ghi CR.
Mọi thứ Done!
Các bước trên đây có thể sẽ khó hiểu cho các bạn lần đầu làm về DMA, các bạn có thể follow theo các bước trên để tự viết, tham khảo Reference Manual hoặc ở đâu đó trên mạng - tùy!
👉 Ví dụ lập trình DMA Memory-to-Memory
Dưới đây là code minh họa lập trình DMA Memory-to-Memory trên STM32F401RE bằng cách sử dụng thanh ghi trực tiếp:
#include "stm32f4xx.h"
// Định nghĩa dữ liệu nguồn và đích
uint32_t src_buffer[10] = {0x1111, 0x2222, 0x3333, 0x4444, 0x5555,
0x6666, 0x7777, 0x8888, 0x9999, 0xAAAA};
uint32_t dst_buffer[10] = {0};
// Hàm cấu hình DMA Memory-to-Memory
void DMA_Init(void) {
// Bật clock cho DMA2 (Memory-to-Memory chỉ hỗ trợ trên DMA2)
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
// Sử dụng DMA2 Stream 0 Channel 0 (Memory-to-Memory)
DMA2_Stream0->CR = 0; // Disable trước khi cấu hình
// Cấu hình thanh ghi DMA_SxCR
DMA2_Stream0->CR |= (0 << DMA_SxCR_CHSEL_Pos) | // Channel 0
(0b10 << DMA_SxCR_DIR_Pos) | // Memory-to-Memory
DMA_SxCR_MINC | // Memory increment
DMA_SxCR_PINC | // Peripheral increment (dùng M0AR làm đích)
(0b10 << DMA_SxCR_MSIZE_Pos) | // Memory size: 32-bit
(0b10 << DMA_SxCR_PSIZE_Pos) | // Peripheral size: 32-bit
(0b01 << DMA_SxCR_PL_Pos) | // Priority: Medium
DMA_SxCR_TCIE; // Transfer complete interrupt
// Kích hoạt interrupt
NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}
// Hàm truyền dữ liệu
void DMA_Transfer(uint32_t src_addr, uint32_t dst_addr, uint32_t size) {
// Đặt số lượng dữ liệu cần truyền
DMA2_Stream0->NDTR = size;
// Đặt địa chỉ nguồn và đích
DMA2_Stream0->PAR = src_addr; // Source (Memory)
DMA2_Stream0->M0AR = dst_addr; // Destination (Memory)
// Enable DMA để bắt đầu truyền
DMA2_Stream0->CR |= DMA_SxCR_EN;
}
// Xử lý interrupt khi truyền hoàn tất
volatile uint8_t transfer_complete = 0;
void DMA2_Stream0_IRQHandler(void) {
if (DMA2->LISR & DMA_LISR_TCIF0) { // Transfer Complete
DMA2->LIFCR |= DMA_LIFCR_CTCIF0; // Clear flag
transfer_complete = 1;
}
}
int main(void) {
DMA_Init();
DMA_Transfer((uint32_t)src_buffer, (uint32_t)dst_buffer, 10);
while (!transfer_complete); // Chờ truyền hoàn tất
while (1); // Kiểm tra dst_buffer trong debugger
}
Giải thích:
- Define thanh ghi: Sử dụng CMSIS (stm32f4xx.h) thay vì tự define địa chỉ base để đơn giản hóa.
- DMA_Init: Cấu hình DMA2 Stream 0 Channel 0 cho Memory-to-Memory (DIR=10), bật increment cho cả nguồn và đích, kích thước dữ liệu 32-bit, ưu tiên trung bình, bật interrupt hoàn tất.
- DMA_Transfer: Đặt NDTR (số lượng), PAR (nguồn), M0AR (đích), sau đó bật EN để truyền.
- Interrupt: Xử lý cờ TCIF0 để báo hoàn tất.
- Ví dụ truyền 10 word (40 byte) từ `src_buffer` sang `dst_buffer`.
👉 Video thực hành
Mình để lại Video sau để các bạn tham khảo nhé!
Video 1: Lập trình DMA Memory-to-Memory trên STM32
>>>>>> 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 😊