• Linux UART编程 驱动蓝牙芯片


    在熟悉了UART概念后,我们要学以致用,在Linux用起来来驱动起来蓝牙芯片!

    我们直接借用man来看下,命令如下: man termios

    1.头文件引用

    1. #include
    2. #include

    2.串口打开关闭

    1. open(“/dev/ttyUSB0”, O_RDWR|O_NOCTTY);
    2. close(fd);

    Linux秉行一切皆文件的,所以打开关是用open,关闭串口使用close

    3.串口配置的重要结构体

    1. struct termios
    2. {
    3. tcflag_t c_iflag; /* input flags */
    4. tcflag_t c_oflag; /* output flags */
    5. tcflag_t c_cflag; /* control flags */
    6. tcflag_t c_lflag; /* local flags */
    7. cc_t c_cc[NCCS]; /* control characters */
    8. };

    下面我们来分别介绍下各个flag!

    3.1 c_iflag输入模式标志,控制终端输入方式

    在输入值传给程序之前控制其处理的方式

    • IGNBRK 忽略BREAK键输入
    • BRKINT 如果设置了IGNBRK,BREAK键的输入将被忽略,如果设置BRKINT ,将产生SIGINT中断
    • IGNPAR 忽略奇偶校验错误
    • PARMRK 标识奇偶校验错误
    • INPCK 允许输入奇偶校验
    • ISTRIP 去除字符的第8个比特
    • INLCR 将输入的NL(换行)转换成CR(回车)
    • IGNCR 忽略输入的回车
    • ICRNL 将输入的回车转化成换行(如果IGNCR未设置的情况下)
    • IUCLC 将输入的大写字符转换成小写字符(非POSIX)
    • IXON 允许输入时对XON/XOFF流进行控制
    • IXANY 输入任何字符将重启停止的输出
    • IXOFF 允许输入时对XON/XOFF流进行控制
    • IMAXBEL 当输入队列满的时候开始响铃,Linux在使用该参数而是认为该参数总是已经设置

    3.2 c_oflag输出模式标志,控制终端输出方式

    负责控制输出字元的处理方式

    • OPOST 处理后输出
    • OLCUC 将输入的小写字符转换成大写字符(非POSIX)
    • ONLCR 将输入的NL(换行)转换成CR(回车)及NL(换行)
    • OCRNL 将输入的CR(回车)转换成NL(换行)
    • ONOCR 第一行不输出回车符
    • ONLRET 不输出回车符
    • OFILL 发送填充字符以延迟终端输出
    • OFDEL 以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符将是NUL(‘/0’)(非POSIX)
    • NLDLY 换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s)
    • CRDLY 回车延迟,取值范围为:CR0、CR1、CR2和 CR3
    • TABDLY 水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3
    • BSDLY 空格输出延迟,可以取BS0或BS1
    • VTDLY 垂直制表符输出延迟,可以取VT0或VT1
    • FFDLY 换页延迟,可以取FF0或FF1

    3.3 c_cflag控制模式标志,指定终端硬件控制信息

    用于控制终端设备的硬件设置

    • CBAUD 波特率(4+1位)(非POSIX)
    • CBAUDEX 附加波特率(1位)(非POSIX)
    • CSIZE 字符长度,取值范围为CS5、CS6、CS7或CS8
    • CSTOPB 设置两个停止位
    • CREAD 使用接收器
    • PARENB 使用奇偶校验
    • PARODD 对输入使用奇偶校验,对输出使用偶校验
    • HUPCL 关闭设备时挂起
    • CLOCAL 忽略调制解调器线路状态
    • CRTSCTS 使用RTS/CTS流控制

    3.4 c_lflag本地模式标志,控制终端编辑功能

    主要用来控制终端设备不同的特色

    • ISIG 当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号
    • ICANON 使用标准输入模式,允许使用特殊字符 EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, 和 WERASE,以及按行的缓冲
    • XCASE 在ICANON和XCASE同时设置的情况下,终端只使用大写。如果只设置了XCASE,则输入字符将被转换为小写字符,除非字符使用了转义字符(非POSIX,且Linux不支持该参数)
    • ECHO 显示输入字符
    • ECHOE 如果ICANON同时设置,ERASE将删除输入的字符,WERASE将删除输入的单词
    • ECHOK 如果ICANON同时设置,KILL将删除当前行
    • ECHONL 如果ICANON同时设置,即使ECHO没有设置依然显示换行符
    • ECHOPRT 如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX)
    • TOSTOP 向后台输出发送SIGTTOU信号

    3.5 C_LINE行控制 c_cc[NCCS]控制字符

    • 提供使用者设定一些特殊的功能, 如Ctrl+C的字元组合。
    • 特殊控制字元主要是利用termios结构里c_cc的阵列成员来做设定。
    • c_cc阵列主要用于正规与非正规两种环境,但要注意的是正规与非正规不可混为一谈。

    符号下标 (初始值) 和意义(即c_cc[]数组对应下标的数值对应含义,如c_cc[VMIN] = 3):

    • VINTR:(003, ETX, Ctrl-C, or also 0177, DEL, rubout) 中断字符。发出 SIGINT 信号。当设置 ISIG 时可被识别,不再作为输入传递。
    • VQUIT :(034, FS, Ctrl-) 退出字符。发出 SIGQUIT 信号。当设置 ISIG 时可被识别,不再作为输入传递。
    • VERASE :(0177, DEL, rubout, or 010, BS, Ctrl-H, or also #) 删除字符。删除上一个还没有删掉的字符,但不删除上一个 EOF 或行首。当设置 ICANON 时可被识别,不再作为输入传递。
    • VKILL :(025, NAK, Ctrl-U, or Ctrl-X, or also @) 终止字符。删除自上一个 EOF 或行首以来的输入。当设置 ICANON 时可被识别,不再作为输入传递。
    • VEOF :(004, EOT, Ctrl-D) 文件尾字符。更精确地说,这个字符使得 tty 缓冲中的内容被送到等待输入的用户程序中,而不必等到 EOL。如果它是一行的第一个字符,那么用户程序的 read() 将返回 0,指示读到了 EOF。当设置 ICANON 时可被识别,不再作为输入传递。
    • VMIN :非 canonical 模式读的最小字符数(MIN主要是表示能满足read的最小字元数)。
    • VEOL :(0, NUL) 附加的行尾字符。当设置 ICANON 时可被识别。
    • VTIME :非 canonical 模式读时的延时,以十分之一秒为单位。
    • VEOL2 :(not in POSIX; 0, NUL) 另一个行尾字符。当设置 ICANON 时可被识别。
    • VSWTCH :(not in POSIX; not supported under Linux; 0, NUL) 开关字符。(只为 shl 所用。)
    • VSTART :(021, DC1, Ctrl-Q) 开始字符。重新开始被 Stop 字符中止的输出。当设置 IXON 时可被识别,不再作为输入传递。
    • VSTOP :(023, DC3, Ctrl-S) 停止字符。停止输出,直到键入 Start 字符。当设置 IXON 时可被识别,不再作为输入传递。
    • VSUSP :(032, SUB, Ctrl-Z) 挂起字符。发送 SIGTSTP 信号。当设置 ISIG 时可被识别,不再作为输入传递。
    • VDSUSP :(not in POSIX; not supported under Linux; 031, EM, Ctrl-Y) 延时挂起信号。当用户程序读到这个字符时,发送 SIGTSTP 信号。当设置 IEXTEN 和 ISIG,并且系统支持作业管理时可被识别,不再作为输入传递。
    • VLNEXT :(not in POSIX; 026, SYN, Ctrl-V) 字面上的下一个。引用下一个输入字符,取消它的任何特殊含义。当设置 IEXTEN 时可被识别,不再作为输入传递。
    • VWERASE :(not in POSIX; 027, ETB, Ctrl-W) 删除词。当设置 ICANON 和 IEXTEN 时可被识别,不再作为输入传递。
    • VREPRINT :(not in POSIX; 022, DC2, Ctrl-R) 重新输出未读的字符。当设置 ICANON 和 IEXTEN 时可被识别,不再作为输入传递。
    • VDISCARD :(not in POSIX; not supported under Linux; 017, SI, Ctrl-O) 开关:开始/结束丢弃未完成的输出。当设置 IEXTEN 时可被识别,不再作为输入传递。
    • VSTATUS :(not in POSIX; not supported under Linux; status request: 024, DC4, Ctrl-T).

    这些符号下标值是互不相同的,除了 VTIME,VMIN 的值可能分别与 VEOL,VEOF 相同。 (在 non-canonical 模式下,特殊字符的含义更改为延时含义MIN 表示应当被读入的最小字符数。TIME 是以十分之一秒为单位的计时器。如果同时设置了它们,read 将等待直到至少读入一个字符,一旦读入 MIN 个字符或者从上次读入字符开始经过了 TIME 时间就立即返回。如果只设置了 MIN,read 在读入 MIN 个字符之前不会返回。如果只设置了 TIME,read 将在至少读入一个字符,或者计时器超时的时候立即返回。如果都没有设置,read 将立即返回,只给出当前准备好的字符。)

    MIN与TIME组合有以下四种:

    • MIN = 0 , TIME =0
      有READ立即回传,否则传回 0 ,不读取任何字元
    • MIN = 0 , TIME >0
      READ 传回读到的字元,或在十分之一秒后传回TIME,若来不及读到任何字元,则传回0
    • MIN > 0 , TIME =0
      READ 会等待,直到MIN字元可读
    • MIN > 0 , TIME > 0
      每一格字元之间计时器即会被启动,READ 会在读到MIN字元,传回值或TIME的字元计时(1/10秒)超过时将值传回

    4.常用的函数

    1. /* 属性相关 */
    2. int tcgetattr(int fd, struct termios *termios_p);
    3. int tcsetattr(int fd, int optional_actions,
    4. const struct termios *termios_p);
    5. void cfmakeraw(struct termios *termios_p);
    6. /* 控制相关 */
    7. int tcsendbreak(int fd, int duration);
    8. int tcdrain(int fd);
    9. int tcflush(int fd, int queue_selector);
    10. int tcflow(int fd, int action);
    11. /* 速度相关 */
    12. speed_t cfgetispeed(const struct termios *termios_p);
    13. speed_t cfgetospeed(const struct termios *termios_p);
    14. int cfsetispeed(struct termios *termios_p, speed_t speed);
    15. int cfsetospeed(struct termios *termios_p, speed_t speed);

    函数名称

    描述

    tcgetattr

    获取串口属性,填充到termios_p入参中

    tcsetattr

    根据termios_p的值改变串口属性

    optional_actions (tcsetattr函数的第二个参数)指定了什么时候改变会起作用:
    TCSANOW:改变立即发生
    TCSADRAIN:改变在所有写入 fd 的输出都被传输后生效。这个函数应当用于修改影响输出的参数时使用。(当前输出完成时将值改变)
    TCSAFLUSH :改变在所有写入 fd 引用的对象的输出都被传输后生效,所有已接受但未读入的输入都在改变发生前丢弃(同TCSADRAIN,但会舍弃当前所有值)。

    cfmakeraw

    把串口属性设置位初始状态,比如以下值:

    termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);

    termios_p->c_oflag &= ~OPOST;

    termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);

    termios_p->c_cflag &= ~(CSIZE | PARENB);

    termios_p->c_cflag |= CS8;

    cfgetispeed

    获取input的串口波特率

    cfgetospeed

    获取output的串口波特率

    cfsetispeed

    设置input的串口波特率

    cfsetospeed

    获取output的串口波特率

    tcsendbreak

    tcdrain

    等待所有写入fd中的数据输出

    tcflush

    清空串口BUFFER中的数据函数

    常用的有三个值,

    TCIFLUSH清除正收到的数据,且不会读取出来;

    TCOFLUSH清除正写入的数据,且不会发送至终端;

    TCIOFLUSH清除所有正在发生的I/O数据;

    tcflow

    挂起 fd 引用的对象上的数据传输或接收,取决于 action 的值

    TCOOFF 挂起输出
    TCOON 重新开始被挂起的输出
    TCIOFF 发送一个 STOP 字符,停止终端设备向系统传送数据
    TCION 发送一个 START 字符,使终端设备向系统传输数据

    框图如下:

    我们写一个sample,用H4 transport来发送一个HCI RESET然后读回来值:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #define UART_MAX_SIZE 256
    11. int uart_fd;
    12. uint8_t uart_buffer[UART_MAX_SIZE] = {0};
    13. void usage()
    14. {
    15. printf("---------------------------------\n");
    16. printf(" ./uart_test port_name \n");
    17. printf("example: ./uart_test /dev/ttyUSB0\n");
    18. printf("---------------------------------\n");
    19. }
    20. #define MAX_COL 16
    21. #define SHOW_LINE_SIZE 16
    22. void bt_hex_dump(uint8_t *data,uint32_t len)
    23. {
    24. uint32_t line;
    25. uint32_t curline = 0;
    26. uint32_t curcol = 0;
    27. char showline[SHOW_LINE_SIZE];
    28. uint32_t data_pos = 0;
    29. if(len % MAX_COL)
    30. {
    31. line = len/MAX_COL+1;
    32. }
    33. else
    34. {
    35. line = len/MAX_COL;
    36. }
    37. for(curline = 0; curline < line; curline++)
    38. {
    39. sprintf(showline,"%08xh:",curline*MAX_COL);
    40. printf("%s",showline);
    41. for(curcol = 0; curcol < MAX_COL; curcol++)
    42. {
    43. if(data_pos < len)
    44. {
    45. printf("%02x ",data[data_pos]);
    46. data_pos++;
    47. continue;
    48. }
    49. else
    50. {
    51. break;
    52. }
    53. }
    54. printf("\n");
    55. }
    56. }
    57. void alarm_send_command()
    58. {
    59. uint8_t hci_reset[] = {0x01,0x03,0x0c,0x00};
    60. printf("send HCI command\n");
    61. write(uart_fd,hci_reset,sizeof(hci_reset));
    62. alarm(1);
    63. }
    64. int main(int argc, char *argv[])
    65. {
    66. struct termios toptions;
    67. usage();
    68. if(argc != 2)
    69. {
    70. printf("Usage error\n");
    71. return 0;
    72. }
    73. uart_fd = open(argv[1], O_RDWR | O_NOCTTY);
    74. printf("uart_fd %d\n",uart_fd);
    75. if (tcgetattr(uart_fd, &toptions) < 0)
    76. {
    77. printf("ERROR:Couldn't get term attributes\n");
    78. return -1;
    79. }
    80. cfmakeraw(&toptions);
    81. // 8N1
    82. toptions.c_cflag &= ~CSTOPB;
    83. toptions.c_cflag |= CS8;
    84. toptions.c_cflag |= CREAD | CLOCAL | CRTSCTS;
    85. toptions.c_iflag &= ~(IXON | IXOFF | IXANY);
    86. toptions.c_cflag &= ~PARENB;
    87. toptions.c_cc[VMIN] = 1;
    88. toptions.c_cc[VTIME] = 0;
    89. if(tcsetattr(uart_fd, TCSANOW, &toptions) < 0)
    90. {
    91. printf("ERROR:Couldn't set term attributes\n");
    92. return -1;
    93. }
    94. if (tcgetattr(uart_fd, &toptions) < 0)
    95. {
    96. printf("ERROR:Couldn't get term attributes\n");
    97. return -1;
    98. }
    99. cfsetospeed(&toptions, B115200);
    100. cfsetispeed(&toptions, B115200);
    101. if( tcsetattr(uart_fd, TCSANOW, &toptions) < 0)
    102. {
    103. printf("ERROR:Couldn't set term attributes\n");
    104. return -1;
    105. }
    106. signal(SIGALRM, alarm_send_command);
    107. alarm(1);
    108. while(1)
    109. {
    110. int read_result = read(uart_fd,uart_buffer,UART_MAX_SIZE);
    111. bt_hex_dump(uart_buffer,read_result);
    112. memset(uart_buffer,0,UART_MAX_SIZE);
    113. }
    114. }

    整个程序实现的效果很简单,就是打开串口(8N1+流控),设置波特率,然后1s发送一次hci reset,然后读取数据uart数据

     

  • 相关阅读:
    CompletableFuture的使用
    微前端的暗位面
    子虔与罗克韦尔自动化合作 进博会签约自动化净零智造联创中心
    linux 设备树of函数学习笔记
    HDFS High Availability(HA)高可用配置
    10min快速回顾C++语法(二)
    ET框架6.0分析二、异步编程
    【技术积累】Mysql中的SQL语言【技术篇】【四】
    【JUC源码专题】AQS 源码分析(JDK8)
    边写代码边学习之Pycaret
  • 原文地址:https://blog.csdn.net/XiaoXiaoPengBo/article/details/128172133