RK3568 GPIO 模拟串口(软串口)实现详解
在嵌入式开发中,硬件 UART 资源可能被其他外设占用。此时,通过 GPIO 模拟 UART(软串口)成为一种灵活的解决方案。本文基于 RK3568 平台,结合 Linux 内核模块,实现高精度软串口通信,并深入分析关键技术细节
- 核心组件:
- GPIO 控制:通过
gpio_*
系列函数操作引脚。 - 中断处理:检测 RX 引脚的下降沿触发起始位。
- 高精度定时器:
hrtimer
确保波特率时序精准。 - 状态机:管理空闲、接收、发送三种状态。
- GPIO 控制:通过
GPIO 初始化
gpio_request(RX_PIN, "soft_uart_rx");
gpio_direction_input(RX_PIN); // RX作为输入
gpio_direction_output(TX_PIN, 1); // TX初始高电平(停止位)
中断处理
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{if (state == IDLE && gpio_get_value(RX_PIN) == 0) {state = RECEIVING;hrtimer_start(&rx_timer, ns_to_ktime(BIT_TIME_NS / 2), HRTIMER_MODE_REL);}return IRQ_HANDLED;
}
- 触发条件:下降沿触发(
IRQF_TRIGGER_FALLING
)。 - 中断逻辑:仅在空闲状态检测到起始位(低电平)时启动接收流程。
定时器回调函数
// 接收定时器回调
static enum hrtimer_restart rx_timer_callback(struct hrtimer *timer)
{// 起始位检测 -> 数据位采样 -> 停止位处理hrtimer_forward_now(timer, ns_to_ktime(BIT_TIME_NS * 1.5)); // 调整采样点
}// 发送定时器回调
static enum hrtimer_restart tx_timer_callback(struct hrtimer *timer)
{// 发送数据位 -> 停止位 -> 恢复空闲状态
}
- 位时间计算:
BIT_TIME_NS = 1e9 / BAUDRATE
(115200 波特率对应约 8.68μs)。 - 时序优化:接收时在起始位后延迟 1.5 个位时间,确保在数据位中间采样。
完整代码:
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>#define RX_PIN 32 // GPIO4_4
#define TX_PIN 33 // GPIO4_5
#define BAUDRATE 115200
#define BIT_TIME_NS (1000000000 / BAUDRATE)static struct hrtimer tx_timer, rx_timer;
static enum { IDLE, RECEIVING, TRANSMITTING } state;
static unsigned char rx_byte, tx_byte;
static int bit_pos;
static int tx_pin_val = 1;// 定时器回调函数
static enum hrtimer_restart rx_timer_callback(struct hrtimer *timer)
{static int start_bit_detected = 0;int current_bit;if (!start_bit_detected) {// 检测起始位(低电平)if (gpio_get_value(RX_PIN) == 0) {start_bit_detected = 1;rx_byte = 0;bit_pos = 0;hrtimer_forward_now(timer, ns_to_ktime(BIT_TIME_NS * 1.5));return HRTIMER_RESTART;}} else {// 采样数据位current_bit = gpio_get_value(RX_PIN);rx_byte |= (current_bit << bit_pos);bit_pos++;if (bit_pos == 8) {// 接收完成,处理数据printk(KERN_INFO "Received: 0x%02x\n", rx_byte);// 回传接收到的数据tx_byte = rx_byte;state = TRANSMITTING;bit_pos = 0;gpio_set_value(TX_PIN, 0); // 起始位hrtimer_start(&tx_timer, ns_to_ktime(BIT_TIME_NS), HRTIMER_MODE_REL);start_bit_detected = 0;return HRTIMER_NORESTART;}}hrtimer_forward_now(timer, ns_to_ktime(BIT_TIME_NS));return HRTIMER_RESTART;
}static enum hrtimer_restart tx_timer_callback(struct hrtimer *timer)
{if (bit_pos < 8) {// 发送数据位tx_pin_val = (tx_byte >> bit_pos) & 0x01;gpio_set_value(TX_PIN, tx_pin_val);bit_pos++;} else if (bit_pos == 8) {// 发送停止位gpio_set_value(TX_PIN, 1);bit_pos++;} else {// 发送完成state = IDLE;return HRTIMER_NORESTART;}hrtimer_forward_now(timer, ns_to_ktime(BIT_TIME_NS));return HRTIMER_RESTART;
}// GPIO中断处理
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{if (state == IDLE && gpio_get_value(RX_PIN) == 0) {state = RECEIVING;hrtimer_start(&rx_timer, ns_to_ktime(BIT_TIME_NS / 2), HRTIMER_MODE_REL);}return IRQ_HANDLED;
}static int __init soft_uart_init(void)
{int ret;// 初始化GPIOif (gpio_request(RX_PIN, "soft_uart_rx") || gpio_request(TX_PIN, "soft_uart_tx")) {printk(KERN_ERR "Failed to request GPIO pins\n");return -EBUSY;}gpio_direction_input(RX_PIN);gpio_direction_output(TX_PIN, 1);// 设置中断ret = request_irq(gpio_to_irq(RX_PIN), gpio_irq_handler,IRQF_TRIGGER_FALLING, "soft_uart_rx", NULL);if (ret) {printk(KERN_ERR "Failed to request IRQ\n");goto err_irq;}// 初始化定时器hrtimer_init(&rx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);rx_timer.function = rx_timer_callback;hrtimer_init(&tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);tx_timer.function = tx_timer_callback;state = IDLE;printk(KERN_INFO "Soft UART (hrtimer) driver loaded\n");return 0;err_irq:gpio_free(RX_PIN);gpio_free(TX_PIN);return ret;
}static void __exit soft_uart_exit(void)
{hrtimer_cancel(&rx_timer);hrtimer_cancel(&tx_timer);free_irq(gpio_to_irq(RX_PIN), NULL);gpio_free(RX_PIN);gpio_free(TX_PIN);printk(KERN_INFO "Soft UART driver unloaded\n");
}module_init(soft_uart_init);
module_exit(soft_uart_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("MY TEST");
MODULE_DESCRIPTION("High precision soft UART driver for RK3568");
再写个Makefile
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
$(warning KERNELRELEASE = $(KERNELRELEASE))ifeq ($(KERNELRELEASE),)#KERNELDIR ?= /lib/modules/$(shell uname -r)/build
KERNELDIR ?= /home/tronlong/RK3568/rk356x_linux_release_v1.3.1_20221120/kernel
PWD := $(shell pwd)modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesclean:rm -rf *.o *.mod *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules* Module*.PHONY: modules cleanelse#obj-m := spi.oobj-m := gpio_uart.o#obj-m := 111.o
endif
将编译好的驱动放到板子上运行
可以看到已经接收到数据了,可以自己创建自定义的设备文件到/dev下,也可以按照串口设备的标准创建一个设备,后面有时间我写一篇和标准串口设备结合的gpio模拟串口文章。