串口(国嵌笔记)

来源:互联网 发布:中国跆拳道网络联盟 编辑:程序博客网 时间:2024/05/15 22:43
在Linux中,TTY(终端)是一类字符设备的统称,包括了3种类型: 控制台,串口和伪终端。




供内核使用的终端为控制台。控制台 在Linux启动时,通过命令console=…指定,如果没有指定控制台 , 系统把第一个注册的终端(tty)作为控制台 。



1.控制台是一个虚拟的终端,它必须映射到真正的终端上。
2.控制台可以简单的理解为printk输出的地方。
3.控制台是个只输出的设备, 功能很简单,只能在内核中访问。


伪终端设备是一种特殊的终端设备, 由主-从两个成对的设备构成, 当打开主设备时, 对应的从设备随之打开, 形成连接状态。 输入到主设备的数据成为从设备的输出 ,输入到从设备的数据成为主设备的输出 , 形成双向管道。
  伪终端设备常用 于远程登录服务器来建立网络和终端的关联。 当 通过telnet远程登录到另 一台 主机时,telnet进程与远程主机的telnet服务器相连接. telnet服务器使用 某个主设备并通过对应的从设备与 telnet进程相互通信。


终端体系

tty core-----》获取用户数据和传递给用户数据

tty line discipline 线路规程(可有可无)-------》规范,协议等,进行格式化

tty driver--------》发送或接受




不管你是否使用串口,系统都会将数据读到buffer


1. 定义一个uart_driver的变量,并初始化;
2. 使用 uart_register_driver来注册这个驱动;
3. 初始化uart_port和ops函数表;

4. 调用 uart_add_one_port()添加初始化好的uart_port。


中断是非常重要的 ,接收:buffer达到设置门限值的时候产生终端。发送:发送fifo低于门限值产生中断


linux 2.4 串口驱动程序

串口驱动分析

(国嵌)

1.发送和接收

发送:循环buffer发送fifo发送移位寄存器

接收:接收移位寄存器接收fifoFlip_buf


发送的过程是:把数据写到发送fifo中,fifo把收到的数据传给发送移位寄存器(自动的,driver控制),然后每个时钟脉冲往串口线上写一bit数据。

接收的过程是:接收移位寄存器收到数据,发送给接收fifo,接收fifo事先设置好了触发门限,当里面的数据量超过门限时就会触发一个中断,调用驱动中的中断处理函数,把数据写到flip_buf中。

2.寄存器







UARTLine Control Register

 


WordLength:数据位长度

Numberof Stop Bit:停止位数

ParityMode:奇偶校验位类型

