linux UART串口驱动开发文档

linux UART串口驱动开发文档
linux UART串口驱动开发文档

linux UART串口驱动开发文档

w83697/w83977 super I/O串口驱动开发

内容简介: 介绍了Linux下的串口驱动的设计层次及接口, 并指出串口与TTY终端之间的关联层次(串口可作TTY终端使用), 以及Linux下的中断处理机制/中断共享机制, 还有串口缓冲机制当中涉及的软中断机制; 其中有关w83697/w83977 IC方面的知识, 具体参考相关手册, 对串口的配置寄存器有详细介绍, 本文不再进行说明.

目录索引:

一. Linux的串口接口及层次.

二. Linux的中断机制及中断共享机制.

三. Linux的软中断机制.

四. TTY与串口的具体关联.

一. Linux的串口接口及层次.

串口是使用已经非常广的设备了, 因此在linux下面的支持已经很完善了, 具有统一的编程接口, 驱动开发者所要完整的工作就是针对不同的串口IC来做完成相应的配置宏, 这此配置宏包括读与写, 中断打开与关闭(如传送与接收中断), 接收状态处理, 有FIFO时还要处理FIFO的状态. 如下我们就首先切入这一部分, 具体了解一下与硬件串口IC相关的部分在驱动中的处理, 这一部分可以说是串口驱动中的最基础部分, 直接与硬件打交道, 完成最底层具体的串口数据传输.

1. 串口硬件资源的处理.

W83697及W83977在ep93xx板子上的映射的硬件物理空间如下:

W83697: 0x20000000起1K空间.

W83977: 0x30000000起1K空间.

因为串口设备的特殊性, 可以当作终端使用, 但是终端的使用在内核还未完全初始化之前(关于串口与终端的关联及层次在第四节中详细), 此时还没有通过mem_init()建立内核的虚存管理机制, 所以不能通过ioreamp来进行物理内存到虚存的映射(物理内存必须由内核映射成系统管理的虚拟内存后才能进行读写访问), 这与先前所讲的framebuffer的物理内存映射是不同的, 具体原因如下:

√终端在注册并使用的调用路径如下:

start_kernel→console_init→uart_console_init→ep93xxuart_console_init→register_conso

le→csambuart_console_write.

√FrameBuffer显卡驱动中的物理内存映射调用路径如下:

start_kern el→ rest_init→init(内核初始线程)→ do_basic_setup→ do_initcalls→fbmem_in it→lanrryfb_init

(Linux下用__setup启动初期初始机制与__initcall系统初始化完成后的调用机制,这两个机制本质没有什么差别,主要是执行时所处的系统时段)

√串口物理内存映射到虚存的时机:

依据上面所介绍的两条执行路径,再看内核的内存初始化的调用时期,只有完成这个初始化后才能进行物理内存到虚存的映射,内存的初始化主要是在start_kernel中调用的mem_ini t,这个调用明显在uart_console_init之后,在fbmem_init之后,到此就全部说明了为何不能在对串口使用ioremap进行物理内存的映射了。那么究竟要在什么时机用什么方法进行串口物理内存的映射呢?

√串口物理内存的映射方式:

参考ep93xx的板载I/O的映射处理,它的处理方式是一次性将所有的物理I/O所在的内存空间映射到虚存空间,映射的基址是IO_BASE_VIRT,大小是IO_SIZE.

/* Where in virtual memory the IO devices (timers, system controllers

* and so on). This gets used in arch/arm/mach-ep93xx/mm.c.*/

#define IO_BASE_VIRT 0xFF000000 // Virtual address of IO

#define IO_BASE_PHYS 0x80000000 // Physical address of IO

#define IO_SIZE 0x00A00000 // How much?

完成映射的函数是ep93xx_map_io, 所有要进行映射内存都在ep93xx_io_desc结构当中描述,我们的串口映射也加在这个地方,基址分别如下:

文件: linux-2.4.21/include/asm-arm/arch-ep93xx/regmap.h

#define IO_W83697_UART_BASE 0x20000000

#define IO_W83697_UART_SIZE 0x1000

#define IO_W83977_UART_BASE 0x30000000

#define IO_W83977_UART_SIZE 0x1000

#define IO_SIZE_2 (IO_SIZE+0x100000)

#define IO_BASE83697_VIRT IO_BASE_VIRT+IO_SIZE

#define IO_BASE83977_VIRT IO_BASE_VIRT+IO_SIZE_2

ep93xx_map_io完成是在arch初始化中赋值给struct machine_desc mdesc这个机器描述结构体,主要由位于mach-ep93xxarch.c文件中如下宏完成此结构的初始化:

MACHINE_START(EDB9302, "edb9302")

…..

MAPIO(ep93xx_map_io) //初始化. map_io= ep93xx_map_io….

MACHINE_END

最终这个函数在调用路径如下:

start_kernel→setup_arch→paging_init→(mdesc->map_io())

至此完成串口物理内存的映射,这个过程在console_init调用之前,因此不会有问题, 此种方法建立映射是直接创建物理内存页与虚存页的对应,大小为4k一页,最终调用的是crea te_mapping(),建立页表映射是与具体的平台相关的,位于mach_ep93xx/mm/ proc-arm9 20.S文件中提供了与具体平台相关的页表建立函数,其中包括TLB表操作/Cache操作/页表操作等:

在上层的start_kernel→setup_arch→ setup_processor调用下,会在proc-arm920.S文件中查找"https://www.360docs.net/doc/0016181179.html,"节的__arm920_proc_info,并从中找到配置的process相关的操作函数,具体的arm页表建立的详情须要参看ARM内存管理的相关手册.

.section "https://www.360docs.net/doc/0016181179.html,", #alloc, #execinstr

.type __arm920_proc_info,#object

__arm920_proc_info:

.long 0x41009200

……

.long arm920_processor_functions

.size __arm920_proc_info, . - __arm920_proc_info

在arm920_processor_functions中包含的页表操作如下:

/* pgtable */

.word cpu_arm920_set_pgd

.word cpu_arm920_set_pmd

.word cpu_arm920_set_pte

2. 与串口硬件相关的宏主.

如下, 下面将详术如下, 并指出其具体被使用的环境上下文:

<1>. 读写数据.

#define UART_GET_CHAR(p) ((readb((p)->membase + W83697_UARTDR)) & 0xff) #define UART_PUT_CHAR(p, c) writeb((c), (p)->membase + W83697_UARTDR)

<2>. 接收发送状态.

#define UART_GET_RSR(p) ((readb((p)->membase + W83697_UARTRSR)) & 0xff) #define UART_PUT_RSR(p, c) writeb((c), (p)->membase + W83697_UARTRSR)

<3>. 发送及接收中断状态.

#define UART_GET_CR(p) ((readb((p)->membase + W83697_UARTCR)) & 0xff)

#define UART_PUT_CR(p,c) writeb((c), (p)->membase + W83697_UARTCR)

#define UART_GET_INT_STATUS(p) ((readb((p)->membase + W83697_UARTIIR)) & 0xff)

<4>. 以及其它的中断使能设置等, 在传送时打开传送中断即会产生传送中断.

#define UART_PUT_ICR(p, c) writeb((c), (p)->membase + W83697_UARTICR)

<5>. FIFO的状态, 是否读空/是否写满; 每次读时必须读至FIFO空, 写时必须等到FIFO 不满时才能写(要等硬件传送完) .

接收中断读空FIFO的判断:

status = UART_GET_FR(port);

while (UART_RX_DATA(status) && max_count--) {

……

}

发送中断写FIFO: 当发送缓冲区中有数据要传送时, 置发送中断使能, 随后即产生传送中断, 此时FIFO为空, 传送半个FIFO大小的字节, 如果发送缓冲区数据传完,则关闭发送中断.

<6>. 传送时可直接写串口数据口, 而不使用中断, 但必须等待检测FIFO的状态

do {

status = UART_GET_FR(port);

} while (!UART_TX_READY(status)); //wait for tx buffer not full...

3. 串口驱动的参数配置

串口的参数主要包括如下几个参数,全部都记录在uart_port结构上,为静态的赋值,本串口驱动支持6个设备,所以驱动中就包括了6个port,一个串口对应一个port口,他们之间除了对应的中断号/寄存器起始基址/次设备号不同之外,其它的参数基本相同.

√串口对应中断, 这里六个串口,其中有3个串口使用的系统外部中断0/1/2, 其中另外几个中断用提GPIO中断,具体有关GPIO中断的内容可参见EP93XX芯片手册, GPIO中断共享一个系统中断向量,涉及中断共享的问题,后面将详述LINUX中的中断共享支持.

