20 Nhận dữ liệu UART bằng DMA và bằng ngắt 
Khởi tạo UART 
Cấu hình GPIO 
Đầu tiên, ta cần cấu hình hai chân PA9 và PA10 của GPIOA để sử dụng cho USART1:
- PA9 → chân TX (truyền dữ liệu)
- PA10 → chân RX (nhận dữ liệu)
Các thiết lập cấu hình như sau:
- Chế độ: Chân đa chức năng (Alternate Function)
- Tốc độ: 100 MHz (cao)
- Kiểu output: Push-pull (đẩy kéo)
- Chân RX (PA10) cần bật pull-up để giữ mức ổn định khi không có tín hiệu
GPIO_InitTypeDef GPIO_InitStructure; // Cấu trúc cấu hình GPIO
// Bật clock cho GPIOA
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
// Gán chức năng chân cho USART1
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);   // PA9 làm chân TX (USART1_TX)
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);  // PA10 làm chân RX (USART1_RX)
// Cấu hình các tham số GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;       // Sử dụng cả hai chân PA9 và PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;                  // Chế độ chức năng thay thế (Alternate Function)
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;            // Tốc độ cao (100MHz)
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;                // Kiểu xuất push-pull
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;                  // Kéo lên (pull-up)
GPIO_Init(GPIOA, &GPIO_InitStructure);                        // Áp dụng cấu hình cho GPIOACấu hình USART1 
Thiết lập các thông số hoạt động cho USART1 bao gồm:
- Tốc độ baud: dùng biến __baudđể dễ thay đổi
- Độ dài dữ liệu: 8 bit
- Số bit dừng: 1 bit
- Kiểm tra chẵn lẻ (parity): không dùng
- Flow control: không dùng phần cứng
- Chế độ: vừa truyền vừa nhận (TX + RX)
USART_InitTypeDef USART_InitStructure; // Cấu trúc cấu hình USART
// Bật clock cho USART1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
// Cấu hình các tham số của USART1
USART_InitStructure.USART_BaudRate = __baud;                       // Tốc độ baud (được truyền vào từ tham số)
USART_InitStructure.USART_WordLength = USART_WordLength_8b;        // Độ dài dữ liệu: 8 bit
USART_InitStructure.USART_StopBits = USART_StopBits_1;             // 1 bit dừng
USART_InitStructure.USART_Parity = USART_Parity_No;                // Không dùng kiểm tra chẵn lẻ
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // Không dùng flow control
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;    // Kích hoạt cả nhận và truyền
// Áp dụng cấu hình cho USART1
USART_Init(USART1, &USART_InitStructure);Lưu ý: Biến
__baudlà tham số đầu vào của hàm, giúp bạn dễ dàng thay đổi tốc độ baud trong chương trình.
Cấu hình ngắt (Interrupt Configuration) 
Cấu hình ưu tiên và kích hoạt ngắt cho:
- USART1 (ngắt khi phát hiện chế độ nhàn rỗi – idle)
- DMA2_Stream5 (DMA nhận)
- DMA2_Stream7 (DMA truyền)
NVIC_InitTypeDef NVIC_InitStructure;  // Cấu trúc cấu hình ngắt
// Cấu hình ngắt USART1
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;                 // Kênh ngắt cho USART1
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;        // Mức ưu tiên ngắt (preemptive) = 2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;               // Mức ưu tiên phụ (sub) = 1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                  // Bật kênh ngắt
NVIC_Init(&NVIC_InitStructure);                                  // Áp dụng cấu hình
// Cấu hình ngắt cho DMA nhận (Stream5)
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream5_IRQn;          // Kênh ngắt cho DMA2_Stream5 (USART1_RX)
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;        // Ưu tiên = 2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;               // Ưu tiên phụ = 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// Cấu hình ngắt cho DMA truyền (Stream7)
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn;          // Kênh ngắt cho DMA2_Stream7 (USART1_TX)
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;        // Ưu tiên = 2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;               // Ưu tiên phụ = 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// Cấu hình ngắt cho USART1 ở chế độ nhàn rỗi (idle line interrupt)
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);       // Bật ngắt phát hiện "đường truyền rảnh"
// Kích hoạt chế độ DMA cho USART1
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);       // Bật DMA cho truyền nhận (RX)
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);       // Bật DMA cho truyền gửi (TX)Giải thích nhanh:
- USART_IT_IDLEgiúp phát hiện kết thúc một frame dữ liệu → cực kỳ hữu ích khi dùng với DMA để xử lý một khối dữ liệu lớn
- DMA2_Stream5thường dùng cho USART1_RX
- DMA2_Stream7thường dùng cho USART1_TX
- Ngắt DMA giúp xác định khi DMA hoàn thành quá trình truyền/nhận
Cấu hình DMA truyền nhận cho USART1 
Cấu hình bao gồm:
- Bật clock DMA
- Cấu hình DMA2_Stream5 → dùng cho USART1_RX
- Cấu hình DMA2_Stream7 → dùng cho USART1_TX
- Thiết lập kênh, địa chỉ, chế độ truyền, ưu tiên, burst, v.v
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); // Bật clock cho DMA2
DMA_DeInit(DMA2_Stream5); // Reset lại cấu hình stream trước đó
DMA_InitStructure.DMA_Channel = DMA_Channel_4;  // USART1 sử dụng DMA Channel 4
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; // Địa chỉ thanh ghi dữ liệu USART1
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)DMA_USART1_RX_BUF; // Bộ đệm đích trong RAM
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; // Ngoại vi → RAM
DMA_InitStructure.DMA_BufferSize = USART_MAX_LEN; // Số byte cần nhận
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // Địa chỉ ngoại vi cố định
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // RAM tăng địa chỉ sau mỗi byte
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // Chế độ truyền đơn
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // Ưu tiên cao
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; // Không dùng FIFO
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream5, &DMA_InitStructure);
DMA_Cmd(DMA2_Stream5, ENABLE); // Bật DMA RXTrình phục vụ ngắt USART1 (USART1_IRQHandler) 
Mục tiêu: Khi ngắt IDLE (đường truyền rảnh) xảy ra, nghĩa là đã nhận xong một frame dữ liệu, ta sẽ:
- Tắt DMA để xử lý dữ liệu
- Tính độ dài dữ liệu đã nhận
- Gửi lại dữ liệu vừa nhận (chức năng echo)
- Khởi động lại DMA để chuẩn bị cho lần nhận tiếp theo
void USART1_IRQHandler(void)  // Trình xử lý ngắt USART1
{
    // 1. Nếu phát hiện ngắt Idle (USART nhận xong 1 frame)
    if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
    {
        usart1_recv_end_flag = 1;  // Đánh dấu đã nhận xong 1 frame
        DMA_Cmd(DMA2_Stream5, DISABLE);  // Tạm thời tắt DMA RX để xử lý dữ liệu
        // Tính số byte đã nhận được
        usart1_rx_len = USART_MAX_LEN - DMA_GetCurrDataCounter(DMA2_Stream5);
        // Xóa cờ DMA truyền xong
        DMA_ClearFlag(DMA2_Stream5, DMA_FLAG_TCIF5);
        // Gửi lại (echo) dữ liệu vừa nhận bằng DMA truyền
        DMA_USART1_Send(DMA_USART1_RX_BUF, usart1_rx_len);
        // Reset lại bộ đếm DMA để chuẩn bị nhận frame tiếp theo
        DMA_SetCurrDataCounter(DMA2_Stream5, USART_MAX_LEN);
        DMA_Cmd(DMA2_Stream5, ENABLE);  // Bật lại DMA nhận
        USART_ReceiveData(USART1); // Đọc 1 byte để xóa cờ ngắt idle
    }
    // 2. Kiểm tra ngắt TXE (gửi xong)
    if (USART_GetFlagStatus(USART1, USART_IT_TXE) == RESET)
    {
        USART_ITConfig(USART1, USART_IT_TC, DISABLE); // Tắt ngắt truyền xong
        usart1_rx_len = 0;  // Reset độ dài dữ liệu đã gửi
    }
}Trình xử lý ngắt DMA khi truyền xong (DMA2_Stream7_IRQHandler) 
Khi DMA2_Stream7 (dùng để truyền dữ liệu qua USART1) hoàn thành việc gửi dữ liệu, trình phục vụ ngắt sẽ thực hiện:
- Xóa cờ ngắt truyền hoàn tất
- Tắt DMA2_Stream7 để chuẩn bị cho lần gửi tiếp theo
- Bật ngắt truyền hoàn tất USART1 để xử lý hậu kỳ (ví dụ: reset cờ, gửi tiếp...)
void DMA2_Stream7_IRQHandler(void)  // Ngắt DMA truyền USART1 TX
{
    if (DMA_GetITStatus(DMA2_Stream7, DMA_IT_TCIF7) != RESET) // Kiểm tra cờ "truyền hoàn tất"
    {
        DMA_ClearITPendingBit(DMA2_Stream7, DMA_IT_TCIF7); // Xóa cờ ngắt
        DMA_Cmd(DMA2_Stream7, DISABLE);  // Tắt DMA truyền
        USART_ITConfig(USART1, USART_IT_TC, ENABLE); // Bật ngắt truyền xong của USART
    }
}Hàm gửi dữ liệu 
Chức năng:
- Sao chép dữ liệu cần gửi vào bộ đệm DMA_USART1_TX_BUF
- Thiết lập số lượng byte cần truyền
- Bật DMA2_Stream7 để bắt đầu truyền qua USART1
void DMA_USART1_Send(uint8_t *data, uint16_t len)
{
    uint16_t i;
    // 1. Sao chép dữ liệu vào buffer DMA TX
    for (i = 0; i < len; i++)
    {
        DMA_USART1_TX_BUF[i] = data[i];
    }
    // 2. Cấu hình độ dài dữ liệu cần truyền
    DMA_SetCurrDataCounter(DMA2_Stream7, len);
    // 3. Bật DMA truyền để bắt đầu gửi qua USART1
    DMA_Cmd(DMA2_Stream7, ENABLE);
}Giải thích:
- data: con trỏ đến dữ liệu cần gửi
- len: số lượng byte cần truyền
- DMA_USART1_TX_BUF: buffer cố định đã được cấp phát trước (ví dụ:- uint8_t DMA_USART1_TX_BUF[USART_MAX_LEN];)
- DMA_SetCurrDataCounter(...): thiết lập lại số byte DMA sẽ xử lý trong lần truyền này
- DMA_Cmd(..., ENABLE): kích hoạt DMA2_Stream7 bắt đầu truyền
Hiện tượng thực nghiệm 
Mã nguồn của chương này có thể tìm thấy tại:
Đường dẫn trong tài liệu của bo mạch: 立创·梁山派·天空星 STM32F407VET6 开发板资料/第03章软件资料/代码例程/010 串口中断DMA接收二合一
Sau khi nạp chương trình vào bo mạch:
- Khi bạn gửi dữ liệu từ phần mềm giao tiếp UART,
- Dữ liệu sẽ được hiển thị lại (echo) trên chính phần mềm đó.
Điều này chứng minh rằng:
- Dữ liệu đã được DMA nhận, sau đó gửi lại bằng DMA
- Quá trình này chỉ gây ra một lần ngắt duy nhất (ngắt IDLE) mỗi frame → hiệu suất cao, CPU gần như không bị chiếm dụng
