Điều Khiển Đèn LED Trên STM32 Thông Qua Giao Tiếp UART Và Phân Tích Lệnh

Trong các hệ thống nhúng, việc sử dụng cổng nối tiếp (UART) để nhận lệnh điều khiển từ máy tính hoặc thiết bị ngoại vi là một kỹ thuật phổ biến. Bài viết này trình bày cách xây dựng một module nhận lệnh qua UART trên vi điều khiển STM32, phân tích cú pháp chuỗi ký tự và thực thi thao tác bật/tắt đèn LED tương ứng.

1. Định Nghĩa Cấu Hình Và Bộ Đệm

Để tối ưu hóa quá trình xử lý, chúng ta sẽ quy ước trước các chuỗi lệnh điều khiển. Thay vì xử lý dữ liệu thô, hệ thống sẽ dựa vào các macro và bộ đệm có kích thước cố định để lưu trữ dữ liệu nhận được. Dưới đây là các định nghĩa cơ bản:

#define TURN_LED_ON()   GPIO_ResetBits(GPIOC, GPIO_Pin_13)
#define TURN_LED_OFF()  GPIO_SetBits(GPIOC, GPIO_Pin_13)
#define UART_BUFFER_SIZE 128

volatile uint8_t uart_rx_buffer[UART_BUFFER_SIZE];
volatile uint8_t rx_write_index = 0;

Do thư viện chuẩn của STM32 không tích hợp sẵn kiểu dữ liệu logic, ta có thể tự định nghĩa thông qua enum:

typedef enum {
    LOGIC_FALSE = 0,
    LOGIC_TRUE = 1
} bool_t;

2. Hàm Trì Hoãn Sử Dụng SysTick

Để tạo độ trễ chính xác mà không phụ thuộc vào vòng lặp rỗng, bộ đếm SysTick được cấu hình trực tiếp. Hai hàm dưới đây hỗ trợ trễ theo micro giây và mili giây:

void sys_tick_delay_us(uint32_t us)
{
    uint32_t ctrl_status;
    SysTick->LOAD = 9 * us;
    SysTick->VAL = 0x00;
    SysTick->CTRL = 0x01;
    do {
        ctrl_status = SysTick->CTRL;
    } while ((ctrl_status & 0x01) && !(ctrl_status & (1 << 16)));
    SysTick->CTRL = 0x00;
    SysTick->VAL = 0x00;
}

void sys_tick_delay_ms(uint16_t ms)
{
    uint32_t ctrl_status;
    SysTick->LOAD = 9000 * ms;
    SysTick->VAL = 0x00;
    SysTick->CTRL = 0x01;
    do {
        ctrl_status = SysTick->CTRL;
    } while ((ctrl_status & 0x01) && !(ctrl_status & (1 << 16)));
    SysTick->CTRL = 0x00;
    SysTick->VAL = 0x00;
}

3. Khởi Tạo Ngoại Vi

Trước khi vận hành, các khối chức năng bao gồm GPIO cho LED và USART1 kèm ngắt nhận dữ liệu cần được cấu hình đúng trình tự.

3.1. Cấu Hình GPIO Cho LED

void configure_led_gpio(void)
{
    GPIO_InitTypeDef gpio_cfg;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    
    gpio_cfg.GPIO_Pin = GPIO_Pin_13;
    gpio_cfg.GPIO_Speed = GPIO_Speed_50MHz;
    gpio_cfg.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOC, &gpio_cfg);
}

3.2. Cấu Hình USART1 Và Ngắt NVIC