√串口时钟, 串口时钟用来转换计算须要设置到配置寄存器当中的波特率比值,其计算方法为:quot = (port->uartclk / (16 * baud));baud为当前设置的波特率,可为115200等值,取决于所选的串口时钟源, quot即为要设置到寄存器当中的比值.

√串口基址, 即串口所有配置寄存器基础址.

√串口次设备号(由驱动中的最低次设备号依次累加)

前面已经讲过了六个串口中断,这里详细列出对应情况如下,方便查找:

w83697的三个串口对应中断如下:

?uart 1: 读写数据寄存器偏移为00x3F8, 对应系统外部中断INT_EXT[0].

?uart 2: 读写数据寄存器偏移为00x2F8, 对应系统外部中断INT_EXT[1].

?uart 3: 读写数据寄存器偏移为00x3e8, 对应系统外部中断INT_EXT[2].

?uart 4: 读写数据寄存器偏移为00x3e8, 对应EGPIO[8].

w83977的两个串口对应中断如下:

?uart 1: 读写数据寄存器偏移为00x3F8, 对应EGPIO[1].

?uart 2: 读写数据寄存器偏移为00x2F8, 对应EGPIO[2].

下面列出其中一个具体的串口port的定义如下:

{

.port = {

.membase = (void *)W83697_UART4_BASE,

.mapbase = W83697_UART4_BASE,

.iotype = SERIAL_IO_MEM,

.irq = W83697_IRQ_UART4, //串口中断号

.uartclk = 1846100, //uart时钟,默认.

.fifosize = 8, //硬件fifo大小.

.ops = &amba_pops, //底层驱动的硬件操作集,如开关中断等.

.flags = ASYNC_BOOT_AUTOCONF,

.line = 3, //串口在次设备数组中的索引号,须注意从0计起…

},

.dtr_mask = 0,

.rts_mask = 0,

}

4. 串口驱动的底层接口函数

驱动文件:linux-2.4.21/drivers/serial/Ep93xx_w83697.c

相关文件: linux-2.4.21/drivers/serial/core.c下面详述.

函数: w83697uart_rx_chars(struct uart_port *port, struct pt_regs *regs)

描述: 串口接收数据中断, 此函数中应当注意的要点如下:

?接收数据时,要注意判断FIFO是否读空(参见上述2点中说明).

?接收数据放入flip缓冲区,此缓冲区专供缓存中断中接收到的数据,是最原始的串口数据,为更上一层中各种终端处理模式的原始数据,可以进行各种加工处理。

?接收数据到flip缓冲区中时,须根据硬件接收状态,置每一个接收到的字符的接收标志,放在flag_buf_ptr当中, 标志类型有TTY_NORMAL/ TTY_PARITY/ TTY_F RAME等,分别表示正常/校验出错/帧出错(无停止位)等.

?每接收数据之后,会通过在退出中断前调用tty_flip_buffer_push()来往tq_timer任务列表中加一个队列任务,串口的队列任务主要是负责将中断接收到flip缓冲区中的数据往上传输至终端终冲区, 队列任务的机制将在后面介绍,它是一种异步执行机制,在软中断中触发执行.

函数: static void w83697uart_tx_chars(struct uart_port *port)

描述: 串口发送数据中断, 发送中断中要做的事比较少,比起接收中断简单了好多,注意事项如下:

?当上层要发送数据时,就会打开串口发送中断,此时FIFO为空,传送半个FIFO大小数据即退出, 通常打开中断是通过更上一层的uart_flush_chars()调用,最终调用的是w83697uart_start_tx().

?检测当没有数据要传输的时候,关闭传送中断,在传送之前与传送完之后都有检测.

?最重要的一点是如果传送缓冲区当中的字符数已经小于WAKEUP_CHARS, 则可以唤醒当前正在使用串口进行传送的进程,这里是通过tasklet机制来完成,这也是一异步执行机制.

顺带介绍开关中断接口:

static void w83697uart_start_tx(struct uart_port *port, unsigned int tty_start)

static void w83697uart_stop_tx(struct uart_port *port, unsigned int tty_stop)

函数: static void w83697uart_int(int irq, void *dev_id, struct pt_regs *regs)

描述: 中断处理函数,为3个使用系统外部中断的的串口的中断入口,其中必须处理的中断状态分为如下几种, 注意必须在处理中断时根据手册中的说明来清除中断,通常是读或写某些寄存器即可。

?接收中断.

?传送中断.

?FIFO超时中断.

?其它不具体处理的中断,必须读相应寄存器清中断.

函数: static void w83697uart_int2(int irq, void *dev_id, struct pt_regs *regs)

描述: 中断处理函数,为另外几个使用串口使用的GPIO中断入口,GPIO中断共享同一个系统中断向量, 必须根据GPIO的中断状态寄存器的相应位来判断对应的中断是属哪一个串口的,从而进行相应的处理,其实这个判断也是无所谓的,因为中断产生时传进来的参数已经含有了相应串口的参数, 在判断完中断产生的GPIO口后立即调用w83697uart_int2 完成具体的中断处理.

函数: static int w83697uart_startup(struct uart_port *port)

描述: 串口开启后的初始化函数,主要完成初始化配置,以及安装中断处理了函数,初始化配置包括打开中断使能标志。

函数: static void w83697uart_shutdown(struct uart_port *port)

描述: 串口关闭函数,清除配置,半闭中断.

函数: static void w83697uart_change_speed(struct uart_port *port, unsigned int cflag, unsigned int iflag, unsigned int quot)

描述: 配置函数,经由上次调用下来,主要配制串口的波特率比,以及各种容错处理,在串口打开初始化时会被调用,在必变串口波特率/校验方式/停止位/传送位数等参数时会被调用.

5. 串口驱动与上层的接口关联

文件: linux-2.4.21/drivers/serial/core.c

这一层接口是串口驱动中的共用部分代码, 核心结构为struct uart_driver. 这一层上承TTY 终端,下启串口底层,串口底层主要处理了与串口硬件相关的部分,并向上提供uart中间层向下的接口. Uart coar向下与底层驱动的接口,通过一个static struct uart_ops amba _pops结构完成? 这个结构直接赋值给串口struct uart_amba_port amba_ports 的.ops成员,最后将串口的port加入到uart_driver当中完成关联, 通过uart_add_one_port加入. static int __init w83697uart_init(void)

{

int ret, i;

ret = uart_register_driver(&amba_reg);

if (ret == 0) {

for (i = 0; i < UART_NR; i++)

uart_add_one_port(&amba_reg, &amba_ports[i].port);

}

return ret;

}

二. Linux的中断机制及中断共享机制.

前面讲到了有6个串口,除了w83697中的前三个串使用的是独立的系统外部中断之外,其它的在个串口是共享一个系统中断向量的,现在我们来看看多个中断是如何挂在一个系统中断向量表当中的,共享中断到底是什么样的一种机制?

进行分析代码可知,linux下的中断采用的是中断向量的方式,每一个中断对应一个中断描述数组当中的一项, 结构为struct irqdesc,其当中对应一成员结构为struct irqactionr 的成员action, 这个即表示此中断向量对应的中断处理动作,这里引用从网上下载的一幅图讲明中断向量表与中断动作之间的关系:

摘自IBM developerWorks 中国

struct irqaction {

void (*handler)(int, void *, struct pt_regs *);

unsigned long flags;

unsigned long mask;

const char *name;

void *dev_id;

struct irqaction *next;

};

从上面的结构体与图当中,我们就可以很清楚的看到,一个中断向量表可以对应一个irqact ion,也可能对应多个由链表链在一起的一个链表irqaction, 这当中主要在安装中断的时候通过中断的标志位来决定:

?安装中断处理,不可共享:

retval = request_irq(port->irq, w83697uart_int, 0, "w83697_uart3", port);

?安装中断处理,可共享:

retval = request_irq(port->irq, w83697uart_int2, SA_SHIRQ, "w83977_uart5",

port);

由上即可知,安装共享中断时,只须指定安装的中断标志位flag为SA_SHIRQ, 进入分析安装中断的处理可知,在安装时,会检测已经安装的中断是否支持共享中断,如果不支持,则新的中断安装动作失败;如果已经安装的中断支持共享中断,则还必须检测将要安装的新中断是否支持中断共享,如果不支持则安装还是会失败,如果支持则将此新的中断处理链接到此中断向量对应的中断动作处理链表当中.

