🌱 Core 7. ARM Bus Protocol & Bus Interface
Bài viết này chúng ta sẽ cùng tìm hiểu về các đường Bus trong Vi điều khiển STM32. Bài viết Core 2 đã nói về cách Processor giao tiếp với các ngoại vi như GPIO, UART, ... bằng các đường bus AHB, APB. Trong bài viết này chúng ta sẽ cùng tìm hiểu kỹ hơn về các đường bus này.
👉 Trong Cortex Mx, các giao diện bus dựa trên một đặc điểm kỹ thuật gọi là AMBA - Advanced Microcontroller Bus Architecture - Kiến trúc Bus vi điều khiển nâng cao. AMBA là một kỹ thuật được thiết kế bở ARM, nhằm quản lý tiêu chuẩn cho truyền thông trên chip.
AMBA hỗ trợ một số giao thức bus:
- AHB Lite (AMBA High – performance Bus)
- APB (AMBA Peripheral Bus)
AHB & APB
                
                    
- AHB Lite Bus chủ yếu được sử dụng cho các giao diện bus chính.
- APB Bus sử dụng cho truy cập ngoại vi private và một số truy cập ngoại vi trên chip, bằng cách sử dụng cầu nối AHB – APB.
- AHB Lite Bus chủ yếu được sử dụng để giao tiếp tốc độ cao với thiết bị ngoại vi yêu cầu tốc độ cao (GPIO).
- APB bus được sử dụng cho các giao tiếp tốc độ thấp hơn so với AHB. Và hầu hết ngoại vi không yêu cầu tốc độ cao đều được kết nối với bus này. Bus này sử dụng để tiết kiệm năng lượng cho bộ xử lý (UART, SPI, I2C, Timer, ADC, ...). Trong vi điều khiển STM32 thường chia làm 2 bus APB1 và APB2 để đáp ứng nhu cầu của nhiều bộ ngoại vi khác nhau.
Bộ xử lý đưa ra 4 giao diện bus AHB (như hình): I-BUS, D-BUS, S-BUS, PPB, sử dụng các giao diện bus này để kết nối nhiều loại vùng nhớ khác nhau.
- Các bus trong vi điều khiển STM32 đều có độ rộng 32 bits.
- Có 2 giao diện bus để truy cập vào vùng nhớ Code: I-CODE - Instruction và D-CODE – Data. Trên I-CODE, bộ xử lý tìm nạp lệnh và bảng vector từ vùng nhớ Code. Trên D-CODE, nó sẽ truy cập dữ liệu từ vùng nhớ Code.
 Điều này có nghĩa là: Bộ xử lý sử dụng 2 giao diện bus chuyên dụng để tìm nạp lệnh và dữ liệu đồng thời từng vùng nhớ Code.
- Bus Hệ thống S-BUS – System để truy cập RAM, ngoại vi, …
- Bus PPB – Private Peripheral Bus: Giao tiếp với vùng bus ngoại vi riêng. Vùng này bao gồm hầu hết các thanh ghi ngoại vi của bộ xử lý Cortex Mx như NVIC, Systick, …
    
            Các bus AHB1/AHB2/AHB3 cũng như các đường bus khác trong bộ xử lý được liên kết qua ma trận bus – Bus Matrix => Nó chính là Arbiter – Trọng tài để xử lý giao thông giữa các bus.
        
