0

Vì sao thêm HAL_Delay() thì UART mới nhận được dữ liệu – hiểu đúng về volatile và tối ưu hóa compiler

Chương trình

#include "main.h"
#include "ThuVien_UART.h"


/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

/* Private user code ---------------------------------------------------------*/
// Bien Global de xu li module USART
char temp_char;
bool flag_rx_done = false;
char BufferData[30];


void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    if(huart->Instance == USART1) {
    // Ham nhan chuoi du lieu
    strcpy(BufferData, receive_string(temp_char)); 
    
    // Bat interrupt cho ki tu tiep theo
    HAL_UART_Receive_IT(&huart1, (uint8_t *)&temp_char, 1);
    }
}

int main(void) {
  /* MCU Configuration--------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();

  start_receive_string(); // Ham bat dau nhan du lieu

  /* Infinite loop */
  while (1) {
      // điều kiện đúng khi flax_rx_done = true;
        if (flag_rx_done) {
            flag_rx_done = 0;
            send_string("Received: ");
            send_string(BufferData);
            send_string("\r\n");
        }
        HAL_Delay(1000);
  }
}

Tại khi khi bỏ HAL_DELAY(1000) thì không nhận được chuỗi user nhập từ terminal.

Trước tiên ta sẽ nói về flag_rx_done là một biến toàn cục, được sử dụng để nhận biết được quá trình nhận dữ liệu từ terminal đã kết thúc.

  • Khi đó flax_rx_done = true.

Ban đầu, compiler nhận thấy

flax_rx_done chỉ được thay đổi trong main().

→ Vậy cho nên

  • Mỗi lần while(1) lặp lại, giá trị của flag_rx_done chắn chắc không thay đổi, vì trong main() không có ai sửa nó.
  • Do đó, để tối ưu hiệu năng, chỉ cần đọc flag_rx_done chỉ một lần từ RAM và lưu trữ vào trong 1 thanh ghi bất kì của CPU, ví dụ ở đây là r3 .

Sau đó mỗi lần, while(1) chạy lại thì chỉ cần đọc r3 chứ không phải từ RAM nữa.

💡 Vấn đề

  • ISR thay đổi flag_rx_done trong RAM (set = 1).
  • Nhưng CPU đang đọc r2 (thanh ghi), không đọc lại từ RAM → không thấy thay đổi. → Vậy điều kiện trong while(1) sẽ không bao giờ được thực thi.

Do đó ta cần thêm từ khóa volatile vào trước bool flag_rx_done để vdk là phải đọc từ RAM mỗi khi truy cập vào flax_rx_done.

Vậy điều gì xảy ra khi ta thêm HAL_Delay() thì mọi thứ lại hoạt động

  • HAL_Delay() hoạt động dựa trên interrupt systemtick của hệ thống. Khi ngắt xảy ra CPU sẽ lưu trữ mọi thứ trên stack(RAM ) - context switch để đi vào ISR và thực thi. Khi thực thi xong ISR, thì CPU sẽ load lại giá trị của các thanh ghi từ RAM.
  • Và compiler không thể đảm bảo các thanh ghi đều "đúng", nên có đọc lại các biến global từ RAM → Điều này đã giúp cập nhập giá trị của flag_rx_done = true, giúp chương trình có thể nhận dữ liệu từ terminal và xử lí bình thường, nhưng cho dù là vậy thì về nguyên tắc vẫn hk chính xác.

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí