Linux下的串口总线驱动(一)

发布者:传邮万里最新更新时间:2016-03-08 来源: eefocus关键字:Linux  串口  总线驱动 手机看文章 扫描二维码
随时随地手机看文章
一.系统理论

PC机南桥的LPC总线(Low Pin Count并行总线,代替以前的ISA总线)上挂接了一个超级I/O模块,而UART是这个超级模块芯片组的一部分,这个UART通过RS232线程转换与串行端口相连。与RS232不同,RS485并不是标准的PC接口,但在嵌入式领域,会为了可靠通信而使用RS485,RS485使用差分信号,因此其传输距离可以达到数百米,而RS232传输距离仅数几米,在处理器一端,RS485接口是半双工的UART操作。

Linux包含如下几种终端设备:串行端口终端(/dev/ttySn)、伪终端(/dev/pty)、控制终端(/dev/tty)、控制台终端(/dev/ttyn,/dev/conslole)。串行端口终端使用的设备名为/dev/ttyS0,/dev/ttyS1等,对应的设备号为(4,0),(4,1)。通过查看/proc/tty/drivers文件可以知道什么类型的tty设备存在以及什么驱动被加载到内核,这个文件包括一个当前存在的不同tty驱动的列表,包括驱动名,缺省的节点名,驱动的主编号,驱动的次编号范围,以及tty驱动的类型。

I/O系统调用是从带有线路规程的TTY I/O核心开始,然后通过TTY层,最后到达UART驱动层。主要涉及串口内核配置、UART层内核代码、TTY层内核代码、线路规程内核代码、串口测试代码五个部分。

 

二.串口内核配置

对于Mini2440串口驱动,我想从配置开始讲起。在内核中Kconfig必须完成一层层调用,如果没有在上一个Kconfig中调用该层Kconfig,那么该层Kconfig中的内容不会在此出现。这种情况下,只有当该层的Kconfig被其他层调用,该层Kconfig中的内容才会被显示。所以我们找找drivers/serial/Kconfig在哪里被调用的呢?

 

 

 

 

在/drivers/char/kconfig中可以看到一行代码source "drivers/serial/Kconfig",那我们就到drivers/serial/Kconfig下看看

 

Samsung SoC serial support对应于samsung.o  serial_core.o

config SERIAL_SAMSUNG

       tristate "Samsung SoC serial support"

       depends on ARM && PLAT_S3C

       select SERIAL_CORE

Support for console on Samsung SoC serial port对应于控制台驱动

 

Samsung S3C2440/S3C2442 Serial port support对应于s3c2440.o

 

在/drivers/char/Makefile中可以看到

obj-y       += mem.o random.o tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o tty_buffer.o tty_port.o

我们知道tty_io.o   n_tty.o   tty_ioctl.o   tty_ldisc.o   tty_buffer.o   tty_port.o已编入内核

 

自此,我们知道关于串口驱动,我们内核中被编译了s3c2440.o   samsung.o  serial_core.o tty_io.o   n_tty.o   tty_ioctl.o   tty_ldisc.o   tty_buffer.o   tty_port.o

我们对此进行分类,属于UART层的是s3c2440.o   samsung.o;属于TTY层的是serial_core.o;属于线路规程的是tty_io.o   n_tty.o   tty_ioctl.o   tty_ldisc.o   tty_buffer.o   tty_port.o 。

 

好了,对于串口的地图我们已经分析好了,那我们就按照UART层,TTY层,线路规程一个个的逛逛吧。

 

三.UART层内核代码

我们先看看samsung.o的init代码吧,这里面完成了uart_driver的注册

static int __init s3c24xx_serial_modinit(void)

{

       int ret;

       ret = uart_register_driver(&s3c24xx_uart_drv);  //注册uart_driver

       if (ret < 0) {

              printk(KERN_ERR "failed to register UART driver\n");

              return -1;

       }

       return 0;

}

 

static struct uart_driver s3c24xx_uart_drv = {

       .owner           = THIS_MODULE,                           

       .dev_name      = "s3c2410_serial",                            //设备名

       .nr          = CONFIG_SERIAL_SAMSUNG_UARTS,  //UART端口个数

       .cons             = S3C24XX_SERIAL_CONSOLE,  //指向控制台结构

