串口应用程序编程

所学均来自百问网

目录

1. 串口硬件介绍

2. TTY体系中设备节点的差别

3. Linux串口编程相关函数

4. 示例代码(回环)


1. 串口硬件介绍

UART(Universal Asynchronous Receiver and Transmitter):异步发送和接收

格式:

起始位(1bit)+ 数据位(8~9bit)+ 奇偶校验位(1bit)+ 停止位(1~1.5bit)

起始位:当未有数据发送时,数据线处于逻辑“1”状态;先发出一个逻辑“0”信号,表示开始传输字符。

数据位:紧随起始位之后,数据位表示真正要发送或接收的信息,位数一般有8位或9位

奇偶校验位: 数据位末尾可以选择是否添加奇偶校验位,用于检测数据传输是否正确

停止位:代表信息传输结束的标志位,可以是1位,1.5位或2位。停止位的位数越多,数据传输的速率也越慢

波特率:每秒钟传输的数据位数,一般设置为9600或115200

2. TTY体系中设备节点的差别

图解:硬件通过驱动串口与RAM串口发送和接收数据,当PC数据发送过去后会先到达行规程,行规程判断数据,若是普通字符则立刻返回给串口回显到虚拟机TTY,若遇到回车,则会发送到app后执行命令并将结果返回给行规程再给串口回显到虚拟机TTY

举例:键盘输入lsa回退格,虚拟机TTY显示ls,但行规格收到ls a 回退格

/dev/ttyN(N=1,2,3,…)

/dev/tty3、/dev/tty4:表示某个程序使用的虚拟终端

// 在tty3、tty4终端来回切换,执行命令
​
echo hello > /dev/tty3
echo hi    > /dev/tty4

/dev/tty0

/dev/tty0:表示前台程序的虚拟终端

  • 你正在操作的界面,就是前台程序

  • 其他后台程序访问/dev/tty0的话,就是访问前台程序的终端,切换前台程序时,/dev/tty0是变化的

// 1. 在tty3终端执行如下命令
// 2. 然后在tty3、tty4来回切换
​
while [ 1 ]; do echo msg_from_tty3 > /dev/tty0; sleep 5; done

/dev/tty

/dev/tty:表示当前程序所使用的终端,可能是虚拟终端,也可能是真实的中断。

程序A在前台、后台间切换,它自己的/dev/tty都不会变。

// 1. 在tty3终端执行如下命令
// 2. 然后在tty3、tty4来回切换
​
while [ 1 ]; do echo msg_from_tty3 > /dev/tty; sleep 5; done

/dev/console 选哪个?内核的打印信息从哪个设备上显示出来? 可以通过内核的cmdline来指定, 比如: console=ttyS0 console=tty 我不想去分辨这个设备是串口还是虚拟终端, 有没有办法得到这个设备? 有!通过/dev/console! console=ttyS0时:/dev/console就是ttyS0 console=tty时:/dev/console就是前台程序的虚拟终端 console=tty0时:/dev/console就是前台程序的虚拟终端 console=ttyN时:/dev/console就是/dev/ttyN console有多个取值时,使用最后一个取值来判断

3. Linux串口编程相关函数

  • 编程套路:

    • open

    • 设置行规程,比如波特率、数据位、停止位、校验位、RAW模式、一有数据就返回

    • read/write

termios用于获取和设置串口参数,它包含了波特率、字符大小、停止位、奇偶校验位等。
这些函数在名称上有一些惯例: 
 *tc:terminal contorl 
 *cf: control flag
struct termios {  
    tcflag_t c_iflag;   // 输入模式标志  
    tcflag_t c_oflag;   // 输出模式标志  
    tcflag_t c_cflag;   // 控制模式标志  
    tcflag_t c_lflag;   // 本地模式标志  
    cc_t c_cc[NCCS];    // 控制字符数组  
    speed_t c_ispeed;   // 输入波特率(非POSIX)  
    speed_t c_ospeed;   // 输出波特率  
};
  • 获取与终端(或串口)相关的参数设置,并将这些设置存储在termios结构体中。这个结构体随后可以被修改,并通过tcsetattr函数应用回终端。

    • int tcgetattr(int fd, struct termios *termios_p);

    • fd:打开的终端或串口设备文件的文件描述符。

    • termios_p:指向termios结构体的指针,用于存储获取的参数设置。

    • 返回值:成功时返回0,失败时返回-1并设置errno。

  • 根据termios结构体中的参数设置来配置终端(或串口)

    • int tcsetattr(int fd, int actions, const struct termios *termios_p);

    • fd:打开的终端或串口设备文件的文件描述符。

    • actions:指定何时更改设置,可以是TCSANOW(立即更改)、TCSADRAIN(等待所有写入的数据都传输出去后再更改)或TCSAFLUSH(等待所有写入的数据都传输出去,并且丢弃所有未读取的数据后再更改)。

    • termios_p:指向包含新设置的termios结构体的指针。

    • 返回值:成功时返回0,失败时返回-1并设置errno。

  • 丢弃未传输或未读取的数据。

    • int tcflush(int fd, int queue_selector);

    • fd:打开的终端或串口设备文件的文件描述符。

    • queue_selector:指定要丢弃的数据类型,可以是TCIFLUSH(丢弃收到的数据,但不读取)、TCOFLUSH(丢弃写入的数据,但不传送)或TCIOFLUSH(同时丢弃收到的数据和写入的数据)。

    • 返回值:成功时返回0,失败时返回-1并设置errno。

  • 设置终端(或串口)的输入波特率。

    • speed_t cfsetispeed(struct termios *termios_p, speed_t speed);

    • termios_p:指向termios结构体的指针,该结构体的c_ispeed字段将被设置为新的输入波特率。

    • speed:新的输入波特率,通常是B0B57600(或其他更高值,取决于系统支持)之间的一个常量。

    • 返回值:返回之前的输入波特率。

  • 设置终端(或串口)的输出波特率。

    • speed_t cfsetospeed(struct termios *termios_p, speed_t speed);

    • termios_p:指向termios结构体的指针,该结构体的c_ospeed字段将被设置为新的输出波特率。

    • speed:新的输出波特率,通常是B0B57600(或其他更高值,取决于系统支持)之间的一个常量。

    • 返回值:返回之前的输入波特率。

  • 内存区域清空

    • void bzero(void *s, size_t n);

    • void *s:指向要清理的内存指针

    • size_t n:清理的字节数

4. 示例代码(回环)

// 此代码只需要一个串口,串口的自己发送消息自己接收消息
// 操作:将串口的RX和TX连接成一个回环
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>
​
/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
    struct termios newtio,oldtio;
    // 获取串口相关参数设置
    if ( tcgetattr( fd,&oldtio) != 0) { 
        perror("SetupSerial 1");
        return -1;
    }
    // 清空内存
    bzero( &newtio, sizeof( newtio ) );
    // 对串口的控制模式和本地模式进行设置
    newtio.c_cflag |= CLOCAL | CREAD; 
    newtio.c_cflag &= ~CSIZE; 
​
    newtio.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/
    newtio.c_oflag  &= ~OPOST;   /*Output*/
​
    switch( nBits )
    {
    case 7:
        newtio.c_cflag |= CS7;
    break;
    case 8:
        newtio.c_cflag |= CS8;
    break;
    }
​
    switch( nEvent )
    {
    case 'O':
        newtio.c_cflag |= PARENB;
        newtio.c_cflag |= PARODD;
        newtio.c_iflag |= (INPCK | ISTRIP);
    break;
    case 'E': 
        newtio.c_iflag |= (INPCK | ISTRIP);
        newtio.c_cflag |= PARENB;
        newtio.c_cflag &= ~PARODD;
    break;
    case 'N': 
        newtio.c_cflag &= ~PARENB;
    break;
    }
​
    switch( nSpeed )
    {
    case 2400:
        cfsetispeed(&newtio, B2400);
        cfsetospeed(&newtio, B2400);
    break;
    case 4800:
        cfsetispeed(&newtio, B4800);
        cfsetospeed(&newtio, B4800);
    break;
    case 9600:
        cfsetispeed(&newtio, B9600);
        cfsetospeed(&newtio, B9600);
    break;
    case 115200:
        cfsetispeed(&newtio, B115200);
        cfsetospeed(&newtio, B115200);
    break;
    default:
        cfsetispeed(&newtio, B9600);
        cfsetospeed(&newtio, B9600);
    break;
    }
    
    if( nStop == 1 )
        newtio.c_cflag &= ~CSTOPB;
    else if ( nStop == 2 )
        newtio.c_cflag |= CSTOPB;
    
    newtio.c_cc[VMIN]  = 1;  /* 读数据时的最小字节数: 没读到这些数据我就不返回! */
    newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间: 
                             * 比如VMIN设为10表示至少读到10个数据才返回,
                             * 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)
                             * 假设VTIME=1,表示: 
                             *    10秒内一个数据都没有的话就返回
                             *    如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回
                             */
    // 清理缓存
    tcflush(fd,TCIFLUSH);
    // 配置串口参数
    if((tcsetattr(fd,TCSANOW,&newtio))!=0)
    {
        perror("com set error");
        return -1;
    }
    //printf("set done!\n");
    return 0;
}
​
int open_port(char *com)
{
    int fd;
    //fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
    fd = open(com, O_RDWR|O_NOCTTY);
    if (-1 == fd){
        return(-1);
    }
    
      if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/
      {
            printf("fcntl failed!\n");
            return -1;
      }
  
      return fd;
}
​
​
/*
 * ./serial_send_recv <dev>
 */
int main(int argc, char **argv)
{
    int fd;
    int iRet;
    char c;
​
    /* 1. open */
​
    /* 2. setup 
     * 115200,8N1
     * RAW mode
     * return data immediately
     */
​
    /* 3. write and read */
    
    if (argc != 2)
    {
        printf("Usage: \n");
        printf("%s </dev/ttySAC1 or other>\n", argv[0]);
        return -1;
    }
​
    fd = open_port(argv[1]);
    if (fd < 0)
    {
        printf("open %s err!\n", argv[1]);
        return -1;
    }
​
    iRet = set_opt(fd, 115200, 8, 'N', 1);
    if (iRet)
    {
        printf("set port err!\n");
        return -1;
    }
​
    printf("Enter a char: ");
    while (1)
    {
        scanf("%c", &c);
        iRet = write(fd, &c, 1);
        iRet = read(fd, &c, 1);
        if (iRet == 1)
            printf("get: %02x %c\n", c, c);
        else
            printf("can not get data\n");
    }
​
    return 0;
}

  • 23
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值