`

linux中断机制简单分析

 
阅读更多


驱动程序使用一个中断,一般首先要申请一个中断,申请中断的函数是
  1. intrequest_irq(unsignedintirq,
  2. irqreturn_t(*handler)(int,void*,struct pt_regs*),
  3. unsigned long flags,
  4. constchar*dev_name,
  5. void*dev_id);
这个函数在interrupt.h中声明,其中的参数,irq是要申请的中断号,handler是要安装的中断处理函数,flags是中断的标志,dev_name标识一个中断的名称。dev_id是将传递给中断处理函数。
简单的看一下此函数的实现和相关部分,
第一个参数irq中断号。内核中已经给我们定义好了中断号的宏,来表示中断在内核中的表示。
mini2440开发板上6个按键分别被连接到了EINT8,EINT11,EINT13,EINT14,EINT15,EINT19。芯片数据手册中,可以得知EINT8-23都共用SRCPND挂起源寄存器中的第5位,在INTPND,INTMASK等寄存器中也都在第5位。如果要进一步确定是那个中断源,则需要查看EINTPEND外部中断挂起寄存器(数据手册并没有将这个寄存器介绍写入中断一章,而在输入输出章,其他方面也感觉2440的数据手册也很不详细,很多东西都没有介绍)。
再看linux中在include\asm-arm\arch-s3c2410\Irq.h中定义了中断的宏,如下,
  1. #define S3C2410_CPUIRQ_OFFSET(16)
  2. #define S3C2410_IRQ(x)((x)+S3C2410_CPUIRQ_OFFSET)

  3. #define IRQ_EINT0 S3C2410_IRQ(0)/*16*/
  4. #define IRQ_EINT1 S3C2410_IRQ(1)
  5. #define IRQ_EINT2 S3C2410_IRQ(2)
  6. #define IRQ_EINT3 S3C2410_IRQ(3)
  7. #define IRQ_EINT4t7 S3C2410_IRQ(4)/*20*/
  8. #define IRQ_EINT8t23 S3C2410_IRQ(5)
  9. #define IRQ_RESERVED6 S3C2410_IRQ(6)/*fors3c2410*/
  10. ..................
  11. #define IRQ_EINT4 S3C2410_IRQ(32)/*48*/
  12. #define IRQ_EINT5 S3C2410_IRQ(33)
  13. #define IRQ_EINT6 S3C2410_IRQ(34)
  14. #define IRQ_EINT7 S3C2410_IRQ(35)
  15. #define IRQ_EINT8 S3C2410_IRQ(36)
  16. #define IRQ_EINT9 S3C2410_IRQ(37)
  17. #define IRQ_EINT10 S3C2410_IRQ(38)
  18. #define IRQ_EINT11 S3C2410_IRQ(39)
  19. #define IRQ_EINT12 S3C2410_IRQ(40)
  20. #define IRQ_EINT13 S3C2410_IRQ(41)
  21. #define IRQ_EINT14 S3C2410_IRQ(42)
  22. #define IRQ_EINT15 S3C2410_IRQ(43)
可以看到 EINT0号中断IRQ_EINT0将宏展开后就等于16,而IRQ_EINT8展开后等于52.这些数字编号是linux内核定义的中断数组的编号。linux内核将所有中断统一编号。使用irq_desc结构数组来描述中断。每个数组项对应一个中断。这样IRQ_EINT0就对应数组的第16项。irq_desc结构如下
  1. struct irq_desc{
  2. irq_flow_handler_thandle_irq;
  3. struct irq_chip*chip;
  4. struct msi_desc*msi_desc;
  5. void*handler_data;
  6. void*chip_data;
  7. struct irqaction*action;/*IRQ action list*/
  8. unsignedintstatus;/*IRQ status*/

  9. unsignedintdepth;/*nested irq disables*/
  10. unsignedintwake_depth;/*nested wake enables*/
  11. unsignedintirq_count;/*Fordetecting broken IRQs*/
  12. unsignedintirqs_unhandled;
  13. spinlock_tlock;
  14. #ifdef CONFIG_SMP
  15. cpumask_taffinity;
  16. unsignedintcpu;
  17. #endif
  18. #ifdefined(CONFIG_GENERIC_PENDING_IRQ)||defined(CONFIG_IRQBALANCE)
  19. cpumask_tpending_mask;
  20. #endif
  21. #ifdef CONFIG_PROC_FS
  22. struct proc_dir_entry*dir;
  23. #endif
  24. constchar*name;
  25. }
分析较为重要的一些成员,成员handle_irq表示当前处理函数,执行到这个irq_desc数组代表的中断时首先调用此函数,此函数再调用chip成员链接的底层函数包括清楚,屏蔽,使能等操作。做一些操作,然后调用action链表中用户注册的处理函数,request_irq函数主要任务就是在此结构中的action链表中添加定义的中断处理函数。这里有三个主要问题有待分析。一.linux中断从异常如何执行到irq_desc结构代表的数据项中,二,irq_desc数组在linux中如何建立初始化。三,request_irq实现的简略分析。

逐项简单分析,
一。linux中断从异常如何执行到irq_desc结构代表的数据项中。
在内核start_kernel(在init/main.c)中调用tarp_init(在arch/arm/kernel/Traps.c中)设置异常处理函数。函数如下
  1. void __init trap_init(void)
  2. {
  3. unsigned long vectors=CONFIG_VECTORS_BASE;
  4. extern char __stubs_start[],__stubs_end[];
  5. extern char __vectors_start[],__vectors_end[];
  6. extern char __kuser_helper_start[],__kuser_helper_end[];
  7. intkuser_sz=__kuser_helper_end-__kuser_helper_start;

  8. /*
  9. *Copy the vectors,stubsandkuser helpers(inentry-armv.S)
  10. *into the vector page,mapped at 0xffff0000,andensure these
  11. *are visibletothe instruction stream.
  12. */
  13. memcpy((void*)vectors,__vectors_start,__vectors_end-__vectors_start);
  14. memcpy((void*)vectors+0x200,__stubs_start,__stubs_end-__stubs_start);
  15. memcpy((void*)vectors+0x1000-kuser_sz,__kuser_helper_start,kuser_sz);

  16. /*
  17. *Copy signal return handlers into the vector page,and
  18. *setsigreturntobe a pointertothese.
  19. */
  20. memcpy((void*)KERN_SIGRETURN_CODE,sigreturn_codes,
  21. sizeof(sigreturn_codes));

  22. flush_icache_range(vectors,vectors+PAGE_SIZE);
  23. modify_domain(DOMAIN_USER,DOMAIN_CLIENT);
  24. }
中断是异常的一种,在CPU出现一样时会跳转到指定地址。ARM V4版本以后,异常向量基地址有两个一是0x00000000,一是0xffff0000。这个通过设置CP15协处理器C1寄存器中v位(bit[13]位)控制。 linux内核使用后者(?)。
CONFIG_VECTORS_BASE是一个配置项在.config文件中可以找到CONFIG_VECTORS_BASE = 0xffff0000.
?--------?
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
这句的目的是将异常向量代码部分,复制到vectors地址处。
__vectors_start标号在arch/arm/kernel/entry_asmv.S中,如下,
  1. .globl__vectors_start
  2. __vectors_start:
  3. swiSYS_ERROR0
  4. bvector_und+stubs_offset
  5. ldrpc,.LCvswi+stubs_offset
  6. bvector_pabt+stubs_offset
  7. bvector_dabt+stubs_offset
  8. bvector_addrexcptn+stubs_offset
  9. bvector_irq+stubs_offset
  10. bvector_fiq+stubs_offset
  11. .globl__vectors_end
  12. __vectors_end:
这就是异常向量表,b vector_irq + stubs_offset表示跳到中断异常,vector_und是一个定义的宏扩展的,下面看一下这个宏并简要注释。

  1. .macrovector_stub,name,mode,correction=0
  2. @name:名字 mode:那种异常模式 correction:返回地址需减去的长度
  3. .align5

  4. vector_\name:
  5. .if\correction
  6. sublr,lr,#\correction
  7. @不同的异常预读取的指令到异常跳转的长度不同,返回地址应减去#\correction
  8. .endif

  9. @
  10. @ Save r0,lr_<exception>(parent PC)andspsr_<exception>
  11. @(parent CPSR)
  12. @
  13. stmiasp,{r0,lr}@ save r0,lr
  14. mrslr,spsr
  15. strlr,[sp,#8]@ save spsr

  16. @
  17. @ PrepareforSVC32 mode.IRQs remain disabled.
  18. @
  19. mrsr0,cpsr
  20. eorr0,r0,#(\mode ^ SVC_MODE)
  21. msrspsr_cxsf,r0

  22. @
  23. @ the branch table must immediately follow this code @次级跳转表要紧跟其后。
  24. @
  25. andlr,lr,#0x0f@保留第4位
  26. movr0,sp@栈指针保存到r0
  27. ldrlr,[pc,lr,lsl #2]@ lr=(lr<<2)+pc
  28. movspc,lr@ branchtohandlerinSVC mode
  29. .endm
  30. 在看一下带入这个宏的扩展。
  31. vector_stubirq,IRQ_MODE,4

  32. .long__irq_usr@ 0(USR_26/USR_32)用户模式的中断
  33. .long__irq_invalid@ 1(FIQ_26/FIQ_32)
  34. .long__irq_invalid@ 2(IRQ_26/IRQ_32)
  35. .long__irq_svc@ 3(SVC_26/SVC_32)管理模式的中断
  36. .long__irq_invalid@ 4
  37. .long__irq_invalid@ 5
  38. .....
看回trap_init函数
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
这句就是上面分析的由宏展开的跳转表代码段复制到vectors + 0x200地址。
关于stubs_offset的分析,
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
引用网上摘抄的一段话,
当汇编器看到B指令后会把要跳转的标签转化为相对于当前PC的偏移量(±32M)写入指令码。从上面的代码可以看到中断向量表和stubs都发生了代码搬移,所以如果中断向量表中仍然写成b vector_irq,那么实际执行的时候就无法跳转到搬移后的vector_irq处,因为指令码里写的是原来的偏移量,所以需要把指令码中的偏移量写成搬移后的。我们把搬移前的中断向量表中的irq入口地址记irq_PC,它在中断向量表的偏移量就是irq_PC-vectors_start, vector_irq在stubs中的偏移量是vector_irq-stubs_start,这两个偏移量在搬移前后是不变的。搬移后 vectors_start在0xffff0000处,而stubs_start在0xffff0200处,所以搬移后的vector_irq相对于中断 向量中的中断入口地址的偏移量就是,200+vector_irq在stubs中的偏移量再减去中断入口在向量表中的偏移量,即200+ vector_irq-stubs_start-irq_PC+vectors_start = (vector_irq-irq_PC) + vectors_start+200-stubs_start,对于括号内的值实际上就是中断向量表中写的vector_irq,减去irq_PC是由汇编器完成的,而后面的 vectors_start+200-stubs_start就应该是stubs_offset,实际上在entry-armv.S中也是这样定义的。

由此可见
b vector_irq + stubs_offset 将跳转到__irq_usr或__irq_svc的分支。

__irq_usr的分支会继续处理,会保存寄存器等,然后跳转到真正的处理程序,摘取片段
  1. __irq_usr:
  2. usr_entry@一个宏

  3. #ifdef CONFIG_TRACE_IRQFLAGS
  4. bltrace_hardirqs_off
  5. #endif
  6. get_thread_info tsk
  7. #ifdef CONFIG_PREEMPT
  8. ldrr8,[tsk,#TI_PREEMPT]@getpreempt count
  9. addr7,r8,#1@ increment it
  10. strr7,[tsk,#TI_PREEMPT]
  11. #endif

  12. irq_handler@中断处理函数(这是一个宏)
  13. #ifdef CONFIG_PREEMPT
  14. ldrr0,[tsk,#TI_PREEMPT]
  15. strr8,[tsk,#TI_PREEMPT]
  16. teqr0,r7
  17. strner0,[r0,-r0]
  18. .......
代码中的irq_handler这个是个宏就在entry_asmv.S文件开始处,会调用到asm_do_IRQ,

  1. .macroirq_handler
  2. get_irqnr_preamble r5,lr
  3. 1:get_irqnr_and_base r0,r6,r5,lr @此宏获得中断号和base(?)
  4. ?--------?

  5. movner1,sp
  6. @
  7. @ routine called with r0=irq number,r1=struct pt_regs*
  8. @
  9. adrnelr,1b
  10. bneasm_do_IRQ
asm_do_IRQ(在arch/arm/kernel/irq.c中),

  1. asmlinkage void __exception asm_do_IRQ(unsignedintirq,struct pt_regs*regs)
  2. {
  3. struct pt_regs*old_regs=set_irq_regs(regs);
  4. struct irq_desc*desc=irq_desc+irq;

  5. /*
  6. *Some hardware gives randomly wrong interrupts.Rather
  7. *than crashing,dosomething sensible.
  8. */
  9. if(irq>=NR_IRQS)
  10. desc=&bad_irq_desc;

  11. irq_enter();

  12. desc_handle_irq(irq,desc);

  13. /*AT91 specific workaround*/
  14. irq_finish(irq);

  15. irq_exit();
  16. set_irq_regs(old_regs);
  17. }
在这个函数中会看到irq_desc变量,desc_handle_irq(irq, desc);则会调用到相应的中断,所以到这里我们已经分析了第一个问题,到了要分析第二个问题的时候了,

二,irq_desc数组在linux中如何建立初始化
irq_desc数组的的定义在kernel/irq/handler.c中,

  1. struct irq_desc irq_desc[NR_IRQS]__cacheline_aligned_in_smp={
  2. [0...NR_IRQS-1]={
  3. .status=IRQ_DISABLED,
  4. .chip=&no_irq_chip,
  5. .handle_irq=handle_bad_irq,
  6. .depth=1,
  7. .lock=__SPIN_LOCK_UNLOCKED(irq_desc->lock),
  8. #ifdef CONFIG_SMP
  9. .affinity=CPU_MASK_ALL
  10. #endif
  11. }
  12. };
我们可以结合文章开始处提到的irq_desc结构体的定义,看下都在定义时初始化了那些成员。进一步,在inin/main.c的start_kernel中,也即trap_init调用的后面会调用的init_IRQ(arch/arm/kernel/irq.c).此函数初始化irq_desc数组中一些成员,如下,

  1. void __init init_IRQ(void)
  2. {
  3. intirq;

  4. for(irq=0;irq<NR_IRQS;irq++)(1)
  5. irq_desc[irq].status|=IRQ_NOREQUEST|IRQ_NOPROBE;

  6. init_arch_irq(); (2)
  7. }
(1)将irq_desc数组中所有成员状态加上|= IRQ_NOREQUEST | IRQ_NOPROBE;
(2)调用相应体系的中断初始化函数,(函数的关联在machine_desc结构中的init_irq成员),在这里它实际指向s3c24xx_init_irq(arch/arm/plat-s3c24xx/irq.c)函数。
这个函数先设置寄存器清除所有中断。然后设置各个中断,我们以IRQ_EINT4到IRQ_EINT23为例。

  1. for(irqno=IRQ_EINT4;irqno<=IRQ_EINT23;irqno++){
  2. irqdbf("registering irq %d (extended s3c irq)\n",irqno);
  3. set_irq_chip(irqno,&s3c_irqext_chip);(1)
  4. set_irq_handler(irqno,handle_edge_irq);
  5. set_irq_flags(irqno,IRQF_VALID);
  6. }
(1)set_irq_chip(irqno, &s3c_irqext_chip),在kernel\irq\Chip.c中。这个函数的作用相当于desc[irqno].chip = &s3c_irqext_chip.这里贴出struct irq_chip的定义的注释。

  1. /**
  2. *struct irq_chip-hardware interrupt chip descriptor
  3. *
  4. *@name:namefor/proc/interrupts
  5. *@startup:start up the interrupt(defaultsto->enableifNULL)
  6. *@shutdown:shut down the interrupt(defaultsto->disableifNULL)
  7. *@enable:enable the interrupt(defaultstochip->unmaskifNULL)
  8. *@disable:disable the interrupt(defaultstochip->maskifNULL)
  9. *@ack:start of a new interrupt
  10. *@mask:mask an interrupt source
  11. *@mask_ack:ackandmask an interrupt source
  12. *@unmask:unmask an interrupt source
  13. *@eoi:endof interrupt-chip level
  14. *@end:endof interrupt-flow level
  15. *@set_affinity:setthe CPU affinityonSMP machines
  16. *@retrigger:resend an IRQtothe CPU
  17. *@set_type:setthe flow type(IRQ_TYPE_LEVEL/etc.)of an IRQ
  18. *@set_wake:enable/disable power-management wake-onof an IRQ
  19. *
  20. *@release:releasefunctionsolely used by UML
  21. *@typename:obsoleted by name,kept as migration helper
  22. */
可以看出这个结构体封装了一些底层的操作函数。我们再继续看s3c_irqext_chip的定义,

  1. static struct irq_chip s3c_irqext_chip={
  2. .name="s3c-ext",
  3. .mask=s3c_irqext_mask,//(1)
  4. .unmask=s3c_irqext_unmask,
  5. .ack=s3c_irqext_ack,
  6. .set_type=s3c_irqext_type,
  7. .set_wake=s3c_irqext_wake
  8. };
(1)看一个示例,屏蔽此中断号对应的中断屏蔽寄存器,s3c_irqext_mask,如下,

  1. s3c_irqext_mask(unsignedintirqno)
  2. {
  3. unsigned long mask;

  4. irqno-=EXTINT_OFF;

  5. mask=__raw_readl(S3C24XX_EINTMASK);//读取EINTMASK寄存器的值(?)
  6. ?------------?
  7. mask|=(1UL<<irqno);
  8. __raw_writel(mask,S3C24XX_EINTMASK);
  9. }
初始化里所有的irq_desc数组成员,第二个问题差不多就分析到这里,在看第三个问题之前,再看一下asm_do_IRQ函数中,desc_handle_irq(irq, desc)这句,它调用desc中的handler_irq函数。在这里说明一下像EINT8-EINT23这样的中断,asm_do_IRQ函数中irq中断号只有32个取值。所以如果是EINT8引起的中断,在asm_do_IRQ通过寄存器INTOFFSET寄存器,在这里首先只能确定到是IRQ_EINT8t32,进而再调用irq_desc[IRQ_EINT8t32].handler_irq。这个函数设置为s3c_irq_demux_extint8,它可以进一步通过寄存器EITPEND等,确定是EINT8引起的中断,软后重新计算中断号,调用新计算出的中断号的hanlde_irq函数。
三。request_irq实现的简略分析

  1. action=kmalloc(sizeof(struct irqaction),GFP_ATOMIC);
  2. if(!action)
  3. return-ENOMEM;

  4. action->handler=handler;
  5. action->flags=irqflags;
  6. cpus_clear(action->mask);
  7. action->name=devname;
  8. action->next=NULL;
  9. action->dev_id=dev_id;

  10. select_smp_affinity(irq);
  11. retval=setup_irq(irq,action);
函数申请了struct irqaction的一个结构体,在irq_desc数组成员中包含此结构体,此结构体可以构成一个链表。即一个irq_desc数组成员可以关联上多个action。
setup_irq(irq, action);函数会检查相应的条件,然后将新申请的action结构添加到此链表中。然后会调用chip->startup等函数来使能中断。总的来数request_irq函数的任务就是,
1.将新的中断处理函数链接到irq_desc数组相应中断号成员的action链表中。
2.设置中断触发方式等
3.中断被使能,此时中断已经可以使用了。
分享到:
评论

相关推荐

    Linux中断处理原理分析

    Linux中断下半部处理有三种方式:软中断、tasklet、工作队列。  曾经有人问我为什么要分这几种,该怎么用。当时用书上的东西蒙混了过去,但是自己明白自己实际上是不懂的。近有时间了,于是试着整理一下linux的...

    深入分析Linux内核源码

    第三章中断机制 3.1 中断基本知识 3.1.1 中断向量 3.1.2 外设可屏蔽中断 3.1.3异常及非屏蔽中断 3.1.4中断描述符表 3.1.5 相关汇编指令 3.2中断描述符表的初始化 3.2. 1 外部中断向量的设置 3.2.2中断描述...

    Linux内核源代码分析视频课-视频教程网盘链接提取码下载.txt

    然后开始分析Linux内核源代码,从系统调用陷入内核,进程调度与进程切换,最后返回到用户态进程,通过仔细分析梳理这一过程,并推广到硬件中断、缺页异常等内核执行路径,最终能从本质上把握Linux内核的实质,乃至在...

    清华大学Linux操作系统原理与应用

    1.5.3 Linux内核源代码分析工具 14 习题1 15 第2章 内存寻址 17 2.1 内存寻址简介 17 2.1.1 Intel x86 CPU寻址方式的演变 18 2.1.2 IA32寄存器简介 19 2.1.3 物理地址、虚拟地址及线性地址 21 2.2 分段机制 22 2.2.1...

    嵌入式Linux应用程序开发标准教程(第2版全)

    接着系统地讲解了嵌入式Linux的环境搭建,以及嵌入式Linux的I/O与文件系统的开发、进程控制开发、进程间通信开发、网络应用开发、基于中断的开发、设备驱动程序的开发以及嵌入式图形界面的开发等,并且还安排了丰富...

    嵌入式Linux应用程序开发详解

    12.2.2 Qt/Embedded信号和插槽机制 405 12.2.3 搭建Qt/Embedded开发环境 409 12.2.4 Qt/Embedded窗口部件 410 12.2.5 Qt/Embedded图形界面编程 414 12.2.6 Qt/Embedded对话框设计 416 12.3 实验内容...

    Linux高性能服务器编程

    内容简介《Linux高性能服务器编程》是Linux服务器编程领域的经典著作,由资深Linux软件开发工程师撰写,从网络协议、服务器编程核心要素、原理机制、工具框架等多角度全面阐释了编写高性能Linux服务器应用的方法、...

    学ARM和学单片机一样简单12

    三、S3C444B0X I/O口与中断的分析(第三讲) 1、S3C444B0X I/O口(37分钟) (1)、I/O口寄存器配置 (2)、I/O口控制LED代码分析 (3)、UART寄存器配置及代码简要分析 2、 中断(46分钟) (1)、中断模式...

    学ARM和学单片机一样简单4

    三、S3C444B0X I/O口与中断的分析(第三讲) 1、S3C444B0X I/O口(37分钟) (1)、I/O口寄存器配置 (2)、I/O口控制LED代码分析 (3)、UART寄存器配置及代码简要分析 2、 中断(46分钟) (1)、中断模式...

    学ARM和学单片机一样简单15

    三、S3C444B0X I/O口与中断的分析(第三讲) 1、S3C444B0X I/O口(37分钟) (1)、I/O口寄存器配置 (2)、I/O口控制LED代码分析 (3)、UART寄存器配置及代码简要分析 2、 中断(46分钟) (1)、中断模式...

    学ARM和学单片机一样简单3

    三、S3C444B0X I/O口与中断的分析(第三讲) 1、S3C444B0X I/O口(37分钟) (1)、I/O口寄存器配置 (2)、I/O口控制LED代码分析 (3)、UART寄存器配置及代码简要分析 2、 中断(46分钟) (1)、中断模式...

    学ARM和学单片机一样简单9

    三、S3C444B0X I/O口与中断的分析(第三讲) 1、S3C444B0X I/O口(37分钟) (1)、I/O口寄存器配置 (2)、I/O口控制LED代码分析 (3)、UART寄存器配置及代码简要分析 2、 中断(46分钟) (1)、中断模式...

    学ARM和学单片机一样简单11

    三、S3C444B0X I/O口与中断的分析(第三讲) 1、S3C444B0X I/O口(37分钟) (1)、I/O口寄存器配置 (2)、I/O口控制LED代码分析 (3)、UART寄存器配置及代码简要分析 2、 中断(46分钟) (1)、中断模式...

    学ARM和学单片机一样简单7

    三、S3C444B0X I/O口与中断的分析(第三讲) 1、S3C444B0X I/O口(37分钟) (1)、I/O口寄存器配置 (2)、I/O口控制LED代码分析 (3)、UART寄存器配置及代码简要分析 2、 中断(46分钟) (1)、中断模式...

    学ARM和学单片机一样简单5

    三、S3C444B0X I/O口与中断的分析(第三讲) 1、S3C444B0X I/O口(37分钟) (1)、I/O口寄存器配置 (2)、I/O口控制LED代码分析 (3)、UART寄存器配置及代码简要分析 2、 中断(46分钟) (1)、中断模式...

    学ARM和学单片机一样简单13

    三、S3C444B0X I/O口与中断的分析(第三讲) 1、S3C444B0X I/O口(37分钟) (1)、I/O口寄存器配置 (2)、I/O口控制LED代码分析 (3)、UART寄存器配置及代码简要分析 2、 中断(46分钟) (1)、中断模式...

    学ARM和学单片机一样简单6

    三、S3C444B0X I/O口与中断的分析(第三讲) 1、S3C444B0X I/O口(37分钟) (1)、I/O口寄存器配置 (2)、I/O口控制LED代码分析 (3)、UART寄存器配置及代码简要分析 2、 中断(46分钟) (1)、中断模式...

    学ARM和学单片机一样简单14

    三、S3C444B0X I/O口与中断的分析(第三讲) 1、S3C444B0X I/O口(37分钟) (1)、I/O口寄存器配置 (2)、I/O口控制LED代码分析 (3)、UART寄存器配置及代码简要分析 2、 中断(46分钟) (1)、中断模式...

Global site tag (gtag.js) - Google Analytics