全志H3开发板U-Boot多串口开发实战:从DTS配置到驱动调试全解析
在嵌入式系统开发中,U-Boot作为系统启动加载器,其外设驱动能力往往被开发者低估。全志H3平台默认仅启用UART0作为调试串口,但当项目需要与多个外设通过串口通信时,如何安全高效地启用第二串口(UART1)就成为关键问题。本文将深入剖析从设备树配置到驱动调试的完整技术链条,帮助开发者避开常见陷阱。
1. 设备树深度配置与硬件映射
全志H3的串口控制器采用ns16550兼容架构,但时钟和引脚管理具有鲜明的Allwinner特色。正确的设备树配置是功能实现的基础。
1.1 DTS节点配置规范
在sun8i-h3-nanopi.dtsi中需要确保以下关键配置:
&uart1 { pinctrl-names = "default"; pinctrl-0 = <&uart1_pins_a>; status = "okay"; };同时需要在aliases节点中明确设备序号:
aliases { serial0 = &uart0; serial1 = &uart1; };注意:全志H3的UART1默认引脚为GPG6(TX)和GPG7(RX),与部分开发板的丝印标注可能存在差异
1.2 时钟域与复位信号
全志H3的UART控制器时钟架构如下表所示:
| 时钟域 | 源时钟 | 分频器 | 门控位 | 复位位 |
|---|---|---|---|---|
| APB2 | OSC24M | apb2_div | bit16 | bit16 |
| UART1 | APB2 | - | bit1 | bit1 |
对应的时钟初始化代码应包含三个关键操作:
static void uart1_clock_init(void) { struct sunxi_ccm_reg *const ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; // 设置APB2时钟源和分频 writel(APB2_CLK_SRC_OSC24M | APB2_CLK_RATE_N_1 | APB2_CLK_RATE_M(1), &ccm->apb2_div); // 打开UART1时钟门控 setbits_le32(&ccm->apb2_gate, CLK_GATE_OPEN << (APB2_GATE_UART_SHIFT + 2 - 1)); // 释放UART1复位 setbits_le32(&ccm->apb2_reset_cfg, 1 << (APB2_RESET_UART_SHIFT + 2 - 1)); }2. U-Boot设备驱动模型深度解析
U-Boot的驱动模型(DM)在2015年后引入,与Linux设备模型有相似性但存在关键差异。
2.1 设备探测机制对比
| 操作类型 | Linux驱动模型 | U-Boot驱动模型 |
|---|---|---|
| 设备查找 | of_find_device_by_node | uclass_find_device |
| 设备初始化 | 自动probe | 需显式调用device_probe |
| 资源分配 | 自动完成 | 常需手动配置 |
| 时钟管理 | 由CCF框架处理 | 常需手动控制 |
关键代码示例展示了正确的设备获取方式:
struct udevice *dev; int ret = uclass_get_device(UCLASS_SERIAL, 1, &dev); if (ret) { printf("Failed to get UART1 device: %d\n", ret); return ret; }2.2 串口操作封装实践
针对ns16550驱动特有的-EAGAIN返回问题,建议封装基础通信函数:
static int safe_serial_putc(struct udevice *dev, char ch) { struct dm_serial_ops *ops = serial_get_ops(dev); int ret; do { ret = ops->putc(dev, ch); if (ret == -EAGAIN) { WATCHDOG_RESET(); udelay(100); } } while (ret == -EAGAIN); return ret; }3. 引脚复用与电气特性配置
全志H3的引脚复用配置需要特别注意GPIO组的时钟使能。
3.1 引脚功能映射表
| 引脚号 | 默认功能 | UART1功能 | 复用配置值 |
|---|---|---|---|
| GPG6 | GPIO | TXD | 2 |
| GPG7 | GPIO | RXD | 2 |
对应的初始化代码:
static void uart1_pinmux_setup(void) { /* 使能GPG组时钟 */ setbits_le32(&ccm->bus_gate4, BIT(AHB_GATE_OFFSET_GPIOG)); /* 配置引脚复用 */ sunxi_gpio_set_cfgpin(SUNXI_GPG(6), SUN8I_H3_GPG_UART1); sunxi_gpio_set_cfgpin(SUNXI_GPG(7), SUN8I_H3_GPG_UART1); /* 配置RX引脚上拉 */ sunxi_gpio_set_pull(SUNXI_GPG(7), SUNXI_GPIO_PULL_UP); }3.2 电气特性优化
对于长距离通信场景,建议增加以下配置:
/* 调整驱动强度为10mA */ sunxi_gpio_set_drv(SUNXI_GPG(6), SUNXI_GPIO_DRV_10MA); sunxi_gpio_set_drv(SUNXI_GPG(7), SUNXI_GPIO_DRV_10MA); /* 启用施密特触发器 */ sunxi_gpio_set_ioctl(SUNXI_GPG(7), SUNXI_GPIO_IOCTL_SCHMITT);4. 调试技巧与性能优化
4.1 常见故障排查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 数据发送卡住 | 时钟未使能 | 检查CCM寄存器APB2_GATE |
| 接收数据全FF | 引脚复用错误 | 用示波器检查引脚波形 |
| 偶发通信失败 | 驱动强度不足 | 增大GPIO驱动电流设置 |
| 波特率误差大 | 时钟分频配置错误 | 检查apb2_div寄存器值 |
4.2 性能优化建议
中断模式改造: 修改
drivers/serial/ns16550.c,实现中断接收而非轮询:static int ns16550_serial_irq(int irq, void *dev_id) { struct ns16550_platdata *plat = dev_id; if (serial_in(&plat->regs->lsr) & UART_LSR_DR) { /* 处理接收数据 */ } return 0; }DMA传输配置: 对于高速通信场景,可启用UART的DMA功能:
/* 使能UART1 DMA */ serial_out(UART_FCR_DMA_SELECT, &com_port->fcr);波特率校准: 使用精确频率计测量实际波特率,调整分频系数:
void uart1_baud_calibrate(void) { /* 基于实际测量值计算最佳分频 */ uint32_t actual_div = OSC24M_CLK / (16 * desired_baud); writel(actual_div, &com_port->divisor_latch); }
在实际项目中,我们发现GPG6引脚在部分批次H3芯片上存在硅缺陷,表现为发送波形畸变。解决方案是在PCB设计时将UART1更换到UART3(PA13/PA14)引脚,或在软件中增加预加重驱动:
sunxi_gpio_set_ioctl(SUNXI_GPG(6), SUNXI_GPIO_IOCTL_PRE_EMPHASIS);