Redirect printf bằng _write() trên STM32 là gì?

Giải thích cách printf trong newlib/newlib-nano đi qua _write() và được redirect ra UART trên STM32.

2 phút đọc
STM32 / Firmware cover

Ghi chú nhanh

Trong STM32CubeIDE dùng GCC/newlib, printf() thường được redirect ra UART bằng cách override hàm _write().

Ví dụ quen thuộc:

int _write(int file, char *ptr, int len)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, HAL_MAX_DELAY);
    return len;
}

Ý tưởng là: thư viện C xử lý format string trước, sau đó gọi _write() để gửi chuỗi byte ra output.

Vì sao lại là _write()?

printf() không tự biết UART của bạn nằm ở đâu. Với embedded GCC/newlib, các hàm chuẩn như printf() cần một “cổng ra” thấp hơn để ghi dữ liệu.

_write() chính là một syscall stub mà bạn có thể tự định nghĩa.

Nói đơn giản:

printf("value=%d", x)
  -> format thành chuỗi byte
  -> gọi _write()
  -> firmware quyết định gửi byte đi đâu

Bạn có thể gửi ra UART, SWO, USB CDC, hoặc thậm chí ghi vào RAM buffer.

Khi nào gặp khái niệm này?

Bạn sẽ gặp _write() khi:

  • làm redirect printf() ra UART;
  • tắt semihosting;
  • dùng newlib/newlib-nano;
  • đọc tutorial STM32 debug bằng UART;
  • muốn thay HAL_UART_Transmit() blocking bằng logger non-blocking.

Điểm cần cẩn thận

Cách redirect phổ biến nhất thường dùng HAL_UART_Transmit() blocking. Nó dễ hiểu, dễ chạy, nhưng không hề miễn phí.

HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, HAL_MAX_DELAY);

Nếu log dài hoặc baudrate thấp, CPU sẽ chờ UART truyền xong. Trong một project nhỏ thì không sao. Nhưng trong firmware có timing nhạy, một dòng printf() vô tình có thể làm thay đổi hành vi hệ thống.

Một bẫy nhỏ về kích thước dữ liệu

Không nên giả định _write() luôn nhận nguyên một dòng log hoàn chỉnh. Tùy cấu hình thư viện C, buffering và cách gọi, dữ liệu có thể đi xuống theo các chunk khác nhau. Vì vậy _write() nên xử lý theo ptrlen, không nên dựa vào ký tự \0 như string C thông thường.

Ví dụ đúng hướng:

int _write(int file, char *ptr, int len)
{
    Debug_UART_Write((const uint8_t *)ptr, (uint16_t)len);
    return len;
}

Cách nhớ

printf() phụ trách format.
_write() phụ trách đưa byte ra ngoài.
UART chỉ là một trong nhiều output có thể chọn.

Bài liên quan nên đọc tiếp

Thấy nội dung này hữu ích?

Lưu lại hoặc chia sẻ cho người cũng đang học firmware, BIOS/UEFI và embedded systems.

Nội dung liên quan

Một số bài viết, ghi chú hoặc project có liên quan đến nội dung bạn vừa đọc.

Biến note thành bài viết hoàn chỉnh

Notes là nơi ghi nhanh khái niệm.