📚 Chi Tiết Bus Transaction
1. AHB Transaction Phases Chi Tiết
Mỗi AHB transaction gồm 2 phase chính:
- Address Phase: Master đưa địa chỉ và control signals lên bus
- Data Phase: Dữ liệu được truyền giữa master và slave
// Ví dụ: AHB Write Transaction Clock Cycle 1 (Address Phase): - HADDR = 0x40020000 // Địa chỉ GPIOA - HWRITE = 1 // Write operation - HTRANS = NONSEQ // Non-sequential transfer - HSIZE = WORD // 32-bit transfer Clock Cycle 2 (Data Phase): - HWDATA = 0x00001000 // Dữ liệu ghi vào - HREADY = 1 // Slave sẵn sàng
2. Wait States và Bus Latency
Khi slave chưa sẵn sàng, nó kéo HREADY xuống 0 để tạo wait states:
// Cấu hình Flash Wait States cho STM32F4
// Khi chạy ở 168MHz, Flash cần 5 wait states
#define FLASH_ACR  (*((volatile uint32_t*)0x40023C00))
void ConfigureFlashLatency(void) {
    // Set 5 wait states cho Flash
    FLASH_ACR &= ~(0xF);        // Clear LATENCY bits
    FLASH_ACR |= 0x5;           // 5 wait states
    
    // Enable prefetch, instruction cache, data cache
    FLASH_ACR |= (1 << 8);      // PRFTEN
    FLASH_ACR |= (1 << 9);      // ICEN
    FLASH_ACR |= (1 << 10);     // DCEN
}
3. Bus Arbitration Chi Tiết
Bus Matrix sử dụng thuật toán Round-Robin để phân xử quyền truy cập:
// Ví dụ: 3 masters cùng request bus
Time T0: DMA1 có quyền truy cập (đang transfer)
Time T1: CPU và DMA2 cùng request
         → Round-robin: DMA2 được ưu tiên (next in queue)
Time T2: DMA2 có quyền truy cập
Time T3: CPU request lại
         → CPU được cấp quyền
         
