🌱 STM32 - 15. Lập Trình UART Truy Xuất Thanh Ghi

🌱 STM32 - 15. Lập Trình UART Truy Xuất Thanh Ghi

    Các bài viết trước về USART:

    Bài viết này mình sẽ cùng các bạn thực hành một ví dụ sử dụng UART trên Vi điều khiển STM32, truy xuất trực tiếp thanh ghi.

👉 Phần cứng sử dụng:

  • STM32F401RE - NUCLEO Board.
  • CP2102 USB-to-UART.

👉 Phần mềm sử dụng:

👉 UART Workflow

🔎 Khởi tạo UART

Để làm việc với giao thức UART, chúng ta cần khởi tạo ngoại vi bằng cách cấu hình một số thông số: Baudrate, Stop Bits, Parity Bits, ...

Chúng ta có thể config theo flow sau:

  1. Enable Clock tương ứng với ngoại vi USART sử dụng.
  2. Setup Pin tương ứng với chức năng UART.
  3. Enable Transmit/Receive bằng bit TX/RX trên thanh ghi USART_CR1.
  4. Config Word Length (8bit or 9bit) bằng bit M thanh ghi USART_CR1.
  5. Config Parity Bit sử dụng bit PCE - Parity Control Bit và PS - Parity Selection thanh ghi USART_CR1.
  6. Config Stop Bits sử dụng trường STOP thanh ghi USART_CR2.
  7. Option: Config Hardware Flow Control sử dụng thanh ghi USART_CR3.
  8. Cấu hình Baudrate sử dụng thanh ghi USART_BRR.

??? Tính toán Baudrate
Bài viết: STM 13. USART: Cấu hình Baudrate
Ví dụ: Tính toán Baudrate = 9600 với F_CLK = 8MHz:
- F_CLK = 8MHz, OVER8 = 0 (oversampling 16).
- USARTDIV = 8,000,000 / (16 * 9600) = 52.083.
- Mantissa = 52 (0x34), Fraction = 0.083 * 16 = 1.33 ≈ 1 (0x1).
- USART_BRR = (52 << 4) | 1 = 0x0341.

🔎 Truyền dữ liệu UART

Truyền từng frame dữ liệu (7/8/9 bits data) bằng flow sau:

  1. Đợi thanh ghi truyền dữ liệu trống - empty bằng cờ TXE - Transmission Data Register Empty, thanh ghi USART_SR.
  2. Đặt Byte Data cần truyền vào thanh ghi USART_DR.
  3. Đợi việc truyền hoàn tất bằng cờ TC - Transmission Complete thanh ghi USART_SR.

🔎 Nhận dữ liệu UART

Nhận dữ liệu từ UART theo flow sau:

  1. Đợi thanh ghi nhận dữ liệu chứa data - not empty bằng cờ RXNE - Receive Data Register Not Empty, thanh ghi USART_SR.
  2. Đọc dữ liệu nhận được từ thanh ghi USART_DR.

👉 Example: STM32 giao tiếp PC USB-to-UART

Mình dùng con CP2102 USB-to-UART để test giao thức UART của STM32, giao tiếp truyền nhận dữ liệu với máy tính (Cài phần mềm Hercules).

Hình 1: Sơ đồ kết nối STM32F401RE với CP2102

Nối dây TX của CP2102 với RX của STM32 (PA3), RX của CP2102 với TX của STM32 (PA2).

Dưới đây là đoạn code bare-metal cho USART2 (PA2-TX, PA3-RX) giao tiếp với PC:

#include 

// Định nghĩa địa chỉ cơ bản
#define RCC_BASE        0x40023800
#define GPIOA_BASE      0x40020000
#define USART2_BASE     0x40004400

// Định nghĩa thanh ghi RCC
#define RCC_CR          (*(volatile uint32_t *)(RCC_BASE + 0x00))
#define RCC_CFGR        (*(volatile uint32_t *)(RCC_BASE + 0x08))
#define RCC_AHB1ENR     (*(volatile uint32_t *)(RCC_BASE + 0x30))
#define RCC_APB1ENR     (*(volatile uint32_t *)(RCC_BASE + 0x40))

// Định nghĩa thanh ghi GPIOA
#define GPIOA_MODER     (*(volatile uint32_t *)(GPIOA_BASE + 0x00))
#define GPIOA_OSPEEDR   (*(volatile uint32_t *)(GPIOA_BASE + 0x08))
#define GPIOA_PUPDR     (*(volatile uint32_t *)(GPIOA_BASE + 0x0C))
#define GPIOA_AFRL      (*(volatile uint32_t *)(GPIOA_BASE + 0x20))

// Định nghĩa thanh ghi USART2
#define USART2_SR       (*(volatile uint32_t *)(USART2_BASE + 0x00))
#define USART2_DR       (*(volatile uint32_t *)(USART2_BASE + 0x04))
#define USART2_BRR      (*(volatile uint32_t *)(USART2_BASE + 0x08))
#define USART2_CR1      (*(volatile uint32_t *)(USART2_BASE + 0x0C))
#define USART2_CR2      (*(volatile uint32_t *)(USART2_BASE + 0x10))
#define USART2_CR3      (*(volatile uint32_t *)(USART2_BASE + 0x14))

