🌱 STM32 - 16. Lập trình Thanh ghi STM32 - UART Interrupt
Ở bài viết trước mình đã giới thiệu với các bạn về một ví dụ giao tiếp UART với PC, chế độ Polling, ở bài viết này mình sẽ sử dụng Interrupt để giao tiếp. Một ưu điểm dễ thấy của Interrupt đó là khi có những sự kiện quan trọng xảy ra chúng ta có thể phát hiện và xử lý ngay lập tức.
Xem bài: Các kỹ thuật thiết kế luồng xử lý trong chương trình nhúng
👉 Phần cứng sử dụng:
- STM32F401RE - NUCLEO Board.
- CP2102 USB-to-UART.
👉 Phần mềm sử dụng:
- STM32CubeIDE ⇒ Xem hướng dẫn sử dụng.
- Hercules.
Việc dùng ngắt giúp công việc này dễ dàng hơn rất nhiều. Chẳng hạn, khi muốn cập nhật Firmware, chúng ta có thể truyền UART đến Vi điều khiển và STM32 sẽ không để lỡ sự kiện này. Khi chúng ta setup ngắt UART ở mức ưu tiên cao để tránh những sự kiện khác xen vào.
Khác với chế độ Polling, UART Interrupt có một số đặc điểm khác biệt sau:
- Ở hàm Init, chúng ta cần Enable ngắt Truyền/Nhận UART.
- Viết Hàm IRQ Handler.
👉 Ví dụ lập trình UART Interrupt
Dưới đây là đoạn code bare-metal cho USART2 (PA2-TX, PA3-RX) sử dụng ngắt để giao tiếp với PC:
#include// Định nghĩa địa chỉ cơ bản #define RCC_BASE 0x40023800 #define GPIOA_BASE 0x40020000 #define USART2_BASE 0x40004400 #define NVIC_BASE 0xE000E100 // Định nghĩa thanh ghi RCC #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)) // Định nghĩa thanh ghi GPIOA #define GPIOA_MODER (*(volatile uint32_t *)(GPIOA_BASE + 0x00)) #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)) // Định nghĩa thanh ghi USART2 #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)) // Định nghĩa thanh ghi NVIC #define NVIC_ISER1 (*(volatile uint32_t *)(NVIC_BASE + 0x004)) // NVIC Interrupt Set-Enable Register 1 // Định nghĩa bit trong RCC #define RCC_CR_HSION (1 << 0) #define RCC_CR_HSIRDY (1 << 1) #define RCC_CFGR_SW_HSI (0 << 0) #define RCC_CFGR_HPRE_DIV2 (1 << 4) // AHB = HSI/2 = 8MHz #define RCC_AHB1ENR_GPIOAEN (1 << 0) #define RCC_APB1ENR_USART2EN (1 << 17) // Định nghĩa bit trong GPIO #define GPIO_MODER_AF 2 #define GPIO_OSPEEDR_HIGH 3 #define GPIO_AF7_USART2 7 // Định nghĩa bit trong USART_CR1 #define USART_CR1_UE (1 << 13) #define USART_CR1_TE (1 << 3) #define USART_CR1_RE (1 << 2) #define USART_CR1_RXNEIE (1 << 5) // Ngắt khi nhận dữ liệu (RXNE Interrupt Enable) #define USART_CR1_TXEIE (1 << 7) // Ngắt khi truyền xong (TXE Interrupt Enable) // Định nghĩa bit trong USART_CR2 #define USART_CR2_STOP_1BIT (0 << 12) // Định nghĩa bit trong USART_SR #define USART_SR_TXE()?; #define USART_SR_TC (1 << 6) #define USART_SR_RXNE (1 << 5) // Định nghĩa bit trong NVIC #define NVIC_USART2_IRQn 6 // USART2 nằm ở vị trí 38 trong vector table, thuộc ISER1 (bit 6) // Biến toàn cục để lưu dữ liệu nhận được volatile uint8_t rxData = 0; volatile uint8_t txReady = 0; // Hàm cấu hình clock 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_DIV2; // AHB = 8MHz, APB1 = 8MHz } // Hàm cấu hình GPIO cho USART2 (PA2-TX, PA3-RX) void GPIO_Init(void) { RCC_AHB1ENR |= RCC_AHB1ENR_GPIOAEN; // PA2 (TX) và PA3 (RX) thành 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)); // Tốc độ cao GPIOA_OSPEEDR &= ~(3 << (2 * 2)); GPIOA_OSPEEDR |= (GPIO_OSPEEDR_HIGH << (2 * 2)); GPIOA_OSPEEDR &= ~(3 << (3 * 2)); GPIOA_OSPEEDR |= (GPIO_OSPEEDR_HIGH << (3 * 2)); // Không pull-up/pull-down GPIOA_PUPDR &= ~(3 << (2 * 2)); GPIOA_PUPDR &= ~(3 << (3 * 2)); // AF7 cho 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)); } // Hàm khởi tạo USART2 với ngắt void UART_Init(void) { RCC_APB1ENR |= RCC_APB1ENR_USART2EN; // Baudrate = 9600, PCLK1 = 8MHz USART2_BRR = (52 << 4) | 1; // 0x0341 // Cấu hình: 8 bits, no parity, 1 stop bit, bật ngắt RXNE USART2_CR1 = 0; USART2_CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE | USART_CR1_UE; USART2_CR2 = 0; USART2_CR2 |= USART_CR2_STOP_1BIT; USART2_CR3 = 0; // Không dùng hardware flow control // Bật ngắt USART2 trong NVIC NVIC_ISER1 |= (1 << NVIC_USART2_IRQn); // Enable IRQ38 (USART2) } // Hàm truyền một ký tự void UART_SendChar(uint8_t data) { while (!(USART2_SR & USART_SR_TXE)); USART2_DR = data; while (!(USART2_SR & USART_SR_TC)); } // Hàm truyền chuỗi void UART_SendString(const char *str) { while (*str) { UART_SendChar(*str++); } } // Hàm xử lý ngắt USART2 void USART2_IRQHandler(void) { // Kiểm tra ngắt nhận dữ liệu (RXNE) if (USART2_SR & USART_SR_RXNE) { rxData = USART2_DR; // Đọc dữ liệu nhận được UART_SendChar(rxData); // Echo lại ngay lập tức } } int main(void) { SystemClock_Config(); GPIO_Init(); UART_Init(); // Gửi chuỗi chào UART_SendString("Hello from STM32 with Interrupt!\r\n"); while (1) { // Vòng lặp chính trống - xử lý trong ngắt } }
👉 Mình để lại Video sau để các bạn tham khảo nhé!
Video: Hướng dẫn lập trình UART Interrupt trên STM32
>>>>>> 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 😊