0


Linux驱动开发——串口设备驱动

Linux驱动开发——串口设备驱动

一、串口简介

串口全称叫做串行接口,通常也叫做 COM 接口,串行接口指的是数据一个一个的顺序传输,通信线路简单。使用两条线即可实现双向通信,一条用于发送,一条用于接收。串口通信距离远,但是速度相对会低,串口是一种很常用的工业接口。

I.MX6U 自带的 UART 外设就是串口的一种,UART 全称是 Universal Asynchronous Receiver/Trasmitter,也就是异步串行收发器。UART 作为串口的一种,其工作原理也是将数据一位一位的进行传输,发送和接收各用一条线,因此通过 UART 接口与外界相连最少只需要三条线:TXD(发送)、RXD(接收)和 GND(地线)
在这里插入图片描述

  • 空闲位:数据线在空闲状态的时候为逻辑“1”状态,也就是高电平,表示没有数据线空闲,没有数据传输。
  • 起始位:当要传输数据的时候先传输一个逻辑“0”,也就是将数据线拉低,表示开始数据传输。
  • 数据位:数据位就是实际要传输的数据,数据位数可选择 5~8 位,我们一般都是按照字节传输数据的,一个字节 8 位,因此数据位通常是 8 位的。低位在前,先传输,高位最后传输。
  • 奇偶校验位:这是对数据中“1”的位数进行奇偶校验用的,可以不使用奇偶校验功能。
  • 停止位:数据传输完成标志位,停止位的位数可以选择 1 位、1.5 位或 2 位高电平,一般都选择 1 位停止位。
  • 波特率:波特率就是 UART 数据传输的速率,也就是每秒传输的数据位数,一般选择 9600、19200、115200 等。

二、Linux下串口驱动框架

Linux 提供了串口驱动框架,我们只需要按照相应的串口框架编写驱动程序即可。串口驱动没有什么主机端和设备端之分,就只有一个串口驱动,而且这个驱动也已经由 NXP 官方已经编写好了,我们真正要做的就是在设备树中添加所要使用的串口节点信息。当系统启动以后串口驱动和设备匹配成功,相应的串口就会被驱动起来,生成/dev/ttymxcX(X=0….n)文件。

uart_driver 结构体

uart_driver 结构体表示 UART 驱动,uart_driver 定义在 include/linux/serial_core.h 文件中

structuart_driver{structmodule*owner;/* 模块所属者 */constchar*driver_name;/* 驱动名字 */constchar*dev_name;/* 设备名字 */int major;/* 主设备号 */int minor;/* 次设备号 */int nr;/* 设备数 */structconsole*cons;/* 控制台 *//*
    * these are private; the low level driver should not
    * touch these; they should be initialised to NULL
    */structuart_state*state;structtty_driver*tty_driver;};
  1. 加载驱动的时候通过 uart_register_driver 函数向系统注册这个 uart_driver,此函数原型如下:
intuart_register_driver(structuart_driver*drv)

函数参数和返回值含义如下:

  • drv :要注册的 uart_driver。
  • 返回值:0,成功;负值,失败。
  1. 注销驱动的时候也需要注销掉前面注册的 uart_driver,需要用到 uart_unregister_driver 函数,函数原型如下:
voiduart_unregister_driver(structuart_driver*drv)

函数参数和返回值含义如下:

  • drv :要注销的 uart_driver。
  • 返回值:无。

uart_port 的添加与移除

uart_port 表示一个具体的 port,uart_port 定义在 include/linux/serial_core.h 文件

117structuart_port{118 spinlock_t lock;/* port lock */119unsignedlong iobase;/* in/out[bwl] */120unsignedchar __iomem *membase;/* read/write[bwl] */......235conststructuart_ops*ops;236unsignedint custom_divisor;237unsignedint line;/* port index */238unsignedint minor;239 resource_size_t mapbase;/* for ioremap */240 resource_size_t mapsize;241structdevice*dev;/* parent device */......250};

