备注:学习记录所用,若有高手不吝赐教,万分感谢!
一、概括
linux将串口都映射成了TTY终端,所以在串口编程时,找到并使能平台的TTY,然后操作TTY终端即可。
例如对于Nuclei平台的轩辕91030M芯片设备树:
uart0: serial@10013000 {
compatible = "sifive,uart0";
reg = <0x0 0x10013000 0x0 0x1000>;
interrupt-parent = <&plic0>;
interrupts = <2>;
clocks = <&hfclk2>;
status = "okay";
};
uart1: serial@10012000 {
compatible = "sifive,uart0";
reg = <0x0 0x10012000 0x0 0x1000>;
interrupt-parent = <&plic0>;
interrupts = <3>;
clocks = <&hfclk2>;
status = "okay";
};
将status都设置为"okay",然后在“/dev/”目录下就会看到ttySIF0和ttySIF1两个串口终端设备。其中ttySIF0是tty终端设备,也就是我们调试时连接到串口助手所用的串口,另一个ttySIF1就可以用来作为RS485串口。
二、收发控制
RS485是半双工,需要一个gpio控制收发(如果硬件可以自动收发控制,则不需要)。
gpio控制收发的写法我了解的主要有三种:
1、修改tty驱动
网上能查到的基本都是这种,可以自行查找。
本人认为移植和维护比较麻烦,还要修改内核的tty驱动。
2、用户态驱动
以gpio5为例,就是在用户态实现以下过程:
echo 485 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio485/direction
echo 0 > /sys/class/gpio/gpio485/value
echo 1 > /sys/class/gpio/gpio485/value
3、内核态驱动
3.1、设备树配置
/*
* GPIO_ACTIVE_HIGH 0
* GPIO_ACTIVE_LOW 1
*/
tl485ctl {
compatible = "tl,rs485_ctl";
de-gpios = <&gpio 8 1>;
};
此配置下,若是gpio有修改,只需要修改设备树即可。
3.2、驱动代码
注册一个杂项设备。
查看代码
//#define TLMOD_DEBUG
#ifdef TLMOD_DEBUG
#define DPRINTK(x...) printk("tl485_ctl DEBUG:" x)
#else
#define DPRINTK(x...)
#endif
#define DRIVER_NAME "tl485_ctl"
struct tl485_ctl{
int de_gpio;
struct device_node *nd; /*设备节点--设备树中的 tl485ctl {...};*/
};
struct tl485_ctl tl485dev;
int tl485_ctl_open(struct inode *inode,struct file *filp)
{
DPRINTK("Device Opened Success!\n");
return nonseekable_open(inode, filp);
}
int tl485_ctl_release(struct inode *inode,struct file *filp)
{
DPRINTK("Device Closed Success!\n");return 0;
}
int tl485_ctl_pm(bool enable)
{
int ret = 0;
DPRINTK("firecxx debug: GPS PM return %d\r\n" , ret);
return ret;
};
long tl485_ctl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
DPRINTK("firecxx debug: tl485_ctl_ioctl cmd is %d\n" , cmd);
switch(cmd)
{
case 1:
gpio_set_value(tl485dev.de_gpio, 1);
udelay(10); /*可忽略*/
DPRINTK("tl485_ctl Set High!\n");
break;
case 0:
gpio_set_value(tl485dev.de_gpio, 0);
udelay(10); /*可忽略*/
DPRINTK("tl485_ctl Set Low!\n");
break;
default:
DPRINTK("tl485_ctl COMMAND ERROR!\n");
return -ENOTTY;
}
return 0;
}
static struct file_operations tl485_ctl_ops = {
.owner = THIS_MODULE,
.open = tl485_ctl_open,
.release= tl485_ctl_release,
.unlocked_ioctl = tl485_ctl_ioctl,
};
static struct miscdevice tl485_ctl_dev = {
.minor = MISC_DYNAMIC_MINOR,
.fops = &tl485_ctl_ops,
.name = "tl485_ctl_pin", /*真正需要操作的设备名*/
};
static int tl485_ctl_probe(struct platform_device *pdev)
{
int err = 0;
int ret;
DPRINTK("tl485_ctl Initialize\n");
tl485dev.nd = pdev->dev.of_node;
if(tl485dev.nd == NULL)
{
printk(KERN_ERR "Can't get node tl485_ctl!\n");
return err;
}
tl485dev.de_gpio = of_get_named_gpio(tl485dev.nd, "de-gpios", 0);
if(tl485dev.de_gpio < 0)
{
printk(KERN_ERR "Can't get de_gpio!\n");
return err;
}
DPRINTK("tl485_ctl Initialize : %d\n", tl485dev.de_gpio);
err = gpio_request(tl485dev.de_gpio, "tl485_ctl");
if (err)
{
printk(KERN_ERR "failed to request GPIO_%d for ""tl485_ctl control\n", tl485dev.de_gpio);
return err;
}
gpio_direction_output(tl485dev.de_gpio, 0); /*真正设置gpio方向的地方*/
//gpio_free(tl485dev.de_gpio);
ret = misc_register(&tl485_ctl_dev); /*里面实现了分配设备号、设备注册、class创建、dev创建等过程*/
if(ret<0)
{
printk(KERN_ERR "tl485_ctl:register device failed!\n");
goto exit;
}
return 0;
exit:
misc_deregister(&tl485_ctl_dev);
return ret;
}
static int tl485_ctl_remove (struct platform_device *pdev)
{
gpio_free(tl485dev.de_gpio);
misc_deregister(&tl485_ctl_dev);
return 0;
}
static int tl485_ctl_suspend (struct platform_device *pdev, pm_message_t state)
{
DPRINTK("tl485_ctl suspend:power off!\n");
return 0;
}
static int tl485_ctl_resume (struct platform_device *pdev)
{
DPRINTK("tl485_ctl resume:power on!\n");
return 0;
}
/* 设备树匹配列表 */
static const struct of_device_id tl485_ctl_of_match[] = {
{.compatible = "tl,rs485_ctl"},
{}
};
static struct platform_driver tl485_ctl_driver = {
.probe = tl485_ctl_probe,
.remove = tl485_ctl_remove,
.suspend = tl485_ctl_suspend,
.resume = tl485_ctl_resume,
.driver = {
.name = "tl485_ctl",
.owner = THIS_MODULE,
.of_match_table = tl485_ctl_of_match,
},
};
static int __init tl485_ctl_init(void)
{
return platform_driver_register(&tl485_ctl_driver);
}
static void __exit tl485_ctl_exit(void)
{
platform_driver_unregister(&tl485_ctl_driver);
}
module_init(tl485_ctl_init);
module_exit(tl485_ctl_exit);
MODULE_LICENSE("GPL");
然后在需要时控制/dev/tl485_ctl_pin即可。
三、用户态代码
1、串口配置
查看代码
static int io_uart_set_opt(int ttyfd,int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio, oldtio;
if (tcgetattr(ttyfd, &oldtio) != 0)
{
perror("SetupSerial 1");
return -1;
}
bzero(&newtio, sizeof(newtio));
newtio.c_cflag |= CLOCAL | CREAD;
/* 设置字符大小 */
newtio.c_cflag &= ~CSIZE;
switch(nBits)
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
switch(nEvent)
{
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N':
newtio.c_cflag &= ~PARENB;
break;
}
switch(nSpeed)
{
case 2400:
cfsetispeed(&newtio, B2400);
cfsetospeed(&newtio, B2400);
break;
case 4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
case 460800:
cfsetispeed(&newtio, B460800);
cfsetospeed(&newtio, B460800);
break;
case 921600:
cfsetispeed(&newtio, B921600);
cfsetospeed(&newtio, B921600);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
if (nStop == 1)
newtio.c_cflag &= ~CSTOPB;
else if (nStop == 2)
newtio.c_cflag |= CSTOPB;
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
tcflush(ttyfd, TCIOFLUSH);
if((tcsetattr(ttyfd, TCSANOW, &newtio))!=0)
{
perror("com set error");
return -1;
}
return 0;
}
需要的有不少,这些都可以在网上找到。
2、串口收发控制
查看代码
static int io_rs485_to_send(void)
{
int ret;
#if KERNEL_RS485_CTRL
int fd;
char *tl485_ctl = "/dev/tl485_ctl_pin";
fd = open(tl485_ctl, O_RDWR);
if(fd < 0)
{
printf("Open %s failed\n", tl485_ctl);
close(fd);
return -1;
}
ret = ioctl(fd, 1, 0);
if(ret<0)
{
printf("tl485 set ctl to high failed!\r\n");
close(fd);
return -1;
}
close(fd);
return 0;
#else
if(devGpioSet(8, 1) < 0)
return -1;
return 0;
#endif
}
static int io_rs485_to_recv(void)
{
int ret;
#if KERNEL_RS485_CTRL
int fd;
char *tl485_ctl = "/dev/tl485_ctl_pin";
fd=open(tl485_ctl, O_RDWR);
if(fd < 0)
{
printf("Open %s failed\n", tl485_ctl);
close(fd);
exit(1);
}
ret = ioctl(fd, 0, 0);
if(ret<0)
{
close(fd);
printf("tl485 set ctl to low failed!\r\n");
return -1;
}
close(fd);
return 0;
#else
if(devGpioSet(8, 0) < 0)
return -1;
return 0;
#endif
}
3、串口初始化
查看代码
int devRS485InitPort(int com)
{
int fd;
int rv;
int flags = 0;
fd = open(USART_RS485_DEV, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0) {
perror("Open error:\n");
exit(1);
}
/* 恢复串口为阻塞状态 */
if (fcntl(fd, F_SETFL, 0) < 0) {
printf("fcntl failed.\n");
return -1;
}
/* 测试该设备是否为tty设备 */
if (isatty(fd) == 0) {
printf("not tty device.\n");
return -1;
}
rv = io_uart_set_opt(fd, 9600, 8, 'N', 1);
if (rv < 0) {
printf("Set uart faild\n");
return -1;
}
return fd;
}
4、串口发送函数
查看代码
STATUS devRS485SendDatas(int ttyfd, const char *buf, int len)
{
int rv = 0;
ssize_t wlen = 0;
int sendflag = 0;
rv = io_rs485_to_send();
if(rv < 0)
{
printf("set 485 to send failed\n");
return -1;
}
wlen = write(ttyfd, buf, len);
if (wlen != len) {
tcflush(ttyfd, TCOFLUSH);
printf("write 485 failed\n");
return -1;
}
/* write只是将数据从文件写到了发送缓存区,
* tcdrain是等待发送缓存区发送完成,完成之前阻塞。
* 从很多教程都说这个函数会在这里阻塞,但是我测试波形时发现:
收发控制管脚总是在RS485数据线刚出波形就置为接收状态了
!!!!!!!这里没搞懂,所以在后面加了延时。
*/
rv = tcdrain(ttyfd);
if(rv < 0)
{
printf("Wite 485 send failed\n");
return -1;
}
usleep(len * 1000); /*9600发送1BYTE数据大约1ms*/
rv = io_rs485_to_recv();
if(rv < 0)
{
printf("Set 485 recv failed\n");
return -1;
}
return 0;
}
5、接收函数
查看代码
int g485fd;
int open485(void)
{
int retval;
fd_set rfds;
struct timeval tv;
int nread;
char gRcvBuf[256];
int gRcvLen = 0;
g485fd = devRS485InitPort(0);
if (g485fd < 0) {
printf("error: open console error.\r\n");
close(g485fd);
return ERROR;
}
// wait 2.5s ,select最后一个参数
tv.tv_sec = 2; //阻塞时间(秒)
tv.tv_usec = 500; //阻塞时间(毫秒)
while (1)
{
FD_ZERO(&rfds);
FD_SET(g485fd, &rfds);
retval = select(g485fd + 1 , &rfds, NULL, NULL, NULL); /*最后一个参数为NULL表示有数据前一直阻塞*/
if (retval == -1) {
perror("select()");
break;
}
else if (retval) { // pan duan shi fou hai you shu ju
if(!FD_ISSET(g485fd,&rfds)) /*判断是不是这个串口触发的*/
break;
/*测试时发现这里每次调用read()只收到一个byte*/
nread = read(g485fd, gRcvBuf + gRcvLen, 256);
gRcvLen += nread;
/*缓存区越界处理,这里只是随便写的,需要修改*/
if (gRcvLen >= sizeof(gRcvBuf))
gRcvLen = 0;
//printf("gRcvLen = %d ", gRcvLen);
if (gRcvBuf[gRcvLen - 2] == '\r' && gRcvBuf[gRcvLen-1] == '\n') {
FD_ZERO(&rfds);
FD_SET(g485fd, &rfds);
retval = select(g485fd + 1 , &rfds, NULL, NULL, NULL);
if (!retval) continue;// no datas, break
}
/*
*包解析流程
......
*/
//for (int i = 0; i < 19; i++) {
// printf("%02x ", gRcvBuf[i]);
//}
//printf("gRcvLen=%d\r\n", gRcvLen);
}
else {
continue;
};
}
创建一个任务处理串口的接收即可。
标签:return,int,串口,tl485,linux,ctl,gpio,学无止境,newtio From: https://www.cnblogs.com/blog-xh/p/17831127.html