串口分析
更新时间:2024-01-30 08:21:01 阅读量: 教育文库 文档下载
串口驱动分析
(国嵌)
1.发送和接收
发送:循环buffer ?发送fifo?发送移位寄存器 发送fifo在串口芯片中,16字节一个硬件缓冲. 循环buffer在驱动程序里实现,保存数据. 循环buffer ?发送fifo由驱动来完成. 发送fifo?发送移位寄存器由硬件来完成. 接收:接收移位寄存器?接收fifo ?Flip_buf
发送的过程是:把数据写到发送fifo中,fifo把收到的数据传给发送移位寄存器(自动的,非driver控制),然后每个时钟脉冲往串口线上写一bit数据。
接收的过程是:接收移位寄存器收到数据,发送给接收fifo,接收fifo事先设置好了触发门限,当里面的数据量超过门限时就会触发一个中断,调用驱动中的中断处理函数,把数据写到flip_buf中。
2.寄存器
UART Line Control Register:
Word Length :数据位长度 Number of Stop Bit :停止位数 Parity Mode :奇偶校验位类型
Infra-Red Mode :UART/红外模式选择(当以UART模式工作时,需设为“0”)
UART Control Register
Receive Mode:选择接收模式。如果是采用DMA模式的话,还需要指定说使用的DMA信道。 Transmit Mode :同上。
Send Break Signal :选择是否在传1帧资料中途发送Break信号。 Loopback Mode :选择是否将UART置于Loopback测试模式。 Rx Error Status Interrupt Enable :选择是否使能当发生接收异常时,是否产生接收错误中断。
Rx Time Out Enable :是否使能接收超时中断。 Rx Interrupt Type :选择接收中断类型。
选择0:Pulse(脉冲式/边沿式中断。非FIFO模式时,一旦接收缓冲区中有数据,即产生一个中断;为FIFO模式时,一旦当FIFO中的资料达到一定的触发水平后,即产生一个中断)
选择1:Level(电平模式中断。非FIFO模式时,只要接收缓冲区中有数据,即产生中断;为FIFO模式时,只有FIFO中的资料达到触发水平后,即产生中断)
Tx Interrupt Type :类同于Rx Interrupt Type
UART FIFO Conrtol Register
FIFO Enable :FIFO使能选择。
Rx FIFO Reset :选择当复位接收FIFO时是否自动清除FIFO中的内容。
Tx FIFO Reset :选择当复位发送FIFO时是否自动清除FIFO中的内容。
Rx FIFO Trigger Level :选择接收FIFO的触发水平。 Tx FIFO Trigger Level :选择发送FIFO的触发水平。
UART TX/RX Status Register
Receive buffer data ready :当接收缓冲寄存器从UART接收端口接收到有效资料时将自动置“1”。反之为“0则表示缓冲器中没有资料。 Transmit buffer empty :当发送缓冲寄存器中为空,自动置“1”;反之表明缓冲器中正有资料等待发送。
Transmitter empty :当发送缓冲器中已经没有有效资料时,自动置“1”;反之表明尚有资料未发送。
UART FIFO Status Register
Rx FIFO Count :接收FIFO中当前存放的字节数。 Tx FIFO Count :发送FIFO中当前存放的字节数。 Rx FIFO Full :为“1“表明接收FIFO已满。 Tx FIFO Full :为“1“表明发送FIFO已满。
3.函数介绍2.4代码
模块初始化函数:
static int __init s3c2410uart_init(void) {
return uart_register_driver(&s3c2410_reg);//在注册前需要
实现uart_driver s3c2410_reg结构 }
使用uart_register_driver注册串口驱动。
static struct uart_driver s3c2410_reg = {
owner:
THIS_MODULE,
SERIAL_S3C2410_MAJOR, \\&normal,
CALLOUT_S3C2410_MAJOR, &callout, s3c2410_table,
s3c2410_termios_locked, MINOR_START, UART_NR,
s3c2410_ports,//重要成员
normal_major: normal_name: callout_name: normal_driver: callout_major: callout_driver: table:
termios: s3c2410_termios, termios_locked: minor: nr: port:
};
2.4与2.6区别在于port是uart_driver的成员 2.6中使用uart_add_one_port()实现关联 cons:
S3C2410_CONSOLE,
static struct uart_port { },
iobase: iotype: irq: uartclk:
s3c2410_ports[UART_NR] = { (unsigned long)(UART0_CTL_BASE), 物理基地址
SERIAL_IO_PORT,//类型io的 IRQ_RXD0,
130252800,//时钟
fifosize: 16,//fifo大小 ops: type: flags:
&s3c2410_pops,实现操作 PORT_S3C2410,
ASYNC_BOOT_AUTOCONF,
。。。。。。 。。。。。。。 。。。。。。。 };
static struct uart_ops s3c2410_pops = { tx_empty:
s3c2410uart_tx_empty, s3c2410uart_set_mctrl, s3c2410uart_get_mctrl, s3c2410uart_stop_tx, s3c2410uart_start_tx,
set_mctrl: get_mctrl: stop_tx: start_tx:
};
stop_rx: enable_ms: break_ctl: startup: shutdown: change_speed: type:
config_port: release_port: request_port:
s3c2410uart_stop_rx, s3c2410uart_enable_ms, s3c2410uart_break_ctl, s3c2410uart_startup,
s3c2410uart_shutdown,
s3c2410uart_change_speed, s3c2410uart_type, s3c2410uart_config_port, s3c2410uart_release_port, s3c2410uart_request_port,
3.1 阻止或停止发送函数uart_stop_tx
static void s3c2410uart_stop_tx(struct uart_port *port, u_int from_tty) {
disable_irq(TX_IRQ(port)); }
停止发送的功能,其内部的函数disable_irq是停止中断的功能 ,发送数据是通过中断来完成的,关闭中断也就关闭了发送。
3.2 发送使能函数uart_start_tx
static void s3c2410uart_start_tx(struct uart_port *port, u_int nonempty, u_int from_tty) {
enable_irq(TX_IRQ(port)); }
与上面的过程类似,就是一个相反的过程
3.3 阻止接收函数uart_stop_rx
static void s3c2410uart_stop_rx(struct uart_port *port) {
disable_irq(RX_IRQ(port)); }
3.4 发送缓冲空判断函数uart_tx_empty
static u_int s3c2410uart_tx_empty(struct uart_port *port) {
return (UART_UTRSTAT(port) & UTRSTAT_TR_EMP ? 0 : TIOCSER_TEMT); }
//直接访问寄存器UART_UTRSTAT(port),在此寄存器中有一位专门判断寄存器是否为空
如果发送缓冲为空则返回0,否则返回1。
3.5 获取控制信息函数uart_get_mctrl
static u_int s3c2410uart_get_mctrl(struct uart_port *port) {
return (TIOCM_CTS | TIOCM_DSR | TIOCM_CAR); }
获得控制信息, TIOCM_CTS ,TIOCM_DSR 和TIOCM_CAR,这几个宏代表串口的控制信息, 分别是clear to send,data set ready和data carrier detect(详见Serial Programming Guide for POSIX Operating Systems)
接收中断和发送中断使用不同的中断号 读走一个字节,就会又进来一个字节 3.6 接收中断函数uart_rx_interrupt
static void s3c2410uart_rx_interrupt(int irq, void *dev_id, struct pt_regs *regs) {
struct uart_info *info = dev_id; struct tty_struct *tty = info->tty;
unsigned int status, ch, max_count = 256; struct uart_port *port = info->port;
status = UART_UTRSTAT(port);
while ((status & UTRSTAT_RX_RDY) && max_count--)
//while中代码每次处理1字节,太浪费资源.为了在一次中断里多处理数据.首先status & UTRSTAT_RX_RDY要有数据处理, max_count—是最大处理次数,本例中max_count为256.最多一次处理256个字节 {
if (tty->flip.count >= TTY_FLIPBUF_SIZE) // 已有数据量大于等于buf容量 {
tty->flip.tqueue.routine((void *) tty); //交换2个buf
if (tty->flip.count >= TTY_FLIPBUF_SIZE) //还是大于buf容量 {
printk(KERN_WARNING \表示处理不了退出
return; } }
//有空间存放数据
ch = UART_URXH(port); //从寄存器读出一字节 *tty->flip.char_buf_ptr = ch; //存到flip_buffer中 *tty->flip.flag_buf_ptr = TTY_NORMAL;
port->icount.rx++; //修改相应的统计计数,比如count tty->flip.flag_buf_ptr++; tty->flip.char_buf_ptr++; tty->flip.count++;
status = UART_UTRSTAT(port); //处理后又去读寄存器,看是否有数据 如果还有数据,而且还没到256次,回while继续处理 }
tty_flip_buffer_push(tty); return; }
功能:主要是是while大循环,首先看循环判断条件status & UTRSTAT_RX_RDY,前面有status = UART_UTRSTAT(port),查2410的datasheet, status & UTRSTAT_RX_RDY这个位是判断接收buffer内是否还有有效数据?按道理一次中断只是把接收的fifobuffer中的数据放到flipbuffer中去,接收的fifo的中断门限是4-12字节,进行一次接收往往要中断好多次,这样中断开销比较大,所以在while的循环条件中判断一下是否还有接收的有效数据,如果有,就继续在中断程序中继续接收,当然,永远都在接收中断中(如果一直有数据要接收)也不合适,所以while循环还有计数,最多循环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
static void s3c2410uart_tx_interrupt(int irq, void *dev_id, struct pt_regs *reg) {
struct uart_info *info = dev_id; struct uart_port *port = info->port; int count;
if (port->x_char) { //首先判断x_char是否是禁止位 UART_UTXH(port) = port->x_char; port->icount.tx++; port->x_char = 0;
return; } //如果 x_char是停止位返回
if (info->xmit.head == info->xmit.tail //如果不是停止位,判断循环buf 头与尾是否相同,相同说明buff为空
|| info->tty->stopped || info->tty->hw_stopped) {
s3c2410uart_stop_tx(info->port, 0); //当没有数据要发送关中断 return; }
count = port->fifosize >> 1; //fifo是16长 右移一次除2为8 do {
UART_UTXH(port) = info->xmit.buf[info->xmit.tail]; // info->xmit.buf内核实现好的循环buf,用于存放数据. 把循环buf的内容进入reg
info->xmit.tail = (info->xmit.tail + 1) & (UART_XMIT_SIZE - 1); //修改循环buf的尾巴
port->icount.tx++; //增加发送的统计计数
if (info->xmit.head == info->xmit.tail) //判断是否发完 break;
} while (--count > 0); //最多8次
if (CIRC_CNT(info->xmit.head, info->xmit.tail, //CIRC_CNT查看剩余数据 如果低于设定值会调用uart_event通知上层 ttycore 有空间可以发数据
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) 首先查看port中的x_char是不是为0,不为0则把x_char发送出去。x_char是xon/xoff的意思,每发一个字节时在开始前先发xon信号,在结束时发xoff。
(2) 如果x_char没有被设置,再看环形缓冲区是否为空,或者info->tty->stopped 和 info->tty->hw_stopped 两个位是不是为1,如果这些条件成立的话,就停止发送。Tty->stop指示tty设备是否停止,tty->hw_stop指示tty设备的硬件是否停止了,以上两个位都可以通过ttydriver来设定,否则的话说明有数据要发送。
(3) 如果以上条件都通过了,就利用一个while循环正式发送数据了,从环形缓冲尾巴上取一个数赋给UART_UTXH(port)(发送FIFO), UART_UTXH(port) = info->xmit.buf[info->xmit.tail],这条语句就是把数据送到发送FIFO中,然后计数++,循环一共进行fifosize/2次,也就是一次只能发送8 byte。
(4)循环传送完一次后,再查看缓冲器里还剩余多少数据,如果少于WAKEUP_CHARS(256)的话,就执行uart_event(info, 0),告诉TTY核心,可以接受更多数据了。这里可以看出,tty_driver和tty_core之间的层次,tty_driver可以知道缓冲空还是满,但是它没有权力让发送数据过来,它只能是通知tty_core,让它来处理。
(5) 最后再察看一下环形寄存器,如果serial core 没有发送来更多的数据,就关闭发送。
3.8 出错中断函数uart_err_interrupt
static void s3c2410uart_err_interrupt(int irq, void *dev_id, struct pt_regs *reg) {
struct uart_info *info = dev_id;
struct uart_port *port =
struct tty_struct *tty = info->tty;
unsigned char err = UART_UERSTAT(port) & UART_ERR_MASK; unsigned int 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;
else if (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
info->port;
首先err = UART_UERSTAT(port) & UART_ERR_MASK确定了err的值,err的值是从是从UART Error Status Register读到的,该erstate只用了四位,所以用UART_ERR_MASK把高四位掩掉,然后测试低四位中哪个位被置1了,从而判断错误种类
UERSTAT_BRK/FRAME/PARITY/OVERRUN 分别代表
1000/0100/0010/0001 ,判断出错误种类再进行相应的中断计数,然后再根据不同的err给flg设上不同的值,有
#define TTY_NORMAL 0 #define TTY_BREAK 1 #define TTY_FRAME 2 #define TTY_PARITY 3 #define TTY_OVERRUN 4
3.9 初始化函数uart_startup
static int s3c2410uart_startup(struct uart_port *port, struct uart_info *info) {
int ret, flags; u_int ucon;
ret = request_irq(RX_IRQ(port), s3c2410uart_rx_interrupt, SA_INTERRUPT,
\if (ret) goto rx_failed;
ret = request_irq(TX_IRQ(port), s3c2410uart_tx_interrupt, SA_INTERRUPT,
\if (ret) goto tx_failed;
#ifdef CONFIG_USE_ERR_IRQ
ret = request_irq(ERR_IRQ(port), s3c2410uart_err_interrupt, SA_INTERRUPT,
\if (ret) goto err_failed; #endif
ucon = (UCON_TX_INT_LVL | UCON_RX_INT_LVL |
UCON_TX_INT | UCON_RX_INT | UCON_RX_TIMEOUT);
#if defined(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); return 0;
#ifdef CONFIG_USE_ERR_IRQ err_failed:
free_irq(TX_IRQ(port), info); #endif tx_failed:
free_irq(RX_IRQ(port), info); rx_failed: return ret; }
如果使用了函数open(ttyS0),那么最后调用的实现open功能的就是这个函数,它打开ttyS0。
1:利用request_irq()申请发送,接收,错误三个中断,如果失败,就要释放调已经申请的全部资源
2:设置UART Control Register
3.10 函数uart_change_speed
static void s3c2410uart_change_speed(struct uart_port *port, u_int cflag, u_int iflag, u_int quot) {
u_int ulcon, ufcon;
int flags;
ufcon = UART_UFCON(port); switch (cflag & CSIZE) {
case CS5: ulcon = ULCON_WL5; break; case CS6: ulcon = ULCON_WL6; break; case CS7: 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) ) (CLK为PCLK或UCLK,baudrate的单位是bps
(1):首先看一下cflag的cs位,同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); }
static struct console s3c2410_cons = { name: \ 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
static void s3c2410_console_write(struct console *co, const char *s, u_int count) { int i;
struct uart_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
static int s3c2410_console_wait_key(struct console *co) { int c;
struct uart_port *port = s3c2410_ports + co->index;
while (!(UART_UTRSTAT(port) & UTRSTAT_RX_RDY)); c = UART_URXH(port);
return c; }
该函数在while循环中等待接收数据,一旦接收缓冲器中有有效数据,该函数立即返回,返回值为接收到的一个字符
4.4 函数console_device
static kdev_t s3c2410_console_device(struct console *co) {
return MKDEV(SERIAL_S3C2410_MAJOR, MINOR_START + co->index); }
通过主,次设备号返回kdev_t结构
4.5 设置函数console_setup
static int __init s3c2410_console_setup(struct console *co, char *options) {
struct uart_port *port; int baud = 115200; int bits = 8; int parity = 'n'; int flow = 'n';
port = uart_get_console(s3c2410_ports, UART_NR, co);
if (options)
uart_parse_options(options, &baud, &parity, &bits, &flow); return uart_set_options(port, co, baud, parity, bits, flow); }
这个函数就是设置控制台(console)的状态,里面主要有三个函数 (1)uart_get_console (struct uart_port *ports, int nr, struct console *co) 该函数检查是否co->index是一个无效的index number,返回指向该index number 对应的uart_port struct的指针
(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)
针对以上给定的信息,对console的cflag进行设置.还要调用一下ops中的change_speed对baud rate进行实际的设置(物理),成功地话return 0
正在阅读:
串口分析01-30
一个什么的过程作文500字06-15
关于母亲节的句子11-21
三年级数学教学计划 - 图文03-30
2016下半年宁夏教师资格面试:解答结构化中逻辑思维类问题06-08
2012届高三英语第二轮复习考点精讲精炼 第3讲 单项选择 - 代词和数词10-16
汉书食货志读后感11-26
专业学位硕士研究生培养方案汇编(最新)3.8.2latest - 图文05-30
种植园日记400字10-29
- exercise2
- 铅锌矿详查地质设计 - 图文
- 厨余垃圾、餐厨垃圾堆肥系统设计方案
- 陈明珠开题报告
- 化工原理精选例题
- 政府形象宣传册营销案例
- 小学一至三年级语文阅读专项练习题
- 2014.民诉 期末考试 复习题
- 巅峰智业 - 做好顶层设计对建设城市的重要意义
- (三起)冀教版三年级英语上册Unit4 Lesson24练习题及答案
- 2017年实心轮胎现状及发展趋势分析(目录)
- 基于GIS的农用地定级技术研究定稿
- 2017-2022年中国医疗保健市场调查与市场前景预测报告(目录) - 图文
- 作业
- OFDM技术仿真(MATLAB代码) - 图文
- Android工程师笔试题及答案
- 生命密码联合密码
- 空间地上权若干法律问题探究
- 江苏学业水平测试《机械基础》模拟试题
- 选课走班实施方案
- 串口
- 分析
- TC367全国数控系统标准化技术委员会及标准情况
- 河北省物价局冀价经费字6号文件
- 《高速铁路无砟轨道施工测量暂行标准》报批稿1
- 南京华为二期安全专项施工方案 - 图文
- 故意伤害案件中被害人过错责任对定罪量刑的影响
- 面向对象程序设计期末复习
- 动车组运用检修设施建设及
- 占豪 与一格的交流
- 辅导员技能大赛案例分析的原则与技巧
- 询标纪要
- 四川大学继续教育教育心理学考试试题
- 《普通物理》作业集(完成)
- 1 两化融合管理手册 - 图文
- 南开2012校长开学典礼讲话稿
- 安徽省芜湖一中2013-2014学年高一自主招生考试物理试题
- 综合管廊支架预埋件施工方案 - 图文
- 中国暴富群体的原罪真相 - 图文
- 《山东省燃气管理条例》
- 第九讲 中华各民族文化的交流融合
- 狼牙山五壮士优秀教案设计