目录

串口驱动层级结构

485配置流程

dts相关

配置注册

初始化

485收发切换

delay_after_send


       

目前linux 内核中已经支持了485的实现,但由于底层驱动的支持情况,导致我们采用不同芯片时需要对底层驱动进行修改,以满足内核485的各个回调接口,进而实现485功能。

485 主要在于收发控制切换的时间。本文以 8250为示例,描述485配置及切换的流程。

串口驱动层级结构

不讨论tty,tty以下分为三层

1)串口通用层

2)8250通用层

3)具体的8250芯片驱动层 

    8250_dw为具体芯片层。在解析到dts中有对应的配置时,则调用此去的probe接口。如果dts里面有三个串口,则调用三次probe接口。而后依次调用上一层的接口将串口信息上报。

485配置流程

dts相关

   在 8250_dw向8250注册串口时,8250层回调用通用的485 dts解析函数 uart_get_rs485_mode,以获取该串口对485的支持情况,该函数的代码 如下。通过此代码,我们可以看到几个dts参数,其功能分别为:

dts 功能
rs485-rts-delay 485收发切换后的延时,单位ms
rs485-term 切换GPIO管脚定义
linux,rs485-enabled-at-boot-time 是否默认使能485,后续也可以通过ioctl改配

  


/**
 * uart_get_rs485_mode() - retrieve rs485 properties for given uart
 * @port: uart device's target port
 *
 * This function implements the device tree binding described in
 * Documentation/devicetree/bindings/serial/rs485.txt.
 */
int uart_get_rs485_mode(struct uart_port *port)
{
	struct serial_rs485 *rs485conf = &port->rs485;
	struct device *dev = port->dev;
	u32 rs485_delay[2];
	int ret;

	ret = device_property_read_u32_array(dev, "rs485-rts-delay",
					     rs485_delay, 2);
	if (!ret) {
		rs485conf->delay_rts_before_send = rs485_delay[0];
		rs485conf->delay_rts_after_send = rs485_delay[1];
	} else {
		rs485conf->delay_rts_before_send = 0;
		rs485conf->delay_rts_after_send = 0;
	}

	/*
	 * Clear full-duplex and enabled flags, set RTS polarity to active high
	 * to get to a defined state with the following properties:
	 */
	rs485conf->flags &= ~(SER_RS485_RX_DURING_TX | SER_RS485_ENABLED |
			      SER_RS485_TERMINATE_BUS |
			      SER_RS485_RTS_AFTER_SEND);
	rs485conf->flags |= SER_RS485_RTS_ON_SEND;

	if (device_property_read_bool(dev, "rs485-rx-during-tx"))
		rs485conf->flags |= SER_RS485_RX_DURING_TX;

	if (device_property_read_bool(dev, "linux,rs485-enabled-at-boot-time"))
		rs485conf->flags |= SER_RS485_ENABLED;

	if (device_property_read_bool(dev, "rs485-rts-active-low")) {
		rs485conf->flags &= ~SER_RS485_RTS_ON_SEND;
		rs485conf->flags |= SER_RS485_RTS_AFTER_SEND;
	}

	/*
	 * Disabling termination by default is the safe choice:  Else if many
	 * bus participants enable it, no communication is possible at all.
	 * Works fine for short cables and users may enable for longer cables.
	 */
	port->rs485_term_gpio = devm_gpiod_get_optional(dev, "rs485-term",
							GPIOD_OUT_LOW);
	if (IS_ERR(port->rs485_term_gpio)) {
		ret = PTR_ERR(port->rs485_term_gpio);
		port->rs485_term_gpio = NULL;
		return dev_err_probe(dev, ret, "Cannot get rs485-term-gpios\n");
	}

	return 0;
}

配置注册

最底层芯片驱动需要注册:

  1) serial层的回调接口

  int            (*rs485_config)(struct uart_port *,   struct serial_rs485 *rs485);

     完成485相关的初始化

 2)8250层的回调接口:

    void            (*rs485_start_tx)(struct uart_8250_port *);
    void            (*rs485_stop_tx)(struct uart_8250_port *);

   用于控制 485的收发切换

初始化

uart_set_rs485_config--》驱动的rs485_config回调

serial8250_em485_config-,除了基本的结构成员初始化,这里主要起了两个高精度定时器的处理函数,用于处理延时后的切换。

static int serial8250_em485_init(struct uart_8250_port *p)
{
	if (p->em485)
		return 0;

	p->em485 = kmalloc(sizeof(struct uart_8250_em485), GFP_ATOMIC);
	if (!p->em485)
		return -ENOMEM;

	hrtimer_init(&p->em485->stop_tx_timer, CLOCK_MONOTONIC,
		     HRTIMER_MODE_REL);
	hrtimer_init(&p->em485->start_tx_timer, CLOCK_MONOTONIC,
		     HRTIMER_MODE_REL);
	p->em485->stop_tx_timer.function = &serial8250_em485_handle_stop_tx;
	p->em485->start_tx_timer.function = &serial8250_em485_handle_start_tx;
	p->em485->port = p;
	p->em485->active_timer = NULL;
	p->em485->tx_stopped = true;

	p->rs485_stop_tx(p);

	return 0;
}

485收发切换

默认接收,切换发送通过 接口 start_tx_rs485 ,相对比较容易。

再切换回接收时,由于存在发送FIFO的情况,需要处理,内核代码中加了判断处理,代码如下:

static inline void __stop_tx(struct uart_8250_port *p)
{
	struct uart_8250_em485 *em485 = p->em485;

	if (em485) {
		unsigned char lsr = serial_in(p, UART_LSR);
		/*
		 * To provide required timeing and allow FIFO transfer,
		 * __stop_tx_rs485() must be called only when both FIFO and
		 * shift register are empty. It is for device driver to enable
		 * interrupt on TEMT.
		 */
		if ((lsr & BOTH_EMPTY) != BOTH_EMPTY)
			return;

		__stop_tx_rs485(p);
	}
	__do_stop_tx(p);
}

这里主要判断了LSR寄存器,如果lsr寄存器没有实现 ,则485的切换一直不会进行。笔者就遇到了这种不支持的情况。

delay_after_send

   由于没有lsr的支持,这时需要靠延时来处理何时切换。内核代码已经支持,即delay_after_send,单位为ms。

    比如发640个字节,发送fifo大小为64,那我们可以将此delay设置为

   10bit*1000ms*len/baud

   =10*1000*64/115200= 5.56~~6ms

   实际测试:配置发送FIFO 为空时触发,每次发送64个,则延时配置7ms

    如果发送650个字节,则最后一次发送10个字节,

  10*1000*10/115200=0~1ms  ,则延时配置2ms

  __stop_tx用户在用户缓存数据发送完毕后调用

由此可见,delay和芯片的FIFO大小及触发门限强相关。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