工作中有个项目,需要通过串口连接设备,通过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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
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