嵌入式Linux系统uart串口编程详解及实例分析

        近来在一个项目开发中,在一个新的硬件平台下的linux系统,使用uart串口进行通讯,结果通讯不畅。代码是以前在其他硬件平台下验证完全没问题的代码,为什么会出问题呢?经过各方面查资料,最终定位为uart串口初始化的问题。在linux系统下,串口的初始化比较复杂,需要设置的东西比较多,如果有一些默认的配置与硬件和应用程序不匹配,而又没有重新配置,就会导致通讯失败的情况。经过对linux系统下串口初始化的进行了整理梳理,最终解决了问题。记录这批位置可以为其他小伙伴提供参考。

一、termios结构体

        termios是在POSIX规范中定义的标准接口,表示终端设备,包括虚拟终端、串口等。串口通过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]; // 控制特性
    speed_t c_ispeed;        // 输入速度
    speed_t c_ospeed;        // 输出速度
}

在这个结构体中包含了串口设置的绝大部分标志位,下面我们对这个结构体中的元素一一进行分析。

1、输入模式标志c_iflag

        c_iflag成员负责控制串口输入数据的处理,下表是c_iflag的部分可用标志

标志

说明

INPCK 

打开输入奇偶校验 

IGNPAR 

忽略奇偶错字符 

PARMRK 

标记奇偶错 

ISTRIP 

剥除字符第8位 

IXON 

启用/停止输出控制流起作用 

IXOFF 

启用/停止输入控制流起作用 

IGNBRK 

忽略BREAK条件 

INLCR 

将输入的NL转换为CR 

IGNCR 

忽略CR 

ICRNL 

将输入的CR转换为NL 

        设置输入校验

当c_cflag成员的PARENB(奇偶校验)选项启用时,c_iflag的也应启用奇偶校验选项。操作方法是启用INPCK和ISTRIP选项:

options.c_iflag |= (INPCK | ISTRIP);

如果关闭奇偶校验,则使用如下方法:

options.c_iflag &= ~(INPCK | ISTRIP);

        设置软件流控制

使用软件流控制是启用IXON、IXOFF和IXANY选项:

options.c_iflag |= (IXON | IXOFF | IXANY);

相反,要禁用软件流控制是禁止上面的选项:

options.c_iflag &= ~(IXON | IXOFF | IXANY);

        输入字符转换

linux系统可以将输入字符转换为其他字符,这个功能如果不注意可能会导致程序,比如,ICRNL这个标志位的作用是将输入的CR转换为NL ,如果要启用这个功能,则使用下面的方法:

options.c_iflag |= ICRNL;

如果要禁用这个功能,则使用下面的方法:

options.c_iflag &= ~ICRNL;

2、输入模式标志c_oflag

        c_oflag成员管理输出模式,下表所示是c_oflag成员的部分选项标志。

标志

说明

BSDLY

退格延迟屏蔽

CMSPAR

标志或空奇偶性

CRDLY

CR延迟屏蔽

FFDLY

换页延迟屏蔽

OCRNL

将输出的CR转换为NL

OFDEL

填充符为DEL,否则为NULL

OFILL

对于延迟使用填充符

OLCUC

将输出的小写字符转换为大写字符

ONLCR

将NL转换为CR-NL

ONLRET

NL执行CR功能

ONOCR

在0列不输出CR

OPOST

执行输出处理

OXTABS

将制表符扩充为空格

启用输出处理

与c_iflag标志类似,串口的输出也会将一些字符进行转换,如果想使能这些转换,需要启用输出处理。启用输出处理需要在c_oflag成员中启用OPOST选项,其操作方法如下:
options.c_oflag |= OPOST;

使用原始输出

使用原始输出,就是禁用输出处理,使数据能不经过处理、过滤地完整地输出到串口接口。当OPOST被禁止,c_oflag其它选项也被忽略,其操作方法如下:
options.c_oflag &= ~OPOST;

3、控制模式标志c_cflag

        c_cflag成员控制着波特率、数据位、奇偶校验、停止位以及流控制,下表列出了c_cflag可用的部分选项。

标志

说明

标志

说明

CBAUD 

波特率位屏蔽 

CSIZE 

数据位屏蔽 

B0 

0位/秒(挂起) 

CS5 

5位数据位 

B110 

100位/秒 

CS6 

6位数据位 

B134 

134位/秒 

CS7 

7位数据位 

B1200 

1200位/秒 

CS8 

8位数据位 

B2400 

2400位/秒 

CSTOPB 

2位停止位,否则为1位 

B4800 

4800位/秒 

CREAD 

启动接收 

B9600 

9600位/秒 

PARENB 

进行奇偶校验 

B19200 

19200位/秒 

PARODD 

奇校验,否则为偶校验 

B57600 

57600位/秒 

HUPCL 

最后关闭时断开 

B115200 

115200位/秒 

CLOCAL 

忽略调制调解器状态行 

B460800 

460800位/秒 

— 

— 

        c_cflag成员的CREAD和CLOCAL选项通常是要启用的,这两个选项使驱动程序启动接收字符装置,同时忽略串口信号线的状态。

