10 In dữ liệu qua UART 
Tài liệu:
UART - Universal Asynchronous Receiver Transmitter
Một số hình ảnh từ bài viết lấy từ tài liệu này.
Quy trình cấu hình 
Khi sử dụng UART để truyền dữ liệu, ta thường thực hiện các bước sau:
- Bật clock (cả clock của UART và của GPIO)
- Cấu hình chế độ chân GPIO làm chức năng thay thế (AF)
- Cấu hình chế độ hoạt động của GPIO
- Cấu hình xuất tín hiệu cho GPIO
- Cấu hình UART (baudrate, stop bit, parity, v.v.)
- Kích hoạt UART (cho phép UART và truyền)
Bật Clock 
Sử dụng UART1 nghĩa là dùng các chân PA9 (TX) và PA10 (RX). Trước tiên ta cần bật clock cho GPIOA và cho USART1. Dùng hàm RCC_APB2PeriphClockCmd để bật clock cho USART1 và RCC_AHB1PeriphClockCmd cho GPIOA.
#define BSP_USART_RCC       RCC_APB2Periph_USART1
#define BSP_USART_TX_RCC    RCC_AHB1Periph_GPIOA
#define BSP_USART_RX_RCC    RCC_AHB1Periph_GPIOAĐoạn mã bật clock tương ứng là
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);    // Bật clock cho USART1
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);      // Bật clock cho GPIOACấu Hình Chế Độ Chân GPIO (AF) 
Các chân trên STM32 có chức năng đa năng (Alternate Function). Mặc định là GPIO, nhưng ta có thể chuyển sang chức năng UART. Hàm sử dụng:
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF);Tra cứu trong datasheet ta có thể thấy bảng Table 9. Alternative function mapping

