Linux 串列埠程式設計 串列埠裝置程式開發
Linux 串列埠程式設計和程式相對來說是很簡單的,之所以用部落格連載來展示,主要是想在學會使用的基礎上掌握相關背景,原理以及注意事項。相信在遇到問題的時候,我們就不會對於技術的概念和 API 的使用淺嘗輒止了。下面進入具體應用案例,由於現在很多電腦已經沒有引出串列埠以及波特率範圍會受到限制,這裡我以 CH340 USB 轉串列埠晶片製作的模組為基礎講解串列埠應用程式開發,關於該晶片在 Linux 系統的使用以及驅動載入可以參考:CH340 Linux驅動使用教程。
裝置的開啟與關閉
1. int libtty_open(const char *devname);
函式功能:根據傳入的串列埠裝置名開啟相應的裝置。成功返回裝置控制程式碼,失敗返回-1。
2. int libtty_close(int fd);
函式功能:關閉開啟的裝置控制程式碼。成功返回0,失敗返回負值。
裝置的配置
1. int libtty_setopt(int fd, int speed, char databits, char stopbits, char parity);
函式功能:配置串列埠裝置,傳入引數依次為波特率設定、資料位設定、停止位設定、檢驗設定。
Notes:裝置開啟前,可以通過 ls /dev 確認自己的硬體裝置名,對於 USB 轉串列埠 IC,在系統下名稱為 "ttyUSBx",裝置序號是根據插入主機的先後順序自動分配的,這裡我的為 "ttyUSB0",讀者根據自己的需要修改。
/**
* 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;
}
else
printf("tty device test ok.\n");
return fd;
}
Note:- 傳入的 devname 引數為裝置絕對路徑;
- O_NOCTTY標誌用於通知系統,這個程式不會成為對應這個裝置的控制終端。如果沒有指定這個標誌,那麼任何一個輸入(如SIGINT等)都將會影響使用者的程式;
- O_NDELAY標誌與O_NONBLOCK 等效,但這裡不僅僅是設定為非阻塞,還用於通知系統,這個程式不關心 DCD 訊號線所處的狀態(即與裝置相連的另一端是否啟用或者停止)。如果使用者指定了這一標誌,則程式將會一直處在休眠狀態,直到 DCD 訊號線被啟用;
- 使用 fcntl 函式恢復裝置狀態為阻塞狀態,在資料收發時就會進行等待;
- 使用 isatty函式測試當前開啟的裝置控制程式碼是否關聯到終端裝置,如果不是 tty 裝置,那麼也返回出錯;
/**
* 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 結構體中的。使用tcgetattr還可以測試配置是否正確、串列埠是否可用等。返回值參見 man 手冊;
- 使用 CLOCAL 用於忽略所有 MODEM 狀態訊號線,CREAD 標誌用於使能接收。CSIZE 為資料位掩碼;
- 呼叫 cfsetispeed與cfsetospeed引數設定波特率,函式中引用了兩個陣列,在後面的完整程式碼中會看到,這樣書寫可以簡化設定程式碼;
- 通過控制 c_cflag 與 c_iflag 配置串列埠資料位、停止位以及校驗設定等;
- VTIME與VMIN作用已經講解多次,不再贅述,值得注意的是,TIME 值的單位是十分之一秒;
- 通過 tcflush清空輸入和輸出緩衝區,根據實際需要修改;
- 最後通過 tcsetattr 函式對將配置實際作用於串列埠;
資料讀寫直接使用 read、write 函式介面就可以了,因此沒有列舉出來。下面給出完整的串列埠讀寫測試程式碼:
/* 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;
}
else
printf("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);
}
}
執行成功的話,會在終端螢幕上看到每隔兩秒輸出串列埠成功傳送和接收的位元組數,測試時可以直接短接串列埠的傳送和接收引腳進行測試。以下為成功測試截圖:
關於 Linux 串列埠程式設計的其他文章,可以移步至以下連結:
有疑問的讀者可以給我郵件或者評論,覺得對你有幫助就點贊吧~:-D
相關文章
- Linux 串列埠程式設計 使用termios與API進行串列埠程式開發Linux串列埠程式設計iOSAPI
- Linux串列埠程式設計Linux串列埠程式設計
- Linux 串列埠程式設計Linux串列埠程式設計
- POSIX 串列埠程式設計指南串列埠程式設計
- Linux下串列埠程式設計基礎Linux串列埠程式設計
- 沒有真實串列埠裝置時使用"虛擬串列埠驅動"除錯你的串列埠程式碼串列埠除錯
- Android之串列埠程式設計Android串列埠程式設計
- Linux 串列埠程式設計 一些背景Linux串列埠程式設計
- Linux 串列埠程式設計 深入瞭解 termiosLinux串列埠程式設計iOS
- android串列埠程式Android串列埠
- 基於多串列埠ETH005裝置的Socket網路程式設計串列埠程式設計
- ROS串列埠程式設計學習筆記ROS串列埠程式設計筆記
- VC++串列埠程式設計之簡訊應用開發(轉)C++串列埠程式設計
- VC++串列埠通訊程式設計詳解C++串列埠程式設計
- Linux下串列埠通訊詳解(下)讀寫串列埠及關閉串列埠Linux串列埠
- C#獲取本地串列埠裝置C#串列埠
- Linux下PCI轉串列埠卡及USB轉串列埠Linux串列埠
- 串列埠屏開發曲線串列埠
- linux 串列埠通訊Linux串列埠
- Windows下的Win32串列埠程式設計WindowsWin32串列埠程式設計
- 迪文屏OS彙編程式碼開發-串列埠篇串列埠
- Xamarin.Forms-手機串列埠除錯程式開發文件ORM串列埠除錯
- 串列埠UART串列埠
- 帶內串列埠 在串列埠中輸入命令串列埠
- 詳解linux下的串列埠通訊開發Linux串列埠
- Linux單裝置多路USB串列埠的實現方法介紹Linux串列埠
- 串列埠通訊利器:SerialPortStream庫詳解,輕鬆實現C#串列埠開發串列埠C#
- 串列埠資料抓取及串列埠通訊模擬串列埠
- 你真的瞭解串列埠嗎(示波器串列埠波形分析)串列埠
- tty,串列埠,控制檯與驅動程式串列埠
- C#串列埠通訊程式SerialPort類C#串列埠
- ubuntu 為USB串列埠繫結固定的裝置名Ubuntu串列埠
- Linux串列埠程式收發16進位制資料錯誤Linux串列埠
- 串列埠流控串列埠
- 串列埠通訊串列埠
- 串列埠blog串列埠
- IBM串列埠線序以及串列埠線的做法(轉)IBM串列埠
- UART串列埠及Linux實現串列埠Linux