4、本地模式标志c_lflag

        本地标志c_lflag控制着串口驱动程序如何管理输入的字符,下表所示是c_lflag的部分可用标志。

标志

说明

ISIG 

启用终端产生的信号 

ICANON 

启用规范输入 

XCASE 

规范大/小写表示 

ECHO 

进行回送 

ECHOE 

可见擦除字符 

ECHOK 

回送kill符 

ECHONL 

回送NL 

NOFLSH 

在中断或退出键后禁用刷清 

IEXTEN 

启用扩充的输入字符处理 

ECHOCTL 

回送控制字符为^(char) 

ECHOPRT 

硬拷贝的可见擦除方式 

ECHOKE 

Kill的可见擦除 

PENDIN 

重新打印未决输入 

TOSTOP 

对于后台输出发送SIGTTOU 

        选择规范模式

规范模式是行处理的。调用read读取串口数据时,每次返回一行数据。当选择规范模式时,需要启用ICANON、ECHO和ECHOE选项:
options.c_lflag |= (ICANON | ECHO | ECHOE);
当串口设备作为用户终端时,通常要把串口设备配置成规范模式。

        选择原始模式

在原始模式下,串口输入数据是不经过处理的,在串口接口接收的数据被完整保留。要使串口设备工作在原始模式,需要关闭ICANON、ECHO、ECHOE和ISIG选项,其操作方法如下:
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

5、控制特性c_cc[NCC] 

      c_cc数组的长度是NCC,一般介于15-20之间。c_cc数组的每个成员的下标都用一个宏表示,下表列出了c_cc的部分下标标志名及其对应说明。

标 志 

说 明 

VINTR 

中断 

VQUIT 

退出 

VERASE 

擦除 

VEOF 

行结束 

VEOL 

行结束 

VMIN 

需读取的最小字节数 

VTIME 

与“VMIN”配合使用,是指限定的传输或等待的最长时间 

二、实例分析

        下面通过一个实例来分析如何使用嵌入式linux的串口,在这个实例中,包含三个文件,分别为:main.cpp、uart.cpp、uart.h。这个实例的作用是通过串口接收数据,并将接收的每个数据加1后再通过串口返回到发送方。

1、uart.h

        uart.h的代码如下所示:

#ifndef UART_H
#define UART_H

#include <iostream>
#include <string>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdio.h>

#define UART1_DEV "/dev/ttyTHS2"
#define UART2_DEV "/dev/ttyTHS1"

#define UART1 1
#define UART2 2

#define BAUDRATE1 B115200
#define BAUDRATE2 B115200

using namespace std;

class Uart {
public :
  ~Uart();
   Uart();
  int uartOpen(int port, int flag, speed_t buadrate);
  int uartWrite(int fd, char *data, int num);
  int  uartRead(int fd, char *data, int num);
  void uartClose(int fd);
private:
  int uartInit(int fd,speed_t buadrate);

  static int uart1_only_fd;
  static int uart2_only_fd;
  static bool uart1_inited_flag;
  static bool uart2_inited_flag;
};

#endif // UART_H

在这个头文件中,定义了若干变量,和基本的串口操作函数。

2、uart.cpp

        uart.cpp的代码如下所示:

#include "uart.h"

int Uart::uart1_only_fd = 0;
int Uart::uart2_only_fd = 0;
bool Uart::uart1_inited_flag = false;
bool Uart::uart2_inited_flag = false;

Uart::Uart()
{

}


int Uart::uartInit(int fd,speed_t buadrate)
{
    struct termios opt;

    tcgetattr(fd,&opt);

    cfsetispeed(&opt,buadrate);
    cfsetospeed(&opt,buadrate);

    opt.c_cflag  |=  CLOCAL | CREAD;
    opt.c_cflag  &=  ~CRTSCTS;
    opt.c_cflag &= ~CSIZE;
    opt.c_cflag |= CS8;
    opt.c_cflag &= ~PARENB;
    opt.c_cflag &= ~CSTOPB;
    opt.c_iflag &= ~INPCK;
    opt.c_iflag &= ~(ICRNL|BRKINT|ISTRIP);
    opt.c_iflag &= ~(IXON|IXOFF|IXANY);
    opt.c_oflag &= ~OPOST;

    opt.c_lflag = ~(ICANON | ECHO | ECHOE | ISIG);

    tcflush(fd,TCIFLUSH);
    if (tcsetattr(fd,TCSANOW,&opt) != 0)
      {
	 return -1;
      }
	return 0;
}


int Uart::uartOpen(int port, int flag, speed_t buadrate)
{
    if(port == UART1)
    {
        if(uart1_inited_flag == false)
        {
            uart1_only_fd = open(UART1_DEV, flag);
            if(uart1_only_fd < 0)
            {
                return -1;
            }
            else
            {
                uartInit(uart1_only_fd,buadrate);
                uart1_inited_flag = true;
                return uart1_only_fd;
            }
        }
        else
        {
            return uart1_only_fd;
        }
    }
    else if(port == UART2)
    {
        if(uart2_inited_flag == false)
        {
            uart2_only_fd = open(UART2_DEV, flag);
            if(uart2_only_fd < 0)
            {
                return -1;
            }
            else
            {
                uartInit(uart2_only_fd,buadrate);
                uart2_inited_flag = true;
                return uart2_only_fd;
            }
        }
        else
        {
            return uart2_only_fd;
        }
    }

    return -1;
}