       .driver_name   = S3C24XX_SERIAL_NAME,    //驱动的名字

       .major            = S3C24XX_SERIAL_MAJOR,   //串口主设备号

       .minor            = S3C24XX_SERIAL_MINOR,   //串口次设备号

};

 

我们关注下上面这个结构体中一个成员S3C24XX_SERIAL_CONSOLE

#define S3C24XX_SERIAL_CONSOLE &s3c24xx_serial_console

static struct console s3c24xx_serial_console = {

       .name             = S3C24XX_SERIAL_NAME,

       .device           = uart_console_device,

       .flags             = CON_PRINTBUFFER,

       .index             = -1,

       .write             = s3c24xx_serial_console_write,

       .setup             = s3c24xx_serial_console_setup

};

上面是控制台的结构体成员。

 

对于UART驱动,我们除了需要注册uart_driver外,还需要注册端口,我们看看s3c2440.o。

这个文件里面注册了一个平台设备,其中平台设备的探测函数最终调用了samsung.o中的s3c24xx_serial_probe函数。

int s3c24xx_serial_probe(struct platform_device *dev,

                      struct s3c24xx_uart_info *info)

{

       struct s3c24xx_uart_port *ourport;

       int ret;

       dbg("s3c24xx_serial_probe(%p, %p) %d\n", dev, info, probe_index);

       ourport = &s3c24xx_serial_ports[probe_index]; //选择s3c24xx_uart_port

       probe_index++; //索引号自增

       dbg("%s: initialising port %p...\n", __func__, ourport);

       ret = s3c24xx_serial_init_port(ourport, info, dev);  //初始化串口

       if (ret < 0)

              goto probe_err;

       dbg("%s: adding port\n", __func__);

       uart_add_one_port(&s3c24xx_uart_drv, &ourport->port); //向内核注册端口

       platform_set_drvdata(dev, &ourport->port); //设置私有数据

       ret = device_create_file(&dev->dev, &dev_attr_clock_source); //添加设备属性

       if (ret < 0)

              printk(KERN_ERR "%s: failed to add clksrc attr.\n", __func__);

       ret = s3c24xx_serial_cpufreq_register(ourport);  //注册CPU频率

       if (ret < 0)

              dev_err(&dev->dev, "failed to add cpufreq notifier\n");

       return 0;

 probe_err:

       return ret;

}

通过上面的函数,我们发现在UART层,我们调用了uart_add_one_port函数完成端口的添加,我们来看看添加了什么端口呢?

static struct s3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {

       [0] = {

              .port = {

                     .lock              = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),

                     .iotype            = UPIO_MEM,    

                     .irq         = IRQ_S3CUART_RX0,

                     .uartclk   = 0,

                     .fifosize   = 16,

                     .ops        = &s3c24xx_serial_ops,    //对UART操作的函数

                     .flags             = UPF_BOOT_AUTOCONF,

                     .line        = 0,

              }

       },

       [1] = {

              .port = {

                     .lock              = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[1].port.lock),

                     .iotype            = UPIO_MEM,

                     .irq         = IRQ_S3CUART_RX1,

                     .uartclk   = 0,

                     .fifosize   = 16,

                     .ops        = &s3c24xx_serial_ops,   //对UART操作的函数

                     .flags             = UPF_BOOT_AUTOCONF,

                     .line        = 1,

              }

       },

#if CONFIG_SERIAL_SAMSUNG_UARTS > 2

 

       [2] = {

              .port = {

                     .lock              = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[2].port.lock),

                     .iotype            = UPIO_MEM,

                     .irq         = IRQ_S3CUART_RX2,

                     .uartclk   = 0,

                     .fifosize   = 16,

                     .ops        = &s3c24xx_serial_ops,    //对UART操作的函数

                     .flags             = UPF_BOOT_AUTOCONF,

                     .line        = 2,

              }

       },

#endif

#if CONFIG_SERIAL_SAMSUNG_UARTS > 3

       [3] = {

              .port = {

                     .lock              = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[3].port.lock),

                     .iotype            = UPIO_MEM,

                     .irq         = IRQ_S3CUART_RX3,

                     .uartclk   = 0,

                     .fifosize   = 16,

                     .ops        = &s3c24xx_serial_ops,    //对UART操作的函数

                     .flags             = UPF_BOOT_AUTOCONF,

                     .line        = 3,

              }

       }

