RK35XX在内核层适配485驱动

本文介绍如何在RK35XX平台的内核层面对485驱动进行适配,使得应用层可以像操作普通串口一样操作485,无需手动控制收发引脚。文中详细说明了在设备树中配置485控制引脚的方法,并展示了如何修改8250串口驱动以支持485功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 一,介绍

在外接485芯片使用时,应用层收发数据需要控制一个收发引脚,这样做在实际使用时很不方便。有一个解决思路是,应用层把485当做串口来使用,无需关注控制引脚,这样做需要在内核串口驱动里适配下485。

该方法的逻辑就是在默认情况下485一直处于接收状态,当需要发送数据时,引脚切换为发送模式,发完数据的瞬间将引脚切换为接收模式,引脚切换时间的长短决定了485的丢包率大小,所以这个时间必须短。

二,方法

1,查找瑞芯微rk3568串口驱动

查看设备树串口的 “compatible”属性,其中有两个名称。

uart0: serial@fdd50000 {
        compatible = "rockchip,rk3568-uart", "snps,dw-apb-uart";
        reg = <0x0 0xfdd50000 0x0 0x100>;
        interrupts = <GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&pmucru SCLK_UART0>, <&pmucru PCLK_UART0>;
        clock-names = "baudclk", "apb_pclk";
        reg-shift = <2>;
        reg-io-width = <4>;
        dmas = <&dmac0 0>, <&dmac0 1>;
        pinctrl-names = "default";
        pinctrl-0 = <&uart0_xfer>;
        status = "disabled";
    };

先在源码搜索"rockchip,rk3568-uart",没有搜索到,再搜索"snps,dw-apb-uart"。在8250_dw.c文件中找到了匹配的名称,采用的是8250的串口驱动。

static const struct of_device_id dw8250_of_match[] = {
    { .compatible = "snps,dw-apb-uart" },
    { .compatible = "cavium,octeon-3860-uart" },
    { .compatible = "marvell,armada-38x-uart" },
    { .compatible = "renesas,rzn1-uart" },
    { /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, dw8250_of_match);

2,设备树适配

首先,在设备树中添加485_ctrl_gpio属性,用于区分普通串口和485

&uart0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_xfer &uart485_ctrl>;
    485_ctrl_gpio = <&gpio3 RK_PA6 GPIO_ACTIVE_HIGH>;
};

3,适配8250驱动

  • probe初始化

查找8250驱动的probe函数dw8250_probe,定义在drivers/tty/serial/8250/8250_dw.c文件中。

在“p->private_data = &data->data;”代码后面添加如下内容:

    //判断是否为485
    struct device_node *nd = pdev->dev.of_node;
    int gpio_crtl = of_get_named_gpio(nd,"485_ctrl_gpio",0);
    if (gpio_crtl > 0)
    {    
    data->data.flag_485 = 0xabcd;
    data->data.gpiocrtl_485 = gpio_crtl;

    gpio_direction_output(gpio_crtl, 0);
    tasklet_init(&((struct dw8250_port_data *)p->private_data)->s485_tasklet, serial8250_485_do_tasklet, (unsigned long)p->private_data);
}
  • 添加数据成员

flag_485和gpio_crtl、tasklet等变量存放到private_data中,需要在原本数据结构下添加几个成员

修改drivers/tty/serial/8250/8250_dwlib.h,添加下面4个成员,其中suart_port用于存储对应的串口结构体

struct dw8250_port_data {
    /* Port properties */
    int            line;
    /* DMA operations */
    struct uart_8250_dma    dma;
    /* Hardware configuration */
    u8            dlf_size;
    int flag_485;
    int gpiocrtl_485;
    struct uart_port *suart_port;
    struct tasklet_struct s485_tasklet;
};

suart_port的初始化是drivers/tty/serial/8250/8250_core.c的serial8250_register_8250_port函数

        uart->port.mapsize      = up->port.mapsize;
		uart->port.private_data = up->port.private_data;

++	    if (((struct dw8250_port_data *)(up->port.private_data))->gpiocrtl_485 > 0)
++		{
++			((struct dw8250_port_data *)(up->port.private_data))->suart_port = &(uart->port);
++		}
  • 数据接收

控制引脚电平默认为低电平接收状态,不影响接收。

  • 数据发送

发送数据时,会先进入到serial8250_start_tx函数

static const struct uart_ops serial8250_pops = {
    .tx_empty    = serial8250_tx_empty,
    .set_mctrl    = serial8250_set_mctrl,
    .get_mctrl    = serial8250_get_mctrl,
    .stop_tx    = serial8250_stop_tx,
    .start_tx    = serial8250_start_tx,
    .throttle    = serial8250_throttle,

在serial8250_start_tx函数中,将控制引脚拉高,切为发送状态,准备发送数据。

static void serial8250_start_tx(struct uart_port *port)
{
    if (((struct dw8250_port_data *)port->private_data)->flag_485 == 0xabcd)
    {
        gpio_direction_output(((struct dw8250_port_data *)port->private_data)->gpiocrtl_485, 1);
    }
    
    struct uart_8250_port *up = up_to_u8250p(port);
    struct uart_8250_em485 *em485 = up->em485;
    
    serial8250_rpm_get_tx(up);
    
    if (em485 &&
         em485->active_timer == &em485->start_tx_timer)
    return;
    
    if (em485)
        start_tx_rs485(port);
    else
        __start_tx(port);
}

8250串口的收发数据是通过中断方式实现的,流程如下:

serial8250_default_handle_irq()->
serial8250_handle_irq()->
serial8250_tx_chars()->

在serial8250_tx_chars函数 if (uart_circ_empty(xmit) && !(up->capabilities & UART_CAP_RPM))里面将tasklet调度起来

if (uart_circ_empty(xmit) && !(up->capabilities & UART_CAP_RPM))
{   __stop_tx(up);

    if (((struct dw8250_port_data *)port->private_data)->flag_485 == 0xabcd)
    {
        struct uart_port *p = &up->port;
        tasklet_hi_schedule(&((struct dw8250_port_data *)p->private_data)->s485_tasklet);
    }   
}

执行完成,会进入tasklet的回调函数,判断是否发送完成,完成后将485切换为接收状态

void serial8250_485_do_tasklet(unsigned long param)
{
    //printk("serial8250_485_do_tasklet\n");
    struct uart_port *port;
    unsigned int lsr;
    
    //printk("param = %ld\n", ((struct dw8250_port_data *)param)->suart_port);
    port = (struct uart_port *)((struct dw8250_port_data *)param)->suart_port; //获取对应的串口结构体

    while (1)
    {
        lsr = serial_port_in(port, UART_LSR);
        if (((lsr & UART_LSR_TEMT) == UART_LSR_TEMT)) //判断是否发送完成
            break;
    }
    gpio_direction_output(((struct dw8250_port_data *)port->private_data)->gpiocrtl_485, 0); //发送完成切回接收状态
}

注意:编译时8250_port文件可能会提示struct dw8250_port_data未定义,单独在该文件定义下即可

三,测试

板子运行一个select机制的串口回环程序,通过电脑往板子串口发数据,间隔设置10ms,波特率为115200

使用示波器测量波形,控制引脚会在100us内切换完成,满足需求。(图中绿线为板子tx管脚,黄线为控制管脚)

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值