Từ hình minh họa có thể thấy chân PA9 tương ứng với chức năng USART1_TX ở chế độ AF7, và PA10 tương ứng với chức năng USART1_RX cũng ở AF7. Trong thư viện chuẩn, các chức năng này đã được định nghĩa sẵn nên ta có thể sử dụng trực tiếp macro định nghĩa GPIO_AF_USART1. Trước tiên, ta cần định nghĩa các macro cho cổng và chức năng thay thế như sau:
#define BSP_USART               USART1
#define BSP_USART_TX_PORT       GPIOA
#define BSP_USART_TX_PIN        GPIO_Pin_9
#define BSP_USART_RX_PORT       GPIOA
#define BSP_USART_RX_PIN        GPIO_Pin_10
#define BSP_USART_AF            GPIO_AF_USART1
#define BSP_USART_TX_AF_PIN     GPIO_PinSource9
#define BSP_USART_RX_AF_PIN     GPIO_PinSource10Sau đó, gọi hàm cấu hình để kích hoạt chức năng thay thế (AF) cho các chân
// Khi sử dụng chân IO làm chân UART, cần cấu hình chế độ chức năng thay thế (AF)
GPIO_PinAFConfig(BSP_USART_TX_PORT, BSP_USART_TX_AF_PIN, BSP_USART_AF);
GPIO_PinAFConfig(BSP_USART_RX_PORT, BSP_USART_RX_AF_PIN, BSP_USART_AF);Chỉ với hai dòng lệnh trên, ta đã cấu hình PA9 và PA10 thành chân chức năng UART.
Cấu hình GPIO 
Việc cấu hình chế độ cho chân GPIO vẫn sử dụng hàm GPIO_Init, tuy nhiên khác biệt ở chỗ tham số thứ hai cần đặt là chế độ chức năng thay thế (AF) thay vì chế độ output, và tham số thứ ba cần cấu hình là kéo lên (pull-up). Mã ví dụ như sau:
GPIO_InitTypeDef GPIO_InitStructure;
// Khởi tạo lại cấu trúc cấu hình GPIO với giá trị mặc định
GPIO_StructInit(&GPIO_InitStructure);
// Cấu hình chân TX (PA9)
GPIO_InitStructure.GPIO_Pin   = BSP_USART_TX_PIN;    // Chân TX GPIO_Pin_9 đã được define là BSP_USART_TX_PIN ở trên
GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;        // Chế độ chức năng thay thế (AF) để dùng làm UART
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;   // Tốc độ cao
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;       // Kiểu push-pull
GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;        // Kéo lên (pull-up)
GPIO_Init(GPIOA, &GPIO_InitStructure);               // Áp dụng cấu hình cho cổng A
// Cấu hình chân RX (PA10)
GPIO_StructInit(&GPIO_InitStructure);                // Đặt lại cấu hình
GPIO_InitStructure.GPIO_Pin   = BSP_USART_RX_PIN;    // Chân RX GPIO_Pin_10 đã được define là BSP_USART_RX_PIN ở trên
GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;        // Chế độ chức năng thay thế
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);               // Áp dụng cấu hình cho cổng ACấu Hình UART 
Chúng ta vẫn sử dụng cấu trúc USART_InitTypeDef để cấu hình các tham số cho UART. Một số thông số bắt buộc cần phải thiết lập gồm:
- Tốc độ baud (Baudrate): sử dụng tham số truyền vào __Baudđể linh hoạt điều chỉnh
- Độ dài dữ liệu: 8 bit
- Số bit dừng: 1 bit
- Bit chẵn lẻ (Parity): không sử dụng
- Chế độ truyền/nhận: bật cả hai chiều (TX/RX)
- Điều khiển luồng: không dùng (None)
Đoạn mã tương ứng:
// Khởi tạo cấu trúc USART_InitStructure với giá trị mặc định
USART_StructInit(&USART_InitStructure);
// Cấu hình các tham số UART
USART_InitStructure.USART_BaudRate            = __Baud;                        // Cấu hình baudrate theo tham số truyền vào
USART_InitStructure.USART_WordLength          = USART_WordLength_8b;          // Độ dài dữ liệu: 8 bit
USART_InitStructure.USART_StopBits            = USART_StopBits_1;             // Sử dụng 1 bit stop
USART_InitStructure.USART_Parity              = USART_Parity_No;              // Không sử dụng bit chẵn lẻ
USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;// Bật cả hai chế độ nhận và truyền
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// Không sử dụng điều khiển luồng (flow control)
// Áp dụng cấu hình cho UART1 (hoặc UART được định nghĩa bởi BSP_USART)
USART_Init(BSP_USART, &USART_InitStructure);
// Xoá cờ RXNE (cờ báo có dữ liệu trong bộ đệm nhận), tránh lỗi ngẫu nhiên khi khởi động
USART_ClearFlag(BSP_USART, USART_FLAG_RXNE);Giải thích thêm:
USART_StructInit()đảm bảo cấu trúc được gán các giá trị mặc định ban đầu trước khi chỉnh sửa.
USART_Init()sẽ thực hiện việc áp dụng cấu hình lên phần cứng USART.
USART_ClearFlag()được gọi để đảm bảo không có dữ liệu "rác" từ lần sử dụng trước hoặc từ khởi động không sạch.
USART_WordLength_8b= mỗi lần truyền gửi đúng 1 byte dữ liệu (8 bit)
Kích Hoạt UART 
Sau khi cấu hình xong, UART vẫn chưa thể hoạt động ngay — ta cần kích hoạt (enable) nó, giống như việc bật một công tắc. Để UART hoạt động, cần bật công tắc tổng như sau:
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
Chuyển thành mã cụ thể:
USART_Cmd(BSP_USART,ENABLE);    // Bật UART1Đến đây, UART đã được kích hoạt và sẵn sàng hoạt động.
Gửi Dữ Liệu Qua UART 
Sau khi cấu hình UART xong, bước tiếp theo là gửi dữ liệu.
Hàm gửi một byte dữ liệu:
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
Hàm kiểm tra trạng thái của bộ truyền:
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)
Hàm này được dùng để kiểm tra các cờ trạng thái trong thanh ghi trạng thái của UART. Các cờ này cho biết trạng thái hiện tại của việc truyền/nhận dữ liệu. Các bit trạng thái cụ thể được hiển thị trong hình minh họa.

Từ hình minh họa có thể thấy rằng, khi ghi dữ liệu vào thanh ghi USART_DATA, bit trạng thái tương ứng sẽ bị xóa về 0, tức là dữ liệu đang được truyền đi. Khi dữ liệu gửi xong, bit này sẽ được đặt thành 1, cho biết rằng bộ đệm truyền đã trống và có thể tiếp tục gửi dữ liệu mới.
Chúng ta có thể đóng gói quá trình gửi dữ liệu qua UART thành một hàm như sau:
// Hàm gửi một byte dữ liệu qua UART
void usart_send_data(uint8_t ucch)
{
    // Ghi dữ liệu vào thanh ghi truyền của UART
    USART_SendData(BSP_USART, (uint8_t)ucch);
    // Chờ cho đến khi bộ đệm truyền (TX buffer) trống (bit TXE = 1)
    while (RESET == USART_GetFlagStatus(BSP_USART, USART_FLAG_TXE)) {}
}Khi chúng ta gọi
usart_send_data('h');
usart_send_data('e');
usart_send_data('l');
usart_send_data('l');
usart_send_data('o');

