ARM裸机开发与UART驱动实现详解

张开发
2026/4/21 16:24:11 15 分钟阅读

分享文章

ARM裸机开发与UART驱动实现详解
1. ARM裸机开发与UART驱动基础在嵌入式系统开发领域裸机编程(Bare-metal Programming)是指不依赖任何操作系统直接操作硬件寄存器的开发方式。这种方式常见于bootloader、实时控制系统和资源受限的嵌入式设备中。UART(Universal Asynchronous Receiver/Transmitter)作为最基础的串行通信接口在嵌入式系统中承担着调试信息输出、设备间通信等重要功能。1.1 PL011 UART硬件架构PL011是ARM公司设计的UART控制器具有以下关键特性支持5-8位数据长度可配置的奇偶校验(无校验/奇校验/偶校验)1或2个停止位波特率范围110bps到460800bps16字节的发送/接收FIFO缓冲区中断触发机制硬件寄存器主要分为以下几类控制寄存器(CR)全局使能、FIFO控制等线路控制寄存器(LCRH)数据位长度、停止位、奇偶校验配置波特率分频寄存器(IBRD/FBRD)整数和小数部分分开配置状态寄存器(FR)传输状态标志位数据寄存器(DR)收发数据缓冲区关键提示在操作UART寄存器时必须严格遵守手册规定的操作顺序。例如修改波特率前需要先禁用UART配置完成后再重新启用。1.2 UART驱动设计模式一个健壮的UART驱动应该包含以下核心功能模块typedef enum { UART_OK 0, UART_INVALID_ARGUMENT_BAUDRATE, UART_INVALID_ARGUMENT_WORDSIZE, UART_INVALID_ARGUMENT_STOP_BITS, UART_RECEIVE_ERROR, UART_NO_DATA } uart_error; typedef struct { uint8_t data_bits; // 5-8位数据长度 uint8_t stop_bits; // 1或2个停止位 bool parity; // 是否启用奇偶校验 uint32_t baudrate; // 波特率值 } uart_config;这种设计模式的优势在于通过枚举类型明确错误代码便于问题定位配置参数集中管理提高代码可维护性输入参数验证机制防止非法配置状态机设计确保操作顺序正确2. UART驱动实现详解2.1 初始化与配置流程UART初始化需要严格按照以下步骤进行禁用UART在修改配置前必须禁用UART功能uart0-CR ~CR_UARTEN; // 清除使能位等待当前传输完成防止配置过程中数据丢失while (uart0-FR FR_BUSY); // 等待BUSY标志位清除清空FIFO缓冲区uart0-LCRH ~LCRH_FEN; // 禁用FIFO功能即等效于清空波特率计算与设置double intpart, fractpart; double baudrate_divisor (double)refclock / (16u * config-baudrate); fractpart modf(baudrate_divisor, intpart); uart0-IBRD (uint16_t)intpart; // 整数部分 uart0-FBRD (uint8_t)((fractpart * 64u) 0.5); // 小数部分转换为6位整数数据格式配置uint32_t lcrh 0u; // 设置数据位长度 switch (config-data_bits) { case 5: lcrh | LCRH_WLEN_5BITS; break; case 6: lcrh | LCRH_WLEN_6BITS; break; case 7: lcrh | LCRH_WLEN_7BITS; break; case 8: lcrh | LCRH_WLEN_8BITS; break; } // 设置奇偶校验 if (config-parity) { lcrh | (LCRH_PEN | LCRH_EPS | LCRH_SPS); // 启用偶校验 } // 设置停止位 if (config-stop_bits 2u) { lcrh | LCRH_STP2; } uart0-LCRH lcrh; // 一次性写入所有配置重新启用UARTuart0-CR | CR_UARTEN; // 设置使能位2.2 数据收发实现2.2.1 发送数据发送功能分为单字符发送和字符串发送两个层次void uart_putchar(char c) { while (uart0-FR FR_TXFF); // 等待发送FIFO非满 uart0-DR c; // 写入数据寄存器 } void uart_write(const char* data) { while (*data) { uart_putchar(*data); // 循环发送每个字符 } }2.2.2 接收数据接收处理需要考虑错误状态检测uart_error uart_getchar(char* c) { if (uart0-FR FR_RXFE) { return UART_NO_DATA; // 接收FIFO为空 } *c uart0-DR DR_DATA_MASK; // 读取数据(清除状态位) if (uart0-RSRECR RSRECR_ERR_MASK) { uart0-RSRECR RSRECR_ERR_MASK; // 清除错误标志 return UART_RECEIVE_ERROR; // 返回错误状态 } return UART_OK; }2.3 轮询模式应用示例典型的轮询式UART应用流程如下int main() { uart_config config { .data_bits 8, .stop_bits 1, .parity false, .baudrate 9600 }; uart_configure(config); uart_write(System Ready\n); char buffer[64]; uint8_t idx 0; while (1) { char c; if (uart_getchar(c) UART_OK) { uart_putchar(c); // 回显接收到的字符 buffer[idx] c; if (c \r) { // 回车键处理 uart_putchar(\n); process_command(buffer); // 处理接收到的命令 idx 0; } } } }实测经验在115200波特率下轮询方式会导致CPU利用率接近100%。这种设计仅适用于低波特率或非实时性要求的场景。3. ARM中断系统解析3.1 ARMv7异常处理机制ARMv7架构将中断视为异常(Exception)的一种主要异常类型包括异常类型偏移量描述Reset0x00系统复位Undefined Instruction0x04非法指令Software Interrupt (SWI)0x08软中断指令Prefetch Abort0x0C指令预取错误Data Abort0x10数据访问错误IRQ (Interrupt Request)0x18普通中断FIQ (Fast Interrupt Request)0x1C快速中断异常处理流程保存当前PC到LR_irq保存CPSR到SPSR_irq切换到IRQ模式CPSR模式位设置为0x12禁用IRQ防止中断嵌套跳转到向量表对应偏移地址执行3.2 GIC(Generic Interrupt Controller)架构GIC是ARM的中断管理核心主要组件包括Distributor(分发器)全局中断使能控制中断优先级管理目标CPU分配中断状态跟踪CPU Interface(CPU接口)向特定CPU核传递中断中断确认机制中断完成通知GIC中断处理流程外设触发中断信号Distributor将中断标记为Pending状态根据优先级和目标CPU配置选择最高优先级中断CPU Interface向目标CPU发送中断请求CPU响应中断读取中断ID执行对应的ISRISR完成后通知GIC3.3 GIC寄存器映射关键寄存器组及其功能Distributor寄存器GICD_CTLR全局控制寄存器GICD_ISENABLERn中断使能设置寄存器GICD_ICENABLERn中断使能清除寄存器GICD_IPRIORITYRn中断优先级寄存器GICD_ITARGETSRn目标CPU设置寄存器CPU Interface寄存器GICC_CTLRCPU接口控制寄存器GICC_PMR优先级掩码寄存器GICC_IAR中断确认寄存器GICC_EOIR中断结束寄存器4. 中断驱动UART实现4.1 GIC初始化流程void gic_init(void) { // 获取GIC基地址(通过CP15协处理器) uint32_t periphbase; asm (mrc p15, #4, %0, c15, c0, #0 : r (periphbase)); gic_dregs (gic_distributor_registers*)(periphbase 0x1000); gic_ifregs (gic_cpu_interface_registers*)(periphbase 0x0100); // 设置CPU接口 gic_ifregs-CCPMR 0xFFFF; // 允许所有优先级中断 gic_ifregs-CCTLR 0x1; // 使能CPU接口 // 使能Distributor gic_dregs-DCTLR 0x1; // 使能全局分发 }4.2 中断使能与配置使能特定中断号的步骤void gic_enable_interrupt(uint8_t int_num) { // 计算寄存器索引和位偏移 uint8_t reg_idx int_num / 32; uint8_t bit_offset int_num % 32; // 设置中断使能位 gic_dregs-DISENABLER[reg_idx] (1 bit_offset); // 配置目标CPU(默认CPU0) reg_idx int_num / 4; bit_offset (int_num % 4) * 8; gic_dregs-DITARGETSR[reg_idx] | (1 bit_offset); }4.3 UART中断配置PL011 UART支持多种中断类型接收中断(RX)接收到数据时触发发送中断(TX)发送FIFO为空时触发错误中断发生帧错误、奇偶校验错误等时触发配置示例// 使能UART接收中断 uart0-IMSC | IMSC_RXIM; // 设置接收中断掩码 uart0-CR | CR_RXIE; // 使能接收中断 // 在GIC中使能UART中断(假设中断号为44) gic_enable_interrupt(44);4.4 中断服务例程(ISR)实现典型的中断处理流程IRQ_Handler: SUB lr, lr, #4 // 调整返回地址 SRSFD sp!, #0x13 // 保存LR和SPSR到IRQ栈 PUSH {r0-r3, r12} // 保存被破坏的寄存器 BL handle_irq_c // 调用C语言处理函数 POP {r0-r3, r12} // 恢复寄存器 RFE sp! // 从IRQ栈恢复PC和CPSR对应的C语言处理函数void handle_irq_c(void) { uint32_t int_id gic_ifregs-CIAR; // 读取中断ID switch(int_id) { case UART_INT_ID: // UART中断 uart_isr(); break; // 其他中断处理... } gic_ifregs-CEOIR int_id; // 通知GIC中断处理完成 } void uart_isr(void) { uint32_t status uart0-MIS; // 读取中断状态 if (status MIS_RXMIS) { // 接收中断 char c uart0-DR; // 读取数据会自动清除中断 uart_rx_callback(c); // 调用应用层回调 } // 其他中断类型处理... }5. 实战优化与调试技巧5.1 性能优化策略中断嵌套控制// 在关键代码段禁用中断 uint32_t old_prio gic_ifregs-CPMR; gic_ifregs-CPMR 0x80; // 只允许高优先级中断 // 执行关键代码... gic_ifregs-CPMR old_prio; // 恢复原优先级DMA配合中断对于大数据量传输可配置DMA与中断协同工作双缓冲技术减少数据拷贝开销提高吞吐量5.2 常见问题排查中断无法触发检查清单GIC Distributor和CPU Interface是否已使能特定中断是否在GIC中使能外设自身的中断是否已配置CPSR的I位是否已清除(全局中断使能)中断向量表是否正确安装中断丢失问题检查ISR执行时间是否过长确认中断清除时序是否正确验证中断优先级配置是否合理UART通信异常使用逻辑分析仪验证实际波特率检查硬件流控信号(如RTS/CTS)状态确认双方数据格式配置一致5.3 QEMU调试技巧查看GIC状态(qemu) info irq (qemu) info registers -a跟踪中断事件(qemu) trace-event irq*模拟UART输入(qemu) sendkey a (qemu) chardev-send-break serial06. 进阶开发建议模块化设计将UART驱动、中断处理和业务逻辑分层实现异步事件处理结合RTOS或事件循环框架提升系统响应能力安全考虑关键操作添加超时机制重要寄存器访问增加保护锁实现看门狗监控机制功耗优化合理使用UART休眠模式动态调整中断触发条件空闲时进入低功耗状态在实际项目中我曾遇到一个典型问题在高波特率(1Mbps)下单纯依靠中断处理会导致系统负载过高。最终的解决方案是结合DMA传输仅在DMA完成时触发中断将中断频率从每字节一次降低到每缓冲区一次CPU利用率从90%降至15%以下。这个案例充分说明了理解硬件特性与合理设计架构的重要性。

更多文章