虚拟串口驱动

工作中有个项目,需要通过串口连接设备,通过Modbus协议读取设备的数据。大多数情况下,公司没有相应的设备,只能写完代码后发现场验证。或公司设备数量不足,无法测试同时连多个相同设备的情况,导致许多问题测试阶段暴露不出来。
此驱动,参照linux内核代码中的dris/tty/serial/21285.c,模拟modbus协议设备,根据请求数据,返回相应报文。

驱动代码如下:
485_driver.c

// SPDX-License-Identifier: GPL-2.0
/*
 * Driver for the serial port on the  StrongArm-110 core logic chip.
 *
 * Based on drivers/char/serial.c
 */
#include <linux/module.h>
#include <linux/tty.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/console.h>
#include <linux/device.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/io.h>

#define SERIAL_NAME	"ttySA"
#define SERIAL_MAJOR	204
#define SERIAL_MINOR	4


static void serial_stop_tx(struct uart_port *port)
{
    printk("serial_stop_tx");
}

static void serial_start_tx(struct uart_port *port)
{
	struct circ_buf *xmit = &port->state->xmit;
	int i_pos = 0;
	struct tty_port *tty = NULL;

    printk("serial_start_tx");

	// 打印发送给驱动的数据
	do {

        printk("dummy_start_tx %02x ", xmit->buf[xmit->tail]);

        xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
        port->icount.tx++;  

        if (uart_circ_empty(xmit)) {
            break;
        }

    } while (++i_pos < 15); 
	printk("serial_start_tx: print tx end");

	// 组装返回数据
	tty = &port->state->port;

    if (NULL != tty)
    {
        tty_insert_flip_char(tty, 1, 0);
        tty_insert_flip_char(tty, 3, 0);
        tty_insert_flip_char(tty, 4, 0);
        tty_insert_flip_char(tty, 1, 0);
        tty_insert_flip_char(tty, 2, 0);
        tty_insert_flip_char(tty, 3, 0);
        tty_insert_flip_char(tty, 4, 0);
        tty_insert_flip_char(tty, 0x5B, 0);
        tty_insert_flip_char(tty, 0x3C, 0);

        tty_flip_buffer_push(tty);  
    }
}

static void serial_stop_rx(struct uart_port *port)
{
    printk("serial_stop_rx");
}

static unsigned int serial_tx_empty(struct uart_port *port)
{
	return 0;
}

/* no modem control lines */
static unsigned int serial_get_mctrl(struct uart_port *port)
{
	return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
}

static void serial_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
}

static void serial_break_ctl(struct uart_port *port, int break_state)
{
    printk("serial_break_ctl");
}

static int serial_startup(struct uart_port *port)
{
    printk("serial_startup");

	return 0;
}

static void serial_shutdown(struct uart_port *port)
{
    printk("serial_shutdown");
}

static void
serial_set_termios(struct uart_port *port, struct ktermios *termios,
			struct ktermios *old)
{
    printk("serial_set_termios");
}

static const char *serial_type(struct uart_port *port)
{
	return  NULL;
}

static void serial_release_port(struct uart_port *port)
{

}

static int serial_request_port(struct uart_port *port)
{
    printk("serial_request_port");

	return 0;
}

static void serial_config_port(struct uart_port *port, int flags)
{
		port->type = PORT_AMBA;

        printk("serial_config_port");
}

/*
 * verify the new serial_struct (for TIOCSSERIAL).
 */
static int serial_verify_port(struct uart_port *port, struct serial_struct *ser)
{
    printk("serial_verify_port");
        
	return 0;
}

static const struct uart_ops serial_ops = {
	.tx_empty	= serial_tx_empty,
	.get_mctrl	= serial_get_mctrl,
	.set_mctrl	= serial_set_mctrl,
	.stop_tx	= serial_stop_tx,
	.start_tx	= serial_start_tx,
	.stop_rx	= serial_stop_rx,
	.break_ctl	= serial_break_ctl,
	.startup	= serial_startup,
	.shutdown	= serial_shutdown,
	.set_termios	= serial_set_termios,
	.type		= serial_type,
	.release_port	= serial_release_port,
	.request_port	= serial_request_port,
	.config_port	= serial_config_port,
	.verify_port	= serial_verify_port,
};