// Priority levels trong STM32:
Highest: DMA2 (AHB2)
Medium:  DMA1 (AHB1)  
Low:     CPU D-Bus
Lowest:  CPU I-Bus
⚡ Tối Ưu Hóa Hiệu Suất Bus
1. Sử Dụng Burst Transfer
Burst transfer giúp truyền nhiều dữ liệu liên tiếp mà chỉ cần 1 address phase:
// DMA Burst Transfer Configuration
void DMA_BurstConfig(void) {
    DMA2_Stream0->CR |= (0x1 << 23);  // MBURST = INCR4
    DMA2_Stream0->CR |= (0x1 << 21);  // PBURST = INCR4
    
    // Với INCR4: 1 address phase → 4 data transfers
    // Tiết kiệm 3 address phases!
    
    // Hiệu suất:
    // - Single transfer: 100 words = 200 cycles
    // - Burst INCR4:     100 words = 125 cycles (↑60% faster)
}
2. Đặt Code và Data Đúng Vùng Nhớ
// Tối ưu: Tách code và data ra các bus khác nhau
// ❌ KHÔNG TỐT: Code và data cùng trên System Bus
void SlowFunction(void) {
    uint32_t data[100];  // Data trên SRAM
    // Code fetch và data access tranh chấp System Bus
    for(int i = 0; i < 100; i++) {
        data[i] = ProcessData(i);  // Bus conflict!
    }
}
// ✅ TỐT: Đặt code vào Flash, data vào CCM RAM
__attribute__((section(".ccmram")))
uint32_t fastData[100];  // CCM RAM - D-Bus riêng
void FastFunction(void) {
    // Code fetch: I-Code bus (Flash)
    // Data access: D-Bus (CCM RAM)
    // Không conflict! → Nhanh hơn 2x
    for(int i = 0; i < 100; i++) {
        fastData[i] = ProcessData(i);
    }
}
3. Tránh Bus Bottleneck
// Ví dụ: APB1 Bus Bottleneck
// APB1 chạy ở 42MHz (HCLK/4 = 168MHz/4)
// Nếu CPU (168MHz) liên tục truy cập APB1 peripheral:
void BadPractice(void) {
    // ❌ Mỗi lần ghi cần ~4 cycles do APB bridge
    for(int i = 0; i < 1000; i++) {
        USART2->DR = data[i];  // APB1 peripheral
        while(!(USART2->SR & USART_SR_TXE));  // Busy wait!
    }
    // Tổng: ~4000 cycles, CPU bị block
}
void GoodPractice(void) {
    // ✅ Dùng DMA để transfer, CPU làm việc khác
    DMA1_Stream6->M0AR = (uint32_t)data;
    DMA1_Stream6->NDTR = 1000;
    DMA1_Stream6->CR |= DMA_SxCR_EN;
    
    // CPU free để làm việc khác!
    DoOtherWork();
    
    // Tổng: ~1000 cycles cho DMA, CPU không bị block
}
🚨 Xử Lý Bus Error
1. Bus Fault Exception
Khi có lỗi bus (truy cập địa chỉ không hợp lệ, timeout, ...), CPU trigger Bus Fault exception:
// Bus Fault Handler
void BusFault_Handler(void) {
    // Đọc Bus Fault Status Register
    uint32_t cfsr = SCB->CFSR;
    uint32_t bfsr = (cfsr >> 8) & 0xFF;
    
    if(bfsr & (1 << 7)) {
        // BFARVALID: BFAR có địa chỉ gây lỗi
        uint32_t faultAddr = SCB->BFAR;
        printf("Bus Fault at address: 0x%08X\n", faultAddr);
    }
    
    if(bfsr & (1 << 0)) {
        printf("Instruction bus error\n");
    }
    
    if(bfsr & (1 << 1)) {
        printf("Precise data bus error\n");
    }
    
    if(bfsr & (1 << 2)) {
        printf("Imprecise data bus error\n");
    }
    
    // Clear fault flags
    SCB->CFSR |= (0xFF << 8);
    
    while(1);  // Halt system
}
// Enable Bus Fault exception
void EnableBusFault(void) {
    SCB->SHCSR |= SCB_SHCSR_BUSFAULTENA_Msk;
}
2. Timeout Protection
// Bảo vệ timeout khi truy cập peripheral
#define TIMEOUT_CYCLES 10000
uint32_t SafePeripheralRead(volatile uint32_t* addr) {
    uint32_t timeout = TIMEOUT_CYCLES;
    
    // Đợi peripheral ready
    while(timeout--) {
        if(*addr & READY_FLAG) {
            return *addr;
        }
    }
    
    // Timeout! Peripheral không response
    TriggerBusFault();
    return 0xFFFFFFFF;
}
📊 So Sánh Hiệu Năng Các Bus
| Bus Type | Tốc độ | Bandwidth | Latency | Use Case | 
|---|---|---|---|---|
| I-Code | 168 MHz | 168 MB/s | 0 wait (cache hit) | Fetch instructions từ Flash | 
| D-Code | 168 MHz | 168 MB/s | 0 wait (cache hit) | Đọc constants từ Flash | 
| System Bus | 168 MHz | 168 MB/s | 0 wait (SRAM) | Truy cập SRAM, peripherals | 
| AHB | 168 MHz | 168 MB/s | 0-1 wait | High-speed peripherals (DMA, USB) | 
| APB2 | 84 MHz | 84 MB/s | 2 wait | Fast peripherals (TIM1, ADC) | 
| APB1 | 42 MHz | 42 MB/s | 4 wait | Slow peripherals (UART, I2C) | 
Benchmark Thực Tế
// Test: Copy 1KB data
Test 1: CPU copy từ SRAM → SRAM (System Bus)
        Time: 1000 cycles
        
Test 2: CPU copy từ Flash → SRAM (I-Code + System Bus)
        Time: 1500 cycles (Flash wait states)
        
Test 3: DMA copy từ SRAM → SRAM (AHB)
        Time: 256 cycles (burst mode)
        CPU free!
        
Test 4: CPU copy từ CCM RAM → SRAM (D-Bus + System Bus)
        Time: 800 cycles (no bus conflict)