在产生中断时,共享中断向量中对应的中断处理程序链表中的每一个都会被调用,依据链表的次序来,这样处理虽然会有影响到效率,但是一般情况下中断传到用户的中断处理服务程序中时,由用户根据硬件的状态来决定是否处理中断,所以能常情况下都是立即就返回了,效率的影响不会是大的问题.

三. Linux的软中断机制.

前面已经简单讲过了LINUX下的硬中断处理机制,其实硬中断的处理都由LINUX底层代码具体完成了,使用者一般在处理硬中断时是相当简单的,只须要用request_irq()简单的挂上中断即可,这里我们进一步介绍一下LINUX下的软中断机制,软中断机制相比起硬中断机制稍微复杂一些,而且在LINUX内核本身应用非常的广, 它作为一种软性的异步执行机制,只有深入理解了它才能灵活的运用.

之所以提到内核的softirq机制,主要是因为在串口中断也使用了这些机制,理解了这些机制就能更加明白串口驱动一些问题, 现在先提出几个问题如下:

?前面提供到中断接收后数据,先放到flip缓冲区当中,这样让人很容易进一步想知道,中断处理的缓冲区的数据,用户进程读取串口时如何读到的?很明显中断处于

内核空间,用户读取串口输入进程是在用户空间,中断缓冲区中的数据如何被处理到终端缓冲区中,供用户读取的?

另外写串口时,是向终端缓冲区当中写入,那么上层的写操作如何知道下层缓冲区中的的数据是否传送完成?用户空间的写串口进程处于什么样的状态?如果是写完缓冲区就睡眠以保证高效的CPU使用率,那么何时才应该醒过来?由谁负责醒过来?

1. 往tq_timer任务队列中添加一项任务.

根据以上这两个问题,我们来深入代码分析,首先看接收缓冲区中的数据如何上传, 前面已经提到过,接收中断处理完成后,会调用tty_flip_buffer_push(),这个函数完成的功能就是往一系统定义的任务队列当中加入一个任务,下面我们将详细的分析加入的任务最终是如何执行起来的.[任务:这里所讲的任务可以直接理解成为一个相应的回调函数,LINUX下术语称作tasklet]

void tty_flip_buffer_push(struct tty_struct *tty)

{

if (tty->low_latency)

flush_to_ldisc((void *) tty);

else

queue_task(&tty->flip.tqueue, &tq_timer);

}

2. tq_timer的执行路径分析.

tq_timer是一个双链表结构任务队列,每项任务包含一个函数指针成员, 它通过run_task_qu eue每次将当中的所有任务(其实是一些函数指针)全部调用一次,然后清空队列, 最终的执行tq_timer的是在中断底半的tqueue_bh 中执行,如下:

void tqueue_bh(void)

{

run_task_queue(&tq_timer);

}

在void __init sched_init(void)当中初始化底半的向量如, tqueue_bh初始化在bh_base的TIMER_BH位置,bh_base为一结构很简单的数组,在什么位置调用什么样的了函数基本已经形成默认的习惯:

init_bh(TIMER_BH, timer_bh);

init_bh(TQUEUE_BH, tqueue_bh);

init_bh(IMMEDIATE_BH, immediate_bh);

看看init_bh相当于初始底半的服务程序,非常简单:

void init_bh(int nr, void (*routine)(void))

{

bh_base[nr] = routine;

mb();

}

最终真正的执行bh_base中保存的函数指针的,在bh_action()当中:

static void bh_action(unsigned long nr)

{

if (bh_base[nr])

bh_base[nr]();

}

关于这里所指出的bh_base,我们在后面就直接称作bh,意即中断底半所做的事.

3. tq_timer实现所依赖的tasklet.

那么bh_action在什么时候执行呢?bh_action被初始化成bh_task_vec这32个tasklet调用的任务, 因此它的依赖机制是tasklet机制,后面将进行简单介绍.

void __init softirq_init()

{

int i;

for (i=0; i<32; i++)

tasklet_init(bh_task_vec+i, bh_action, i);

….

}

至此已经把任务队列的执行流程及原理分析完成,tasklet是须要激活的,这里我们先指出任务队列是如何激活的,在时钟中断的do_timer()当中会调用mark_bh(TIMER_BH), 来激时钟底半所依赖运行的tasklet,其中bh_task_vec的所有成员的函数指针全部指向bh_act

ion.

static inline void mark_bh(int nr)

{

tasklet_hi_schedule(bh_task_vec+nr);

}

tasklet_hi_schedule的功能就是往tasklet当中加入一个新的tasklet.

4. tasklet的机制简单分析.

讲到tasklet,我们才与我们真正要讲的softirq最近了,因为目前在软中断当中有主要的应用就是tasklet,而且在所有32个软中断中仅有限的几个软中断如下:

enum{

HI_SOFTIRQ=0,

NET_TX_SOFTIRQ,

NET_RX_SOFTIRQ,

TASKLET_SOFTIRQ

};

struct softirq_action{

void (*action)(struct softirq_action *);

void *data;

};

static struct softirq_action softirq_vec[32] __cacheline_aligned; //软中断的中断向量表,实为数组.

[1]. 初始化软中断向量.

我们这里所要讲的,就是HI_SOFTIRQ / TASKLET_SOFTIRQ 两项,据我理解这两项根本在实现机制上一样的,之所以分开两个名字叫主要是为了将不同的功能分开,就类似于虽然同是软中断,但是各处所完成的功能不一样,所以分在两个软中断完成, 后面我们仅取其中用于执行时钟底半的任务队列HI_SOFTIRQ为例进行讲解, 而且我们不讲及多个CPU情况下的tasklet相关机制, 这两项软中断的实始化如下:

void __init softirq_init()

{

….

open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);

open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);

}

open_softirq下所做的事相当简单, 即往软中断向量中赋值, 相当于硬中断当中的request_i rq挂硬件中断:

void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)

{

softirq_vec[nr].data = data;

softirq_vec[nr].action = action;

}

[2]. 软中断中断服务程序

对于HI_SOFTIRQ , 相应的中断服务程序为tasklet_hi_action , 由上文所讲的初始化过程给出,这个函数目前完成的功能相当简单,它的任务就是遍历执行此中断所对应一个taskl et链表,

NR_CPUS= 1.

struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned;

[3]. 往软中断对应的tasklet链表中加入新的tasklet, 加在尾部.

void __tasklet_hi_schedule(struct tasklet_struct *t)

{

t->next = tasklet_hi_vec[cpu].list;

tasklet_hi_vec[cpu].list = t;

cpu_raise_softirq(cpu, HI_SOFTIRQ);

}

最重要的一点是,在安装了新的tasklet后,还必须将软中断设置为激活,告诉系统有软中断须要执行了,下面一点即提到系统如何检测是否有软中断须要处理:

#define __cpu_raise_softirq(cpu, nr) do { softirq_pending(cpu) |= 1UL << (nr); } whil

e (0)

[4]. 软中断所依赖的执行机制.

讲到最后还没有指出软中断是如何触发执行的,其实很简单:

?在系统处理所有硬中断信号时,他们的入口是统一的,在这个入口函数当中除了执行do_IRQ()完成硬件中断的处理之外,还会执行do_softirq()来检测是否有软中断须要执行,所以软中断所依赖的是硬件中断机制;

?另外还有一个专门处理软中断内核线程ksoftirqd(),这个线程处理软中断级别是比较低的,他是一个无限LOOP不停的检测是否有软中断须要处理,如果没有则进行任务调度.

在do_softirq()中有如下的判断,以决定是否有软中断须要执行,如果没有就直接退出,在[3]中提到的激活软中断时,要将相应软中断位置1, 软中断有32个,因此一个整型数即可以表示32个软中断,即可判断有什么样的软中断须要处理,代码如下:

pending = softirq_pending(cpu);

if (pending) {

}

….

do { //检测32个软中断位标志中是否有为1的…

if (pending & 1)

h->action(h);

h++;

pending >>= 1;

} while (pending);

[4]. 软中断所依赖的执行时期问题.

之所以将这个问题单独列开来讲,是因为他特别的重要,上面我已经讲过了软中断是依赖硬中断触发执行的,但是产生如下疑问:

?是不是一有硬中断发生就会触发软中断的执行?

?软中断的执行会不会影响到系统的性能?

?会不会影响到硬中断的处理效率?也就是说会不会导致在处理软中断时而引起硬中断无法及时响应呢?

再看do_softirq的代码当中有如下判断:

if (in_interrupt())

