linux UART驱动_应用程序例程

Posted 九章_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux UART驱动_应用程序例程相关的知识,希望对你有一定的参考价值。

系列文章

I.MX6ULL 手册查找使用方法 实战点亮LED(寄存器版)
I.MX6ULL 手册查找使用方法 实战点亮LED(固件库版本)
linux 字符设备驱动实战
linux LED设备驱动文件
linux 设备树(.dts)实战解析
linux 使用设备树点亮LED 实战
linux 驱动中并发与竞争
linux 内核定时器
linux 内核中断理解
linux 驱动阻塞和非阻塞
linux 内核异步通知
linux platform驱动框架
linux 内核自带的LED灯驱动
linux misc设备驱动
linux input子系统
linux 深入理解I2C内核驱动
linux SPI 驱动
linux USB无线网卡(RTL8188EUS)驱动

Linux 下 UART 驱动框架

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

数据结构 uart_driver、uart_port、uart_ops

struct uart_driver {
        struct module           *owner;
        const char              *driver_name;
        const char              *dev_name;
        int                      major;
        int                      minor;
        int                      nr;
        struct console          *cons;
        struct uart_state       *state;
        struct tty_driver       *tty_driver;
};

uart_driver 注册与注销

int uart_register_driver(struct uart_driver *drv)
void uart_unregister_driver(struct uart_driver *drv)

uart_port

struct uart_port {
        spinlock_t              lock;                   /* port lock */
        unsigned long           iobase;                 /* in/out[bwl] */
        unsigned char __iomem   *membase;               /* read/write[bwl] */
        unsigned int            (*serial_in)(struct uart_port *, int);
        void                    (*serial_out)(struct uart_port *, int, int);
        void                    (*set_termios)(struct uart_port *,
                                               struct ktermios *new,
                                               struct ktermios *old);
        void                    (*set_mctrl)(struct uart_port *, unsigned int);
        int                     (*startup)(struct uart_port *port);
        void                    (*shutdown)(struct uart_port *port);
        void                    (*throttle)(struct uart_port *port);
        void                    (*unthrottle)(struct uart_port *port);
        int                     (*handle_irq)(struct uart_port *);
        void                    (*pm)(struct uart_port *, unsigned int state,
                                      unsigned int old);
        void                    (*handle_break)(struct uart_port *);
        int                     (*rs485_config)(struct uart_port *,
                                                struct serial_rs485 *rs485);
         const struct uart_ops   *ops;
         ...

uart_port 的添加与移除

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)

uart_ops (包含对UART具体收发数据等驱动函数)

struct uart_ops {
        unsigned int    (*tx_empty)(struct uart_port *);
        void            (*set_mctrl)(struct uart_port *, unsigned int mctrl);
        unsigned int    (*get_mctrl)(struct uart_port *);
        void            (*stop_tx)(struct uart_port *);
        void            (*start_tx)(struct uart_port *);
        void            (*throttle)(struct uart_port *);
        void            (*unthrottle)(struct uart_port *);
        void            (*send_xchar)(struct uart_port *, char ch);
        void            (*stop_rx)(struct uart_port *);
        void            (*enable_ms)(struct uart_port *);
        void            (*break_ctl)(struct uart_port *, int ctl);
        int             (*startup)(struct uart_port *);
        void            (*shutdown)(struct uart_port *);
        void            (*flush_buffer)(struct uart_port *);
        void            (*set_termios)(struct uart_port *, struct ktermios *new,
                                       struct ktermios *old);
        void            (*set_ldisc)(struct uart_port *, struct ktermios *);
        void            (*pm)(struct uart_port *, unsigned int state,
                              unsigned int oldstate);

UART 驱动分析

分析UART 的platform 驱动框架,在文件imx6ull.dtsi文件中,uart3对应节点

uart3: serial@021ec000 {
          compatible = "fsl,imx6ul-uart",
                       "fsl,imx6q-uart", "fsl,imx21-uart";
          reg = <0x021ec000 0x4000>;
          interrupts = <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>;
          clocks = <&clks IMX6UL_CLK_UART3_IPG>,
                   <&clks IMX6UL_CLK_UART3_SERIAL>;
          clock-names = "ipg", "per";
          dmas = <&sdma 29 4 0>, <&sdma 30 4 0>;
          dma-names = "rx", "tx";
          status = "disabled";
  };

通过compatible “fsl,imx6q-uart” 查找驱动文件

static struct platform_device_id imx_uart_devtype[] = {
        {
                .name = "imx1-uart",
                .driver_data = (kernel_ulong_t) &imx_uart_devdata[IMX1_UART],
        }, {
                .name = "imx21-uart",
                .driver_data = (kernel_ulong_t) &imx_uart_devdata[IMX21_UART],
        }, {
                .name = "imx6q-uart",
                .driver_data = (kernel_ulong_t) &imx_uart_devdata[IMX6Q_UART],
        }, {
                /* sentinel */
        }
};

static const struct of_device_id imx_uart_dt_ids[] = {
        { .compatible = "fsl,imx6q-uart", .data = &imx_uart_devdata[IMX6Q_UART], },
        { .compatible = "fsl,imx1-uart", .data = &imx_uart_devdata[IMX1_UART], },
        { .compatible = "fsl,imx21-uart", .data = &imx_uart_devdata[IMX21_UART], },
        { /* sentinel */ }
};
static struct platform_driver serial_imx_driver = {
        .probe          = serial_imx_probe,
        .remove         = serial_imx_remove,