uart_port 中最主要的就是第 235 行的 ops,ops 包含了串口的具体驱动函数,UART 驱动编写人员需要实现 uart_ops,因为 uart_ops 是最底层的 UART 驱动接口,是实实在在的和 UART 寄存器打交道的。

  1. 那么 uart_port 是怎么和 uart_driver 结合起来,用到 uart_add_one_port 函数函数原型如下:
intuart_add_one_port(structuart_driver*drv,structuart_port*uport)

函数参数和返回值含义如下:

  • drv:此 port 对应的 uart_driver。
  • uport :要添加到 uart_driver 中的 port。
  • 返回值:0,成功;负值,失败。
  1. 卸载 UART 驱动的时候也需要将 uart_port 从相应的 uart_driver 中移除,需要用到uart_remove_one_port 函数,函数原型如下:
intuart_remove_one_port(structuart_driver*drv,structuart_port*uport)

函数参数和返回值含义如下:

  • drv:要卸载的 port 所对应的 uart_driver。
  • uport :要卸载的 uart_port。
  • 返回值:0,成功;负值,失败。

三、Linux下串口驱动工作流程

  1. UART 本质上是一个 platform 驱动
  2. platform 驱动框架结构体 serial_imx_driver
  3. 驱动入口函数中调用uart_register_driver 函数向 Linux 内核注册 uart_driver
  4. 驱动出口函数中调用uart_unregister_driver 函数注销掉前面注册的 uart_driver
  5. UART 设备和驱动匹配成功以后 serial_imx_probe 函数就会执行,此函数的重点工作就是初始化 uart_port,然后将其添加到对应的 uart_driver 中
  6. 在初始化uart_port过程中,设置 uart_ops 为 imx_pops。imx_pops 就是 I.MX6ULL 最底层的驱动函数集合

四、Linux下串口应用开发

串口的应用编程就是通过 ioctl()对串口进行配置,调用 read()读取串口的数据、调用 write()向串口写入数据

Linux 为上层用户做了一层封装,将这些 ioctl()操作封装成了一套标准的 API,这些 API 其实是 C 库函数。

  • 这一套接口并不是针对串口开发的,而是针对所有的终端设备,串口是一种终端设备,计算机系统本地连接的鼠标、键盘也是终端设备,通过 ssh 远程登录连接的伪终端也是终端设备
  • 使用 termios API,需要在我们的应用程序中包含 termios.h 头文件

终端工作模式

  • 规范模式 - 基于进行处理的。在用户输入一个行结束符(回车符、EOF 等)之前,系统调用 read()函数是读不到用户输入的任何字符的
  • 非规范模式 - 所有的输入是即时有效的,不需要用户另外输入行结束符,而且不可进行行编辑
  • 原始模式 - 是一种特殊的非规范模式。在原始模式下,所有的输入数据以字节为单位被处理。在这个模式下,终端是不可回显的,并且禁用终端输入和输出字符的所有特殊处理。调用 cfmakeraw()函数将终端设置为原始模式

多线程例程