Kết luận: DMA burst mode nhanh nhất (4x), CCM RAM tốt cho CPU-intensive tasks
🎯 Ví Dụ Thực Tế: Tối Ưu DMA Transfer
// Ví dụ hoàn chỉnh: DMA transfer từ ADC → Memory với tối ưu bus
#define ADC_BUFFER_SIZE 1024
// Đặt buffer trong SRAM (không dùng CCM vì DMA không access được CCM)
__attribute__((aligned(4)))
uint16_t adcBuffer[ADC_BUFFER_SIZE];
void OptimizedDMA_ADC_Init(void) {
    // 1. Enable clocks
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    
    // 2. Configure DMA2 Stream 0 (ADC1)
    DMA2_Stream0->CR = 0;  // Reset
    while(DMA2_Stream0->CR & DMA_SxCR_EN);  // Wait disable
    
    // Peripheral to Memory, circular mode
    DMA2_Stream0->CR |= (0 << 6);           // Peripheral to memory
    DMA2_Stream0->CR |= (1 << 8);           // Circular mode
    DMA2_Stream0->CR |= (1 << 10);          // Memory increment
    DMA2_Stream0->CR |= (1 << 13);          // Half-word (16-bit)
    DMA2_Stream0->CR |= (1 << 11);          // Half-word peripheral
    
    // ⚡ Tối ưu: Enable burst mode
    DMA2_Stream0->CR |= (0x1 << 23);        // MBURST = INCR4
    
    // ⚡ Tối ưu: Set high priority
    DMA2_Stream0->CR |= (0x3 << 16);        // Very high priority
    
    // Addresses
    DMA2_Stream0->PAR = (uint32_t)&(ADC1->DR);
    DMA2_Stream0->M0AR = (uint32_t)adcBuffer;
    DMA2_Stream0->NDTR = ADC_BUFFER_SIZE;
    
    // Enable transfer complete interrupt
    DMA2_Stream0->CR |= DMA_SxCR_TCIE;
    NVIC_EnableIRQ(DMA2_Stream0_IRQn);
    
    // 3. Configure ADC
    ADC1->CR1 = 0;
    ADC1->CR2 = ADC_CR2_DMA | ADC_CR2_DDS;  // DMA mode, continuous
    ADC1->SMPR2 = 0x7;                       // 480 cycles sampling
    ADC1->SQR1 = 0;                          // 1 conversion
    ADC1->SQR3 = 0;                          // Channel 0
    
    // 4. Start
    DMA2_Stream0->CR |= DMA_SxCR_EN;
    ADC1->CR2 |= ADC_CR2_ADON;
    ADC1->CR2 |= ADC_CR2_SWSTART;
}
void DMA2_Stream0_IRQHandler(void) {
    if(DMA2->LISR & DMA_LISR_TCIF0) {
        // Transfer complete
        DMA2->LIFCR |= DMA_LIFCR_CTCIF0;  // Clear flag
        
        // Process data
        ProcessADCData(adcBuffer, ADC_BUFFER_SIZE);
    }
}
// Hiệu suất:
// - Không dùng DMA: CPU phải đọc ADC mỗi sample → 1024 interrupts
// - Dùng DMA: CPU chỉ xử lý 1 interrupt khi đủ 1024 samples
// - Burst mode: Giảm 75% bus cycles
// → CPU utilization giảm từ 80% xuống 5%!
💡 Lưu Ý Quan Trọng
- Bus Matrix tự động arbitrate - Không cần can thiệp thủ công, nhưng cần hiểu để tối ưu
- DMA luôn nhanh hơn CPU cho memory transfer - Dùng DMA cho mọi transfer > 100 bytes
- CCM RAM không thể dùng với DMA - Chỉ CPU mới access được qua D-Bus
- Flash có wait states - Luôn enable cache và prefetch buffer
- APB peripherals chậm hơn AHB - Tránh busy-wait trên APB, dùng interrupt hoặc DMA
- Memory barriers cần thiết khi làm việc với DMA, cache, hoặc multi-master systems
>>>>>> 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 😊
 
        