static struct uart_port serial_port = {
	.mapbase	= 0x42000160,
	.iotype		= UPIO_MEM,
	.irq		= 0,
	.fifosize	= 16,
	.ops		= &serial_ops,
	.flags		= UPF_BOOT_AUTOCONF,
};

static void serial_setup_ports(void)
{
	printk("serial_setup_ports");
}

static struct uart_driver serial_reg = {
	.owner			= THIS_MODULE,
	.driver_name		= SERIAL_NAME,
	.dev_name		= SERIAL_NAME,
	.major			= SERIAL_MAJOR,
	.minor			= SERIAL_MINOR,
	.nr			= 1,
	.cons			= NULL,
};

static int __init serial_init(void)
{
	int ret;

	printk(KERN_INFO "Serial:  driver\n");

	serial_setup_ports();

	ret = uart_register_driver(&serial_reg);
	if (ret == 0)
		uart_add_one_port(&serial_reg, &serial_port);

	return ret;
}

static void __exit serial_exit(void)
{
	uart_remove_one_port(&serial_reg, &serial_port);
	uart_unregister_driver(&serial_reg);
}

module_init(serial_init);
module_exit(serial_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("serial 485 test driver");
MODULE_ALIAS_CHARDEV(SERIAL_MAJOR, SERIAL_MINOR);

Makefile内容如下:

ifneq ($(KERNELRELEASE),)
obj-m := 485_driver.o
 
else
PWD  := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions modules.*  Module.*
endif

驱动安装

执行make后,生成485_driver.ko
执行 insmod 485_driver.ko ,/dev/ttySA0为生成的驱动文件

驱动卸载

rmmod 485_driver

测试代码

利用libmodbus库,功能码3,从0开始读取2个寄存器,并打印读取到的数据,代码如下:
test.c


#include "include/modbus.h"
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int main()
{
    modbus_t *p_ctx = modbus_new_rtu("/dev/ttySA0", 9600, 'N', 8, 1);
    if (NULL == p_ctx)
    {
        printf("open failed\n");
        return -1;
    }

    printf("open ret: %p\n", p_ctx);

    modbus_set_debug(p_ctx, 1);

    int i_ret = modbus_connect(p_ctx);
    modbus_set_response_timeout(p_ctx, 0, 500000);

    modbus_set_slave(p_ctx, 1);

    for (int i = 0; i < 5; ++i)
    {
        uint16_t u16Buf[10] = { 0 };
        i_ret = modbus_read_registers(p_ctx, 0, 2, u16Buf);
        //printf("ret2 = %d, errno = %d\n", i_ret, errno);
        sleep(1);

        printf("read data: %X  %X\n\n", u16Buf[0], u16Buf[1]);
    }

    printf("modbus_close\n");
    modbus_close(p_ctx);

    printf("modbus_free\n");
    modbus_free(p_ctx);

    return 0;
}

测试打印日志

wang@wang:~/test$ sudo ./test 
open ret: 0x161c010
Opening /dev/ttySA0 at 9600 bauds (N, 8, 1)
[01][03][00][00][00][02][C4][0B]
Waiting for a confirmation...
<01><03><04><01><02><03><04><5B><3C>
read data: 102  304

[01][03][00][00][00][02][C4][0B]
Waiting for a confirmation...
<01><03><04><01><02><03><04><5B><3C>
read data: 102  304

[01][03][00][00][00][02][C4][0B]
Waiting for a confirmation...
<01><03><04><01><02><03><04><5B><3C>
read data: 102  304

[01][03][00][00][00][02][C4][0B]
Waiting for a confirmation...
<01><03><04><01><02><03><04><5B><3C>
read data: 102  304

[01][03][00][00][00][02][C4][0B]
Waiting for a confirmation...
<01><03><04><01><02><03><04><5B><3C>
read data: 102  304

modbus_close
modbus_free

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值