作者:亞洲程序員盟主
串口文件
在linux中,針對所有的周邊設備都提供了設備文件供用戶訪問,所以如果要訪問串口,只要打開相關的設備文件即可。
在LInux下串口文件是位于/dev下的
COM1串口一為/dev/ttyS0
COM2串口2為/dev/ttyS1
或者
COM1串口一為/dev/ttyUSB0
COM2串口2為/dev/ttyUSB1
命令查詢串口:
?
~$ ls /dev/ttyS* /dev/ttyS0 /dev/ttyS12 /dev/ttyS16 /dev/ttyS2 /dev/ttyS23 /dev/ttyS27 /dev/ttyS30 /dev/ttyS6 /dev/ttyS1 /dev/ttyS13 /dev/ttyS17 /dev/ttyS20 /dev/ttyS24 /dev/ttyS28 /dev/ttyS31 /dev/ttyS7 /dev/ttyS10 /dev/ttyS14 /dev/ttyS18 /dev/ttyS21 /dev/ttyS25 /dev/ttyS29 /dev/ttyS4 /dev/ttyS8 /dev/ttyS11 /dev/ttyS15 /dev/ttyS19 /dev/ttyS22 /dev/ttyS26 /dev/ttyS3 /dev/ttyS5 /dev/ttyS9
?
?
方法1:輪詢
1. 打開串口
?
fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY); if (fd == -1) { perror("open_port: Unable to open serial port"); return -1; }
?
2. 配置串口
?
tcgetattr(fd, &options); cfsetispeed(&options, B115200); cfsetospeed(&options, B115200); options.c_cflag |= (CLOCAL | CREAD); options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_cflag &= ~CRTSCTS; tcsetattr(fd, TCSANOW, &options);
?
其中,tcgetattr 和 tcsetattr 函數用于獲取和設置串口參數。cfsetispeed 和 cfsetospeed 函數用于設置串口的輸入和輸出波特率,這里設置為 115200。options.c_cflag 表示控制標志位,用于配置串口控制參數,具體含義如下:
CLOCAL:忽略調制解調器的狀態線,只允許本地使用串口。
CREAD:允許從串口讀取數據。
PARENB:啟用奇偶校驗。&= ~PARENB則為禁用校驗。
CSTOPB:使用兩個停止位而不是一個。&= ~CSTOPB停止位為1。
CSIZE:表示字符長度的位掩碼。在這里設置為 0,表示使用默認的 8 位數據位。
CS8:表示使用 8 位數據位。
CRTSCTS:啟用硬件流控制,即使用 RTS 和 CTS 狀態線進行流控制。
在示例程序中,我們將 CLOCAL 和 CREAD 標志位置為 1,表示允許本地使用串口,并允許從串口讀取數據。我們將 PARENB、CSTOPB 和 CRTSCTS 標志位都設置為 0,表示不啟用奇偶校驗、使用一個停止位和禁用硬件流控制。最后,我們將 CSIZE 標志位設置為 0,然后將 CS8 標志位設置為 1,以表示使用 8 位數據位。
3. 讀寫
?
read(fd, buf, sizeof(buf)); // 返回接收個數 write(fd, buf, strlen(buf)); // 返回發送長度,負值表示發送失敗
?
4. 關閉串口
?
close(fd);
?
完整示例
?
int open_port(const char *port) { int fd; struct termios options; // 打開串口設備 fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY); if (fd == -1) { perror("open_port: Unable to open serial port"); return -1; } // 配置串口參數 tcgetattr(fd, &options); cfsetispeed(&options, B115200); cfsetospeed(&options, B115200); options.c_cflag |= (CLOCAL | CREAD); options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_cflag &= ~CRTSCTS; tcsetattr(fd, TCSANOW, &options); return fd; } int main() { int fd; char buf[255]; int n; // 打開串口設備 fd = open_port("/dev/ttyUSB0"); if (fd == -1) { printf("open err "); exit(1); } while (1) { // 讀取串口數據 n = read(fd, buf, sizeof(buf)); if (n > 0) { printf("Received: %.*s ", n, buf); } // 發送串口數據 strcpy(buf, "Hello, world! "); n = write(fd, buf, strlen(buf)); if (n < 0) { perror("write failed "); } usleep(10 * 1000); } // 關閉串口設備 close(fd); printf("close uart "); return 0; }
?
方法2:中斷讀取示例
上面給出的串口示例是使用輪詢的方式讀取串口數據,這種方式在某些場景下可能會占用大量 CPU 資源。實際上,對于 Linux 系統來說,還可以使用中斷方式接收串口數據,這樣可以大大減少 CPU 的占用率,并且能夠更快地響應串口數據。
要使用中斷方式接收串口數據,可以使用 select 函數來監聽串口文件描述符的可讀事件。當串口數據可讀時,select 函數將返回,并且可以調用 read 函數來讀取串口數據。這種方式可以避免輪詢操作,只有在串口數據可讀時才會執行讀取操作,因此能夠減少 CPU 的占用率。
以下是一個簡單的使用中斷方式接收串口數據的示例程序:
?
#include#include #include #include #include #include int main() { int fd; struct termios options; fd_set rfds; // 打開串口設備 fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY); if (fd < 0) { perror("open"); return -1; } // 配置串口參數 tcgetattr(fd, &options); options.c_cflag = B9600 | CS8 | CLOCAL | CREAD; options.c_iflag = IGNPAR; options.c_oflag = 0; options.c_lflag = 0; options.c_cc[VTIME] = 0; options.c_cc[VMIN] = 1; tcsetattr(fd, TCSANOW, &options); while (1) { // 使用 select 函數監聽串口文件描述符的可讀事件 FD_ZERO(&rfds); FD_SET(fd, &rfds); select(fd + 1, &rfds, NULL, NULL, NULL); // 讀取串口數據 char buf[256]; int n = read(fd, buf, sizeof(buf)); if (n > 0) { printf("Received data: %.*s ", n, buf); } } // 關閉串口設備 close(fd); return 0; }
?
需要注意的是,在使用中斷方式接收串口數據時,需要對串口文件描述符設置為非阻塞模式,以便在 select 函數返回時立即讀取串口數據。可以使用 fcntl 函數來設置文件描述符的標志位,如下所示:
?
// 設置串口文件描述符為非阻塞模式 int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK);
?
方法3:信號的方式接收數據
?
#include#include #include #include #include #include int fd; void sigio_handler(int sig) { char buf[256]; int n = read(fd, buf, sizeof(buf)); if (n > 0) { printf("Received data: %.*s ", n, buf); } } int main() { struct termios options; struct sigaction sa; // 打開串口設備 fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY); if (fd < 0) { perror("open"); return -1; } // 配置串口參數 tcgetattr(fd, &options); options.c_cflag = B9600 | CS8 | CLOCAL | CREAD; options.c_iflag = IGNPAR; options.c_oflag = 0; options.c_lflag = 0; options.c_cc[VTIME] = 0; options.c_cc[VMIN] = 1; tcsetattr(fd, TCSANOW, &options); // 設置串口文件描述符為異步通知模式 /* 將串口文件描述符設置為當前進程的擁有者,從而接收該文件描述符相關的信號。*/ fcntl(fd, F_SETOWN, getpid()); int flags = fcntl(fd, F_GETFL, 0); // 先獲取當前配置, 下面只更改O_ASYNC標志 /* 將串口文件描述符設置為非阻塞模式,從而允許該文件描述符異步地接收數據和信號。*/ fcntl(fd, F_SETFL, flags | O_ASYNC); // 設置 SIGIO 信號的處理函數 sa.sa_handler = sigio_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; /* 設置了 SIGIO 信號的處理函數為 sigio_handler,從而在該信號被觸發時讀取串口數據并進行處理。*/ sigaction(SIGIO, &sa, NULL); while (1) { // 等待 SIGIO 信號 sleep(1); } // 關閉串口設備 close(fd); return 0; }
?
上述代碼中,使用了 fcntl 函數將串口文件描述符設置為異步通知模式,并使用 SIGIO 信號來通知程序串口數據已經可讀。當程序接收到 SIGIO 信號時,會調用 sigio_handler 函數來讀取并處理串口數據。
在這段代碼中,sigemptyset(&sa.sa_mask);的作用是將信號處理函數在執行時要屏蔽的信號集合清空,即將其設置為空集。
每個進程都有一個信號屏蔽字,它表示了當前被阻塞的信號集合。當一個信號被阻塞時,它將被加入到信號屏蔽字中,而當信號被解除阻塞時,它將被從信號屏蔽字中移除。如果信號處理函數在執行時需要屏蔽其他的信號,則可以使用sigaddset等函數將需要屏蔽的信號添加到信號屏蔽字中。但是,在本例中,我們需要處理的信號是SIGIO,它通常不需要被屏蔽,因此我們使用sigemptyset函數將信號屏蔽字清空,以確保在處理SIGIO信號時不會屏蔽任何其他信號。
在Linux系統中,使用sigaction函數注冊信號處理函數時,可以設置一些標志來指定信號處理的行為。例如,可以使用SA_RESTART標志來指定當系統調用被信號中斷時自動重啟該系統調用。在本例中,由于我們并不需要設置任何標志,因此將sa.sa_flags字段設置為0即可。這表示信號處理函數不需要任何特殊的行為,只需要按照默認的方式處理信號即可。
方法4:使用線程接收串口數據:
?
#include#include #include #include #include #include void *read_thread(void *arg) { int fd = *(int *)arg; char buf[256]; int n; while (1) { // 讀取串口數據 n = read(fd, buf, sizeof(buf)); if (n > 0) { printf("Received data: %.*s ", n, buf); } } return NULL; } int main() { int fd; struct termios options; pthread_t tid; // 打開串口設備 fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY); if (fd < 0) { perror("open"); return -1; } // 配置串口參數 tcgetattr(fd, &options); options.c_cflag = B9600 | CS8 | CLOCAL | CREAD; options.c_iflag = IGNPAR; options.c_oflag = 0; options.c_lflag = 0; options.c_cc[VTIME] = 0; options.c_cc[VMIN] = 1; tcsetattr(fd, TCSANOW, &options); // 創建讀取線程 if (pthread_create(&tid, NULL, read_thread, &fd) != 0) { perror("pthread_create"); return -1; } while (1) { // 主線程的其他處理邏輯 sleep(1); } // 關閉串口設備 close(fd); return 0; }
?
上述代碼中,創建了一個讀取線程,不斷讀取串口數據并進行處理。主線程可以在讀取線程運行的同時進行其他處理邏輯。
評論
查看更多