void configure_uart1(uint32_t baud_rate)
{
    GPIO_InitTypeDef gpio_cfg;
    USART_InitTypeDef uart_cfg;
    NVIC_InitTypeDef nvic_cfg;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
    USART_DeInit(USART1);

    // Chân TX (PA9)
    gpio_cfg.GPIO_Pin = GPIO_Pin_9;
    gpio_cfg.GPIO_Mode = GPIO_Mode_AF_PP;
    gpio_cfg.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &gpio_cfg);

    // Chân RX (PA10)
    gpio_cfg.GPIO_Pin = GPIO_Pin_10;
    gpio_cfg.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &gpio_cfg);

    uart_cfg.USART_BaudRate = baud_rate;
    uart_cfg.USART_WordLength = USART_WordLength_8b;
    uart_cfg.USART_StopBits = USART_StopBits_1;
    uart_cfg.USART_Parity = USART_Parity_No;
    uart_cfg.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    uart_cfg.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &uart_cfg);

    nvic_cfg.NVIC_IRQChannel = USART1_IRQn;
    nvic_cfg.NVIC_IRQChannelPreemptionPriority = 2;
    nvic_cfg.NVIC_IRQChannelSubPriority = 2;
    nvic_cfg.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&nvic_cfg);

    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    USART_Cmd(USART1, ENABLE);
}

4. Xử Lý Ngắt UART Và Phân Tích Lệnh

Khi dữ liệu đến chân RX, ngắt USART_IT_RXNE được kích hoạt. Trình phục vụ ngắt sẽ đọc từng byte, lưu vào bộ đệm vòng và kiểm tra ký tự kết thúc dòng (\r\n). Khi nhận đủ một dòng lệnh, hệ thống sẽ gọi hàm phân tích.

void USART1_IRQHandler(void)
{
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        uint8_t received_byte = USART_ReceiveData(USART1);

        // Kiểm tra tràn bộ đệm và reset chỉ số
        if (rx_write_index >= UART_BUFFER_SIZE) {
            rx_write_index = 0;
        }

        uart_rx_buffer[rx_write_index++] = received_byte;

        // Phát hiện ký tự xuống dòng \r\n
        if (rx_write_index >= 2 && 
            uart_rx_buffer[rx_write_index - 1] == '\n' && 
            uart_rx_buffer[rx_write_index - 2] == '\r') 
        {
            uart_rx_buffer[rx_write_index - 2] = '\0'; // Kết thúc chuỗi
            execute_uart_command(uart_rx_buffer);
            rx_write_index = 0; // Xóa bộ đệm cho lần nhận tiếp theo
        }
    }
}

Hàm phân tích lệnh sẽ so sánh chuỗi nhận được với các từ khóa định sẵn. Nếu khớp, thao tác GPIO tương ứng được thực thi và phản hồi trạng thái qua UART.

int execute_uart_command(uint8_t *cmd_str)
{
    if (strcmp((char*)cmd_str, "LED_ON") == 0) {
        TURN_LED_ON();
        uart_transmit_string((uint8_t*)"SUCCESS: LED ENABLED\r\n");
        return 0;
    } 
    else if (strcmp((char*)cmd_str, "LED_OFF") == 0) {
        TURN_LED_OFF();
        uart_transmit_string((uint8_t*)"SUCCESS: LED DISABLED\r\n");
        return 0;
    } 
    else {
        uart_transmit_string((uint8_t*)"INVALID_CMD\r\n");
        return -1;
    }
}

5. Hàm Truyền Dữ Liệu Qua UART

Việc gửi dữ liệu được thực hiện bằng cách chờ cờ TXE (Transmit Data Register Empty) trước khi đẩy byte tiếp tục vào thanh ghi dữ liệu.

void uart_transmit_byte(uint8_t byte_data)
{
    USART_SendData(USART1, byte_data);
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

void uart_transmit_string(uint8_t *str_data)
{
    while (*str_data != '\0') {
        uart_transmit_byte(*str_data++);
    }
}

6. Hàm Chính

Vòng lặp chính chỉ cần duy trì hoạt động của hệ thống. Toàn bộ logic điều khiển đã được xử lý bất đồng bộ thông qua cơ chế ngắt UART.

int main(void)
{
    bool_t system_ready = LOGIC_FALSE;
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    configure_uart1(115200);
    configure_led_gpio();
    
    system_ready = LOGIC_TRUE;
    
    while (1)
    {
        sys_tick_delay_ms(500);
        // Các tác vụ nền khác có thể được thêm tại đây
    }
}

Thẻ: STM32 UART GPIO NVIC SysTick

Đăng vào ngày 2 tháng 10 lúc 06:04