#endif

};

在端口的定义中,我们知道s3c24xx_uart_port中定义了一个uart_port结构体,继续跟踪对UART的操作函数

static struct uart_ops s3c24xx_serial_ops = {

       .pm         = s3c24xx_serial_pm,

       .tx_empty       = s3c24xx_serial_tx_empty,  //发送是否忙

       .get_mctrl       = s3c24xx_serial_get_mctrl,

       .set_mctrl       = s3c24xx_serial_set_mctrl,

       .stop_tx   = s3c24xx_serial_stop_tx,   

       .start_tx  = s3c24xx_serial_start_tx,   //类似于write

       .stop_rx  = s3c24xx_serial_stop_rx,  

       .enable_ms     = s3c24xx_serial_enable_ms,

       .break_ctl       = s3c24xx_serial_break_ctl,

       .startup   = s3c24xx_serial_startup,  //类似于open

       .shutdown      = s3c24xx_serial_shutdown,  //类似于close

       .set_termios    = s3c24xx_serial_set_termios,  //设置线路规程

       .type              = s3c24xx_serial_type,

       .release_port   = s3c24xx_serial_release_port,  //释放端口资源

       .request_port  = s3c24xx_serial_request_port,  //申请端口资源

       .config_port    = s3c24xx_serial_config_port,  //配置端口

       .verify_port    = s3c24xx_serial_verify_port,

};

 

对于上述uart_ops函数,我们需要自己去实现uart层的具体操作。

 

我们在UART层主要涉及uart_driver,uart_port,uart_ops三个结构体,并调用tty层的uart_register_driver和uart_add_one_port完成驱动和端口的注册,UART层具体操作函数需要用户自己设计。

好了,总结下UART驱动层需要完成的任务:

其一,定义uart_driver、uart_ops、uart_port等结构体的实例并在适当的地方根据具体硬件和驱动的情况初始化它们,当然具体设备XXX的驱动可以将这些结构套在新定义的XXX_uart_driver、XXX_uart_ops、XXX_uart_port之内。

其二,在模块初始化时调用uart_register_driver()和uart_add_one_port()以注册UART驱动并添加端口,在模块卸载时调用uart_unregister_driver()和uart_remove_one_port()以注销UART驱动并移除端口。

其三,根据具体硬件的datasheet实现uart_ops中的成员函数,这些函数的实现成为UART驱动的主体工作。

关键字:Linux  串口  总线驱动 引用地址:Linux下的串口总线驱动(一)

上一篇:关于IIC协议及代码实现
下一篇:Linux下的串口总线驱动(二)

推荐阅读最新更新时间:2024-03-16 14:46

单片机串口通信UART与USART的区别
UART与USART都是单片机上的串口通信,他们之间的区别如下: 首先从名字上看: UART:universal asynchronous receiver and transmitter通用异步收/发器 USART:universal synchronous asynchronous receiver and transmitter通用同步/异步收/发器 从名字上可以看出,USART在UART基础上增加了同步功能,即USART是UART的增强型,事实也确实是这样。但是具体增强到了什么地方呢? 其实当我们使用USART在异步通信的时候,它与UART没有什么区别,但是用在同步通信的时候,区别就很明显了:大家都知道
[单片机]
Linux驱动曝光AMD Vega20核心:有望对应7nm加速卡
  这两天发硬件新闻总市心有戚戚,即便看到挺靠谱的消息,也总是有一种愚人节埋雷的隐忧。不过,下面这则,笔者仔细查验了一番,应该可信。下面就随嵌入式小编一起来了解一下相关内容吧。   据VideoCardz,在 Linux 的Freedesktop分支中,出现了Vega20的芯片识别代码。   Vega20识别出来了6种型号,此前Vega 10在驱动中识别出9种,覆盖Instinct、Radeon Pro和RX系列产品。     不过,按照 AMD 的规划,GPU产品线在2018年分为三个级别,其中高端桌面的Vega 56/64已经与我们见面,Vega Mobile已经用在APU和Intel的Kaby Lake-G中。而唯一称