return;

这个条件就是能否进行软中断处理的关键条件,因此由此也可以了解到软中断是一种优先级

低于硬中断的软性机制,具体来看看这个判断条件是什么:

/*Are we in an interrupt context? Either doing bottom half

* or hardware interrupt processing?*/

#define in_interrupt() ({ const int __cpu = smp_processor_id();

(local_irq_count(__cpu) + local_bh_count(__cpu) != 0); })

/* softirq.h is sensitive to the offsets of these fields */

typedef struct {

unsigned int __softirq_pending;

unsigned int __local_irq_count;

unsigned int __local_bh_count;

unsigned int __syscall_count;

struct task_struct * __ksoftirqd_task; /* waitqueue is too large */

} ____cacheline_aligned irq_cpustat_t;

#define irq_enter(cpu,irq) (local_irq_count(cpu)++)

#define irq_exit(cpu,irq) (local_irq_count(cpu)--)

看到这里,不得不再多注意一个结构,那就是irq_cpustat_t, 先前我们讲是否有软中断产生的标志位,但没有提到__softirq_pending,这个变量就是记载32个软中断是否产生的标志,每一个软中断对应一个位; 在中断执行的do_softirq中有如下几个重要的动作,说明如下:

?in_interrupt判断是否可以进行软中断处理,判断的条件就是没有没处在硬件中断环境中,而且还没有软中断正在执行(即不允许软中断嵌套),软中断的嵌套避免是

通过local_bh_disable()/local_bh_enable()实现,至于带有bh,其意也即指softirq

是中断底半(bh), 在处理硬件中断时,一进行即会调用irq_enter来表示已经进入硬

件中断处理程序,处理完硬件中断后再调用irq_exit表示已经完成处理;

?pending判断是否有软中断须要处理, 每个位用作当作一个软中断是否产生的标志.

?清除所有软中断标志位,因为下面即将处理; 但清除之前先缓存起来, 因为下面还要使用这个变量一次.

?在进入软中断处理后,会关闭bh功能的执行,执行完后才打开,这样在in_interru pt判断当中就会直接发现已经有bh在执行,不会再次进入bh执行了,这严格保证了bh执行的串行化.

?打开硬件中断,让软中断在有硬件中断的环境下执行.

处理完软中断后关闭硬中断,再次检测是否有新的软中断产生,如果有的话,却只须立即处理本次软中断过程未发生过的软中断向量. 之所以会有新的软中断产生,那是因为软中断是在开硬件中断的情况下执行,硬件中断处理是可能又产生了新的软中断. 之所以只处理本次软中断未发生的软中断向量,依据我自己的理解,其目的是为了不加重软中断处理的负担而不马上处理,只是相应的唤醒一个wakeup_so ftirqd线程,这是专门处理软中断的,这样虽然延误了软中断的处理,但避免了在硬中断服务程序中拖延太长的时间.[关于软中断的处理在后绪版本变化也很大,可以进一步学习研究,如何使软中断不至影响中断处理效率]

软中断处理这个函数虽然不长,但是相当的关键,每一句代码都很重要,结合上面所说的几点,与源码交互起来理解才能根本理解软中断的设计机制:

asmlinkage void do_softirq()

{

int cpu = smp_processor_id();

__u32 pending;

unsigned long flags;

__u32 mask;

if (in_interrupt()) return;

local_irq_save(flags);

pending = softirq_pending(cpu);

if (pending) {

struct softirq_action *h;

mask = ~pending;

local_bh_disable();

restart:

/* Reset the pending bitmask before enabling irqs */

softirq_pending(cpu) = 0;

local_irq_enable();

h = softirq_vec;

do {

if (pending & 1)

h->action(h);

h++;

pending >>= 1;

} while (pending);

local_irq_disable();

pending = softirq_pending(cpu);

if (pending & mask) {

mask &= ~pending;

goto restart;

}

__local_bh_enable();

if (pending)

wakeup_softirqd(cpu);

}

local_irq_restore(flags);

}

}

四. TTY与串口的具体关联.

串口设备可以当作TTY终端来使用,这又使串口设备比一般的设备稍微复杂一些,因为他还必须与终端驱动关联起来,虽然这部分与TTY的关联已经是属于公用部分的代码,并不须要驱动编写者特别做些什么来进行支持,但对它与TTY的层次关联的了解有助于理解整个串口的数据流向.

串口要能够成为终端,必须客外加入终端注册及初始化的代码,这部分很简单,基本上所有的串口驱动都是固定的模式,并无什么修改,主要包括如下结构:

static struct console cs_amba_console = {

.name = "ttyBM",

.write = w83697uart_console_write,

.device = w83697uart_console_device,

.setup = w83697uart_console_setup,

.flags = CON_PRINTBUFFER,

.index = -1,

};

串口终端的注册通过下面的函数,将cs_amba_console注册成为终端, 这个函数调用路径是:

start_kernel()→console_init()→ep93xxuart_w83697_console_init()

void __init ep93xxuart_w83697_console_init(void)

终端会对应一种具体设备的driver, 相对于串口这个结构是uart_driver, 在驱动中我们已经提供了一个这样的结构. static struct uart_driver amba_reg, uart_register_driver会将它注册成为终端对应的driver, 因此真正串口与终端的关联就在此处建立.

函数: static int __init w83697uart_init(void)

描述: 调用uart_register_driver()完成串口与终端的关联,将串口注册成为一种TTY设备,在uart_register_driver()当中调用tty_register_driver()完成TTY设备注册; 其次是完成串口port口的注册,将静态描述的所有串口port(结构为struct uart_port)注册到uart_driver当中.

特别说明: 注册串口TTY设备时,由于历史的原因会注册两个TTY设备,一个是normal, 另一个是callout, 是两个设备来的, 在我们这里两者没有什么差别,请看源码中的注解:

.normal_name = "ttyBM",

.callout_name = "cuaam",

/*

* The callout device is just like the normal device except for

* the major number and the subtype code.

*/

函数: static void __exit w83697uart_exit(void)

描述: 卸截设备,卸截port口,因为我编译的驱动是与内核绑定在一起的,因此实际上根本不会调用此函数.

linux UART串口驱动开发文档

linux UART串口驱动开发文档 w83697/w83977 super I/O串口驱动开发 内容简介: 介绍了Linux下的串口驱动的设计层次及接口, 并指出串口与TTY终端之间的关联层次(串口可作TTY终端使用), 以及Linux下的中断处理机制/中断共享机制, 还有串口缓冲机制当中涉及的软中断机制; 其中有关w83697/w83977 IC方面的知识, 具体参考相关手册, 对串口的配置寄存器有详细介绍, 本文不再进行说明. 目录索引: 一. Linux的串口接口及层次. 二. Linux的中断机制及中断共享机制. 三. Linux的软中断机制. 四. TTY与串口的具体关联. 一. Linux的串口接口及层次. 串口是使用已经非常广的设备了, 因此在linux下面的支持已经很完善了, 具有统一的编程接口, 驱动开发者所要完整的工作就是针对不同的串口IC来做完成相应的配置宏, 这此配置宏包括读与写, 中断打开与关闭(如传送与接收中断), 接收状态处理, 有FIFO时还要处理FIFO的状态. 如下我们就首先切入这一部分, 具体了解一下与硬件串口IC相关的部分在驱动中的处理, 这一部分可以说是串口驱动中的最基础部分, 直接与硬件打交道, 完成最底层具体的串口数据传输. 1. 串口硬件资源的处理. W83697及W83977在ep93xx板子上的映射的硬件物理空间如下: W83697: 0x20000000起1K空间. W83977: 0x30000000起1K空间. 因为串口设备的特殊性, 可以当作终端使用, 但是终端的使用在内核还未完全初始化之前(关于串口与终端的关联及层次在第四节中详细), 此时还没有通过mem_init()建立内核的虚存管理机制, 所以不能通过ioreamp来进行物理内存到虚存的映射(物理内存必须由内核映射成系统管理的虚拟内存后才能进行读写访问), 这与先前所讲的framebuffer的物理内存映射是不同的, 具体原因如下: √终端在注册并使用的调用路径如下: start_kernel→console_init→uart_console_init→ep93xxuart_console_init→register_conso

串口驱动内部实现机制