Kết quả là chữ hello sẽ được in ra trong cửa sổ UART.

Việc in từng ký tự một như vậy khá phiền phức phải không? Không sao, chúng ta có thể đóng gói thêm một lớp nữa để gửi cả một chuỗi cùng lúc.
// Hàm gửi một chuỗi ký tự qua UART
void usart_send_String(uint8_t *ucstr)
{
    // Lặp cho đến khi con trỏ bằng NULL hoặc ký tự hiện tại là '\0'
    while (ucstr && *ucstr)  // Nếu địa chỉ NULL hoặc kết thúc chuỗi thì thoát
    {
        usart_send_data(*ucstr++);  // Gửi từng ký tự một
    }
}
// Gọi hàm để gửi chuỗi "hello" kèm ký tự xuống dòng
usart_send_String("hello\r\n");Kết quả là chữ hello sẽ được in ra trong cửa sổ UART.
Chuyển Hướng UART Cho printf 
Hàm gửi chuỗi mà ta vừa viết ở trên giúp việc in thông tin trở nên thuận tiện hơn. Tuy nhiên, nếu bạn muốn in ra số nguyên, số thực thì sao? Lúc này, hẳn bạn sẽ muốn sử dụng hàm printf quen thuộc để in ra các giá trị với định dạng như %d, %f, v.v.
Phần này sẽ hướng dẫn cách chuyển hướng đầu ra của printf sang UART, để có thể sử dụng printf như bình thường và các kết quả sẽ được in ra qua cổng UART.
Giới Thiệu Về Chuyển Hướng UART 
Trong ngôn ngữ C, hàm printf mặc định sẽ xuất dữ liệu ra màn hình (stdout). Tuy nhiên, nếu bạn muốn hiển thị kết quả qua cổng UART, bạn cần định nghĩa lại các hàm liên quan đến thiết bị xuất dữ liệu trong thư viện chuẩn.
Một lưu ý quan trọng: khi sử dụng printf trong Keil, bạn cần bật tùy chọn "Use MicroLib" (sử dụng thư viện nhỏ) để đảm bảo hoạt động đúng và tránh lỗi liên quan đến thư viện chuẩn C.
Chuyển hướng printf sang UART 
Trong ngôn ngữ C, hàm printf thực chất liên tục gọi hàm fputc để xuất từng ký tự một. Vì vậy, nếu muốn printf hoạt động với UART, chúng ta cần viết lại (ghi đè) hàm fputc.
Chức năng của fputc là xuất một ký tự ra thiết bị đầu ra — và điều này giống hệt như những gì hàm usart_send_data mà ta đã viết làm được.
Hàm fputc có thể được định nghĩa lại như sau:
#if !defined(__MICROLIB)
// Nếu không sử dụng MicroLib thì cần thêm các hàm sau
#if (__ARMCLIB_VERSION <= 6000000)
// Nếu trình biên dịch là ARMCC v5 thì cần định nghĩa lại cấu trúc FILE
struct __FILE
{
    int handle;
};
#endif
FILE __stdout;  // Định nghĩa đối tượng xuất chuẩn
// Định nghĩa hàm _sys_exit để tránh lỗi khi kết thúc chương trình (tránh dùng semihosting)
void _sys_exit(int x)
{
    x = x;  // Tránh warning biến không dùng
}
#endif
/* Chuyển hướng hàm printf trong thư viện C để xuất ra UART */
int fputc(int ch, FILE *f)
{
    // Gửi ký tự qua UART
    USART_SendData(BSP_USART, (uint8_t)ch);
    // Chờ đến khi bộ đệm truyền trống (TXE được set)
    while (RESET == USART_GetFlagStatus(BSP_USART, USART_FLAG_TXE)) {}
    return ch;  // Trả về ký tự đã gửi
}Sau khi viết xong hàm fputc, bạn đã có thể sử dụng printf để in thông tin ra UART.
Hiện Tượng Thử Nghiệm 
Mã nguồn của chương này nằm trong:
https://github.com/leybme/stm32f407_resource/tree/main/C03%20Code/Code%20Sample/005Print%20information%20via%20UART
Sau khi nạp chương trình vào bo mạch, mở phần mềm giao tiếp UART (UART terminal), bạn sẽ thấy mỗi 1 giây sẽ in ra một dòng thông tin, bao gồm:
- Một số nguyên, tăng dần mỗi giây (ví dụ: 1, 2, 3, …)
- Một số thực, tăng mỗi lần khoảng 0.11
Kết quả hiển thị sẽ giống như hình minh họa.
