GPIO 模拟Uart 通信 (soft uart/serial

2020-12-19  本文已影响0人  Nothing_655f

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 是输出高的情况是设置为了输入模式,这是为什么???

结合图来看


1608206854412.png

设置为输出,芯片管脚下拉为低,所以电压为低

设置为输入,芯片管脚为阻态,由于有外部上拉,所以电压为高

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 typing groups. To add an user to the group dialout, 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.

上一篇 下一篇

猜你喜欢

热点阅读