HPM6E80作为先辑半导体推出的高性能微控制器,其UART通信功能在工业控制、智能家居等领域有着广泛应用。最近在调试一个通过UART与传感器通信的项目时,发现直接使用轮询方式会导致系统资源浪费,于是决定改用中断方式实现数据收发。本文将详细记录HPM6E80的UART中断配置全过程,包含寄存器操作细节和实际调试中遇到的坑。
我使用的是HPM6E80-EVB开发板,通过板载的USB转串口芯片(通常是CH340系列)与PC通信。硬件连接时需要注意:
先辑官方提供了完整的SDK和开发工具链。我使用的是:
在工程配置中需要特别注意:
首先需要配置UART基本参数:
c复制uart_config_t config = {
.baudrate = 115200,
.parity = uart_parity_none,
.stop_bit = uart_stop_bits_1,
.word_length = uart_word_length_8bit,
.fifo_enable = true // 启用FIFO可提高中断效率
};
然后初始化UART外设:
c复制void uart_init(UART_Type *ptr)
{
uart_init(ptr, &config);
uart_enable_irq(ptr, uart_intr_rx_data_avail_or_timeout); // 使能接收中断
intc_m_enable_irq_with_priority(UART_IRQn, 1); // 设置中断优先级
}
HPM6E80的中断处理采用向量表方式,需要先注册中断处理函数:
c复制void uart_isr(void)
{
uint32_t status = uart_get_irq_id(UART0);
if (status == uart_intr_id_rx_data_avail_or_timeout) {
while (uart_check_status(UART0, uart_status_rx_data_ready)) {
uint8_t data = uart_read_byte(UART0);
// 处理接收到的数据
rx_buffer[rx_index++] = data;
if (rx_index >= BUFFER_SIZE) rx_index = 0;
}
}
uart_clear_irq(UART0, status); // 必须清除中断标志
}
重要提示:中断服务程序中一定要及时清除中断标志,否则会导致重复进入中断!
为了避免数据丢失,我设计了一个简单的环形缓冲区:
c复制#define BUF_SIZE 256
typedef struct {
uint8_t buffer[BUF_SIZE];
volatile uint32_t head;
volatile uint32_t tail;
} ring_buf_t;
void ring_buf_put(ring_buf_t *buf, uint8_t data)
{
buf->buffer[buf->head] = data;
buf->head = (buf->head + 1) % BUF_SIZE;
if (buf->head == buf->tail) {
buf->tail = (buf->tail + 1) % BUF_SIZE; // 缓冲区满时丢弃最旧数据
}
}
与接收中断不同,发送中断需要手动控制:
c复制void uart_send_byte(UART_Type *ptr, uint8_t data)
{
while (!uart_check_status(ptr, uart_status_tx_empty)) {
// 等待发送缓冲区空闲
}
uart_write_byte(ptr, data);
uart_enable_irq(ptr, uart_intr_tx_empty); // 发送完成后触发中断
}
为了提高发送效率,我实现了基于DMA的中断发送:
c复制void uart_send_dma(UART_Type *ptr, uint8_t *data, uint32_t len)
{
dma_config.src = core_local_mem_to_sys_address(0, (uint32_t)data);
dma_config.dst = core_local_mem_to_sys_address(0, (uint32_t)&ptr->THR);
dma_config.size = len;
dma_config.src_width = DMA_TRANSFER_WIDTH_BYTE;
dma_config.dst_width = DMA_TRANSFER_WIDTH_BYTE;
dma_channel_config(DMA0, DMA_CH0, &dma_config, true);
uart_enable_tx_dma(ptr); // 使能UART的DMA发送
}
现象:配置正确但中断始终不触发
排查步骤:
最终发现是GPIO复用功能寄存器配置错误,正确配置如下:
c复制HPM_IOC->PAD[IOC_PAD_PB07].FUNC_CTL = IOC_PB07_FUNC_CTL_UART0_RXD;
HPM_IOC->PAD[IOC_PAD_PB06].FUNC_CTL = IOC_PB06_FUNC_CTL_UART0_TXD;
现象:高速通信时偶发数据丢失
解决方案:
优化后的中断服务程序:
c复制__attribute__((section(".fast"))) void uart_isr(void)
{
uint32_t status = uart_get_irq_id(UART0);
if (status == uart_intr_id_rx_data_avail_or_timeout) {
uint8_t data = uart_read_byte(UART0);
ring_buf_put(&rx_buf, data); // 快速存入缓冲区
}
uart_clear_irq(UART0, status);
}
HPM6E80支持多种中断触发条件组合:
c复制// 同时使能接收和发送中断
uart_enable_irq(ptr, uart_intr_rx_data_avail_or_timeout | uart_intr_tx_empty);
通过修改UART_FCR寄存器可以调整FIFO触发阈值:
c复制ptr->FCR = UART_FCR_FIFO_EN_MASK | UART_FCR_RX_FIFO_TRIG_1;
在电池供电场景下,可以配置UART唤醒功能:
c复制uart_config.wakeup_config.enable = true;
uart_config.wakeup_config.rx_pin = BOARD_UART_RX_PIN;
以下是经过验证的完整实现:
c复制#include "hpm_uart.h"
#include "hpm_clock_drv.h"
#define UART_BAUDRATE 115200
#define RX_BUF_SIZE 256
typedef struct {
uint8_t buffer[RX_BUF_SIZE];
volatile uint32_t head;
volatile uint32_t tail;
} ring_buf_t;
ring_buf_t rx_buf;
void uart_init(void)
{
uart_config_t config = {
.baudrate = UART_BAUDRATE,
.parity = uart_parity_none,
.stop_bit = uart_stop_bits_1,
.word_length = uart_word_length_8bit,
.fifo_enable = true
};
uart_init(BOARD_UART, &config);
uart_enable_irq(BOARD_UART, uart_intr_rx_data_avail_or_timeout);
intc_m_enable_irq_with_priority(BOARD_UART_IRQn, 1);
}
void BOARD_UART_IRQ_HANDLER(void)
{
uint32_t status = uart_get_irq_id(BOARD_UART);
if (status == uart_intr_id_rx_data_avail_or_timeout) {
while (uart_check_status(BOARD_UART, uart_status_rx_data_ready)) {
uint8_t data = uart_read_byte(BOARD_UART);
rx_buf.buffer[rx_buf.head] = data;
rx_buf.head = (rx_buf.head + 1) % RX_BUF_SIZE;
}
}
uart_clear_irq(BOARD_UART, status);
}
void uart_send_string(const char *str)
{
while (*str) {
while (!uart_check_status(BOARD_UART, uart_status_tx_empty));
uart_write_byte(BOARD_UART, *str++);
}
}
基于上述UART中断实现,可以方便地扩展Modbus协议通信:
c复制typedef enum {
MODBUS_STATE_IDLE,
MODBUS_STATE_ADDR,
MODBUS_STATE_FUNC,
MODBUS_STATE_DATA,
MODBUS_STATE_CRC
} modbus_state_t;
void process_modbus_frame(uint8_t data)
{
static modbus_state_t state = MODBUS_STATE_IDLE;
static uint8_t frame[256], index = 0;
switch (state) {
case MODBUS_STATE_IDLE:
if (data == DEVICE_ADDR) {
frame[index++] = data;
state = MODBUS_STATE_ADDR;
}
break;
// 其他状态处理...
}
}
通过UART地址匹配功能实现多机通信:
c复制void uart_set_address(UART_Type *ptr, uint8_t addr)
{
ptr->DLF = UART_DLF_LIN_MODE_EN_MASK | (addr << UART_DLF_LIN_ADDR_SHIFT);
ptr->IER |= UART_IER_LIN_ADDR_DETECT_EN_MASK;
}
使用Saleae逻辑分析仪捕获UART波形时,建议设置:
在低功耗应用中,使用电流探头测量不同状态下的功耗:
利用Segger RTT实现不占用UART的调试输出:
c复制#include "SEGGER_RTT.h"
void debug_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
SEGGER_RTT_printf(0, fmt, args);
va_end(args);
}
经过两周的调试和优化,最终实现的UART中断收发系统具有以下特点:
未来可以进一步优化:
在实际项目中,UART中断方式相比轮询可以节省约30%的CPU资源,特别适合需要同时处理多个外设的应用场景。