[嵌入式]
再造STM32---第十七部分:USART—串口通讯
本章参考资料:《STM32F4xx 中文参考手册》 USART 章节。 学习本章时,配合《STM32F4xx 中文参考手册》 USART 章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。 特别说明, 本书内容是以 STM32F42xxx 系列控制器资源讲解。 17.1 串口通讯协议简介: 物理层: 规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。其实就是硬 件部分。 协议层: 协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。其实就是软件部分。 1-RS232标准: RS232标准串口通讯结构图: 1、 RS232标准串口主要用于工业设备直接通信 2、电平转换芯片一般有M
[单片机]
再造STM32---第十七部分:USART—<font color='red'>串口</font>通讯
#51单片机#蓝牙模块(ATKSPPHC06从机串口)的使用方法
#include AT89X51.H #include intrins.h // 函数原形定义 #define uchar unsigned char #define uint unsigned int void main (void); // 主函数 void LED_OUT(uchar X); // LED单字节串行移位函数 void LED_print (uchar p, uchar x) ; unsigned char code LED_0F ; // LED字模表 sbit DIO = P1^0; //串行数据输入 sbit RCLK = P1^1; //时钟脉冲信号 上升沿有效 sbit SCLK =
[单片机]
#51单片机#蓝牙模块(ATKSPPHC06从机<font color='red'>串口</font>)的使用方法
TI MSP430系列单片机串口通信波特率计算方法
TI MSP430系列单片机,usart模块的波特率值设定是通过以下三个寄存器决定的:UxBR0,UxBR1,UxMCTL 波特率=BRCLK/N ,主要是计算出N。 BRCLK:时钟源,可以通过寄存器设定何为时钟源; 通过寄存器UCAxCTL1的SSEL两位选择,01:ACLK,02:SMCLK N:波特率产生的分频因子。N=UxBR1+UxBR0+UxMCTL,其中UxBR1+UxBR0为整数部分,UxMCTL为设定小数部分,其中 UxBR1为高位,UxBR0为低位,两者结合起来为一个16位的字。 举例说明:波特率=115200,时钟源=8MHz ,为外部晶体振荡器 N=8000000/115200=69.44
[单片机]
嵌入式linux在工业控制领域中的应用
摘要:针对嵌入网络设备的应用特点,介绍了嵌入式linux的主要技术及在工业控制领域的应用方法。结合硬件平台详细说明了嵌入式linux系统的主要实现方法同时也简要介绍了该嵌入式系统的实时内核、内存机制和文件系统的设计等内容。 关键词:嵌入式系统;嵌入式linux;工业控制 1 前言 随着Internet的飞速发展,网络应用越来越广泛,对各种工业控制设备的网络功能要求也越来越高。当前的要求是希望工业控制设备能够支持TCP/IP以及其它Internet协议,从而能够通过用户熟悉的浏览器查看设备状态、设置设备参数,或者将设备采集到的数据通过网络传送到Windows或Unix/Linux服务器上的数据库中。这就要求工控系统必须具备两
[应用]
AVR串口多机通讯过程的解答
网友NE5532提问: AVR的串口说有多机通讯功能,就是在第一帧发送从机的地址,再发数据,呢么请问,从机的地址是在什么地方写的呢?是从机自己就包含地址(硬件)还是在软件上给从机赋值?DATASHEET里面好像没写啊。 -------------------------------------------------------------------------------- 马潮老师的解答: 根据你提的问题看,你对串口多机通信的过程是不清楚的,应该先深入了解和掌握多机通信的过程。 在多机通信过程中,所有设备的RS232接口是并在通信线上的,其中只能有一个设备为主机,其他为从机,通信由主机发起。数据帧一般采用1位起始位、9 位
[单片机]
arm linux 从入口到start_kernel 代码分析 - 5
4. 调用平台特定的 __cpu_flush 函数 当 __create_page_tables 返回之后 此时,一些特定寄存器的值如下所示: r4 = pgtbl (page table 的物理基地址) r8 = machine info (struct machine_desc的基地址) r9 = cpu id (通过cp15协处理器获得的cpu id) r10 = procinfo (struct proc_info_list的基地址) 在我们需要在开启mmu之前,做一些必须的工作:清除ICache, 清除 DCache, 清除 Writeb
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved