提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
linux下实现RS485驱动,消除DE引脚抖动
RS485驱动其实就是串口驱动外加一个GPIO驱动,GPIO驱动很好实现,但是RS485最大的问题在于DE引脚拉高拉低的时候,带来的延时,会影响到数据的收发,本文主要介绍6M串口下,几种收发影响。
本文使用的串口波特率是6M串口,因为使用场景比较固定,所以就把波特率写死了,动态调整也是同一个思路。
目前我使用的是linux 4.9.113版本,linux定义串口波特率的位置是
/** Routine which returns the baud rate of the tty** Note that the baud_table needs to be kept in sync with the* include/asm/termbits.h file.*/
static const speed_t baud_table[] = {0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,9600, 19200, 38400, 57600, 115200, 230400, 460800,
#ifdef __sparc__76800, 153600, 307200, 614400, 921600
#else500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000,2500000, 3000000, 3500000, 6000000
#endif
};#ifndef __sparc__
static const tcflag_t baud_bits[] = {B0, B50, B75, B110, B134, B150, B200, B300, B600,B1200, B1800, B2400, B4800, B9600, B19200, B38400,B57600, B115200, B230400, B460800, B500000, B576000,B921600, B1000000, B1152000, B1500000, B2000000, B2500000,B3000000, B3500000, B6000000
};
#else
static const tcflag_t baud_bits[] = {B0, B50, B75, B110, B134, B150, B200, B300, B600,B1200, B1800, B2400, B4800, B9600, B19200, B38400,B57600, B115200, B230400, B460800, B76800, B153600,B307200, B614400, B921600
};
#endif
按照上述数组,我把4M对应的改为自己的6M波特率,我是用的是ARM64架构,头文件修改的路径是:
linux/include/uapi/asm-generic/termbits.h。。。。。。。。。。
#define B460800 0010004
#define B500000 0010005
#define B576000 0010006
#define B921600 0010007
#define B1000000 0010010
#define B1152000 0010011
#define B1500000 0010012
#define B2000000 0010013
#define B2500000 0010014
#define B3000000 0010015
#define B6000000 0010017 //这里是修改的地方
。。。。。
下面修改相关程序,在sdk里面找到你自己底层的uart驱动程序,修改
baud = uart_get_baud_rate(port, termios, old, 9600, 6100000);
这个函数是设置波特率的函数,限制了波特率的最大和最小值,当然有的sdk驱动里面不一定调用该函数,所以需要自己排查一下,有的话就把最大值修改一下。
做完以上工作后驱动就可以编译通过了,但是,在实际编译应用程序的时候,还有可能报错,因为应用程序的库链接是和你的交叉编译环境有关,所以用户需要手动到自己对应编译环境下,修改termbits.h,这里有个技巧,如果用户找不到自己的环境路径,就把linux/include/uapi/asm-generic/termbits.h这个路径链家进去,编译器会提示冲突的(哈哈)
GPIO驱动的实现网上教程很多,这里就不直接贴代码了:
首先需要修改设备树,在修改自己的GPIO驱动,gpiod_direction_output或者gpiod_set_value设置的值是逻辑值,不一定是物理值。
使用这个方式适合读写响应比较慢的场景,不适合速度快的场景
首先每一个串口包含两个FIFO,一个是发送FIFO,一个是接收FIFO,而且每一个FIFO的深度是可以调节的(用具体的寄存器),驱动里面默认配置是32字节的FIFO深度,这里一般在probe里面会设置FIFO的深度,这个决定了中断的触发方式。
需要注意的是一般来说SDK里面所有的串口,都是共有一个串口驱动的,所以我们在修改这个驱动前一定要特别注意,用能力级区分开RS485驱动和一般的驱动。
下面我们使用最简单的区分方式,使用串口的ID区分。比如串口四是RS485,可以使用pdev->id==4来判断,该方法写死了,不具备通用性,不建议照搬(会被主管骂死)
static int meson_uart_probe(struct platform_device *pdev)
{struct resource *res_mem, *res_irq;struct uart_port *port;struct meson_uart_port *mup;struct clk *clk;const void *prop;int ret = 0;if (pdev->dev.of_node)pdev->id = of_alias_get_id(pdev->dev.of_node, "serial");if (pdev->id < 0 || pdev->id >= AML_UART_PORT_MAX)return -EINVAL;res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (!res_mem)return -ENODEV;res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);if (!res_irq)return -ENODEV;if (meson_ports[pdev->id]) {dev_err(&pdev->dev, "port %d already allocated\n", pdev->id);return -EBUSY;}mup = devm_kzalloc(&pdev->dev,sizeof(struct meson_uart_port), GFP_KERNEL);if (!mup)return -ENOMEM;
#if 1if (pdev->id == 4){gpio_de = gpiod_get(&pdev->dev, "ys485de", 0);if (IS_ERR(gpio_de)){printk("gpiod get error %d \n", pdev->id);}else{printk("gpiod get success %d \n", pdev->id);}}
#endif//spin_lock_init(&mup->wr_lock);port = &mup->port;
#ifdef CONFIG_AMLOGIC_CLKclk = devm_clk_get(&pdev->dev, "clk_gate");if (IS_ERR(clk)) {pr_err("%s: clock gate not found\n", dev_name(&pdev->dev));/* return PTR_ERR(clk); */} else {ret = clk_prepare_enable(clk);if (ret) {pr_err("uart: clock failed to prepare+enable: %d\n",ret);clk_put(clk);/* return ret; */}}clk = devm_clk_get(&pdev->dev, "clk_uart");if (IS_ERR(clk)) {pr_err("%s: clock source not found\n", dev_name(&pdev->dev));/* return PTR_ERR(clk); */}if (!IS_ERR(clk))port->uartclk = clk_get_rate(clk);
#endifport->fifosize = 64;prop = of_get_property(pdev->dev.of_node, "fifosize", NULL);if (prop)port->fifosize = of_read_ulong(prop, 1);if (!xtal_tick_en) {prop = of_get_property(pdev->dev.of_node, "xtal_tick_en", NULL);if (prop) {xtal_tick_en = of_read_ulong(prop, 1);if (xtal_tick_en == 1)xtal_tick_en = 0;}}port->iotype = UPIO_MEM;port->mapbase = res_mem->start;port->irq = res_irq->start;port->flags = UPF_BOOT_AUTOCONF | UPF_IOREMAP | UPF_LOW_LATENCY;port->dev = &pdev->dev;port->line = pdev->id;port->type = PORT_MESON;port->x_char = 0;port->ops = &meson_uart_ops;meson_ports[pdev->id] = mup;platform_set_drvdata(pdev, port);if (of_get_property(pdev->dev.of_node, "pinctrl-names", NULL)) {mup->p = devm_pinctrl_get_select_default(&pdev->dev);/* if (!mup->p) *//* return -1; */}ret = uart_add_one_port(&meson_uart_driver, port);if (ret)meson_ports[pdev->id] = NULL;prop = of_get_property(pdev->dev.of_node, "support-sysrq", NULL);if (prop)support_sysrq = of_read_ulong(prop, 1);return ret;
} corresponding to the given hw number for this chip.
在串口的读写函数比较复杂,这里不在赘述,可以参考下面几个博客的内容,这里只说方法。
https://blog.csdn.net/Luckiers/article/details/123577836
https://blog.csdn.net/lizuobin2/article/details/51773305
https://blog.csdn.net/sharecode/article/details/9196591
串口的open函数,对应于,struct uart_ops结构体的 .startup回调函数
static struct gpio_desc *gpio_de = NULL;
struct tasklet_struct stGpioTasklet;static int meson_uart_startup(struct uart_port *port)
{u32 val;int ret = 0;val = readl(port->membase + AML_UART_CONTROL);val |= (AML_UART_RX_RST | AML_UART_TX_RST | AML_UART_CLR_ERR);writel(val, port->membase + AML_UART_CONTROL);val &= ~(AML_UART_RX_RST | AML_UART_TX_RST | AML_UART_CLR_ERR);writel(val, port->membase + AML_UART_CONTROL);val |= (AML_UART_RX_EN | AML_UART_TX_EN);writel(val, port->membase + AML_UART_CONTROL);val |= (AML_UART_RX_INT_EN | AML_UART_TX_INT_EN);val &= ~UART_CTS_EN;writel(val, port->membase + AML_UART_CONTROL);
#if 1if (port->line == 4){gpiod_direction_output(gpio_de, 0);tasklet_init(&stGpioTasklet, gpio_485_do_tasklet, (unsigned long)port);}
#endifreturn ret;
}void gpio_485_do_tasklet(unsigned long param)
{struct uart_port *port;port = (struct uart_port *)param;while (meson_uart_tx_empty(port) != TIOCSER_TEMT){;} gpiod_set_value(gpio_de, 0);
}
struct uart_ops结构体里面.start_tx的回调函数实现了write的函数
static void meson_uart_start_tx(struct uart_port *port)
{struct circ_buf *xmit = &port->state->xmit;unsigned int ch;struct meson_uart_port *mup = to_meson_port(port);unsigned long flags;spin_lock_irqsave(&mup->wr_lock, flags);if (port->line == 4){gpiod_set_value(gpio_de, 1);}while (!uart_circ_empty(xmit)) {if (!(readl(port->membase + AML_UART_STATUS) &AML_UART_TX_FULL)) {ch = xmit->buf[xmit->tail];writel(ch, port->membase + AML_UART_WFIFO);xmit->tail = (xmit->tail + 1) & (SERIAL_XMIT_SIZE - 1);port->icount.tx++;} elsebreak;}#if 0if (port->line == 4){while (meson_uart_tx_empty(port) != TIOCSER_TEMT){;}gpiod_set_value(gpio_de, 0);}
#endifspin_unlock_irqrestore(&mup->wr_lock, flags);
}
需要解释一下,当meson_uart_start_tx(struct uart_port *port)函数里面把GPIO拉高以后,函数结尾
#if 0 … #endif 里面的部分是不可以直接拉低的,因为uart_circ_empty里面为空后,硬件层面可能没有吧数据发送完,会出现发送不及时的情况,如下图:
在EO后面应该还有数据,但是我已经无法继续写入。
到这里基本上就可以实现us级别的延时,如果您还想延时更加精确点,可以做的改善有: