驱动编译

  1. buildroot 勾选对应的模块
    1
    2
    [*]   Serial gadget console support  
    <M> Serial Gadget (with CDC ACM and CDC OBEX support)

驱动加载

  1. 编译完模块后,会生成u_serial.ko usb_f_serial.ko usb_f_acm.ko g_serial.ko ,复制到对应目录。
  2. 执行modprobe g_serial.ko,会生成/dev/ttyGS0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [  517.894143] g_serial gadget: Gadget Serial v2.4
    [ 517.898825] g_serial gadget: g_serial ready
    [ 518.661918] g_serial gadget: high-speed config #2: CDC ACM config
    [ 518.668090] gs_console_connect: port num [0] is not support console

    lsmod看看
    Module Size Used by Not tainted
    g_serial 16384 0
    usb_f_acm 16384 1
    usb_f_serial 16384 0
    u_serial 20480 2 usb_f_acm,usb_f_serial
    libcomposite 45056 3 g_serial,usb_f_acm,usb_f_serial

    如果手动加载注意顺序
    insmod u_serial.ko -> usb_f_serial.ko -> usb_f_acm.ko ->g_serial.ko

驱动测试

  1. 开发板执行cat /dev/ttyGS0 开发板可以接收到串口调试助手发来的数据

  2. 开发板执行echo “hello” > /dev/ttyGS0 电脑可以接收到开发板发来的数据

作为控制终端使用

  1. /etc/inittab 加上 下面的配置,可以做为控制终端来使用

    1
    2
    3
    4
    5
    6
    7
    8
    ::respawn:/sbin/getty -L ttyAM0 115200 vt100
    ::respawn:/sbin/getty -L ttyGS0 115200 vt100
    console::respawn:/sbin/getty -L console 0 vt100 # GENERIC_SERIAL

    上面记得放这个前面才行:
    console::respawn:/sbin/getty -L console 0 vt100 # GENERIC_SERIAL
    否则串口终端貌似有点问题.

  2. 下是板子默认配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    # cat /etc/inittab 
    # /etc/inittab

    # Startup the system
    ::sysinit:/bin/mount -t proc proc /proc
    ::sysinit:/bin/mount -o remount,rw /
    ::sysinit:/bin/mkdir -p /dev/pts /dev/shm
    ::sysinit:/bin/mount -a
    ::sysinit:/sbin/swapon -a
    null::sysinit:/bin/ln -sf /proc/self/fd /dev/fd
    null::sysinit:/bin/ln -sf /proc/self/fd/0 /dev/stdin
    null::sysinit:/bin/ln -sf /proc/self/fd/1 /dev/stdout
    null::sysinit:/bin/ln -sf /proc/self/fd/2 /dev/stderr
    ::sysinit:/bin/hostname -F /etc/hostname
    # now run any rc scripts
    ::sysinit:/etc/init.d/rcS

    # Put a getty on the serial port
    console::respawn:/sbin/getty -L console 0 vt100 # GENERIC_SERIAL

    # Stuff to do for the 3-finger salute
    #::ctrlaltdel:/sbin/reboot

    # Stuff to do before rebooting
    ::shutdown:/etc/init.d/rcK
    ::shutdown:/sbin/swapoff -a
    ::shutdown:/bin/umount -a -r

做为普通的串口

  1. 查看串口配置
    1
    2
    3
    4
    5
    6
    # stty -F /dev/ttyGS0 
    speed 9600 baud; line = 0;
    intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
    eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
    werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
    -brkint -imaxbel
  2. 去掉串口回显

#stty -F /dev/ttyGS0 -echo

  1. 串口不做为控制终端

#stty -F /dev/ttyGS0 raw min 1 time 10

  1. 这样设置之后就可以像单片机串口一样接收 不用带\n结尾了,发送接收都是原始数据,也不受特殊字符的影响

