一,介绍
在外接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管脚,黄线为控制管脚)