惠州网站建设服务,做照片视频的网站,wordpress 免费 主题 下载,驾校网站源码下载Linux驱动开发——串口设备驱动
一、串口简介
串口全称叫做串行接口#xff0c;通常也叫做 COM 接口#xff0c;串行接口指的是数据一个一个的顺序传输#xff0c;通信线路简单。使用两条线即可实现双向通信#xff0c;一条用于发送#xff0c;一条用于接收。串口通信距…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(X0….n)文件。
uart_driver 结构体
uart_driver 结构体表示 UART 驱动uart_driver 定义在 include/linux/serial_core.h 文件中
struct uart_driver
{struct module *owner; /* 模块所属者 */const char *driver_name; /* 驱动名字 */const char *dev_name; /* 设备名字 */int major; /* 主设备号 */int minor; /* 次设备号 */int nr; /* 设备数 */struct console *cons; /* 控制台 *//** these are private; the low level driver should not* touch these; they should be initialised to NULL*/struct uart_state *state;struct tty_driver *tty_driver;
};加载驱动的时候通过 uart_register_driver 函数向系统注册这个 uart_driver此函数原型如下
int uart_register_driver(struct uart_driver *drv)函数参数和返回值含义如下
drv 要注册的 uart_driver。返回值0成功负值失败。
注销驱动的时候也需要注销掉前面注册的 uart_driver需要用到 uart_unregister_driver 函数函数原型如下
void uart_unregister_driver(struct uart_driver *drv)函数参数和返回值含义如下
drv 要注销的 uart_driver。返回值无。
uart_port 的添加与移除
uart_port 表示一个具体的 portuart_port 定义在 include/linux/serial_core.h 文件
117 struct uart_port {
118 spinlock_t lock; /* port lock */
119 unsigned long iobase; /* in/out[bwl] */
120 unsigned char __iomem *membase; /* read/write[bwl] */
......
235 const struct uart_ops *ops;
236 unsigned int custom_divisor;
237 unsigned int line; /* port index */
238 unsigned int minor;
239 resource_size_t mapbase; /* for ioremap */
240 resource_size_t mapsize;
241 struct device *dev; /* parent device */
......
250 };uart_port 中最主要的就是第 235 行的 opsops 包含了串口的具体驱动函数UART 驱动编写人员需要实现 uart_ops因为 uart_ops 是最底层的 UART 驱动接口是实实在在的和 UART 寄存器打交道的。
那么 uart_port 是怎么和 uart_driver 结合起来用到 uart_add_one_port 函数函数原型如下
int uart_add_one_port(struct uart_driver *drv,struct uart_port *uport)函数参数和返回值含义如下
drv此 port 对应的 uart_driver。uport 要添加到 uart_driver 中的 port。返回值0成功负值失败。
卸载 UART 驱动的时候也需要将 uart_port 从相应的 uart_driver 中移除需要用到uart_remove_one_port 函数函数原型如下
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)函数参数和返回值含义如下
drv要卸载的 port 所对应的 uart_driver。uport 要卸载的 uart_port。返回值0成功负值失败。
三、Linux下串口驱动工作流程
UART 本质上是一个 platform 驱动platform 驱动框架结构体 serial_imx_driver在驱动入口函数中调用uart_register_driver 函数向 Linux 内核注册 uart_driver在驱动出口函数中调用uart_unregister_driver 函数注销掉前面注册的 uart_driverUART 设备和驱动匹配成功以后 serial_imx_probe 函数就会执行此函数的重点工作就是初始化 uart_port然后将其添加到对应的 uart_driver 中在初始化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.htypedef struct uart_hardware_cfg {unsigned int baudrate; /* 波特率 */unsigned char dbit; /* 数据位 */char parity; /* 奇偶校验 */unsigned char sbit; /* 停止位 */
} uart_cfg_t;static struct termios old_cfg; //用于保存终端的配置参数
static int fd; //串口终端对应的文件描述符/**** 串口初始化操作** 参数device表示串口终端的设备节点**/
static int uart_init(const char *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;}return 0;
}/**** 串口配置** 参数cfg指向一个uart_cfg_t结构体对象**/
static int uart_cfg(const uart_cfg_t *cfg)
{struct termios new_cfg {0}; //将new_cfg对象清零speed_t speed;/* 设置为原始模式 */cfmakeraw(new_cfg);/* 使能接收 */new_cfg.c_cflag | CREAD;/* 设置波特率 */switch (cfg-baudrate) {case 1200: speed B1200;break;case 1800: speed B1800;break;case 2400: speed B2400;break;case 4800: speed B4800;break;case 9600: speed B9600;break;case 19200: speed B19200;break;case 38400: speed B38400;break;case 57600: speed B57600;break;case 115200: speed B115200;break;case 230400: speed B230400;break;case 460800: speed B460800;break;case 500000: speed B500000;break;default: //默认配置为115200speed 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) {case 5:new_cfg.c_cflag | CS5;break;case 6:new_cfg.c_cflag | CS6;break;case 7:new_cfg.c_cflag | CS7;break;case 8:new_cfg.c_cflag | CS8;break;default: //默认数据位大小为8new_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) {case 1: //1个停止位new_cfg.c_cflag ~CSTOPB;break;case 2: //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 退出 */return 0;
}/***
--dev/dev/ttymxc2
--brate115200
--dbit8
--parityN
--sbit1
--typeread
***/
/**** 打印帮助信息**/
static void show_help(const char *app)
{printf(Usage: %s [选项]\n\n必选选项:\n --devDEVICE 指定串口终端设备名称, 譬如--dev/dev/ttymxc2\n\n可选选项:\n --brateSPEED 指定串口波特率, 譬如--brate115200\n --dbitSIZE 指定串口数据位个数, 譬如--dbit8(可取值为: 5/6/7/8)\n --parityPARITY 指定串口奇偶校验方式, 譬如--parityN(N表示无校验、O表示奇校验、E表示偶校验)\n --sbitSIZE 指定串口停止位个数, 譬如--sbit1(可取值为: 1/2)\n --help 查看本程序使用帮助信息\n\n, app);
}/**** 信号处理函数当串口有数据可读时会跳转到该函数执行**/
static void io_handler(int sig, siginfo_t *info, void *context)
{unsigned char 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初始化函数**/
static void async_io_init(void)
{struct sigaction 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);
}static void *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;
}
static void *write_thread(void *arg)
{unsigned char 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;
}int main(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];else if (!strncmp(--brate, argv[n], 8))cfg.baudrate atoi(argv[n][8]);else if (!strncmp(--dbit, argv[n], 7))cfg.dbit atoi(argv[n][7]);else if (!strncmp(--parity, argv[n], 9))cfg.parity argv[n][9];else if (!strncmp(--sbit, argv[n], 7))cfg.sbit atoi(argv[n][7]);else if (!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);
}