int Uart::uartWrite(int fd, char *data , int num)
{    
  int ret  = -1;
  ret = write(fd, data, num); 
  return ret;
}

int  Uart::uartRead(int fd, char *data, int num)
{
   int ret = -1;
   int i=0;
   char buf[1024] = {0};
   if(num > 1024)
   {
     ret = read(fd, buf, 1024);
   }
   else
   {
      ret = read(fd, buf, num);
   }

   for (i=0;i<num;i++)
    {
       data[i]=buf[i];
    }

   return ret;
}
void Uart::uartClose(int fd)
{
   if(fd > 0)
      close(fd);
}

Uart::~Uart()
{

}

在这个文件中,int Uart::uartInit(int fd,speed_t buadrate)为串口初始化函数,在这个函数中,首先通过tcgetattr(fd,&opt);函数将操作系统原来的串口配置结构体读出来,并且在这个结构体的基础上进行配置。这样做的好处是在原有配置的基础上进行修改,成功率比较高,如果在一个全新的结构体上修改,很容易导致失败。

        读取配置结构体之后,在下面的代码中对结构体的成员进行配置。之后调用tcflush(fd,TCIFLUSH);函数来清空输入队列。再之后调用tcsetattr(fd,TCSANOW,&opt)函数来配置串口。

        在这个文件中,int Uart::uartOpen(int port, int flag, speed_t buadrate)函数的作用是开启串口,在函数里调用uartInit()函数来初始化串口,并返回串口对应的文件描述符。

        int Uart::uartWrite(int fd, char *data , int num)为串口输出函数,int  Uart::uartRead(int fd, char *data, int num)为读取串口的函数,void Uart::uartClose(int fd)为关闭串口的函数。

3、main.cpp 

        main.cpp中调用uart.cpp中的函数实现串口的接收和发送,代码如下:

#include "uart.h"
#include <iostream>
#include <ostream>
#include <semaphore.h>

int main(void)
{
    Uart uart;
    int uart1_fd; 
    int num;
    char rbuff[200];
    
    uart1_fd = uart.uartOpen(UART1,O_RDWR |O_NONBLOCK | O_NOCTTY | O_NDELAY,BAUDRATE1);
    if(uart1_fd < 0)
        cout<<"uart1 init error!"<<endl;   
    
    
    while(1)
    {
        sleep(1);
        num = uart.uartRead(uart1_fd,rbuff,200);
		if(num>0)
		{
			for(int i = 0;i < num;i++)
			{
				rbuff[i] = rbuff[i]+1;
			}
			uart.uartWrite(uart1_fd,rbuff,num);
		}       
        
    }
    
    return 0;
}

 

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UART(Universal Asynchronous Receiver-Transmitter)是一种常见的串口通信协议,常用于将数据从一个设备传输到另一个设备,如将数据从计算机发送到外部设备,或者在嵌入式系统中进行设备之间的通信。下面是UART串口通信协议的详解: 1. 物理连接:UART通信使用两根信号线,一根用于传输数据(TX线),一根用于接收数据(RX线)。这两根线连接在两个设备之间,其中TX线与对方的RX线相连,RX线与对方的TX线相连。 2. 数据传输:UART是一种异步通信协议,数据通过一个字节一个字节地传输。每个字节由起始位(Start bit)、数据位(Data bits)、可选的校验位(Parity bit)和停止位(Stop bit)组成。 - 起始位:起始位用于标识一个数据传输的开始,它是一个低电平信号。 - 数据位:数据位用于传输实际的数据,通常有8位或9位数据位可供选择。 - 校验位:校验位用于检测数据传输过程中的错误。常见的校验方式有奇偶校验、偶校验和无校验。 - 停止位:停止位用于标识一个数据传输的结束,它是一个高电平信号。通常有1位或2位停止位可供选择。 3. 波特率:UART通信中的波特率(Baud rate)表示单位时间内传输的比特数。常见的波特率有9600、115200等,波特率越高,传输速率越快。 4. 通信模式:UART通信可以在全双工或半双工模式下进行。在全双工模式下,两个设备可以同时发送和接收数据;在半双工模式下,两个设备交替发送和接收数据。 5. 数据流控制:UART通信中可以使用硬件流控制或软件流控制来控制数据的传输。硬件流控制通过额外的信号线实现,如RTS(Request to Send)和CTS(Clear to Send)信号线;软件流控制通过特定的数据字节来实现,如XON和XOFF字符。 总结起来,UART串口通信协议是一种常见的异步串口通信协议,通过起始位、数据位、校验位和停止位来传输数据。它可以使用不同的波特率和通信模式,并支持硬件或软件流控制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值