Linux 串口編程和程序相對來說是很簡單的,之所以用博客連載來展示,主要是想在學會使用的基礎(chǔ)上掌握相關(guān)背景,原理以及注意事項。相信在遇到問題的時候,我們就不會對于技術(shù)的概念和 API 的使用淺嘗輒止了。下面進入具體應(yīng)用案例,由于現(xiàn)在很多電腦已經(jīng)沒有引出串口以及波特率范圍會受到限制,這里我以 CH340 USB 轉(zhuǎn)串口芯片制作的模塊為基礎(chǔ)講解串口應(yīng)用程序開發(fā),關(guān)于該芯片在 Linux 系統(tǒng)的使用以及驅(qū)動加載可以參考:CH340 Linux驅(qū)動使用教程。
設(shè)備的打開與關(guān)閉
1. int libtty_open(const char *devname);
函數(shù)功能:根據(jù)傳入的串口設(shè)備名打開相應(yīng)的設(shè)備。成功返回設(shè)備句柄,失敗返回-1。
2. int libtty_close(int fd);
函數(shù)功能:關(guān)閉打開的設(shè)備句柄。成功返回0,失敗返回負值。
設(shè)備的配置
1. int libtty_setopt(int fd, int speed, char databits, char stopbits, char parity);
函數(shù)功能:配置串口設(shè)備,傳入?yún)?shù)依次為波特率設(shè)置、數(shù)據(jù)位設(shè)置、停止位設(shè)置、檢驗設(shè)置。
Notes: 設(shè)備打開前,可以通過 ls /dev 確認自己的硬件設(shè)備名,對于 USB 轉(zhuǎn)串口 IC,在系統(tǒng)下名稱為 "ttyUSBx",設(shè)備序號是根據(jù)插入主機的先后順序自動分配的,這里我的為 "ttyUSB0",讀者根據(jù)自己的需要修改。
/*** libtty_open - open tty device* @devname: the device name to open** In this demo device is opened blocked, you could modify it at will.*/
int libtty_open(const char *devname)
{int fd = open(devname, O_RDWR | O_NOCTTY | O_NDELAY); int flags = 0;if (fd == -1) { perror("open device failed");return -1; }flags = fcntl(fd, F_GETFL, 0);flags &= ~O_NONBLOCK;if (fcntl(fd, F_SETFL, flags) < 0) {printf("fcntl failed.\n");return -1;}if (isatty(fd) == 0){printf("not tty device.\n");return -1;}elseprintf("tty device test ok.\n");return fd;
}
Note:
傳入的 devname 參數(shù)為設(shè)備絕對路徑; O_NOCTTY 標志用于通知系統(tǒng),這個程序不會成為對應(yīng)這個設(shè)備的控制終端。如果沒有指定這個標志,那么任何一個輸入(如SIGINT等)都將會影響用戶的進程;O_NDELAY 標志與O_NONBLOCK 等效,但這里不僅僅是設(shè)置為非阻塞,還用于通知系統(tǒng),這個程序不關(guān)心 DCD 信號線所處的狀態(tài)(即與設(shè)備相連的另一端是否激活或者停止)。如果用戶指定了這一標志,則進程將會一直處在休眠狀態(tài),直到 DCD 信號線被激活;使用 fcntl 函數(shù)恢復(fù)設(shè)備狀態(tài)為阻塞狀態(tài),在數(shù)據(jù)收發(fā)時就會進行等待; 使用 isatty 函數(shù)測試當前打開的設(shè)備句柄是否關(guān)聯(lián)到終端設(shè)備,如果不是 tty 設(shè)備,那么也返回出錯;
/*** libtty_setopt - config tty device* @fd: device handle* @speed: baud rate to set* @databits: data bits to set* @stopbits: stop bits to set* @parity: parity set** The function return 0 if success, or -1 if fail.*/
int libtty_setopt(int fd, int speed, int databits, int stopbits, char parity)
{struct termios newtio;struct termios oldtio;int i;bzero(&newtio, sizeof(newtio));bzero(&oldtio, sizeof(oldtio));if (tcgetattr(fd, &oldtio) != 0) {perror("tcgetattr"); return -1; }newtio.c_cflag |= CLOCAL | CREAD;newtio.c_cflag &= ~CSIZE;/* set tty speed */for (i = 0; i < sizeof(speed_arr) / sizeof(int); i++) {if (speed == name_arr[i]) { cfsetispeed(&newtio, speed_arr[i]); cfsetospeed(&newtio, speed_arr[i]); } }/* set data bits */switch (databits) {case 5: newtio.c_cflag |= CS5;break;case 6: newtio.c_cflag |= CS6;break;case 7: newtio.c_cflag |= CS7;break;case 8: newtio.c_cflag |= CS8;break; default: fprintf(stderr, "unsupported data size\n");return -1; }/* set parity */switch (parity) { case 'n':case 'N':newtio.c_cflag &= ~PARENB; /* Clear parity enable */newtio.c_iflag &= ~INPCK; /* Disable input parity check */break; case 'o': case 'O': newtio.c_cflag |= (PARODD | PARENB); /* Odd parity instead of even */newtio.c_iflag |= INPCK; /* Enable input parity check */break; case 'e': case 'E': newtio.c_cflag |= PARENB; /* Enable parity */ newtio.c_cflag &= ~PARODD; /* Even parity instead of odd */ newtio.c_iflag |= INPCK; /* Enable input parity check */break;default: fprintf(stderr, "unsupported parity\n");return -1; } /* set stop bits */ switch (stopbits) { case 1: newtio.c_cflag &= ~CSTOPB; break;case 2: newtio.c_cflag |= CSTOPB; break;default: perror("unsupported stop bits\n"); return -1;}newtio.c_cc[VTIME] = 0; /* Time-out value (tenths of a second) [!ICANON]. */newtio.c_cc[VMIN] = 0; /* Minimum number of bytes read at once [!ICANON]. */tcflush(fd, TCIOFLUSH); if (tcsetattr(fd, TCSANOW, &newtio) != 0) {perror("tcsetattr");return -1;}return 0;
}
Note:
首先保存了原先串口配置,為了方便演示,這里保存為局部變量,實際使用時是需要把配置保存到全局 termios 結(jié)構(gòu)體中的。使用tcgetattr 還可以測試配置是否正確、串口是否可用等。返回值參見 man 手冊; 使用 CLOCAL 用于忽略所有 MODEM 狀態(tài)信號線,CREAD 標志用于使能接收。CSIZE 為數(shù)據(jù)位掩碼; 調(diào)用 cfsetispeed 與cfsetospeed 參數(shù)設(shè)置波特率,函數(shù)中引用了兩個數(shù)組,在后面的完整代碼中會看到,這樣書寫可以簡化設(shè)置代碼; 通過控制 c_cflag 與 c_iflag 配置串口數(shù)據(jù)位、停止位以及校驗設(shè)置等; VTIME 與VMIN 作用已經(jīng)講解多次,不再贅述,值得注意的是,TIME 值的單位是十分之一秒 ;通過 tcflush 清空輸入和輸出緩沖區(qū),根據(jù)實際需要修改; 最后通過 tcsetattr 函數(shù)對將配置實際作用于串口;
數(shù)據(jù)讀寫直接使用 read 、write 函數(shù)接口就可以了,因此沒有列舉出來。下面給出完整的串口讀寫測試代碼:
/* TTY testing utility (using tty driver)* Copyright (c) 2017* This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License as published by* the Free Software Foundation; either version 2 of the License.** Cross-compile with cross-gcc -I /path/to/cross-kernel/include*/#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h> int speed_arr[] = {B115200,B57600,B38400,B19200,B9600,B4800,B2400,B1200,B300
};int name_arr[] = {115200,57600,38400,19200,9600,4800,2400,1200,300
};/*** libtty_setopt - config tty device* @fd: device handle* @speed: baud rate to set* @databits: data bits to set* @stopbits: stop bits to set* @parity: parity set** The function return 0 if success, or -1 if fail.*/
int libtty_setopt(int fd, int speed, int databits, int stopbits, char parity)
{struct termios newtio;struct termios oldtio;int i;bzero(&newtio, sizeof(newtio));bzero(&oldtio, sizeof(oldtio));if (tcgetattr(fd, &oldtio) != 0) {perror("tcgetattr"); return -1; }newtio.c_cflag |= CLOCAL | CREAD;newtio.c_cflag &= ~CSIZE;/* set tty speed */for (i = 0; i < sizeof(speed_arr) / sizeof(int); i++) {if (speed == name_arr[i]) { cfsetispeed(&newtio, speed_arr[i]); cfsetospeed(&newtio, speed_arr[i]); } }/* set data bits */switch (databits) {case 5: newtio.c_cflag |= CS5;break;case 6: newtio.c_cflag |= CS6;break;case 7: newtio.c_cflag |= CS7;break;case 8: newtio.c_cflag |= CS8;break; default: fprintf(stderr, "unsupported data size\n");return -1; }/* set parity */switch (parity) { case 'n':case 'N':newtio.c_cflag &= ~PARENB; /* Clear parity enable */newtio.c_iflag &= ~INPCK; /* Disable input parity check */break; case 'o': case 'O': newtio.c_cflag |= (PARODD | PARENB); /* Odd parity instead of even */newtio.c_iflag |= INPCK; /* Enable input parity check */break; case 'e': case 'E': newtio.c_cflag |= PARENB; /* Enable parity */ newtio.c_cflag &= ~PARODD; /* Even parity instead of odd */ newtio.c_iflag |= INPCK; /* Enable input parity check */break;default: fprintf(stderr, "unsupported parity\n");return -1; } /* set stop bits */ switch (stopbits) { case 1: newtio.c_cflag &= ~CSTOPB; break;case 2: newtio.c_cflag |= CSTOPB; break;default: perror("unsupported stop bits\n"); return -1;}newtio.c_cc[VTIME] = 0; /* Time-out value (tenths of a second) [!ICANON]. */newtio.c_cc[VMIN] = 0; /* Minimum number of bytes read at once [!ICANON]. */tcflush(fd, TCIOFLUSH); if (tcsetattr(fd, TCSANOW, &newtio) != 0) {perror("tcsetattr");return -1;}return 0;
}/*** libtty_open - open tty device* @devname: the device name to open** In this demo device is opened blocked, you could modify it at will.*/
int libtty_open(const char *devname)
{int fd = open(devname, O_RDWR | O_NOCTTY | O_NDELAY); int flags = 0;if (fd == -1) { perror("open device failed");return -1; }flags = fcntl(fd, F_GETFL, 0);flags &= ~O_NONBLOCK;if (fcntl(fd, F_SETFL, flags) < 0) {printf("fcntl failed.\n");return -1;}if (isatty(fd) == 0){printf("not tty device.\n");return -1;}elseprintf("tty device test ok.\n");return fd;
}/*** libtty_close - close tty device* @fd: the device handle**/
int libtty_close(int fd)
{return close(fd);
}void tty_test(int fd)
{int nwrite, nread;char buf[100];memset(buf, 0x32, sizeof(buf));while (1) {nwrite = write(fd, buf, sizeof(buf));printf("wrote %d bytes already.\n", nwrite);nread = read(fd, buf, sizeof(buf));printf("read %d bytes already.\n", nread);sleep(2);}}int main(int argc, char *argv[])
{int fd;int ret;fd = libtty_open("/dev/ttyUSB0");if (fd < 0) {printf("libtty_open error.\n");exit(0);}ret = libtty_setopt(fd, 9600, 8, 1, 'n');if (ret != 0) {printf("libtty_setopt error.\n");exit(0);}tty_test(fd);ret = libtty_close(fd);if (ret != 0) {printf("libtty_close error.\n");exit(0);}
}
執(zhí)行成功的話,會在終端屏幕上看到每隔兩秒輸出串口成功發(fā)送和接收的字節(jié)數(shù),測試時可以直接短接串口的發(fā)送和接收引腳進行測試。以下為成功測試截圖:
關(guān)于?Linux?串口編程的其他文章,可以移步至以下鏈接:
《Linux 串口編程<一> 一些背景》 《Linux 串口編程<二> 深入了解 termios》 《Linux 串口編程<三> 使用termios與API 進行串口程序開發(fā)》 《Linux 串口編程<四> 串口設(shè)備程序開發(fā)》
有疑問的讀者可以給我郵件或者評論,覺得對你有幫助就點贊吧~:-D
總結(jié)
以上是生活随笔 為你收集整理的Linux 串口编程四 串口设备程序开发 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔 推薦給好友。