#define_GNU_SOURCE//在源文件开头定义_GNU_SOURCE宏#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<sys/ioctl.h>#include<errno.h>#include<string.h>#include<signal.h>#include<termios.h>#include<pthread.h>typedefstructuart_hardware_cfg{unsignedint baudrate;/* 波特率 */unsignedchar dbit;/* 数据位 */char parity;/* 奇偶校验 */unsignedchar sbit;/* 停止位 */} uart_cfg_t;staticstructtermios old_cfg;//用于保存终端的配置参数staticint fd;//串口终端对应的文件描述符/**
 ** 串口初始化操作
 ** 参数device表示串口终端的设备节点
 **/staticintuart_init(constchar*device){/* 打开串口终端 */
    fd =open(device, O_RDWR | O_NOCTTY);if(0> fd){fprintf(stderr,"open error: %s: %s\n", device,strerror(errno));return-1;}/* 获取串口当前的配置参数 */if(0>tcgetattr(fd,&old_cfg)){fprintf(stderr,"tcgetattr error: %s\n",strerror(errno));close(fd);return-1;}return0;}/**
 ** 串口配置
 ** 参数cfg指向一个uart_cfg_t结构体对象
 **/staticintuart_cfg(const uart_cfg_t *cfg){structtermios new_cfg ={0};//将new_cfg对象清零
    speed_t speed;/* 设置为原始模式 */cfmakeraw(&new_cfg);/* 使能接收 */
    new_cfg.c_cflag |= CREAD;/* 设置波特率 */switch(cfg->baudrate){case1200: speed = B1200;break;case1800: speed = B1800;break;case2400: speed = B2400;break;case4800: speed = B4800;break;case9600: speed = B9600;break;case19200: speed = B19200;break;case38400: speed = B38400;break;case57600: speed = B57600;break;case115200: speed = B115200;break;case230400: speed = B230400;break;case460800: speed = B460800;break;case500000: speed = B500000;break;default://默认配置为115200
        speed = B115200;printf("default baud rate: 115200\n");break;}if(0>cfsetspeed(&new_cfg, speed)){fprintf(stderr,"cfsetspeed error: %s\n",strerror(errno));return-1;}/* 设置数据位大小 */
    new_cfg.c_cflag &=~CSIZE;//将数据位相关的比特位清零switch(cfg->dbit){case5:
        new_cfg.c_cflag |= CS5;break;case6:
        new_cfg.c_cflag |= CS6;break;case7:
        new_cfg.c_cflag |= CS7;break;case8:
        new_cfg.c_cflag |= CS8;break;default://默认数据位大小为8
        new_cfg.c_cflag |= CS8;printf("default data bit size: 8\n");break;}/* 设置奇偶校验 */switch(cfg->parity){case'N'://无校验
        new_cfg.c_cflag &=~PARENB;
        new_cfg.c_iflag &=~INPCK;break;case'O'://奇校验
        new_cfg.c_cflag |=(PARODD | PARENB);
        new_cfg.c_iflag |= INPCK;break;case'E'://偶校验
        new_cfg.c_cflag |= PARENB;
        new_cfg.c_cflag &=~PARODD;/* 清除PARODD标志,配置为偶校验 */
        new_cfg.c_iflag |= INPCK;break;default://默认配置为无校验
        new_cfg.c_cflag &=~PARENB;
        new_cfg.c_iflag &=~INPCK;printf("default parity: N\n");break;}/* 设置停止位 */switch(cfg->sbit){case1://1个停止位
        new_cfg.c_cflag &=~CSTOPB;break;case2://2个停止位
        new_cfg.c_cflag |= CSTOPB;break;default://默认配置为1个停止位
        new_cfg.c_cflag &=~CSTOPB;printf("default stop bit size: 1\n");break;}/* 将MIN和TIME设置为0 */
    new_cfg.c_cc[VTIME]=0;
    new_cfg.c_cc[VMIN]=0;/* 清空缓冲区 */if(0>tcflush(fd, TCIOFLUSH)){fprintf(stderr,"tcflush error: %s\n",strerror(errno));return-1;}/* 写入配置、使配置生效 */if(0>tcsetattr(fd, TCSANOW,&new_cfg)){fprintf(stderr,"tcsetattr error: %s\n",strerror(errno));return-1;}/* 配置OK 退出 */return0;}/***
--dev=/dev/ttymxc2
--brate=115200
--dbit=8
--parity=N
--sbit=1
--type=read
***//**
 ** 打印帮助信息
 **/staticvoidshow_help(constchar*app){printf("Usage: %s [选项]\n""\n必选选项:\n""  --dev=DEVICE     指定串口终端设备名称, 譬如--dev=/dev/ttymxc2\n""\n可选选项:\n""  --brate=SPEED    指定串口波特率, 譬如--brate=115200\n""  --dbit=SIZE      指定串口数据位个数, 譬如--dbit=8(可取值为: 5/6/7/8)\n""  --parity=PARITY  指定串口奇偶校验方式, 譬如--parity=N(N表示无校验、O表示奇校验、E表示偶校验)\n""  --sbit=SIZE      指定串口停止位个数, 譬如--sbit=1(可取值为: 1/2)\n""  --help           查看本程序使用帮助信息\n\n", app);}/**
 ** 信号处理函数,当串口有数据可读时,会跳转到该函数执行
 **/staticvoidio_handler(int sig, siginfo_t *info,void*context){unsignedchar buf[10]={0};int ret;int n;if(SIGRTMIN != sig)return;/* 判断串口是否有数据可读 */if(POLL_IN == info->si_code){
        ret =read(fd, buf,8);//一次最多读8个字节数据printf("[ ");for(n =0; n < ret; n++)printf("0x%hhx ", buf[n]);printf("]\n");}}/**
 ** 异步I/O初始化函数
 **/staticvoidasync_io_init(void){structsigaction sigatn;int flag;/* 使能异步I/O */
    flag =fcntl(fd, F_GETFL);//使能串口的异步I/O功能
    flag |= O_ASYNC;fcntl(fd, F_SETFL, flag);/* 设置异步I/O的所有者 */fcntl(fd, F_SETOWN,getpid());/* 指定实时信号SIGRTMIN作为异步I/O通知信号 */fcntl(fd, F_SETSIG, SIGRTMIN);/* 为实时信号SIGRTMIN注册信号处理函数 */
    sigatn.sa_sigaction = io_handler;//当串口有数据可读时,会跳转到io_handler函数
    sigatn.sa_flags = SA_SIGINFO;sigemptyset(&sigatn.sa_mask);sigaction(SIGRTMIN,&sigatn,NULL);}staticvoid*read_thread(void*arg){printf("read_thread , process id=%d , thread id= %lu\n",getpid(),pthread_self());async_io_init();//我们使用异步I/O方式读取串口的数据,调用该函数去初始化串口的异步I/Owhile(1)sleep(1);//进入休眠、等待有数据可读,有数据可读之后就会跳转到io_handler()函数return(void*)0;}staticvoid*write_thread(void*arg){unsignedchar w_buf[8]={0};printf("write_thread , process id=%d , thread id= %lu\n",getpid(),pthread_self());while(1){scanf("%s",w_buf);write(fd, w_buf,8);//一次向串口写入8个字节memset(w_buf,0,sizeof w_buf);}return(void*)0;}intmain(int argc,char*argv[]){
    uart_cfg_t cfg ={0};char*device =NULL;int rw_flag =-1;int n;/* 解析出参数 */for(n =1; n < argc; n++){if(!strncmp("--dev=", argv[n],6))
            device =&argv[n][6];elseif(!strncmp("--brate=", argv[n],8))
            cfg.baudrate =atoi(&argv[n][8]);elseif(!strncmp("--dbit=", argv[n],7))
            cfg.dbit =atoi(&argv[n][7]);elseif(!strncmp("--parity=", argv[n],9))
            cfg.parity = argv[n][9];elseif(!strncmp("--sbit=", argv[n],7))
            cfg.sbit =atoi(&argv[n][7]);elseif(!strcmp("--help", argv[n])){show_help(argv[0]);//打印帮助信息exit(EXIT_SUCCESS);}}/* 串口初始化 */if(uart_init(device))exit(EXIT_FAILURE);/* 串口配置 */if(uart_cfg(&cfg)){tcsetattr(fd, TCSANOW,&old_cfg);//恢复到之前的配置close(fd);exit(EXIT_FAILURE);}

    pthread_t tid1,tid2;int ret =pthread_create(&tid1,NULL,read_thread,NULL);if(ret !=0){fprintf(stderr,"error:%s\n",strerror(ret));exit(-1);}
    
     ret =pthread_create(&tid2,NULL,write_thread,NULL);if(ret !=0){fprintf(stderr,"error:%s\n",strerror(ret));exit(-1);}while(1);/* 退出 */tcsetattr(fd, TCSANOW,&old_cfg);//恢复到之前的配置close(fd);exit(EXIT_SUCCESS);}
标签: 驱动开发 linux

本文转载自: https://blog.csdn.net/qq_44814825/article/details/129661547
版权归原作者 songwei4615 所有, 如有侵权,请联系我们删除。

“Linux驱动开发——串口设备驱动”的评论:

还没有评论