🌱 STM32 - 14. Lập trình Thanh ghi STM32 - Ngoại vi UART (Pooling)
Post này sẽ tập trung vào quá trình khởi tạo, truyền, nhận qua ngoại vi USART của vi điều khiển STM32. Dù bạn lập trình thanh ghi hay thư viện thì cũng đều tuân thủ theo Workflow cơ bản này. (Mình sẽ chủ yếu nói đến quá trình truyền/nhận theo kiểu Polling).
Để thực hành với USART, các bạn cần nắm được các kiến thức cơ bản và các thanh ghi của nó:
- Tổng quan về USART trong STM32
- Các thanh ghi sử dụng trong USART (STM32F401)
- Tính toán USART Baudrate
👉 Khởi tạo USART
Việc khởi tạo USART, chúng ta sẽ khởi tạo 2 đặc điểm quan trọng nhất của USART là khung truyền (bao gồm độ dài khung truyền, số bit Stop) và tốc độ Baudrate.
- Cấu hình bit M trong thanh ghi USART_CR1 để xác định độ dài khung truyền là 8 bits hay 9 bits (Word Length).
- Cấu hình số bit Stop bằng bit STOP trong thanh ghi USART_CR2.
- Tính toán Baudrate và cấu hình bằng thanh ghi USART_BRR.
- Cho phép quá trình truyền dữ liệu bằng bit TE trên thanh ghi USART_CR1.
- Cho phép quá trình nhận dữ liệu bằng bit RE trên thanh ghi USART_CR1.
- Cho phép ngoại vi USART bằng bit UE trên thanh ghi USART_CR1.
Các bước 4, 5 có thể chọn 1 trong 2 nếu USART của chúng ta chỉ cho phép truyền hoặc nhận.
➤ Định nghĩa các thanh ghi cần thiết (UART, Clock, GPIO)
- // Base address definitions
- #define RCC_BASE 0x40023800
- #define GPIOA_BASE 0x40020000
- #define USART2_BASE 0x40004400
- // RCC register definitions
- #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))
- // GPIOA register definitions
- #define GPIOA_MODER (*(volatile uint32_t *)(GPIOA_BASE + 0x00))
- #define GPIOA_OTYPER (*(volatile uint32_t *)(GPIOA_BASE + 0x04))
- #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))
- // USART2 register definitions
- #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))
- // RCC bit definitions
- #define RCC_CR_HSION (1 << 0)
- #define RCC_CR_HSIRDY (1 << 1)
- #define RCC_CFGR_SW_HSI (0 << 0)
- #define RCC_CFGR_HPRE_DIV1 (0 << 4)
- #define RCC_CFGR_PPRE1_DIV2 (4 << 10)
- #define RCC_AHB1ENR_GPIOAEN (1 << 0)
- #define RCC_APB1ENR_USART2EN (1 << 17)
- // GPIO bit definitions
- #define GPIO_MODER_AF 2
- #define GPIO_OSPEEDR_HIGH 3
- #define GPIO_PUPDR_NONE 0
- #define GPIO_AF7_USART2 7
- // USART_CR1 bit definitions
- #define USART_CR1_UE (1 << 13)
- #define USART_CR1_TE (1 << 3)
- #define USART_CR1_RE (1 << 2)
- // USART_CR2 bit definitions
- #define USART_CR2_STOP_1BIT (0 << 12) // 1 stop bit
- // USART_SR bit definitions
- #define USART_SR_TXE (1 << 7)
- #define USART_SR_TC (1 << 6)
- #define USART_SR_RXNE (1 << 5)
- #define USART_SR_ORE (1 << 3) // Overrun Error
- #define USART_SR_FE (1 << 1) // Framing Error
- #define USART_SR_PE (1 << 0) // Parity Error
➤ Khởi tạo các module dependency cần thiết - Clock, GPIO
- // Clock configuration function
- 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_DIV1;
- RCC_CFGR |= RCC_CFGR_PPRE1_DIV2; // PCLK1 = 12 MHz
- }
- // GPIO configuration for USART2 (PA2-TX, PA3-RX)
- void GPIO_Init(void)
- {
- RCC_AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
- // Set PA2 (TX) and PA3 (RX) to 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));
- // High speed
- GPIOA_OSPEEDR &= ~(3 << (2 * 2));
- GPIOA_OSPEEDR |= (GPIO_OSPEEDR_HIGH << (2 * 2));
- GPIOA_OSPEEDR &= ~(3 << (3 * 2));
- GPIOA_OSPEEDR |= (GPIO_OSPEEDR_HIGH << (3 * 2));
- // No pull-up/pull-down
- GPIOA_PUPDR &= ~(3 << (2 * 2));
- GPIOA_PUPDR &= ~(3 << (3 * 2));
- // AF7 for 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));
- }
➤ Khởi tạo module UART với Baudrate 9600
- // USART2 initialization function
- void USART2_Init(void)
- {
- RCC_APB1ENR |= RCC_APB1ENR_USART2EN;
- // Configure Baudrate = 9600
- USART2_BRR = (78 << 4) | 2; // 0x04E2
- // Configure CR1: 8 bits, no parity, enable TX/RX
- USART2_CR1 = 0;
- USART2_CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
- // Configure CR2: 1 stop bit
- USART2_CR2 = 0;
- USART2_CR2 |= USART_CR2_STOP_1BIT;
- // Configure CR3: No hardware flow control
- USART2_CR3 = 0;
- }
👉 Cấu hình truyền USART - TX
- Kiểm tra xem thanh ghi data có trống hay không bằng cờ TXE, nếu cờ TXE = 1 thì có thể ghi dữ liệu vào thanh ghi data để gửi đi.
- Ghi dữ liệu vào thanh ghi data USART_DR.
- Đợi đến khi data truyền xong bằng cờ TC (TC = 1).
➤ Code truyền dữ liệu qua USART (Hàm truyền 1 byte và 1 chuỗi - Polling)
- // Function to transmit a character with error checking
- void USART2_TransmitChar(uint8_t data)
- {
- while (!(USART2_SR & USART_SR_TXE));
- USART2_DR = data;
- while (!(USART2_SR & USART_SR_TC));
- }
- // Function to transmit a string
- void USART2_TransmitString(const char *str)
- {
- while (*str) {
- USART2_TransmitChar(*str++);
- }
- }
👉 Cấu hình nhận USART - RX
- Đợi đến khi nhận dữ liệu xong bằng cờ RXNE, khi nhận xong thì cờ RXNE = 1.
- Đọc dữ liệu từ thanh ghi data USART_DR.
➤ Code nhận dữ liệu qua USART (Hàm nhận 1 byte - Polling)
- // Function to receive a character with error checking
- uint8_t USART2_ReceiveChar(void)
- {
- while (!(USART2_SR & USART_SR_RXNE))
- {
- // Check for errors
- if (USART2_SR & (USART_SR_ORE | USART_SR_FE | USART_SR_PE))
- {
- // Error handling: Read DR to clear error flags
- (void)USART2_DR;
- return 0xFF; // Return error value
- }
- }
- return (uint8_t)USART2_DR;
- }
👉 Ví dụ code minh họa Workflow
Dưới đây là full đoạn code bare-metal minh họa Workflow khởi tạo, truyền và nhận dữ liệu qua USART2 trên STM32F401:
- #include <stdint.h> // Add header for standard data types
- // Base address definitions
- #define RCC_BASE 0x40023800
- #define GPIOA_BASE 0x40020000
- #define USART2_BASE 0x40004400
- // RCC register definitions
- #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))
- // GPIOA register definitions
- #define GPIOA_MODER (*(volatile uint32_t *)(GPIOA_BASE + 0x00))
- #define GPIOA_OTYPER (*(volatile uint32_t *)(GPIOA_BASE + 0x04))
- #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))
- // USART2 register definitions
- #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))
- // RCC bit definitions
- #define RCC_CR_HSION (1 << 0)
- #define RCC_CR_HSIRDY (1 << 1)
- #define RCC_CFGR_SW_HSI (0 << 0)
- #define RCC_CFGR_HPRE_DIV1 (0 << 4)
- #define RCC_CFGR_PPRE1_DIV2 (4 << 10)
- #define RCC_AHB1ENR_GPIOAEN (1 << 0)
- #define RCC_APB1ENR_USART2EN (1 << 17)
- // GPIO bit definitions
- #define GPIO_MODER_AF 2
- #define GPIO_OSPEEDR_HIGH 3
- #define GPIO_PUPDR_NONE 0
- #define GPIO_AF7_USART2 7
- // USART_CR1 bit definitions
- #define USART_CR1_UE (1 << 13)
- #define USART_CR1_TE (1 << 3)
- #define USART_CR1_RE (1 << 2)
- // USART_CR2 bit definitions
- #define USART_CR2_STOP_1BIT (0 << 12) // 1 stop bit
- // USART_SR bit definitions
- #define USART_SR_TXE (1 << 7)
- #define USART_SR_TC (1 << 6)
- #define USART_SR_RXNE (1 << 5)
- #define USART_SR_ORE (1 << 3) // Overrun Error
- #define USART_SR_FE (1 << 1) // Framing Error
- #define USART_SR_PE (1 << 0) // Parity Error
- // Simple delay function (using loop)
- void Delay(uint32_t count) {
- while (count--) {
- __asm("nop"); // NOP instruction to create delay
- }
- }
- // Clock configuration function
- 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_DIV1;
- RCC_CFGR |= RCC_CFGR_PPRE1_DIV2; // PCLK1 = 12 MHz
- }
- // GPIO configuration for USART2 (PA2-TX, PA3-RX)
- void GPIO_Init(void) {
- RCC_AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
- // Set PA2 (TX) and PA3 (RX) to 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));
- // High speed
- GPIOA_OSPEEDR &= ~(3 << (2 * 2));
- GPIOA_OSPEEDR |= (GPIO_OSPEEDR_HIGH << (2 * 2));
- GPIOA_OSPEEDR &= ~(3 << (3 * 2));
- GPIOA_OSPEEDR |= (GPIO_OSPEEDR_HIGH << (3 * 2));
- // No pull-up/pull-down
- GPIOA_PUPDR &= ~(3 << (2 * 2));
- GPIOA_PUPDR &= ~(3 << (3 * 2));
- // AF7 for 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));
- }
- // USART2 initialization function
- void USART2_Init(void)
- {
- RCC_APB1ENR |= RCC_APB1ENR_USART2EN;
- // Configure Baudrate = 9600
- USART2_BRR = (78 << 4) | 2; // 0x04E2
- // Configure CR1: 8 bits, no parity, enable TX/RX
- USART2_CR1 = 0;
- USART2_CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
- // Configure CR2: 1 stop bit
- USART2_CR2 = 0;
- USART2_CR2 |= USART_CR2_STOP_1BIT;
- // Configure CR3: No hardware flow control
- USART2_CR3 = 0;
- }
- // Function to transmit a character with error checking
- void USART2_TransmitChar(uint8_t data) {
- while (!(USART2_SR & USART_SR_TXE));
- USART2_DR = data;
- while (!(USART2_SR & USART_SR_TC));
- }
- // Function to transmit a string
- void USART2_TransmitString(const char *str) {
- while (*str) {
- USART2_TransmitChar(*str++);
- }
- }
- // Function to receive a character with error checking
- uint8_t USART2_ReceiveChar(void)
- {
- while (!(USART2_SR & USART_SR_RXNE))
- {
- // Check for errors
- if (USART2_SR & (USART_SR_ORE | USART_SR_FE | USART_SR_PE))
- {
- // Error handling: Read DR to clear error flags
- (void)USART2_DR;
- return 0xFF; // Return error value
- }
- }
- return (uint8_t)USART2_DR;
- }
- int main(void) {
- SystemClock_Config();
- GPIO_Init();
- USART2_Init();
- // Transmit "Hello" string with delay
- USART2_TransmitString("Hello\r\n");
- Delay(1000000); // Wait about 1 second
- while (1) {
- uint8_t received = USART2_ReceiveChar();
- if (received != 0xFF) { // Not an error
- USART2_TransmitChar(received); // Echo
- }
- }
- }
Ok, bài này đến đây thôi, bài sau mình sẽ lấy một ví dụ về các hàm truyền nhận để các bạn hiểu rõ hơn về USART.
>>>>>> 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 😊
Anh lm thêm về SPI, I2C vs CAN đi anh, cảm ơn anh nhiều ạ.
Trả lờiXóaOk em có thời gian anh sẽ làm thêm về những giao thức khác :D
Xóa