串口编程参考

  1. 如果是要编程控制串口,那么上边的所有设置都不需要,可以直接在代码把参数设置好,以下是测试过的代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    //串口相关的头文件
    #include <errno.h> /*错误号定义*/
    #include <fcntl.h> /*文件控制定义*/
    #include <stdio.h> /*标准输入输出定义*/
    #include <stdlib.h> /*标准函数库定义*/
    #include <string.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <linux/serial.h>
    #include <sys/ioctl.h>
    #include <termios.h> /*PPSIX 终端控制定义*/
    #include <unistd.h> /*Unix 标准函数定义*/

    //宏定义
    #define FALSE -1
    #define TRUE 0

    /*******************************************************************
    * 名称: tty_Open
    * 功能: 打开串口并返回串口设备文件描述
    * 入口参数: fd :文件描述符 port :串口号(ttyS0,ttyS1,ttyS2)
    * 出口参数: 正确返回为1,错误返回为0
    *******************************************************************/
    int tty_open(int fd, char *port)
    {

    fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
    if (FALSE == fd)
    {
    perror("Can't Open Serial Port");
    return (FALSE);
    }
    //恢复串口为阻塞状态
    if (fcntl(fd, F_SETFL, 0) < 0)
    {
    printf("fcntl failed!\n");
    return (FALSE);
    }
    else
    {
    printf("fcntl=%d\n", fcntl(fd, F_SETFL, 0));
    }
    //测试是否为终端设备
    if (0 == isatty(STDIN_FILENO))
    {
    printf("standard input is not a terminal device\n");
    return (FALSE);
    }
    else
    {
    printf("isatty success!\n");
    }
    printf("fd->open=%d\n", fd);
    return fd;
    }
    /*******************************************************************
    * 名称: tty_Close
    * 功能: 关闭串口并返回串口设备文件描述
    * 入口参数: fd :文件描述符 port :串口号(ttyS0,ttyS1,ttyS2)
    * 出口参数: void
    *******************************************************************/

    void tty_close(int fd) { close(fd); }

    /*******************************************************************
    * 名称: tty_Set
    * 功能: 设置串口数据位,停止位和效验位
    * 入口参数: fd 串口文件描述符
    * peed 串口速度
    * low_ctrl 数据流控制
    * databits 数据位 取值为 7 或者8
    * stopbits 停止位 取值为 1 或者2
    * parity 效验类型 取值为N,E,O,,S
    *出口参数: 正确返回为1,错误返回为0
    *******************************************************************/
    int tty_set(int fd, int speed, int flow_ctrl, int databits, int stopbits, int parity)
    {

    int i;
    int status;
    int speed_arr[] = {B115200, B19200, B9600, B4800, B2400, B1200, B300};
    int name_arr[] = {115200, 19200, 9600, 4800, 2400, 1200, 300};

    struct termios options;

    /*tcgetattr(fd,&options)得到与fd指向对象的相关参数,并将它们保存于options,该函数还可以测试配置是否正确,该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为1.
    */
    if (tcgetattr(fd, &options) != 0)
    {
    perror("SetupSerial 1");
    return (FALSE);
    }

    //设置串口输入波特率和输出波特率
    for (i = 0; i < sizeof(speed_arr) / sizeof(int); i++)
    {
    if (speed == name_arr[i])
    {
    cfsetispeed(&options, speed_arr[i]);
    cfsetospeed(&options, speed_arr[i]);
    }
    }

    //修改控制模式,保证程序不会占用串口
    options.c_cflag |= CLOCAL;
    //修改控制模式,使得能够从串口中读取输入数据
    options.c_cflag |= CREAD;

    //设置数据流控制
    switch (flow_ctrl)
    {

    case 0: //不使用流控制
    options.c_cflag &= ~CRTSCTS;
    break;

    case 1: //使用硬件流控制
    options.c_cflag |= CRTSCTS;
    break;
    case 2: //使用软件流控制
    options.c_cflag |= IXON | IXOFF | IXANY;
    break;
    }
    //设置数据位
    //屏蔽其他标志位
    options.c_cflag &= ~CSIZE;
    switch (databits)
    {
    case 5:
    options.c_cflag |= CS5;
    break;
    case 6:
    options.c_cflag |= CS6;
    break;
    case 7:
    options.c_cflag |= CS7;
    break;
    case 8:
    options.c_cflag |= CS8;
    break;
    default:
    fprintf(stderr, "Unsupported data size\n");
    return (FALSE);
    }
    //设置校验位
    switch (parity)
    {
    case 'n':
    case 'N': //无奇偶校验位。
    options.c_cflag &= ~PARENB;
    options.c_iflag &= ~INPCK;
    break;
    case 'o':
    case 'O': //设置为奇校验
    options.c_cflag |= (PARODD | PARENB);
    options.c_iflag |= INPCK;
    break;
    case 'e':
    case 'E': //设置为偶校验
    options.c_cflag |= PARENB;
    options.c_cflag &= ~PARODD;
    options.c_iflag |= INPCK;
    break;
    case 's':
    case 'S': //设置为空格
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    break;
    default:
    fprintf(stderr, "Unsupported parity\n");
    return (FALSE);
    }
    // 设置停止位
    switch (stopbits)
    {
    case 1:
    options.c_cflag &= ~CSTOPB;
    break;
    case 2:
    options.c_cflag |= CSTOPB;
    break;
    default:
    fprintf(stderr, "Unsupported stop bits\n");
    return (FALSE);
    }

    //修改输出模式,原始数据输出
    options.c_oflag &= ~OPOST;

    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    // Linux串口接收字节0x11,0x0d,0x13丢失
    // https://www.jianshu.com/p/ae699af72b82
    options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    // options.c_lflag &= ~(ISIG | ICANON);

    //设置等待时间和最小接收字符
    options.c_cc[VTIME] = 1; /* 读取一个字符等待1*(1/10)s */
    options.c_cc[VMIN] = 1; /* 读取字符的最少个数为1*/

    //如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读
    tcflush(fd, TCIFLUSH);

    //激活配置 (将修改后的termios数据设置到串口中)
    if (tcsetattr(fd, TCSANOW, &options) != 0)
    {
    perror("com set error!\n");
    return (FALSE);
    }

    // struct serial_struct serial;
    // int ret = ioctl(fd,TIOCGSERIAL,&serial);
    // if(ret !=0){
    // close(fd);
    // return -2;
    // }
    // serial.xmit_fifo_size =1024;
    // ret = ioctl(fd,TIOCSSERIAL,&serial);
    // if(ret !=0){
    // close(fd);
    // return -3;
    // }

    return (TRUE);
    }

    /*******************************************************************
    * 名称: tty_Recv
    * 功能: 接收串口数据
    * 入口参数: fd :文件描述符
    * rcv_buf :接收串口中数据存入rcv_buf缓冲区中
    * data_len :一帧数据的长度
    * 出口参数: 正确返回为1,错误返回为0
    *******************************************************************/
    int tty_recv(int fd, char *rcv_buf, int data_len)
    {
    int len, fs_sel;
    fd_set fs_read;

    struct timeval time;

    FD_ZERO(&fs_read);
    FD_SET(fd, &fs_read);

    time.tv_sec = 0;
    time.tv_usec = 1000*10;

    //使用select实现串口的多路通信
    fs_sel = select(fd + 1, &fs_read, NULL, NULL, &time);
    //这里延时一下,否则数据超过16个会被拆包!!!
    usleep(1000*5);
    if (fs_sel)
    {
    len = read(fd, rcv_buf, data_len);
    return len;
    }
    else
    {
    return FALSE;
    }
    }
    /********************************************************************
    * 名称:tty_Send
    * 功能:发送数据
    * 入口参数:fd:文件描述符
    * send_buf:存放串口发送数据
    * data_len:一帧数据的个数
    * 出口参数:正确返回为1,错误返回为0
    *******************************************************************/
    int tty_send(int fd, char *send_buf, int data_len)
    {
    int len = 0;

    len = write(fd, send_buf, data_len);
    if (len == data_len)
    {
    //printf("send data is %s\n", send_buf);
    return len;
    }
    else
    {

    tcflush(fd, TCOFLUSH);
    return FALSE;
    }
    }
    /*
    int main(int argc, char **argv)
    {
    int fd; //文件描述符
    int err; //返回调用函数的状态
    int len;
    int i;
    char rcv_buf[4096];
    char send_buf[20] = "tiger john";
    if (argc != 3)
    {
    printf("Usage: %s /dev/ttySn 0(send data)/1 (receive data) \n", argv[0]);
    return FALSE;
    }
    fd = tty_open(fd, argv[1]); //打开串口,返回文件描述符
    do
    {
    err = tty_set(fd, 115200, 0, 8, 1, 'N');
    printf("Set Port Exactly!\n");
    } while (FALSE == err || FALSE == fd);

    // if (0 == strcmp(argv[2], "0"))
    // {
    // for (i = 0; i < 10; i++)
    // {
    // len = tty_send(fd, send_buf, 10);
    // if (len > 0)
    // printf(" %d time send %d data successful\n", i, len);
    // else
    // printf("send data failed!\n");

    // sleep(2);
    // }
    // tty_close(fd);
    // }
    // else
    // {
    while (1) //循环读取数据
    {
    len = tty_recv(fd, rcv_buf, 4096);
    if (len > 0)
    {
    rcv_buf[len] = '\0';
    printf("%s", rcv_buf);
    }

    // sleep(2);
    }
    tty_close(fd);
    //}
    }
    */

参考资料

来源于whycan.com

  1. 原来是linux下串口的会带部分收发规则 参考https://blog.csdn.net/lvliang2008/article/details/6192863这个 pc发给板子 然后pc收到了发的内容 的现象是因为默认开启了串口回显功能

  2. 还有串口默认规则是 板子使用echo “send data” > /dev/ttyGS0 发送给PC时 会自动对发送数据添加换行符\n 并且接收数据时 也是通过换行符\n作为接收结束 所以PC发送给板子时必须在发送数据末尾加换行符\n

  3. linux应用程序下的串口编程 参考
    https://www.cnblogs.com/silencehuan/p/11103074.html

  4. shell环境下通过stty配置
    stty -F /dev/ttyGS0 raw min 1 time 10
    其中raw是设置本设备不作为串口的‘控制终端’”。如果不使用该选项,会有一些输入字符影响进程运行(如一些产生中断信号的键盘输入字符 以及上面说的串口回显等)
    其中min是从串口缓冲里读出的最小的接收数据
    其中time是每次cat 串口的读取超时时间 单位100ms

  5. shell操作串口参考
    https://blog.csdn.net/chenliang0224/article/details/100593900

  6. stty命令参考
    http://linux.51yip.com/search/stty