        .suspend        = serial_imx_suspend,
        .resume         = serial_imx_resume,
        .id_table       = imx_uart_devtype,
        .driver         = {
                .name   = "imx-uart",
                .of_match_table = imx_uart_dt_ids,
        },
};

static int __init imx_serial_init(void)
{
        int ret = uart_register_driver(&imx_reg);

        if (ret)
                return ret;

        ret = platform_driver_register(&serial_imx_driver);
        if (ret != 0)
                uart_unregister_driver(&imx_reg);

        return ret;
}

static void __exit imx_serial_exit(void)
{
        platform_driver_unregister(&serial_imx_driver);
        uart_unregister_driver(&imx_reg);
}

module_init(imx_serial_init);
module_exit(imx_serial_exit);

1)uart本质上是platform驱动
2)设备树匹配,compatible属性值“fsl,imx6q-uart”
3) platform驱动架构 serial_imx_driver
4)uart_register_driver向内核注册uart_driver
5) uart_unregister_driver注销uart_driver

uart_driver 初始化

imx_serial_init 函数中向 Linux 内核注册了 imx_reg,imx_reg 就是 uart_driver 类型的结
构体变量,imx_reg 定义如下

static struct uart_driver imx_reg = {
        .owner          = THIS_MODULE,
        .driver_name    = DRIVER_NAME,
        .dev_name       = DEV_NAME,
        .major          = SERIAL_IMX_MAJOR,
        .minor          = MINOR_START,
        .nr             = ARRAY_SIZE(imx_ports),
        .cons           = IMX_CONSOLE,
};

uart_port 初始化与添加
当 UART 设备和驱动匹配成功以后 serial_imx_probe 函数就会执行,此函数的重点工作就
是初始化 uart_port,然后将其添加到对应的 uart_driver 中。

struct imx_port {
	struct uart_port port;
	...
}

imx_pops 结构体变量

static struct uart_ops imx_pops = {
        .tx_empty       = imx_tx_empty,
        .set_mctrl      = imx_set_mctrl,
        .get_mctrl      = imx_get_mctrl,
        .stop_tx        = imx_stop_tx,
        .start_tx       = imx_start_tx,
        .stop_rx        = imx_stop_rx,
        .enable_ms      = imx_enable_ms,
        .break_ctl      = imx_break_ctl,
        .startup        = imx_startup,
        .shutdown       = imx_shutdown,
        .flush_buffer   = imx_flush_buffer,
        .set_termios    = imx_set_termios,
        .type           = imx_type,
        .config_port    = imx_config_port,
        .verify_port    = imx_verify_port,
};

static int serial_imx_probe(struct platform_device *pdev)
{
    struct imx_port *sport;
    sport = devm_kzalloc(&pdev->dev,sizeof(*sport),GFP_KERNEL);
    ...
	sport->port.ops = &imx_pops;
	...
	return uart_add_one_port(&imx_reg, &sport->port);
}