串口驱动内部实现机制 串行设备系统内部实现架构 串行设备系统内部实现架构图: 串行通信各层使用的结构体及调用关系 I/O层 I/O层是指7个操作系统提供的API函数的实现过程,即这7个API函数调用中间层提供的函数实现自身功能。 I/O层使用的结构体(三个) 一.驱动程序描述符 驱动程序描述表:驱动程序描述符数组 (iosLibP.h文件) typedef struct /* DRV_ENTRY - entries in driver jump table */ {

FUNCPTR de_create; FUNCPTR de_delete; FUNCPTR de_open; FUNCPTR de_close; FUNCPTR de_read; FUNCPTR de_write; FUNCPTR de_ioctl; BOOL de_inuse; } DRV_ENTRY; DRV_ENTRY * drvTable; devTable指向驱动程序描述表的首地址,在iosInit()函数中初始化。 二.设备描述符 设备列表: (iosLib.h文件) typedef struct /* DEV_HDR - device header for all device structures */ { DL_NODE node; /* device linked list node */ short drvNum; /* driver number for this device */ char * name; /* device name */ } DEV_HDR; 三.文件描述符 文件描述表: (iosLibP.h文件) typedef struct /* FD_ENTRY - entries in file table */ { DEV_HDR * pDevHdr;/* device header for this file */ int value; /* driver's id for this file */ char * name; /* actual file name */ BOOL inuse; /* active entry */ } FD_ENTRY; I/O层的调用关系 I/O层的调用关系和一般的I/O系统的调用关系相同。 中间层(包括TY层和TTY层) 中间层是指ttyDrv和tyLib组成的一层。在一般的字符设备中,没有这一层。由I/O层直接调用底层驱动,实现字符设备的功能。

wdm驱动开发之路

WDM驱动开发之路 写在前面:在专栏的前几期中,我们一起初步学习了vxd的开发技术。Vxd技术是很深奥的,不是一篇两篇文章能讲清楚,但你已经入了门,剩下的就要看你的修行了。多看书,多泡论坛(当然是上咱们的驱动开发网论坛了:->),多写程序…我的手不够用了。功到自然成嘛。不过话又说回来,vxd只是权宜之计,WDM才符合当今的潮流(程序员都是时髦人士,君不见先是VB、VC然后是asp、JSP、PHP,数也数不过来呀),Win9x寿终正寝时也就是vxd的末日,你不想随它而去吧(开个玩笑),那就随我来。 按笔者的想法,这篇文章写成连载形式,一次讲一个主题,并且必要时带着例子,让大伙step by step地把WDM驱动弄个透底,不想让大家觉得稀里糊涂,也不想让大家觉得白买杂志了。 今天我们一起讨论第一部分,了解篇。 (一)了解篇 WDM模型(Windows Driver Model)是微软公司为当前主流操作系统Windows98和Windows 2000的驱动程序设计的一种构架。它和传统的win3.x和win95使用的vxd的驱动是完全不同的体系结构。不过对于最终用户来说,WDM驱动程序在Windows98和Windows2000下的表现很相似。作为驱动开发人员来说,它在两者中有很多的不同。并且Windows98中的WDM只能算是Windowss2000中的WDM的一个了集。在Windows98中有一些驱动程序只能使用VXD来实现,如串行通讯驱动等。 要写驱动程序,首先要了解操作系统的结构。在WDM体系中,windows2000操作系统中是最标准的实现方式,Windows98则是部分兼容WDM结构。照微软的说法,Windows98和Windows2000 X86(Intel 架构)版本实现二进制码兼容(参见98DDK),Windows2000 x86版本与其它CPU平台版本实现源码级兼容(因为Windows 2000是基本NT相似的结构,最底层是硬件抽象层HAL,所有我们相信它们之间能源码级兼容)。但实际上,Windows2000的WDM实现中有很多例程在Windows98中没有实现,一旦试图加载这样的WDM驱动程序到Windows98中,则不能正常加载,当然我们也有办法实现它,那就是利用“桩”技术。具体可参见Walter Oney写的《Programming the Microsoft Windows Driver Model》一书。我们首先来看看Windows 2000的系统结构,然后再来看看Windows 98的。 图一是Windows 2000的系统结构图。从图中我们可以看出:整个系统被分为两个态,用户态和核心态。 从图中可以明显看出I/O操作最后是怎样作用到硬件上的。用户态应用程序对Windows 子系统进行win32 API调用,这个调用由系统服务接口作用到I/O管理器(严格地说,在Windows 系统中不存在I/O管理器这样的独立模块,这个只是为了方便叙述而将各种核心功能调用的集合称作I/O管理器,业界人士都这样称呼这个部分),I/O管理器进行必要的参数匹配和操作安全性检查,然后由这个请求构造出合适的IRP(IO Request Package,I/O请求包),并把此IRP传给驱动程序。简单情况下,驱动程序直接执行这个请求包,并与硬件打交道,从而完成I/O请求工作,最后由I/O管理器将执行结果返回给用户态程序。但在WDM体系结构中,大部分实行分层处理。即在图中“设备驱动“这部分,分成了若干层,典型地分成高层驱动程序、中间层驱动程序、底层驱动程序。每层驱动再把I/O请求划分成更简单的请求,以传给更下层的驱动执行。以文件系统驱动为例,最高层驱动只知道文件如何在磁盘上表示,但不知到怎样得到数据。最低层驱动程序只知道怎样从磁盘取出512B为单的数据块,但不知道文件怎样表示。举个更具体的生活例子。主人(最高层驱动)知道(并且需要)笔计本电脑,但不知道具体放在什么位置;而仆人(最底层驱动)却知道它放在具体什么地方,但

从零开始搭建Linux驱动开发环境

参考: 韦东山视频第10课第一节内核启动流程分析之编译体验 第11课第三节构建根文件系统之busybox 第11课第四节构建根文件系统之构建根文件系统韦东山书籍《嵌入式linux应用开发完全手册》 其他《linux设备驱动程序》第三版 平台: JZ2440、mini2440或TQ2440 交叉网线和miniUSB PC机(windows系统和Vmware下的ubuntu12.04) 一、交叉编译环境的选型 具体的安装交叉编译工具,网上很多资料都有,我的那篇《arm-linux- gcc交叉环境相关知识》也有介绍,这里我只是想提示大家:构建跟文件系统中所用到的lib库一定要是本系统Ubuntu中的交叉编译环境arm-linux- gcc中的。即如果电脑ubuntu中的交叉编译环境为arm-linux-

二、主机、开发板和虚拟机要三者互通 w IP v2.0》一文中有详细的操作步骤,不再赘述。 linux 2.6.22.6_jz2440.patch组合而来,具体操作: 1. 解压缩内核和其补丁包 tar xjvf linux-2.6.22.6.tar.bz2 # 解压内核 tar xjvf linux-2.6.22.6_jz2440.tar.bz2 # 解压补丁

cd linux_2.6.22.6 patch –p1 < ../linux-2.6.22.6_jz2440.patch 3. 配置 在内核目录下执行make 2410_defconfig生成配置菜单,至于怎么配置,《嵌入式linux应用开发完全手册》有详细介绍。 4. 生成uImage make uImage 四、移植busybox 在我们的根文件系统中的/bin和/sbin目录下有各种命令的应用程序,而这些程序在嵌入式系统中都是通过busybox来构建的,每一个命令实际上都是一个指向bu sybox的链接,busybox通过传入的参数来决定进行何种命令操作。 1)配置busybox 解压busybox-1.7.0,然后进入该目录,使用make menuconfig进行配置。这里我们这配置两项 一是在编译选项选择动态库编译,当然你也可以选择静态,不过那样构建的根文件系统会比动态编译的的大。 ->Busybox Settings ->Build Options

行为驱动开发

行为驱动开发 行为驱动开发(简称BDD)是测试驱动开发的升级版。它是一套软件工程实践方法,能帮助研发团队更快地构建和交付更有价值和更高质量的软件产品。采用BDD思想编写的测试读起来更像规格说明书而不是单元测试,所以它是使用测试作为表达和验证行为的一种手段。基于这个特性,BDD也非常适合应用在需求分析中。 一、行为驱动开发的原则 1.聚焦交付业务价值。使用验收标准作为目标,帮助业务实现更实际的可交付的功能。 2.团队共同确定交付标准。业务分析人员,开发人员,测试人员与最终用户一起定义和指定功能。 3.拥抱变化。项目开始时不锁定需求,而是假设需求,从用户那里得到早期的反馈,对需求的理解将在项目的整个生命周期中演进和变更。 4.不仅仅编写自动化测试,而是编写可执行规范和底层规范。团队将验收标准转换为自动化的验收测试,更准确地说是转换为可执行规范。在编写任何代码之前,开发人员将考虑代码实际上应该做什么,并将其表示为底层的可执行规范。可执行规范是一种自动化测试,它演示和验证应用程序如何交付特定的业务需求。自动化测试作为构建过程的一部分运行,并在对应用程序进行更改时运行,进行验收测试和回归测试。 5.交付活文档,并使用活文档来支持后续维护工作。在项目结束后持续维护项目可执行规范。 二、行为驱动开发的优势 1.专注业务目标,避免工程师把工作量浪费在不提供业务价值的功能上,能够降低成本,减少浪费。

