GPIO 模拟Uart 通信 (soft uart/serial)
在Uart不够用的时候可以通过GPIO 来模拟,但是GPIO 模拟有一个缺点就是时钟可能不准,Uart是异步的,我们可以设置两个定时器来模拟其对应的输出。
流程
linux下的GPIO模拟Uart涉及到如下几个内容
1、GPIO初始化、设定输入输出、以及输入中断设置
2、初始化定时器,建议是高精度定时器
3、中断处理数据接收处理和发送数据
4、fifo管理数据
5、注册tty 驱动管理
代码移植
我这边主要是移植了树莓派的soft_uart 驱动,驱动代码路径 soft_uart/soft_serial
找了好久的资料和网址才找到适用的,感谢github!!!求点赞收藏~~
我这边修改为platform_dirver 设备驱动模型了, 这个取决于你的使用场景
我这边改用platform_dirver的原因有如下几个
1、DTS可以动态配置中断号,GPIO管脚
2、通过 struct platform_device *pdev 可以获取更多数据结构来操作
3、本人使用的arm 平台申请IRQ方法跟github 对应的gpio irq 方法不一样,需要依赖指定的中断号
Module.c
static const struct of_device_id soft_uart_of_match[] = {
{.compatible = "soft_uart",},
};
static struct platform_driver soft_uart = {
.driver = {
.name = "soft_uart",
.owner = THIS_MODULE,
.of_match_table = soft_uart_of_match,
},
.probe = soft_uart_probe,
.remove = soft_uart_remove,
};
static int __init soft_uart_init(void)
{
return platform_driver_register(&soft_uart);
}
static void __exit soft_uart_exit(void)
{
platform_driver_unregister(&soft_uart);
}
获取指定的GPIO
#include <linux/gpio/consumer.h>
struct gpio_desc {
struct gpio_chip *chip;
unsigned long flags;
/* flag symbols are bit numbers */
#define FLAG_REQUESTED 0
#define FLAG_IS_OUT 1
#define FLAG_EXPORT 2 /* protected by sysfs_lock */
#define FLAG_SYSFS 3 /* exported via /sys/class/gpio/control */
#define FLAG_TRIG_FALL 4 /* trigger on falling edge */
#define FLAG_TRIG_RISE 5 /* trigger on rising edge */
#define FLAG_ACTIVE_LOW 6 /* value has active low */
#define FLAG_OPEN_DRAIN 7 /* Gpio is open drain type */
#define FLAG_OPEN_SOURCE 8 /* Gpio is open source type */
#define FLAG_USED_AS_IRQ 9 /* GPIO is connected to an IRQ */
#define ID_SHIFT 16 /* add new flags before this one */
#define GPIO_FLAGS_MASK ((1 << ID_SHIFT) - 1)
#define GPIO_TRIGGER_MASK (BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE))
#ifdef CONFIG_DEBUG_FS
const char *label;
#endif
};
static int soft_uart_probe(struct platform_device *pdev)
{
struct gpio_desc *dt_gpio_tx, *dt_gpio_rx;
struct device *dev = &pdev->dev;
printk(KERN_INFO "soft_uart: Initializing module...\n");
dt_gpio_tx = gpiod_get(dev, "gpio_tx");
if(IS_ERR(dt_gpio_tx)) {
pr_err("gpio_tx gpiod_get failed\n");
dt_gpio_tx = NULL;
} else {
set_bit(FLAG_OPEN_DRAIN, &dt_gpio_tx->flags);
gpio_tx = desc_to_gpio(dt_gpio_tx);
}
dt_gpio_rx = gpiod_get(dev, "gpio_rx");
if(IS_ERR(dt_gpio_rx)) {
pr_err("gpio_rx gpiod_get failed\n");
dt_gpio_rx = NULL;
} else {
set_bit(FLAG_OPEN_DRAIN, &dt_gpio_rx->flags);
gpio_rx = desc_to_gpio(dt_gpio_rx);
}
// ........
}
Dts 配置
soft_uart{
compatible = "soft_uart";
dev_name = "soft_uart";
status = "okay";
interrupts = <0 67 1>;
gpio_tx-gpios = <&gpio GPIOH_3 GPIO_ACTIVE_HIGH>;
gpio_rx-gpios = <&gpio GPIOH_2 GPIO_ACTIVE_HIGH>;
};
调试问题
修复好编译问题后就是一些移植调试问题
记录一下这个过程中遇到的一些问题吧
1、probe中的 gpiod_get 调用后, 调用了 raspberry_soft_uart_init 中的 gpio_request 会返回 -16 (EBUSY)导致驱动挂载不上,看了下gpiolib中的源码后发现 gpiod_get 其实是有调用了 gpio_request ,两者取一就好了
2、另外硬件选择的这组GPIO是OD的,一开始并不清楚,还在疑惑为什么Tx端的IO老是拉不高,如果是OD,需要外接上拉,目前硬件是已经外接上拉了,关于更多一些OD的介绍,可以看这篇文章 https://blog.csdn.net/qq_43033547/article/details/88759002 ,
不过代码中需要配置IO的属性为OD的属性,即前面init中配置的
set_bit(FLAG_OPEN_DRAIN, &dt_gpio_tx->flags);
简单介绍下设置成OD Flag 后一个电压设置流程
static void _gpiod_set_raw_value(struct gpio_desc *desc, int value)
{
struct gpio_chip *chip;
chip = desc->chip;
trace_gpio_value(desc_to_gpio(desc), 0, value);
if (test_bit(FLAG_OPEN_DRAIN, &desc->flags))
_gpio_set_open_drain_value(desc, value);
else if (test_bit(FLAG_OPEN_SOURCE, &desc->flags))
_gpio_set_open_source_value(desc, value);
else
chip->set(chip, gpio_chip_hwgpio(desc), value);
}
/*
* _gpio_set_open_drain_value() - Set the open drain gpio's value.
* @desc: gpio descriptor whose state need to be set.
* @value: Non-zero for setting it HIGH otherise it will set to LOW.
*/
static void _gpio_set_open_drain_value(struct gpio_desc *desc, int value)
{
int err = 0;
struct gpio_chip *chip = desc->chip;
int offset = gpio_chip_hwgpio(desc);
//pr_info("%s %d\n", __func__, __LINE__);
if (value) {
err = chip->direction_input(chip, offset);
if (!err)
clear_bit(FLAG_IS_OUT, &desc->flags);
} else {
err = chip->direction_output(chip, offset, 0);
if (!err)
set_bit(FLAG_IS_OUT, &desc->flags);
}
trace_gpio_direction(desc_to_gpio(desc), value, err);
if (err < 0)
gpiod_err(desc,
"%s: Error in set_value for open drain err %d\n",
__func__, err);
}
可以看到OD flag 是输出高的情况是设置为了输入模式,这是为什么???
结合图来看

设置为输出,芯片管脚下拉为低,所以电压为低
设置为输入,芯片管脚为阻态,由于有外部上拉,所以电压为高
3、使用的是时候需要配置波特率,最好是配置为4800bps
stty -F /dev/ttySOFT0 speed 4800
参照ReadMe
Usage
The device will appear as
/dev/ttySOFT0
. Use it as any usual TTY device.You must be included in the group
dialout
. You can verify in what groups you are included by typinggroups
. To add an user to the groupdialout
, type:sudo usermod -aG dialout <username>
Usage examples:
minicom -b 4800 -D /dev/ttySOFT0 cat /dev/ttySOFT0 echo "hello" > /dev/ttySOFT0
Baud rate
When choosing the baud rate, take into account that:
- The Raspberry Pi is not very fast.
- You will probably not be running a real-time operating system.
- There will be other processes competing for CPU time.
As a result, you can expect communication errors when using fast baud rates. So I would not try to go any faster than 4800 bps.
网友评论