使用 uart_add_one_port 向 uart_driver 添加 uart_port,在这里就是向 imx_reg 添加 sport->port。
imx_pops 中的函数基本都是和 I.MX6ULL 的 UART 寄存器打交道的

RS232原理图

在这里插入图片描述

RS232 驱动编写

在设备树中添加 UART3 对应的设备节点
1,IO 节点创建,UART3 用到了 UART3_TXD 和 UART3_RXD 这两个 IO,因此要先在 iomuxc 中创建 UART3对应的 pinctrl 子节点(电气属性),并检查IO有没有被其他引脚使用

pinctrl_uart3: uart3grp {
           fsl,pins = <
                   MX6UL_PAD_UART3_RX_DATA__UART3_DCE_RX   0x1b0b1
                   MX6UL_PAD_UART3_TX_DATA__UART3_DCE_TX   0x1b0b1
           >;
};

2、添加 uart3 节点 imx6ull-alientek-emmc.dts

&uart3 {
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_uart3>;
        status = "okay";
};

重新编译设备树文件,系统启动以后就会生成一个名为“/dev/ttymxc2”的设备文件,ttymxc2 就是 UART3 对应的设备文件。

/dev/ttymxc2 后面的名字是如果得到的?
1)mxc,是uart_driver的dev_name来确定的。源码中 #define DEV_NAME “ttymxc”
2)2具体数字怎么得到,系统给的排序,说明0 ,1 已经被用掉。如果不知道具体数字是多少,可以在驱动加载之前看一下编号是多少,驱动加载成功后新出现的节点就是驱动对应的编号。

应用层编程

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>


#define MODULE_NAME 	"uart_lib"

int speed_arr[] = { B115200, B38400, B19200, B9600, B4800, B2400, B1200, B300,
          B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {115200, 38400,  19200,  9600,  4800,  2400,  1200,  300, 38400,  
          19200,  9600, 4800, 2400, 1200,  300, };
void set_speed(int fd, int speed){
  int   i; 
  int   status; 
  struct termios   Opt;
  tcgetattr(fd, &Opt); 
  for ( i= 0;  i < sizeof(speed_arr) / sizeof(int);  i++) { 
    if  (speed == name_arr[i]) {     
      tcflush(fd, TCIOFLUSH);     
      cfsetispeed(&Opt, speed_arr[i]);  
      cfsetospeed(&Opt, speed_arr[i]);   
      status = tcsetattr(fd, TCSANOW, &Opt);  
      if  (status != 0) {        
        perror("tcsetattr fd1");  
        return;     
      }    
      tcflush(fd,TCIOFLUSH);   
    }  
  }
}



/**
*@brief   设置串口数据位,停止位和效验位
*@param  fd     类型  int  打开的串口文件句柄
*@param  databits 类型  int 数据位   取值 为 7 或者8
*@param  stopbits 类型  int 停止位   取值为 1 或者2
*@param  parity  类型  int  效验类型 取值为N,E,O,,S
*/
int set_Parity(int fd,int databits,int stopbits,int parity)
{ 
	struct termios options; 
	if  ( tcgetattr( fd,&options)  !=  0) { 
		perror("SetupSerial ");     
              SYS_LOG("setup serial failurer\\n");
		return(-1);  
	}
	options.c_cflag &= ~CSIZE; 
	switch (databits) /*设置数据位数*/
	{   
	case 7:		
		options.c_cflag |= CS7; 
		break;
	case 8:     
		options.c_cflag |= CS8;
		break;   
	default:    
		fprintf(stderr,"Unsupported data size\\n"); 
            return (-1);  
	}
    
    switch (parity) 
    {   
	case 'n':
	case 'N':    
		options.c_cflag &= ~PARENB;   /* Clear parity enable */
		options.c_iflag &= ~INPCK;     /* Enable parity checking */ 
		break;  
	case 'o':   
	case 'O':     
		options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/  
		options.c_iflag |= INPCK;             /* Disnable parity checking */ 
		break;  
	case 'e':  
	case 'E':   
		options.c_cflag |= PARENB;     /* Enable parity */    
		options.c_cflag &= ~PARODD;   /* 转换为偶效验*/     
		options.c_iflag |= INPCK;       /* Disnable parity checking */
		break;
	case 'S': 
	case 's':  /*as no parity*/   
	    options.c_cflag &= ~PARENB;
		options.c_cflag &= ~CSTOPB;
        break;  
	default:   
		fprintf(stderr,"Unsupported parity\\n");    
		return (-1);  
	}  
    /* 设置停止位*/  
    switch (stopbits)
    {   
	case 1:    
		options.c_cflag &= ~CSTOPB;  
		break;  
	case 2:    
		options.c_cflag |= CSTOPB;  
	   break;
	default:    
		 fprintf(stderr,"Unsupported stop bits\\n");  
		 return (-1); 
    } 
    /* Set input parity option */ 
    if (parity != 'n' && parity != 'N')   
    	options.c_iflag |= INPCK; 
    tcflush(fd,TCIFLUSH);
    options.c_cc[VTIME] = 150; /* 设置超时15 seconds*/   
    options.c_cc[VMIN] = 0; /* Update the options and do it NOW */

    options.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/
    options.c_oflag  &= ~(OPOST|ONLCR|OCRNL);   /*Output*/
    options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON | INLCR|IGNCR);
    if (tcsetattr(fd,TCSANOW,&options) != 0)   
    { 
    	perror("SetupSerial 3");   
    	return (-1);  
    } 
    return (0);  
}


