关于嵌入式Linux串口编程
Linux下的串口设备主要在dev目录下,比如ttySx或者ttySUx或者ttyOx。比am1808 是ttyS0-S2 SU0-SU7,而am335x是ttyO0-O5。
1. 本文基本编程API介绍,最基本的设置串口包括波特率设置,效验位和停止位设置。
1.1 串口的设置主要是设置 struct termios结构体的各成员值。
struct termios
{
unsigned short c_iflag; /* 输入模式标志 */
unsigned short c_oflag; /* 输出模式标志 */
unsigned short c_cflag; /* 控制模式标志*/
unsigned short c_lflag; /* 本地模式标志 */
unsigned char c_line; /* 控制协议 */
unsigned char c_cc[NCC]; /* 控制模式字符*/
};
所有对串口的操作都是通过结构体 struct termios和几个函数实现的,其中两个最常用的函数是tcgetattr()和tcsetattr(),几乎在所有情况下,程序都是通过tcgetattr()函数获取设备当前的设置,然后修改这些设置,最后用tcsetattr()使这些设置生效。许多程序会保存终端初始的设置并在终止运行前恢复这些设置。
函数返回0表示成功。
1.2 tcgetattr和tcsetattr函数
int tcgetattr (int fd ,struct termios *t)
用于获得文件描述符fd所表示设备的当前设置值,并写入指针t内
int tcsetattr (int fd, int options ,struct termios *t)
用来将termios结构指针t内的设置值赋给当前用文件描述符fd表示的设备终端
参数options决定什么时候改变才生效
TCSANOW———修改立即生效
TCSADRAIN——所有已经发送的输出写入fd后生效
TCSAFLUSH——输出队列为空时生效
例如:下面是修改波特率的代码:
struct termios Opt;
tcgetattr(fd, &Opt);
cfsetispeed(&Opt,B19200); /*设置为19200Bps*/ cfsetospeed(&Opt,B19200);
tcsetattr(fd,TCANOW,&Opt);
1.3 波特率设定
cfsetispeed()和cfsetospeed()函数分别用来设置设备的输入输出速度。
int cfsetispeed(struct termios *t ,speed_t speed)
int cfsetospeed(struct termios *t ,speed_t speed)
通过结构体t分别将设备的输入输出速度设为speed。它们只是设置了termios结构体的速度,若要修改设备的速度还需要调用tcsetattr()函数,下面看一个设置波特率的例子:
int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300, B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400, 19200, 9600, 4800, 2400, 1200, 300, };
void set_speed(int fd, int speed)
{
int i; int status;
struct termios Opt;
tcgetattr(fd, &Opt);
for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++)
{
if (speed == name_arr[i])
{
tcflush(fd, TCIOFLUSH);
cfsetispeed(&Opt, speed_arr[i]);
cfsetospeed(&Opt, speed_arr[i]);
status = tcsetattr(fd1, TCSANOW, &Opt);
if (status != 0)
{
perror("tcsetattr fd1");
return;
}
tcflush(fd,TCIOFLUSH);
}
}
}
1.4 tcflush()函数
用于丢弃设备上的一些数据 ,丢弃的数据取决于第二个参数queue_selector
原型为:int tcflush(int fd ,int queue_selector)
TCIFLUSH——丢弃接口上已经接收到但还没读入的所有数据
TCOFLUSH——丢弃所有已经写入接口但还没发送的数据
TCIOFLUSH——丢弃所有输入输出队列上还没有读取或发送的数据
1.5 校验位和停止位的设置
No parity (8N1):
options.c_cflag &= ~PARENB;//无校验
options.c_cflag &= ~CSTOPB;//1位停止位
options.c_cflag &= ~CSIZE; //无位掩码
options.c_cflag |= CS8; //8数据位
Even parity (7E1):
options.c_cflag |= PARENB ;//有校验
options.c_cflag &= ~PARODD ;//偶校验
options.c_cflag &= ~CSTOPB;//一位停止位
options.c_cflag &= ~CSIZE; //无位掩码
options.c_cflag |= CS7; //7数据位
Odd parity (7O1):
options.c_cflag |= PARENB ;//有校验
options.c_cflag |= PARODD;//奇校验
options.c_cflag &= ~CSTOPB ; //一位停止位
options.c_cflag &= ~CSIZE; //无位掩码
options.c_cflag |= CS7; //7数据位
CSIZE ——bit mask for data bits
CS7——7 data bits
CS8——8 data bits
CSTOPB——2 stop bits (1 otherwise)
PARENB——enable parity
PARODD——use odd parity instead of even
1.6 读写串口
设置好串口之后,读写串口就很容易了,可以把串口当作文件读写
发送数据:
char buffer[1024];
int Length=1024;
int nByte;
nByte = write(fd, buffer ,Length)
使用文件操作read函数读取,如果设置为原始模式(Raw Mode)传输数据,那么read函数返回的字符数是实际串口收到的字符数。
char buff[1024];
int Len=1024;
int readByte = read(fd, buff, Len);
2. 具体编程
具体的程序在code目录下,以下有几个需要特别注意的地方需要说明的。
options.c_iflag &= ~ (IXON | IXOFF | IXANY); c_cc数组的VSTART和VSTOP元素被设定成DC1和DC3,代表ASCII标准的XON和XOFF字符,如果在传输这两个字符的时候就传不过去,需要把软件流控制屏蔽,
options.c_lflag &= ~ (ICANON | ECHO | ECHOE | ISIG); 有时候,在用write发送数据时没有键入回车,信息就发送不出去,这主要是因为我们在输入输出时是按照规范模式接收到回车或换行才发送,而更多情况下我们是不必键入回车或换行的。此时应转换到行方式输入,不经处理直接发送
options.c_iflag &= ~ (INLCR | ICRNL | IGNCR); options.c_oflag &= ~(ONLCR | OCRNL);
还存在这样的情况:发送字符0X0d的时候,往往接收端得到的字符是0X0a,原因是因为在串口设置中c_iflag和c_oflag中存在从NL-CR和CR-NL的映射,即串口能把回车和换行当成同一个字符,可以进行如下设置屏蔽之:
3. 以上的串口适用于AM335x、AM62x、G2L等通用处理器
比如维芯科AM62x核心板,基于TI AM62x处理器设计的工业级核心板,广泛用于各种串口服务器,多串口网关等应用场景。