2.完整的可执行规范,可充当开发人员的辅助技术文档,更容易理解现有的代码库并进行更改。 3.全面的自动化验收测试和回归测试,不仅可以提升执行效率,也能降低手工测试的出错率,使得迭代速度更快更可靠。 三、行为驱动开发的缺陷 1. 需要多个角色高度参与和协作,涉众如果不愿意或不能参与对话和协作,或者等到项目结束后才给出反馈,就很难充分利用BDD的优点。 2.比较适用于敏捷开发,但不太适用于瀑布式开发。 3.对参与角色能力要求很高,尤其是测试团队,不仅需要精通业务,对业务目标清晰,而且对测试技术能力要求更高,如果编写的自动化测试很烂,会导致更高的测试维护成本。

单片机驱动开发班

单片机驱动开发班 课程背景: 随着经济的发展,科技的突飞猛进,芯片技术也取得了飞速发展,这就使单片机技术在各种民用和工业测控等领域得到更为广泛应用。包括如今异常火爆的汽车电子中的车身控制、底盘控制、发动机控制、安全控制、娱乐系统等;包括传统的工业控制中的电机控制、温控系统、仪表设备、楼宇自控系统、数据采集系统等;包括计算机网络通信、数据传输、军用设备、航空航天等。单片机凭借其低成本、高性能的不可替代优势,已经成为微电脑控制的主力军。据统计,我国的单片机年需求量已达2亿片以上,且每年以大约15%的速度增长,发展迅速的单片机行业有着广阔的前景。 相比于发展迅猛的单片机行业,国内的单片机设计开发从业人员缺口很大。据统计,到2015年,我国单片机开发从业人员将达350万人,而目前的从业者只有大约一百五十万人,两百万的人才缺口正驱动大量人员加入这个庞大的群体。 学院优势: 硅谷芯微是深圳市硅谷龙科技有限公司教育产业下属专门从事IT实训的独立机构,深圳市硅谷龙科技有限公司始创于中国深圳,由侯工单片机工作室投资成立,致力于通过创新、高品质的课程体系和高效、实用的教育服务推动我国芯片级IT教育体系的建设。 深圳市硅谷龙科技有限公司致力于提供个性化、着眼于未来的教育服务,把国际先进且具有自主知识产权的案例全面应用于教育服务,在IT职业教育领域,硅谷龙以实用型的工程师人才培养理念作导引,以学生就业和职业生涯发展为指向,以成熟的IT开发经验为基础,通过完善、创新的课程体系以及全球化的企业合作为保障开展IT各类职业人才教育。硅谷龙在全国范围内建立实训基地,通过系统的实训,帮助学生提高职业素质及就业竞争力,并最终完成学员的就业服务。 面向行业及岗位: 面向行业:消费类电子、工控、汽车电子、监控电子等 主要岗位:单片机工程师、单片机硬件工程师、单片机技术支持工程师、单片机销售工程师、单片机驱动工程师。 教学目标:

串口驱动概述

串口驱动概述 1.启动顺序 在usrConfig.c中,usrInit()调用sysHwInit(), 对系统硬件进行基本的初始化,使其处于安静状态。sysHwInit()[在sysLib.c中]调用sysSerial.c中的sysSerialHwInit()对BSP串行器件进行初始化,使其处于静态;sysSerialHwInit()再通过xxDevInit()复位串行通道。 在usrInit()函数的最后,产生根任务usrRoot()。usrRoot()调用sysClkConnect()。sysHwInit2()主要安装系统中断,它调用sysSerialHwInit2()连接串行中断。如果定义了INCLUDE_TTY_DEV,而没有定义INCLUDE_TYCODRV_5_2,在usrRoot()任务中调用ttyDrv()来初始化串行设备驱动,并通过ttyDevCreate()函数创建串行设备。 串行驱动是在VxWorks系统开始过程中被初始化的。 2. 编码步骤 (1)初始化 ①定义系统可支持的串行通道数。 ②初始化驱动的设备描述 ③写设备初始化代码 (2)写入口程序 (3)写中断服务程序管理中断 (4)使用模板 wind/target/src/drv/ssio/templateSio.c 3.详细步骤介绍 (1)定义系统可支持的串行通道数。 在config.h中定义串口通道数NUM_TTY。 (2)初始化驱动的设备描述 TEMPLATE_CHAN例子如下所示: typedef struct { /* SIO_CHAN *MUST* be first */ SIO_CHAN sio; /* standard SIO_CHAN element */ UINT32 ioBase; UINT32 vecBase; UINT32 intLevel; /* callbacks */ STATUS (*getTxChar) (void *, char *); void (*putRcvChar) (void *, char); void (*errorRtn) (void *, int, void *, int); void * getTxArg; void * putRcvArg;

windows ce RS232C串行口驱动程序设计

实验5 RS232C串行口驱动程序设计 实验目的 掌握RS232串行口设备驱动程序的编写方法,实现串行口的初始化、中断管理、发送与接收处理的程序设计技巧。 实验环境 ?操作系统:windows 2000以上版本,要求安装.NET Framework 1.1 ?软件工具列表: ?Microsoft ActiveSyn 4.5; ?Platform Builder 5.0; 实验学时 2学时,必做实验。 实验内容 理解驱动程序的原理与功能,掌握流式接口驱动程序的结构、编写、加载及调试过程 预备知识 驱动程序是对底层硬件的抽象。应用程序开发者不需要真正理解底层驱动的工作原理,他们只需要通过Windows CE提供的API函数,就可以直接与硬件进行交互。例如,如果应用程序要对串口进行操作,只需要: 1.在COMx上调用CreateFile( ) 2.调用WriteFile( ) 往串口写数据 3.调用CloseHandle( ) 关闭串口 对于其他类型的API也是一样的。如果我们需要向显示器输出,我们只需要调用PolyLine( ), 或者MoveToEx( ), LineTo( )等函数,而不需要理解显示器的真正工作原理。 在Windows CE中,驱动就是一个简单的用户态动态链接库(DLL),DLL 会导出一些公共的接口,DLL被父进程(通常是device.exe或者gwes.exe)加载,然后,父进程通过DLL导出的接口调用DLL。 流式接口的驱动程序导出一系列大家熟知的接口。还拿串口驱动当例子。我们希望可以往串口读写数据,因此,我们希望驱动程序可以导出Open, Close, Read, 和Write接口。此外,流式接口的驱动程序还导出如下函数:PowerUp, PowerDown, IO Control, Init, 和DeInit.

串口驱动程序

/* linux/drivers/serial/s3c6400.c * * Driver for Samsung S3C6400 and S3C6410 SoC onboard UARTs. * * Copyright 2008 Openmoko, Inc. * Copyright 2008 Simtec Electronics * Ben Dooks * https://www.360docs.net/doc/0016181179.html,/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include "samsung.h" static int s3c6400_serial_setsource(struct uart_port *port, struct s3c24xx_uart_clksrc *clk) { unsigned long ucon = rd_regl(port, S3C2410_UCON); if (strcmp(字符串函数,比较两个字符串的大小)(clk->name, "uclk0") == 0) { ucon &= ~S3C6400_UCON_CLKMASK; ucon |= S3C6400_UCON_UCLK0; } else if (strcmp(clk->name, "uclk1") == 0) ucon |= S3C6400_UCON_UCLK1; else if (strcmp(clk->name, "pclk") == 0) { /* See notes about transitioning from UCLK to PCLK */ ucon &= ~S3C6400_UCON_UCLK0; } else { printk(KERN_ERR "unknown clock source %s\n", clk->name);

基于项目驱动的嵌入式综合开发

实训报告 实训名称:基于项目驱动的嵌入式综合开发姓名: 院(系): 专业班级: 学号: 指导教师: 实习时间:

一、实训目的 (一)实习目的 本实训课程是针对嵌入式软件专业学生专门设计的,通过本课程设置的几个嵌入式综合项目的系统学习,可以使学生由浅入深的对嵌入式Linux系统进行全面学习,能够独立胜任嵌入式Linux应用开发、系统开发、驱动开发等多方面工作,并注重敬业团队精神培养。 1)增强学生的理论联系实际的能力 2)通过实训了解企业项目开发流程和学习新技术的方法 3)通过实训项目了解企业项目开发过程中文档的整理方法和问题的分析方法 4)通过实训项目加强学生对基础课程的运用能力,使其认识到基础知识的重要性5)通过实训争强学生对本专业和未来工作岗位的理解,端正心态,明确就业目标6)通过实训争强学生的编程技能,培养其良好的编码风格和编码习惯 (二)方法 本实训课程安排在学校实验室统一进行实训,学生上机独立完成规定实训项目。 (三)任务 要求每位同学独立完成实训题目的编程、调试、优化与测试,并交付使用。要求强化编程思维、编程能力和代码优化的能力,撰写《实训报告》(含:需求分析、总体设计、算法分析及设计中遇到的主要问题和解决方法,设计中尚存的不足与心得体会)。上交完成的所有源程序及相关文件。

三、实训报告 3.1 项目1名称 智能手环 3.1.1 实训内容 本次实训内容是制作智能手环,需要实现计步,测量温度,显示时间,电量,报警等功能。具体模块如下: (1)LED模块:显示电量 (2)ADC模块:模数转换 (3)PWM模块:蜂鸣器报警 (4)KEY模块:按键控制 (5)RTC模块:实时时钟 (6)计步以及温度显示模块 (7)总体实现 3.1.2 实训过程及相关结果 首先需要搭建软硬件环境,安装Ubuntu系统,安装交叉编译工具链。然后需要下载调试硬件连接,安装串口驱动等。环境搭建完成之后需要实现相应的每一个功能,具体功能如下: (1)LED模块:显示电量 此模块主要功能为点亮LED灯,以此来实现手环的点亮显示功能。 原理图如下: 在项目目录下分别创建led.c,led.h,main.c文件,根据芯片手册所分析的对应寄存器数据,在

ARM裸机驱动开发说明书

附件1: 学号: 实验设计说明书 题目ARM裸机驱动 学院计算机科学与信息工程学院 专业计算机科学与信息技术 班级12嵌入式 学生姓名古应波、卢圣 指导教师朱超平 2015年6月10日

写在前面 本文档以ARM9(三星2410/2440)为平台,介绍了如何实现裸机驱动程序的编写。初次接触裸机程序,感觉什么都是一头雾水,由于对硬件的不熟悉,所以前期花了很多时间来熟悉S3C2440的硬件原理图,初步熟悉了硬件原理之后,发现其实简单的逻辑驱动程序开发并不是很难。学习就应该这样,循序渐进。把一步步把简单的东西弄懂了,便没有复杂的了,所谓水到渠成。这篇文章是面对初学者的,把很多问题简化了。希望对刚接触ARM裸机程序开发的同学有所帮助。 由于时间和技术的限制,程序还存在不少bug,欢迎指正。

目录 第一节、项目建立 (3) 第二节、相关参数的配置 (4) 第三节、程序的编译、烧写、执行 (7) 第四节、串口驱动 (8) 第五节、LED驱动 (11) 第六节、按键及蜂鸣器驱动 (12) 第七节、步进电机驱动 (13) 第八节、RTC驱动 (14) 第九节、定时器驱动 (16) 第十节、中断驱动 (17) 第十一节、数码管驱动 (18) 第十二节、I2C矩阵键盘驱动 (19) 第十三节、LCD驱动 (20) 第十四节、触摸板驱动 (24) 第十五节、驱动整合 (26) 串口模式: (26) LCD模式: (27)

第一节、项目建立 本次项目使用的软/硬件包括: CodeWarrior for ARM Developer Suite v1.2(以下简称ADS). MagicARM2440硬件仿真平台 工程建立步骤: 打开ADS,选择File->New。在弹出的面板框中选择ARM Executable Image,项目名填写Test(可根据自己需要自行更改),项目存储位置此处选择F://ADS/Test(可根据自己需要自行更改)。注意:项目的储存位置一定不要包含中文路径,否则会出现不可预期的错误。 建立项目工程组织文件夹。在屏幕的中部点击右键,选择Create Group新建项目的组织文件夹,方便对源文件进行管理。如下图

usb 转串口驱动程序

