學(xué)無止境--Linux串口編程(RS485)
備注:學(xué)習(xí)記錄所用,若有高手不吝賜教,萬分感謝!
一、概括
??linux將串口都映射成了TTY終端,所以在串口編程時(shí),找到并使能平臺的TTY,然后操作TTY終端即可。
??例如對于Nuclei平臺的軒轅91030M芯片設(shè)備樹:
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都設(shè)置為"okay",然后在“/dev/”目錄下就會(huì)看到ttySIF0和ttySIF1兩個(gè)串口終端設(shè)備。其中ttySIF0是tty終端設(shè)備,也就是我們調(diào)試時(shí)連接到串口助手所用的串口,另一個(gè)ttySIF1就可以用來作為RS485串口。
二、收發(fā)控制
??RS485是半雙工,需要一個(gè)gpio控制收發(fā)(如果硬件可以自動(dòng)收發(fā)控制,則不需要)。
??gpio控制收發(fā)的寫法我了解的主要有三種:
??1、修改tty驅(qū)動(dòng)
????網(wǎng)上大部分都是基于imux6ull的教程,我看到的基本都是這種,大家可以自行查找。
????本人認(rèn)為移植和維護(hù)比較麻煩,還要修改內(nèi)核的tty驅(qū)動(dòng)。
??2、用戶態(tài)驅(qū)動(dòng)
????以gpio5為例,就是在用戶態(tài)實(shí)現(xiàn)以下過程:
????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、內(nèi)核態(tài)驅(qū)動(dòng)
????3.1、設(shè)備樹配置
/*
* GPIO_ACTIVE_HIGH 0
* GPIO_ACTIVE_LOW 1
*/
tl485ctl {
compatible = "tl,rs485_ctl";
de-gpios = <&gpio 8 1>;
};
????此配置下,若是gpio有修改,只需要修改設(shè)備樹即可。
????3.2、驅(qū)動(dòng)代碼
??????注冊一個(gè)雜項(xiàng)設(shè)備。
查看代碼
//#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; /*設(shè)備節(jié)點(diǎn)--設(shè)備樹中的 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", /*真正需要操作的設(shè)備名*/
};
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); /*真正設(shè)置gpio方向的地方*/
//gpio_free(tl485dev.de_gpio);
ret = misc_register(&tl485_ctl_dev); /*里面實(shí)現(xiàn)了分配設(shè)備號、設(shè)備注冊、class創(chuàng)建、dev創(chuàng)建等過程*/
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;
}
/* 設(shè)備樹匹配列表 */
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");
??然后在需要時(shí)控制/dev/tl485_ctl_pin即可。
三、用戶態(tài)代碼
??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;
/* 設(shè)置字符大小 */
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;
}
??需要的有不少,這些都可以在網(wǎng)上找到。
??2、串口收發(fā)控制
查看代碼
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) /* 用戶態(tài)驅(qū)動(dòng)接口 */
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) /* 用戶態(tài)驅(qū)動(dòng)接口 */
return -1;
return 0;
#endif
}
??3、串口初始化
查看代碼
int devRS485InitPort(int com)
{
int fd;
int rv;
int flags = 0;
/*USART_RS485_DEV就是 /dev/ 下的tty設(shè)備,如本例中就是"/dev/ttySIF1" */
fd = open(USART_RS485_DEV, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0) {
perror("Open error:\n");
exit(1);
}
/* 恢復(fù)串口為阻塞狀態(tài) */
if (fcntl(fd, F_SETFL, 0) < 0) {
printf("fcntl failed.\n");
return -1;
}
/* 測試該設(shè)備是否為tty設(shè)備 */
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、串口發(fā)送函數(shù)
查看代碼
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只是將數(shù)據(jù)從文件寫到了發(fā)送緩存區(qū),
* tcdrain是等待發(fā)送緩存區(qū)發(fā)送完成,完成之前阻塞。
* 從很多教程都說這個(gè)函數(shù)會(huì)在這里阻塞,但是我測試波形時(shí)發(fā)現(xiàn):
收發(fā)控制管腳總是在RS485數(shù)據(jù)線剛出波形就置為接收狀態(tài)了
!!!!!!!這里沒搞懂,所以在后面加了延時(shí)。
*/
rv = tcdrain(ttyfd);
if(rv < 0)
{
printf("Wite 485 send failed\n");
return -1;
}
usleep(len * 1000); /*9600發(fā)送1BYTE數(shù)據(jù)大約1ms*/
rv = io_rs485_to_recv();
if(rv < 0)
{
printf("Set 485 recv failed\n");
return -1;
}
return 0;
}
??5、接收函數(shù)
查看代碼
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最后一個(gè)參數(shù)
tv.tv_sec = 2; //阻塞時(shí)間(秒)
tv.tv_usec = 500; //阻塞時(shí)間(毫秒)
while (1)
{
FD_ZERO(&rfds);
FD_SET(g485fd, &rfds);
retval = select(g485fd + 1 , &rfds, NULL, NULL, NULL); /*最后一個(gè)參數(shù)為NULL表示有數(shù)據(jù)前一直阻塞*/
if (retval == -1) {
perror("select()");
break;
}
else if (retval) { // pan duan shi fou hai you shu ju
if(!FD_ISSET(g485fd,&rfds)) /*判斷是不是這個(gè)串口觸發(fā)的*/
continue;
/*測試時(shí)發(fā)現(xiàn)這里每次調(diào)用read()只收到一個(gè)byte*/
nread = read(g485fd, gRcvBuf + gRcvLen, 256);
gRcvLen += nread;
/*緩存區(qū)越界處理,這里只是隨便寫的,需要修改*/
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;
};
}
??創(chuàng)建一個(gè)任務(wù)處理串口的接收即可。
參考:
??https://blog.csdn.net/weixin_45003868/article/details/130263090
??https://zhuanlan.zhihu.com/p/521283753?utm_id=0&wd=&eqid=b51aeee60009016800000003647efd5b

浙公網(wǎng)安備 33010602011771號