int set_serial_port(int fd, speed_t speed, int databits,int stopbits,int parity)
{
    int ret;
   set_speed(fd, speed);
   ret = set_Parity(fd, databits, stopbits, parity);

   return ret;

}


int open_serial_port(char *dev_path)
{
    int fd;
    if (dev_path == NULL)
        return -1;
    fd=open(dev_path,O_RDWR | O_NOCTTY);
    if(fd==-1)
    {
        SYS_LOG("open serialport failed [%s]\\n", dev_path);
        perror("open serialport failed\\n");
        return(-1);
    }
    fcntl(fd, F_SETFL, 0);

    return fd;

}
	
int init_uart(char * device_name, speed_t speed, int databits,int stopbits,int parity)
{
    int ret;
    int uart_fd = 0;
    uart_fd = open_serial_port(device_name);
    if(uart_fd<0)
    {
        SYS_LOG("open  serial port failed\\n");
        return -1;
    }
	
    ret=set_serial_port(uart_fd, speed, databits, stopbits, parity);
    if(ret==-1)
    {
        SYS_LOG("set  serial port failed\\n");
        return -1;
    }
    return uart_fd;


}

int close_uart(int uart_fd)
{
    if (uart_fd > 0)
        close(uart_fd);

    return 0;
}

unsigned char buf[5]={'A','B','C','D','\\n'};

void main()
{

     int gblUartFd = -1;
     int ret;
	 gblUartFd = init_uart("/dev/ttymxc2”, 115200, 8, 1, 'N');
	 while(1)
	 {
	 	ret = read(gblUartFd , buf,sizeof(char)*5);
     	printf("buf=%s",buf);
     	sleep(1);
     }
}

交叉编译

arm-linux-gnueabihf-gcc uart.c -o uart

在串口下运行

./uart

将外接RS232连接好,并用securecrt串口软件打开,显示ABCD
在这里插入图片描述

测试二:使用外接 securecrt 发送,使用cat /dev/ttymxc2 读出

在这里插入图片描述

以上是关于linux UART驱动_应用程序例程的主要内容,如果未能解决你的问题,请参考以下文章

Linux UART介绍

由于系统错误代码 998,无法加载 Instantclient_12_1 ODBC 驱动程序中 Oracle 的设置例程

六 linux UART串口驱动代分析

Linux——Linux驱动之iMX6ULL平台下串口UART驱动实现RS232数据通信开发实战(UART驱动框架源码分析串口应用程序编写)

Linux——Linux驱动之iMX6ULL平台下串口UART驱动实现RS232数据通信开发实战(UART驱动框架源码分析串口应用程序编写)

Linux——Linux驱动之iMX6ULL平台下串口UART驱动实现RS232数据通信开发实战(UART驱动框架源码分析串口应用程序编写)