// Định nghĩa bit trong RCC
#define RCC_CR_HSION    (1 << 0)
#define RCC_CR_HSIRDY   (1 << 1)
#define RCC_CFGR_SW_HSI (0 << 0)
#define RCC_CFGR_HPRE_DIV2  (1 << 4)  // AHB = HSI/2 = 8MHz
#define RCC_AHB1ENR_GPIOAEN (1 << 0)
#define RCC_APB1ENR_USART2EN (1 << 17)

// Định nghĩa bit trong GPIO
#define GPIO_MODER_AF   2
#define GPIO_OSPEEDR_HIGH 3
#define GPIO_AF7_USART2 7

// Định nghĩa bit trong USART_CR1
#define USART_CR1_UE    (1 << 13)
#define USART_CR1_TE    (1 << 3)
#define USART_CR1_RE    (1 << 2)

// Định nghĩa bit trong USART_CR2
#define USART_CR2_STOP_1BIT (0 << 12)

// Định nghĩa bit trong USART_SR
#define USART_SR_TXE    (1 << 7)
#define USART_SR_TC     (1 << 6)
#define USART_SR_RXNE   (1 << 5)

// Hàm cấu hình clock
void SystemClock_Config(void) {
    RCC_CR |= RCC_CR_HSION;
    while (!(RCC_CR & RCC_CR_HSIRDY));
    RCC_CFGR &= ~(RCC_CFGR_SW_HSI);
    RCC_CFGR |= RCC_CFGR_HPRE_DIV2; // AHB = 8MHz, APB1 = 8MHz
}

// Hàm cấu hình GPIO cho USART2 (PA2-TX, PA3-RX)
void GPIO_Init(void) {
    RCC_AHB1ENR |= RCC_AHB1ENR_GPIOAEN;

    // PA2 (TX) và PA3 (RX) thành Alternate Function
    GPIOA_MODER &= ~(3 << (2 * 2));
    GPIOA_MODER |= (GPIO_MODER_AF << (2 * 2));
    GPIOA_MODER &= ~(3 << (3 * 2));
    GPIOA_MODER |= (GPIO_MODER_AF << (3 * 2));

    // Tốc độ cao
    GPIOA_OSPEEDR &= ~(3 << (2 * 2));
    GPIOA_OSPEEDR |= (GPIO_OSPEEDR_HIGH << (2 * 2));
    GPIOA_OSPEEDR &= ~(3 << (3 * 2));
    GPIOA_OSPEEDR |= (GPIO_OSPEEDR_HIGH << (3 * 2));

    // Không pull-up/pull-down
    GPIOA_PUPDR &= ~(3 << (2 * 2));
    GPIOA_PUPDR &= ~(3 << (3 * 2));

    // AF7 cho USART2
    GPIOA_AFRL &= ~(0xF << (2 * 4));
    GPIOA_AFRL |= (GPIO_AF7_USART2 << (2 * 4));
    GPIOA_AFRL &= ~(0xF << (3 * 4));
    GPIOA_AFRL |= (GPIO_AF7_USART2 << (3 * 4));
}

// Hàm khởi tạo USART2
void UART_Init(void) {
    RCC_APB1ENR |= RCC_APB1ENR_USART2EN;

    // Baudrate = 9600, PCLK1 = 8MHz
    USART2_BRR = (52 << 4) | 1; // 0x0341

    // Cấu hình: 8 bits, no parity, 1 stop bit
    USART2_CR1 = 0;
    USART2_CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;

    USART2_CR2 = 0;
    USART2_CR2 |= USART_CR2_STOP_1BIT;

    USART2_CR3 = 0; // Không dùng hardware flow control
}

// Hàm truyền một ký tự
void UART_SendChar(uint8_t data) {
    while (!(USART2_SR & USART_SR_TXE));
    USART2_DR = data;
    while (!(USART2_SR & USART_SR_TC));
}

// Hàm truyền chuỗi
void UART_SendString(const char *str) {
    while (*str) {
        UART_SendChar(*str++);
    }
}

// Hàm nhận một ký tự
uint8_t UART_ReceiveChar(void) {
    while (!(USART2_SR & USART_SR_RXNE));
    return (uint8_t)USART2_DR;
}

int main(void) {
    SystemClock_Config();
    GPIO_Init();
    UART_Init();

    // Gửi chuỗi chào
    UART_SendString("Hello from STM32!\r\n");

    while (1) {
        // Echo dữ liệu nhận được
        uint8_t received = UART_ReceiveChar();
        UART_SendChar(received);
    }
}
    

👉 Mình để lại Video sau để các bạn tham khảo nhé!

Video 1: Hàm UART_Init()

Video 2: Hàm UART_SendData() và UART_ReceiveData()

Các bạn thấy Video hữu ích có thể cho mình xin 1 Like + 1 Subcribe nhé 💗

>>>>>> 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
//