Infra-RedModeUART/红外模式选择(当以UART模式工作时,需设为0”












UARTControl Register

 

ReceiveMode:选择接收模式。如果是采用DMA模式的话,还需要指定说使用的DMA信道。

TransmitMode:同上。

SendBreak Signal:选择是否在传1帧资料中途发送Break信号。

LoopbackMode:选择是否将UART置于Loopback测试模式。

RxError Status Interrupt Enable:选择是否使能当发生接收异常时,是否产生接收错误中断。

RxTime Out Enable:是否使能接收超时中断。

RxInterrupt Type:选择接收中断类型。

选择0Pulse(脉冲式/边沿式中断。非FIFO模式时,一旦接收缓冲区中有数据,即产生一个中断;为FIFO模式时,一旦当FIFO中的资料达到一定的触发水平后,即产生一个中断)

选择1Level(电平模式中断。非FIFO模式时,只要接收缓冲区中有数据,即产生中断;为FIFO模式时,只有FIFO中的资料达到触发水平后,即产生中断)

TxInterrupt Type:类同于RxInterrupt Type












UARTFIFO Conrtol Register

 


FIFOEnableFIFO使能选择。
RxFIFO Reset
:选择当复位接收FIFO时是否自动清除FIFO中的内容。
TxFIFO Reset
:选择当复位发送FIFO时是否自动清除FIFO中的内容。
RxFIFO Trigger Level
:选择接收FIFO的触发水平。
TxFIFO Trigger Level
:选择发送FIFO的触发水平。








UARTTX/RX Status Register

 


Receivebuffer data ready:当接收缓冲寄存器从UART接收端口接收到有效资料时将自动置1”。反之为0则表示缓冲器中没有资料。

Transmitbuffer empty:当发送缓冲寄存器中为空,自动置1”;反之表明缓冲器中正有资料等待发送。

Transmitterempty:当发送缓冲器中已经没有有效资料时,自动置1”;反之表明尚有资料未发送。







UARTFIFO Status Register

 


RxFIFO Count :接收FIFO中当前存放的字节数。

TxFIFO Count :发送FIFO中当前存放的字节数。

RxFIFO Full :1“表明接收FIFO已满。

TxFIFO Full :1“表明发送FIFO已满。







3.函数介绍

模块初始化函数:

staticint __init s3c2410uart_init(void)

{

returnuart_register_driver(&s3c2410_reg);

}

使用uart_register_driver注册串口驱动。


staticstruct uart_driver s3c2410_reg = {

owner: THIS_MODULE,

normal_major: SERIAL_S3C2410_MAJOR,

normal_name: "ttyS%d",

callout_name: "cua%d",

normal_driver: &normal,

callout_major: CALLOUT_S3C2410_MAJOR,

callout_driver: &callout,

table: s3c2410_table,

termios: s3c2410_termios,

termios_locked: s3c2410_termios_locked,

minor: MINOR_START,

nr: UART_NR,

port: s3c2410_ports,

cons: S3C2410_CONSOLE,

};


staticstruct uart_port s3c2410_ports[UART_NR] = {

{

iobase: (unsignedlong)(UART0_CTL_BASE),

iotype: SERIAL_IO_PORT,

irq: IRQ_RXD0,

uartclk: 130252800,

fifosize: 16,

ops: &s3c2410_pops,

type: PORT_S3C2410,

flags: ASYNC_BOOT_AUTOCONF,

},

。。。。。。。。。。。。。。。。。。。。

};


staticstruct uart_ops s3c2410_pops = {

tx_empty: s3c2410uart_tx_empty,

set_mctrl: s3c2410uart_set_mctrl,

get_mctrl: s3c2410uart_get_mctrl,

stop_tx: s3c2410uart_stop_tx,

start_tx: s3c2410uart_start_tx,

stop_rx: s3c2410uart_stop_rx,

enable_ms: s3c2410uart_enable_ms,

break_ctl: s3c2410uart_break_ctl,

startup: s3c2410uart_startup,

shutdown: s3c2410uart_shutdown,

change_speed: s3c2410uart_change_speed,

type: s3c2410uart_type,

config_port: s3c2410uart_config_port,

release_port: s3c2410uart_release_port,

request_port: s3c2410uart_request_port,

};

3.1阻止发送函数uart_stop_tx

staticvoid s3c2410uart_stop_tx(struct uart_port *port, u_int from_tty)

{

disable_irq(TX_IRQ(port));

}

停止发送的功能,其内部的函数disable_irq是停止中断的功能,发送数据是通过中断来完成的,关闭中断也就关闭了发送。



3.2发送使能函数uart_start_tx

staticvoid s3c2410uart_start_tx(struct uart_port *port, u_int nonempty,

u_intfrom_tty)

{

enable_irq(TX_IRQ(port));

}

与上面的过程类似,就是一个相反的过程

3.3阻止接收函数uart_stop_rx

staticvoid s3c2410uart_stop_rx(struct uart_port *port)

{

disable_irq(RX_IRQ(port));

}





3.4发送缓冲空判断函数uart_tx_empty

staticu_int s3c2410uart_tx_empty(struct uart_port *port)

{

return(UART_UTRSTAT(port) & UTRSTAT_TR_EMP ? 0 : TIOCSER_TEMT);

}


如果发送缓冲为空则返回0,否则返回1


3.5获取控制信息函数uart_get_mctrl

staticu_int s3c2410uart_get_mctrl(struct uart_port *port)

{

return(TIOCM_CTS | TIOCM_DSR | TIOCM_CAR);

}

获得控制信息,TIOCM_CTS ,TIOCM_DSRTIOCM_CAR,这几个宏代表串口的控制信息,分别是clearto send,data set readydatacarrier detect(详见SerialProgramming Guide for POSIX Operating Systems)

3.6接收中断函数uart_rx_interrupt

staticvoid s3c2410uart_rx_interrupt(int irq, void *dev_id, struct pt_regs*regs)

{

structuart_info *info = dev_id;

structtty_struct *tty = info->tty;

unsignedint status, ch, max_count = 256;

structuart_port *port = info->port;


status= UART_UTRSTAT(port);

while((status & UTRSTAT_RX_RDY) && max_count--)

{

if(tty->flip.count >= TTY_FLIPBUF_SIZE)

{

tty->flip.tqueue.routine((void*) tty);


if(tty->flip.count >= TTY_FLIPBUF_SIZE) {

printk(KERN_WARNING"TTY_DONT_FLIP set\n");

return;

}

}

ch= UART_URXH(port);

*tty->flip.char_buf_ptr= ch;

*tty->flip.flag_buf_ptr= TTY_NORMAL;

port->icount.rx++;

tty->flip.flag_buf_ptr++;

tty->flip.char_buf_ptr++;

tty->flip.count++;

status= UART_UTRSTAT(port);

}

tty_flip_buffer_push(tty);

return;

}

功能:主要是是while大循环,首先看循环判断条件status& UTRSTAT_RX_RDY,前面有status= UART_UTRSTAT(port),2410datasheet,status &UTRSTAT_RX_RDY这个位是判断接收buffer内是否还有有效数据?按道理一次中断只是把接收的fifobuffer中的数据放到flipbuffer中去,接收的fifo的中断门限是4-12字节,进行一次接收往往要中断好多次,这样中断开销比较大,所以在while的循环条件中判断一下是否还有接收的有效数据,如果有,就继续在中断程序中继续接收,当然,永远都在接收中断中(如果一直有数据要接收)也不合适,所以while循环还有计数,最多循环256次(最多256个字节)。一次中断中可以实现多个数据的接受。

在循环中,首先是要判断一下接收数据用的flip-buffer是不是已经满了,if(tty->flip.count >= TTY_FLIPBUF_SIZE)如果满了,就要跳到另一个buffer上去,tty->flip.tqueue.routine((void *)tty)是用来实现跳到另一个buffer上的功能,然后把收到的数据写到flip-buffer,相应的状态,统计数据都要改,接着再来while循环,循环结束后就要调用tty_flip_buffer_push(tty)来让用户把存在缓冲里的数据取走,接收一次都要把缓存清空。


3.7发送中断函数uart_tx_interrupt

staticvoid s3c2410uart_tx_interrupt(int irq, void *dev_id,

structpt_regs *reg) {

structuart_info *info = dev_id;

structuart_port *port = info->port;

intcount;


if(port->x_char) {

UART_UTXH(port)= port->x_char;

port->icount.tx++;

port->x_char= 0;

return;

}


if(info->xmit.head == info->xmit.tail

||info->tty->stopped || info->tty->hw_stopped) {

s3c2410uart_stop_tx(info->port,0);

return;

}

count= port->fifosize >> 1;


do{

UART_UTXH(port)= info->xmit.buf[info->xmit.tail];

info->xmit.tail= (info->xmit.tail + 1) & (UART_XMIT_SIZE - 1);

port->icount.tx++;

if(info->xmit.head == info->xmit.tail)

break;

}while (--count > 0);

if(CIRC_CNT(info->xmit.head, info->xmit.tail,

UART_XMIT_SIZE)< WAKEUP_CHARS)

uart_event(info,EVT_WRITE_WAKEUP);

if(info->xmit.head == info->xmit.tail)

s3c2410uart_stop_tx(info->port,0);

}



  1. (1)首先查看port中的x_char是不是为0,不为0则把x_char发送出去。x_charxon/xoff的意思,每发一个字节时在开始前先发xon信号,在结束时发xoff

  2. (2)如果x_char没有被设置,再看环形缓冲区是否为空,或者info->tty->stoppedinfo->tty->hw_stopped两个位是不是为1,如果这些条件成立的话,就停止发送。Tty->stop指示tty设备是否停止,tty->hw_stop指示tty设备的硬件是否停止了,以上两个位都可以通过ttydriver来设定,否则的话说明有数据要发送。

  3. (3)如果以上条件都通过了,就利用一个while循环正式发送数据了,从环形缓冲尾巴上取一个数赋给UART_UTXH(port)(发送FIFO),UART_UTXH(port)=info->xmit.buf[info->xmit.tail],这条语句就是把数据送到发送FIFO中,然后计数++,循环一共进行fifosize/2,也就是一次只能发送8byte。fifo是16字节。

  4. (4)循环传送完一次后,再查看缓冲器里还剩余多少数据,如果少于WAKEUP_CHARS(256)的话,就执行uart_event(info,0),告诉TTY核心,可以接受更多数据了。这里可以看出,tty_drivertty_core之间的层次,tty_driver可以知道缓冲空还是满,但是它没有权力让发送数据过来,它只能是通知tty_core,让它来处理。

  5. (5)最后再察看一下环形寄存器,如果serialcore没有发送来更多的数据,就关闭发送。


3.8出错中断函数uart_err_interrupt

staticvoid s3c2410uart_err_interrupt(int irq, void *dev_id,

structpt_regs *reg) {

structuart_info *info = dev_id;

structuart_port *port = info->port;


structtty_struct *tty = info->tty;

unsignedchar err = UART_UERSTAT(port) & UART_ERR_MASK;

unsignedint ch, flg;

ch= UART_URXH(port);

if(!(err & (UERSTAT_BRK | UERSTAT_FRAME |

UERSTAT_PARITY| UERSTAT_OVERRUN)))

return;

if(err & UERSTAT_BRK)

port->icount.brk++;

if(err & UERSTAT_FRAME)

port->icount.frame++;

if(err & UERSTAT_PARITY)

port->icount.parity++;

if(err & UERSTAT_OVERRUN)

port->icount.overrun++;

err&= port->read_status_mask;

if(err & UERSTAT_PARITY)

flg= TTY_PARITY;

elseif (err & UERSTAT_FRAME)

flg= TTY_FRAME;

else

flg= TTY_NORMAL;

if(err & UERSTAT_OVERRUN) {

*tty->flip.char_buf_ptr= ch;

*tty->flip.flag_buf_ptr= flg;

tty->flip.flag_buf_ptr++;

tty->flip.char_buf_ptr++;

tty->flip.count++;

if(tty->flip.count < TTY_FLIPBUF_SIZE) {

ch= 0;

flg= TTY_OVERRUN;

}

}

*tty->flip.flag_buf_ptr++= flg;

*tty->flip.char_buf_ptr++= ch;

tty->flip.count++;

}

#endif



首先err= UART_UERSTAT(port) & UART_ERR_MASK确定了err的值,err的值是从是从UARTError StatusRegister读到的,erstate只用了四位,所以用UART_ERR_MASK把高四位掩掉,然后测试低四位中哪个位被置1,从而判断错误种类UERSTAT_BRK/FRAME/PARITY/OVERRUN分别代表1000/0100/0010/0001,判断出错误种类再进行相应的中断计数,然后再根据不同的errflg设上不同的值,

#defineTTY_NORMAL 0

#defineTTY_BREAK 1

#defineTTY_FRAME 2

#defineTTY_PARITY 3

#defineTTY_OVERRUN 4



3.9初始化函数uart_startup

staticint s3c2410uart_startup(struct uart_port *port, struct uart_info*info)

{

intret, flags;

u_intucon;

ret= request_irq(RX_IRQ(port), s3c2410uart_rx_interrupt, SA_INTERRUPT,

"serial_s3c2410_rx",info);

if(ret) goto rx_failed;

ret= request_irq(TX_IRQ(port), s3c2410uart_tx_interrupt, SA_INTERRUPT,

"serial_s3c2410_tx",info);

if(ret) goto tx_failed;

#ifdefCONFIG_USE_ERR_IRQ

ret= request_irq(ERR_IRQ(port), s3c2410uart_err_interrupt,SA_INTERRUPT,

"serial_s3c2410_err",info);

if(ret) goto err_failed;

#endif

ucon= (UCON_TX_INT_LVL | UCON_RX_INT_LVL |

UCON_TX_INT| UCON_RX_INT | UCON_RX_TIMEOUT);

#ifdefined(CONFIG_IRDA) || defined(CONFIG_IRDA_MODULE)

ULCON2|= ULCON_IR | ULCON_PAR_NONE | ULCON_WL8 | ULCON_ONE_STOP;

#endif


save_flags(flags);

cli();

UART_UCON(port)= ucon;

sti();

restore_flags(flags);

return0;

#ifdefCONFIG_USE_ERR_IRQ

err_failed:

free_irq(TX_IRQ(port),info);

#endif

tx_failed:

free_irq(RX_IRQ(port),info);

rx_failed:

returnret;

}

如果使用了函数open(ttyS0),那么最后调用的实现open功能的就是这个函数,它打开ttyS0

1:利用request_irq()申请发送,接收,错误三个中断,如果失败,就要释放调已经申请的全部资源

2:设置UARTControl Register

3.10函数uart_change_speed

staticvoid s3c2410uart_change_speed(struct uart_port *port, u_int cflag,u_int iflag, u_int quot)

{

u_intulcon, ufcon;


intflags;

ufcon= UART_UFCON(port);

switch(cflag & CSIZE) {

caseCS5: ulcon = ULCON_WL5; break;

caseCS6: ulcon = ULCON_WL6; break;

caseCS7: ulcon = ULCON_WL7; break;

default:ulcon = ULCON_WL8; break;

}

if(cflag & CSTOPB)

ulcon|= ULCON_STOP;

if(cflag & PARENB) {

if(!(cflag & PARODD))

ulcon|= ULCON_PAR_EVEN;

}

if(port->fifosize > 1)

ufcon|= UFCON_FIFO_EN;

port->read_status_mask= UERSTAT_OVERRUN;

if(iflag & INPCK)

port->read_status_mask|= UERSTAT_PARITY | UERSTAT_FRAME;

port->ignore_status_mask= 0;

if(iflag & IGNPAR)

port->ignore_status_mask|= UERSTAT_FRAME | UERSTAT_PARITY;

if(iflag & IGNBRK) {

if(iflag & IGNPAR)

port->ignore_status_mask|= UERSTAT_OVERRUN;

}

quot-= 1;

save_flags(flags);

cli();

UART_UFCON(port)= ufcon;

UART_ULCON(port)= (UART_ULCON(port) & ~(ULCON_PAR | ULCON_WL)) | ulcon;

UART_UBRDIV(port)= quot;

sti();

restore_flags(flags);


}

1:

UBRDIVn=(int)(CLK/(bps*16))-1

quot=(CLK/ (baudrate x 16) ) (CLKPCLKUCLK,baudrate的单位是bps


(1):首先看一下cflagcs位,同CS5/6/7比较,然后设置ulcon,接下来的几个if也是将ulcon根据cflag的设置进行一下设置,设置了停止位,校验位。

(2):如果port中设置了fifosize,就把UFCON(物理地址0x50000008)的第0位设为1


4.控制台

4.1注册控制台

void__init s3c2410_console_init(void)

{

register_console(&s3c2410_cons);

}


staticstruct console s3c2410_cons = {

name: "ttyS",

write: s3c2410_console_write,

device: s3c2410_console_device,

wait_key: s3c2410_console_wait_key,

setup: s3c2410_console_setup,

flags: CON_PRINTBUFFER,

index: -1,

};

4.2函数console_write

staticvoid s3c2410_console_write(struct console *co, const char *s, u_intcount)

{

inti;

structuart_port *port = s3c2410_ports + co->index;

for(i = 0; i < count; i++) {

while(!(UART_UTRSTAT(port) & UTRSTAT_TX_EMP));

UART_UTXH(port)= s[i];

if(s[i] == '\n') {

while(!(UART_UTRSTAT(port) & UTRSTAT_TX_EMP));

UART_UTXH(port)= '\r';

}

}

}

通过串口往外发送数据

for循环count次,每次发送一个字符,当发送缓冲寄存器为空时,就往里写一个字符,如果写的数据是回车加换行,就要再写一个换行符







4.3函数console_waitkey

staticint s3c2410_console_wait_key(struct console *co)

{

intc;

structuart_port *port = s3c2410_ports + co->index;

while(!(UART_UTRSTAT(port) & UTRSTAT_RX_RDY));

c= UART_URXH(port);


returnc;

}

该函数在while循环中等待接收数据,一旦接收缓冲器中有有效数据,该函数立即返回,返回值为接收到的一个字符

4.4函数console_device

statickdev_t s3c2410_console_device(struct console *co)

{

returnMKDEV(SERIAL_S3C2410_MAJOR, MINOR_START + co->index);

}

通过主,次设备号返回kdev_t结构

4.5设置函数console_setup

staticint __init s3c2410_console_setup(struct console *co, char *options)

{

structuart_port *port;

intbaud = 115200;

intbits = 8;

intparity = 'n';

intflow = 'n';

port= uart_get_console(s3c2410_ports, UART_NR, co);

if(options)

uart_parse_options(options,&baud, &parity, &bits, &flow);

returnuart_set_options(port, co, baud, parity, bits, flow);

}

这个函数就是设置控制台(console)的状态,里面主要有三个函数

(1)uart_get_console(struct uart_port *ports, int nr, struct console *co)

该函数检查是否co->index是一个无效的indexnumber,返回指向该indexnumber对应的uart_portstruct的指针

(2)uart_parse_options(options, &baud, &parity, &bits, &flow)

如果option被设置了,那么就要调用该函数,该函数解析options,options来自上一层函数的参数,它是一个字符串,应该包含baudrate,parity,bits,flow这些信息。

(3)uart_set_options(port, co, baud, parity, bits, flow)

针对以上给定的信息,consolecflag进行设置.还要调用一下ops中的change_speedbaudrate进行实际的设置(物理),成功地话return0



0 0
原创粉丝点击