* Prolific PL2303 USB to serial adaptor driver * * Copyright (C) 2001-2007 Greg Kroah-Hartman (greg@https://www.360docs.net/doc/0016181179.html,) * Copyright (C) 2003 IBM Corp. * * Original driver for 2.2.x by anonymous * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 as published by the Free Software Foundation. * * See Documentation/usb/usb-serial.txt for more information on using this * driver * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pl2303.h" /* * Version Information */ #define DRIVER_DESC "Prolific PL2303 USB to serial adaptor driver" static int debug; #define PL2303_CLOSING_WAIT (30*HZ) #define PL2303_BUF_SIZE 1024 #define PL2303_TMP_BUF_SIZE 1024

Windows驱动开发技术详解 第六章的(Windows内核函数)自我理解

Windows驱动开发技术详解第六章的(Windows内核函数)自我理解 学习各种高级外挂制作技术,马上去百度搜索(魔鬼作坊),点击第一个站进入,快速成为做挂达人。 其实这章主要就是讲函数DDK有自己的函数跟SDK一样编写DDK使用DDK提供的函数就OK了 /////////////////////////////////////////////////////////////////////////////// ASCII字符串和宽字符串 ASCII字符构造 char*str1="abc"; 打印ASCII字符串 char*string="hello"; KdPrint("%s\n",string);\\注意是小写%s ///// UNICODE字符构造 wchar_t*str2=L"abc"; 打印宽字符串 WCHAR*string=L"hello"; KdPrint("%S\n",string);\\注意是大写%S /////////////////////////////////////////////////////////////////////////////// ANSI_STRING字符串和UNICODE_STRING字符串 ASCII字符串进行了封装 typedef struct_STRING{ USHORT Length;//字符的长度。 USHORT MaximumLength;//整个字符串缓冲区的最大长度。 PCHAR Buffer;//缓冲区的指针。 }STRING; 输出字符串 ANSI_STRING ansiString; KdPrint("%Z\n",&ansiString);//注意是%Z UNICODE_STRING宽字符串封装 typedef struct_UNICODE_STRING{ USHORT Length;//字符的长度,单位是字节。如果是N个字符,那么Length等于N的2倍。USHORT MaximumLength;//整个字符串缓冲区的最大长度,单位也是字节。 PWSTR Buffer;//缓冲区的指针。 }UNICODE_STRING*PUNICODE_STRING; 输出字符串 UNICODE_STRING ansiString; KdPrint("%wZ\n",&ansiString);//注意是%wZ ///////////////////////////////////////////////////////////////////////////////

12864串口驱动程序(郭天祥)

/****************************************************************************** * * 标题: PIC-ST2学习板演示程序--12864LCD-1 * 文件名: LCD12864-1 * 建立日期: 2010.01.28 * 修改日期: 2010.05.15 * 版本: V1.0 * 作者: simomli ******************************************************************************* * * 功能描述: LCD12864C液晶串口方式显示字符,只用到三根数据线 ******************************************************************************* * *【版权】Copyright(C) 2009-2019 All Rights Reserved *【声明】此程序仅用于学习与参考,引用请注明版权和作者信息! ******************************************************************************* / //#include #include #include #define uint8 unsigned char #define uint16 unsigned int #define readbusy 0xFC //读忙指令 #define readdata 0xFE //读数据指令 #define writecom 0xF8 //写命令指令 #define writedata 0xFA //写数据指令 __CONFIG(WDTDIS & LVPDIS & HS & PWRTDIS & BORDIS);//设置配置位 //WDTDIS:disable watchdog timer //LVPDIS:low voltage programming disabled //HS:high speed crystal/resonator //PWRTDIS:disable power up timer //BORDIS:disable brown out reset #define CS RE0 //使能线 #define SID RE1 //数据线 #define SCLK RE2 //时钟线 /************************定义显示字符*****************************************/

UART串口驱动程序移植

UART串口驱动程序移植 仇洁婷,陈儒军 在嵌入式控制系统采集站的设计方案中,需要用到AT91RM9200的5个UART 串口,就会涉及到多个中断的优先级分配问题,而且也可能造成串行通信的中断不能及时响应,从而造成数据丢失。在Linux2.6.21内核中,UART驱动程序成功地解决了这个问题,它采用了DMA传输的串行通信方式。DMA(Direct Memory Access)是一种高速的数据传输操作,在外部设备和存储器之间直接读/写数据,提高了通信的可靠性。 Linux对串口驱动支持很完善,具有统一的编程接口。包含3层:tty核心、tty线路规范和tty驱动。tty核心层向上提供统一的访问接口,使得用户不必关注具体终端的类型。tty线路规范指定数据交互的形式。tty驱动层用来操作硬件。一般只需针对不同的串口功能在tty驱动层中添加相应的配置宏即可。本系统将Linux-2.6.21打上at91rm9200dk体系结构的补丁后,基本上就能够使用UART设备驱动。 根据本系统的硬件设计要求,还需进行如下修改: 一、注册串口次设备号 在Linux2.6内核中,就只引出了2个串口的驱动注册信息,需要增加其他3个串口。可以在linux2.6.21/arch/mach-at91/board-dk.c文件中只定义了DEBUG UART和UART1,因此要改为 .nr_tty = 5, .tty_map = { 4, 0, 1, 2, 3 } UART0、UART1、UART2、UART3和Debug UART这些串行端口和设备名称、 在本方案中,需要用到UART0和UART2串口的时钟信号SCK,但是在 Linux2.6内核的串口驱动程序中,并没有引出UART0~UART3所带有的时钟信号,所以需要进行修改。 二、注册时钟信号SCK引脚 在AT91RM9200芯片的定义中,UART0和UART2串口的时钟信号SCK分别是PA19和PA24管脚中的外设A功能,因此,需要用at91_set_A_periph函数把PA19和PA24引脚赋予Periph A。 在linux-2.6.21\arch\arm\mach-at91\at91rm9200_devices.c文件中 的configure_ usart0_pins和configure_usart2_pins函数中增加: at91_set_A_periph(AT91_PIN_PA19, 0);

学习笔记windows驱动开发技术详解

<学习笔记>Windows驱动开发技术详解 派遣函数是Windows驱动程序中的重要概念。驱动程序的主要功能是负责处理I/O请求,其中大部分I/O请求是在派遣函数中处理的。 用户模式下所有对驱动程序的I/O请求,全部由操作系统转换为一个叫做IRP数据结构,不同的IRP会被“派遣”到不同的派遣函数中。IRP与派遣函数 IRP的处理机制类似于Windows应用程序中的“消息处理”,驱动程序接收到不同的IRP后,会进入不同的派遣函数,在派遣函数中IRP得到处理。1.IRP 在Windows内核中,有一种数据结构叫做IRP(I/O Request Package),即输入输出请求包。上层应用程序与底层驱动程序通信时,应用程序会发出I/O请求。操作系统将I/O请求转化为相应的IRP数据,不同类型的IRP会被传递到不同的派遣函数中。 IRP有两个基本的重要属性,一个是MajorFunction,另一个MinorFunction,分别记录IRP的主类型和子类型,操作系统根据MajorFunction将IRP“派遣”到不同的派遣函数中,在派遣函数中还可以继续判断这个IRP属于哪种MinorFunction。 下面是HelloDDK的DriverEntry中关于派遣函数的注册:

view plaincopy to clipboardprint? #pragma INITCODE extern "C" NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegisterPath ) { NTSTATUS status; KdPrint(("Enter DriverEntry\n")); //设置卸载函数 pDriverObject->DriverUnload = HelloDDKUnload; //设置派遣函数 pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine; pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;

linux的lcd驱动详细讲解

嵌入式驱动程序Day12 Top 1. LCD驱动设计开发 1 LCD驱动设计开发 1.1问题 通过led驱动开发掌握linux内核framebuffer 驱动开发通用方法。 1.2方案 一、帧缓冲(Framebuffer )。 帧缓冲(Framebuffer )是Linux为显示设备提供的一个接口,Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。Framebuffer机制模仿显卡的功 能,将显卡硬件结构抽象掉,可以通过Framebuffer的读写直接对显存进行操作。用户 可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer 设备驱动 来完成的。 Framebuffer本身不具备任何运算数据的能力,就只好比是一个暂时存放水的水池。CPU将运算后的结果放到这个水池,水池再将结果流到显示器,中间不会对数据做处 理。应用程序也可以直接读写这个水池的内容。在应用程序中,一般通过将FrameBuffer 设备映射到进程地址空间的方式使用,比如下面的程序就打开/dev/fbO 设备,并通过mmap系统调用进行地址映射。 FrameBuffer设备还提供了若干ioctl 命令,通过这些命令,可以获得显示设备的 一些固定信息(比如显示内存大小)、与显示模式相关的可变信息(比如分辨率、象素结构、每扫描线的字节宽度),以及伪彩色模式下的调色板信息等等。 二、FrameBuffer 在Linux中的实现和机制。 Framebuffer对应的源文件在linux/drivers/video/ 目录下。总的抽象设备文件为fbcon.c,在这个目录下还有与各种显卡驱动相关的源文件。 1. 分析Framebuffer设备驱动。 FrameBuffer设备驱动基于如下两个文件: (1) linux/include/linux/fb.h (2) linux/drivers/video/fbmem.c 2. 分析这两个文件。

电源控制器PSC_PSI的串口驱动开发及其应用

第25卷第5期核电子学与探测技术 VoI.25No.52005年 9月 NucIear EIectronics &Detection TeclnoIogy ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!Sept. 2005 电源控制器PSC /PSI 的串口驱动开发及其应用 刘 佳,王春红,赵籍九 (中国科学院高能物理研究所,北京 100049) 摘要:北京正负电子对撞机重大改造工程BEPCII 的磁铁电源样机预制工作中,电源控制系统采用美国SNS 实验室研制的电源控制器PSC /PSI 。PSC 有一个串行接口能与控制计算机进行通讯,可以用一组PSC /PSI 作为独立测试系统。主要介绍了电源控制器PSC /PSI 的串口驱动程序开发和在LabVIEW 环境下电源独立测试程序的开发及测试。 关键词:驱动程序;串口;LabVIEW ;电源控制器;BEPCII 中图分类号:TP29 文献标识码:A 文章编号:0258-0934(2005)05-0550-05 收稿日期:2004-04-03 作者简介:刘佳(1980—),男,河北行唐人,学士,从事BEPCII 中磁铁电源控制系统的研究 BEPCII 储存环磁铁电源系统,按磁铁种类及供电方式,可分为B 铁电源、O 铁电源、S 铁电源、斜四极子电源、校正磁铁电源、超导磁铁电源等几种。对于主B 铁、O 铁、S 铁电源,其控制结构及原理基本相同,有效控制精度均为16bit , 部分电源的稳定度要求为5>10-5。为保证较高的控制精度和提高系统的抗干扰能力,采用美国SNS 实验室研制的电源控制器 PSC /PSI 。根据磁铁电源的控制要求,对PSC /PSI 原来的控制接口进行重新定义, 大电源样机以及Clopper 电源的设计和制造均带有与PSI 接口的电路。电源独立测试系统的方框图如图1所示。 图1电源独立测试系统方框图 !电源控制器PSC /PSI 简介 电源控制器PSC /PSI 由两部分组成,安装 在VME 机箱里的电源控制器PSC 和安装在电 源里的电源接口PSI 。PSC 和PSI 之间通过一对光纤连接,一条传送数据到PSI ,一条从PSI 接收数据。一个PSC 可以控制6个PSI 。PSI 与电源之间通过两根短电缆连接,一根传输数字信号,一根传输模拟信号。简单的硬件线路连接,较短的模拟信号传输,极大的简化了控制系统与电源的接口,同时也可提高系统的抗干扰 能力 [1] 。1.1电源控制器PSC 通常,电源控制器PSC 是一块VME 插件, 它的前面板有以下接口: 1)两个事件触发输入接口,一个用于写输入,一个用于只读输入;2)6个光纤通道,每个通道连接一对光纤,一条发送,一条接收;3) 一个远程控制RS232接口。在模块背板上,96针DIN 接口连接到VME 总线,用于控制、内存访问和提供电源。PSC 可以通过VME 总线接收数据和命令,也可通过前面板RS232端口接收数据和命令,RS232端口主要用于离线调试。原理见图2。 其中,串行控制模块是一块FPGA ,它提供一个远程控制数据路径,通过串行的RS232口连接到PSC 模块,可实现确定速率的串行连接。此控制方式主要用于独立的测试系统,PSC 模块可放置在一个单独的小盒子中完成电 源的评估测试 [2] 。0 55

相关文档
最新文档