1. 项目背景与需求分析
最近在开发一款基于展锐平台的平板设备时,遇到了一个特殊需求:需要通过串口连接外接键盘。这种设计在消费类平板产品中并不常见,但对特定行业应用(如工业控制、医疗设备等)却非常实用。经过评估,我们选择了JD32F5302型号的串口键盘模块,它采用UART通信协议,支持标准键盘按键和触摸板功能。
选择串口键盘而非USB键盘主要基于以下几点考虑:
- 节省USB接口资源(平板通常只有1-2个USB口)
- 更低的功耗(串口设备通常比USB设备省电)
- 更简单的硬件连接(只需要TX/RX两根线)
- 更好的抗干扰能力(工业环境中更稳定)
2. 硬件设计与DTS配置
2.1 原理图分析
从硬件原理图可以看出,键盘模块需要两个关键GPIO:
- 供电使能GPIO(docking-vcc-gpio):控制键盘模块的电源
- 中断GPIO(irq-gpio):用于检测键盘连接状态

2.2 DTS节点配置
在设备树中为键盘添加了如下节点配置:
c复制docking_kb: docking-kb {
compatible = "uart_kb,power";
docking-vcc-gpios = <&ap_gpio 189 GPIO_ACTIVE_HIGH>;
irq-gpio = <&eic_async 30 GPIO_ACTIVE_LOW>;
};
配置说明:
docking-vcc-gpios:使用AP_GPIO_189作为电源控制,高电平有效irq-gpio:使用EIC_ASYNC_30作为中断引脚,低电平有效
注意:GPIO编号和控制器名称需要根据具体硬件平台调整。展锐平台通常使用
ap_gpio作为通用GPIO控制器,eic_async用于外部中断控制器。
3. 键盘驱动开发
3.1 驱动框架设计
整个键盘系统需要两个驱动协同工作:
- 键盘数据处理驱动:处理来自串口的按键数据(供应商提供)
- 电源管理驱动:控制键盘电源和连接状态(需要自行开发)
3.2 按键数据处理驱动
3.2.1 驱动文件放置
将供应商提供的驱动文件放置在kernel驱动目录:
code复制bsp/kernel5.15/kernel5.15/drivers/input/keyboard/jd32f5302.c
3.2.2 Kconfig和Makefile修改
在drivers/input/keyboard/Kconfig中添加配置选项:
diff复制+config UART_KB_JD32F5302
+ tristate "UART keyboard jd32f5302"
在drivers/input/keyboard/Makefile中添加编译规则:
diff复制+obj-$(CONFIG_UART_KB_JD32F5302) += jd32f5302.o
3.2.3 关键代码解析
驱动核心是键码映射和数据处理:
c复制static unsigned char jdbt_keycode[256] = {
[0x04] = KEY_A, // HID 'a' -> Linux KEY_A
[0x05] = KEY_B, // HID 'b' -> Linux KEY_B
// ... 其他键码映射
[0x29] = KEY_ESC,
[0x4C] = KEY_DELETE,
// 特殊功能键
[0x71] = KEY_FIND,
[0x72] = KEY_MUTE,
// ... 更多键码
};
数据处理流程:
- 通过串口中断接收数据
- 解析键码和功能键状态
- 生成对应的input事件上报
c复制static void jdbt_key_process_data(struct jdbt *jdbt) {
// 解析功能键
jdbt_find_fun_key_code(jdbt);
// 解析普通键
jdbt_find_key_code(jdbt);
// 上报按键事件
for (i = 0; (i < 7 && jdbt->transmit_fun_code[i]); i++) {
input_report_key(input_dev, jdbt_hid_to_linux_keycode(jdbt->transmit_fun_code[i]), 1);
input_sync(input_dev);
}
// ... 其他按键处理
}
3.3 电源管理驱动
3.3.1 驱动框架
c复制struct uart_kb_data {
struct device *dev;
struct gpio_desc *docking_vcc; // 电源控制GPIO
unsigned int irq_gpio; // 中断GPIO
int kb_irq; // 中断号
struct input_dev *input_dev; // 输入设备
struct workqueue_struct *uart_wq;
struct delayed_work uart_work;
struct notifier_block fb_notif; // 帧缓冲通知
struct mutex ops_lock; // 操作锁
int is_suspend; // 挂起状态
};
3.3.2 电源控制逻辑
c复制// 唤醒时供电
case 1:
if(!gpio_get_value(kb_data->irq_gpio) && !gpiod_get_value(kb_data->docking_vcc)) {
input_report_key(kb_data->input_dev, KEY_BCONNECT, 1);
input_sync(kb_data->input_dev);
gpiod_direction_output(kb_data->docking_vcc, 1);
}
break;
// 挂起时断电
case 0:
if(!gpio_get_value(kb_data->irq_gpio) && gpiod_get_value(kb_data->docking_vcc)) {
input_report_key(kb_data->input_dev, KEY_BDISCONNECT, 1);
input_sync(kb_data->input_dev);
gpiod_direction_output(kb_data->docking_vcc, 0);
}
break;
4. 系统集成与调试
4.1 内核配置
在平台配置文件sprd_gki_qogirn6pro.fragment中启用驱动:
diff复制+CONFIG_UART_KB_JD32F5302=m
4.2 模块化与动态加载
将驱动编译为模块,通过以下文件控制加载:
device/sprd/vnd_mpool/module/vnd_others/bsp/mfeature/kernel/kernel5.15/msoc/qogirn6pro/ko/km.mkmodules.load- 添加模块加载指令
4.3 调试技巧
- 查看GPIO状态:
bash复制cat /sys/kernel/debug/gpio
- 监控输入事件:
bash复制getevent -l
- 查看内核日志:
bash复制dmesg | grep uart_kb
- 手动控制电源(调试用):
bash复制echo 1 > /sys/class/gpio/gpio189/value # 供电
echo 0 > /sys/class/gpio/gpio189/value # 断电
5. 常见问题与解决方案
5.1 键盘无响应
可能原因:
- 电源未正确供电
- 串口波特率不匹配
- 驱动未正确加载
排查步骤:
- 检查
/proc/interrupts确认中断是否注册 - 测量键盘模块供电电压
- 使用逻辑分析仪抓取串口信号
5.2 按键错乱
解决方案:
- 检查键码映射表是否正确
- 确认键盘固件版本
- 添加调试打印确认原始数据:
c复制printk(KERN_DEBUG "Received data: %02x %02x %02x\n",
data[0], data[1], data[2]);
5.3 休眠唤醒问题
典型表现:
- 休眠后键盘无法唤醒设备
- 唤醒后键盘无响应
解决方法:
- 在驱动中添加正确的电源管理回调:
c复制static const struct dev_pm_ops uart_kb_pm_ops = {
.suspend = uart_kb_suspend,
.resume = uart_kb_resume,
};
- 确保在suspend时正确处理键盘状态:
c复制static int uart_kb_suspend(struct device *dev) {
struct uart_kb_data *data = dev_get_drvdata(dev);
disable_irq(data->kb_irq);
gpiod_set_value(data->docking_vcc, 0);
return 0;
}
6. 性能优化建议
-
中断优化:
- 使用
IRQF_NO_SUSPEND标志注册中断,允许在休眠时唤醒 - 实现中断共享(如果与其他设备共用中断线)
- 使用
-
电源管理:
- 实现自动休眠功能(无操作时自动断电)
- 添加心跳检测,自动恢复断开的连接
-
输入子系统优化:
- 使用
input_set_capability()精确设置输入设备能力 - 实现多点触控支持(如果键盘带触摸板)
- 使用
-
调试接口:
- 通过sysfs暴露调试信息
- 实现动态日志级别控制
c复制// 示例:动态日志控制
static int debug_level = 1;
module_param(debug_level, int, 0644);
#define dbg_print(level, fmt, ...) \
do { if (debug_level >= level) printk(KERN_DEBUG fmt, ##__VA_ARGS__); } while (0)
7. 关键经验总结
-
GPIO配置要点:
- 电源控制GPIO应设置为推挽输出
- 中断GPIO需要正确配置上下拉电阻
- 展锐平台的GPIO编号需要参考具体芯片手册
-
中断处理注意事项:
- 中断处理函数要尽可能短小
- 使用工作队列处理耗时操作
- 注意中断共享时的冲突问题
-
输入子系统最佳实践:
- 及时调用
input_sync()同步事件 - 正确处理按键的按下和释放事件
- 为特殊按键定义合适的键码
- 及时调用
-
跨平台兼容性:
- 使用设备树抽象硬件差异
- 通过
compatible属性匹配驱动 - 为不同平台提供配置选项
这个项目让我深刻体会到嵌入式Linux输入设备开发的复杂性,特别是当涉及到自定义硬件和外设时。通过这次实践,我总结了几个关键点:
- 一定要在早期阶段确认好硬件接口协议,特别是像串口键盘这种非标准设备
- 电源管理在移动设备上至关重要,需要仔细测试各种状态转换
- 输入子系统的调试可以借助Android提供的工具链,如getevent、dumpsys input等
- 对于生产环境,还需要考虑固件升级、错误恢复等可靠性设计
希望这篇记录能帮助遇到类似需求的开发者。如果有任何问题或建议,欢迎在评论区交流讨论。