基于设备树的内核中断子系统分析(二)

内核版本:linux-4.19.8

本文以s3c2440处理器为例,分析内核中断子系统具体函数调用过程。

一、中断控制子系统的初始化

1、irq_desc初始化,是对struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp这个数组的初始化,

start_kernel
	early_irq_init();
int __init early_irq_init(void)
{
	int count, i, node = first_online_node;
	struct irq_desc *desc;

	init_irq_default_affinity();

	printk(KERN_INFO "NR_IRQS: %d\n", NR_IRQS);

	desc = irq_desc;//irq_desc数组指针
	count = ARRAY_SIZE(irq_desc);

	for (i = 0; i < count; i++) {
		desc[i].kstat_irqs = alloc_percpu(unsigned int);//每个cpu的irq统计数据
		alloc_masks(&desc[i], node);
		raw_spin_lock_init(&desc[i].lock);
		lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
		desc_set_defaults(i, &desc[i], node, NULL, NULL);//初始化每一项irq_desc
	}
	return arch_early_irq_init();
}

2、irq domain注册

   在drivers/irqchip/irq-s3c24xx.c中存在这样一个函数,采用宏定义在段属性中,如下所示:

int __init s3c2410_init_intc_of(struct device_node *np,
            struct device_node *interrupt_parent){
    return s3c_init_intc_of(np, interrupt_parent,
                s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl));
}
IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);

#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
#define OF_DECLARE_2(table, name, compat, fn) \
        _OF_DECLARE(table, name, compat, fn, of_init_fn_2)
#define _OF_DECLARE(table, name, compat, fn, fn_type)           \
    static const struct of_device_id __of_table_##name      \
        __used __section(__##table##_of_table)          \
         = { .compatible = compat,              \
             .data = (fn == (fn_type)NULL) ? fn : fn  }

展开为:
    static const struct of_device_id __of_table_s3c2410_irq     \
        __used __section("__irqchip_of_table")          \
         = { .compatible = "samsung,s3c2410-irq",               \
             .data = s3c2410_init_intc_of  }

它定义了一个of_device_id结构体, 段属性为"__irqchip_of_table", 在编译内核时这些段被放在__irqchip_of_table地址处。即__irqchip_of_table起始地址处,放置了一个或多个 of_device_id, 它含有compatible成员;设备树中的设备节点含有compatible属性,如果双方的compatible相同, 并且设备节点含有"interrupt-controller"属性,则调用of_device_id中的函数来处理该设备节点。所以: IRQCHIP_DECLARE 是用来声明设备树中的中断控制器的处理函数。

我们分析内核启动:

start_kernel
	init_IRQ();
		if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
			irqchip_init();
				of_irq_init(__irqchip_of_table);
of_irq_init
	for_each_matching_node_and_match(np, matches, &match) //查找device node是否匹配__irqchip_of_table
	desc = kzalloc(sizeof(*desc), GFP_KERNEL);
	list_add_tail(&desc->list, &intc_desc_list);
	while (!list_empty(&intc_desc_list)) {
		ret = desc->irq_init_cb(desc->dev,desc->interrupt_parent);//调用初始化函数,初始化处理器的父中断控制器
	}

如上面分析所示,当内核启动在初始化IRQ时,发现设备树和__irqchip_of_table这个段属性中,双方的compatible相同, 并且设备节点含有"interrupt-controller"属性,则调用of_device_id中的函数来处理。即调用s3c2410_init_intc_of 。

接下来分析s3c2410_init_intc_of:

s3c2410_init_intc_of
	s3c_init_intc_of(np, interrupt_parent,s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl));
		reg_base = of_iomap(np, 0);//映射中断控制器地址
		irq_domain_add_linear(np, num_ctrl * 32,&s3c24xx_irq_ops_of, NULL);
			__irq_domain_add(of_node_to_fwnode(of_node), size, size, 0, ops, host_data);//将irq domain添加到链表中,设置irq domain 操作函数、中断控制器基本信息	
		s3c24xx_clear_intc(intc); //清中断	
		set_handle_irq(s3c24xx_handle_irq);//为中断控制器设置callback,当中断发生时,会调用这个函数

分析函数调用关系发现,s3c2410_init_intc_of通过传入的父中断控制器和中断节点为每一个中断建立一个irq domain,并挂接在irq domain链表中,完成irq domain的注册。

二、设备树解析获得中断资源,进行irq domain映射

1、设备树解析获取硬件中断资源

of_platform_default_populate_init
	of_platform_default_populate(NULL, NULL, NULL);
		of_platform_populate(root, of_default_bus_match_table, lookup,parent);
			of_platform_bus_create(child, matches, lookup, parent, true);
				of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
					of_device_alloc(np, bus_id, parent);
						of_irq_to_resource_table//获取到硬件中断资源
							of_irq_to_resource
								of_irq_get(dev, index);
									of_irq_parse_one(dev, index, &oirq);//解析硬件中断信息
									domain = irq_find_host(oirq.np);//根据device node查找之前注册的irq domain

分析内核代码,就可以发现,内核platform在处理设备树信息时,就会解析硬件中断资源。为irq domain提供硬件中断信息。

2、irq domain映射

irq_create_of_mapping(&oirq);//进入irq domain开始映射
	irq_create_fwspec_mapping(&fwspec);
		irq_domain_translate(domain, fwspec, &hwirq, &type)
			if (d->ops->xlate)//这里会调用xlate函数判断中断是不是父中断,如果是父中断,设分发函数
				return d->ops->xlate(d, to_of_node(fwspec->fwnode),
									fwspec->param, fwspec->param_count,
									hwirq, type);	
		virq = irq_find_mapping(domain, hwirq);
		if (virq) {
			/*如果HW interrupt number已经映射,那么获取中断触发方式,如果没有设置,就设置它,如果已经设置则判断设置是否正确*/
			irq_get_trigger_type
			irqd_set_trigger_type
			
			return 0;
		}
		virq = irq_create_mapping(domain, hwirq);//没有创建映射,就创建映射
			of_node = irq_domain_get_of_node(domain);
			/* Check if mapping already exists */
			virq = irq_find_mapping(domain, hwirq);//再次检查,搞不懂为啥要检查两次
			//如果没有映射,就在irq_desc[NR_IRQS]查找以hwirq为下标依次第一项标记为0的项,数组标号即为虚拟中断号
			virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);
				__irq_alloc_descs(-1, hint, cnt, node, THIS_MODULE,affinity);
					bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS,from, cnt, 0);
			irq_domain_associate
				struct irq_data *irq_data = irq_get_irq_data(virq);
				if (domain->ops->map) {
					ret = domain->ops->map(domain, virq, hwirq);
						irq_domain_set_mapping(domain, hwirq, irq_data);
							if (hwirq < domain->revmap_size)
								domain->linear_revmap[hwirq] = irq_data->irq;//将HW interrupt number和 IRQ number的映射保存
				irq_clear_status_flags(virq, IRQ_NOREQUEST);//表示这个中断号IRQ number可以被驱动程序使用了

irq domain通过platform传过来的硬件中断信息,处理中断控制器的各种复杂情况,如:级联、直达CPU等。设置irq desc结构数据,chip结构数据。为中断提供如屏蔽中断、使能中断、中断回复等接口。最终将HW interrupt number和IRQ number一一对应,保存在domain->linear_revmap[hwirq] = irq_data->irq。

3、关于map xlate

这两个函数在irq domain映射中,至关重要。xlate用来识别中断源,输出硬件中断号,获取中断触发方式,调用irq domain为每一个HW interrupt number映射一个IRQ number。map函数为每个虚拟中断号绑定的irq desc结构分配handle、chip结构函数,以及一些私有的数据。

static const struct irq_domain_ops s3c24xx_irq_ops_of = {
	.map = s3c24xx_irq_map_of,
	.xlate = s3c24xx_irq_xlate_of,
};
irq domain在映射IRQ number时,会先调用xlate,判断是不是父中断节点
	*out_hwirq = intspec[0] * 32 + intspec[2];//获取硬件中断号
	*out_type = intspec[3] & IRQ_TYPE_SENSE_MASK;//获取中断触发方式
	if (parent_intc) {//如果是父中断,设置分发函数
		irq_data = &intc->irqs[intspec[2]];
		irq_data->parent_irq = intspec[1];
		parent_irq_data = &parent_intc->irqs[irq_data->parent_irq];
		parent_irq_data->sub_intc = intc;
		parent_irq_data->sub_bits |= (1UL << intspec[2]);

		/* parent_intc is always s3c_intc[0], so no offset */
		irqno = irq_create_mapping(parent_intc->domain, intspec[1]);
		if (irqno < 0) {
			pr_err("irq: could not map parent interrupt\n");
			return irqno;
		}
		irq_set_chained_handler(irqno, s3c_irq_demux);
	}
map 函数,为每个虚拟中断号的irq_desc设置handler、chip和chip data
s3c24xx_irq_map_of
	if (!parent_intc)
		irq_set_chip_and_handler(virq, &s3c_irq_chip, handle_edge_irq);
	else
		irq_set_chip_and_handler(virq, &s3c_irq_level_chip,handle_edge_irq);
	irq_set_chip_data(virq, irq_data);	
irq_set_chip_and_handler
	irq_set_chip_and_handler_name
		irq_set_chip(irq, chip);
			desc->irq_data.chip = chip;//设置desc的chip
		__irq_set_handler(irq, handle, 0, name);
			__irq_do_set_handler(desc, handle, is_chained, name);
				desc->handle_irq = handle;//设置desc的handle
				desc->name = name;
				irq_activate_and_startup(desc, IRQ_RESEND);//使能中断

三、中断发生

在处理器注册irq domain时,s3c_init_intc_of中提供了中断接口set_handle_irq(s3c24xx_handle_irq);,当中断发生时,进入s3c24xx_handle_irq,通过硬件中断号,获取虚拟中断号,调用handle处理中断。

s3c24xx_handle_irq
	s3c24xx_handle_intc
		handle_domain_irq(intc->domain, intc_offset + offset, regs);
			__handle_domain_irq(domain, hwirq, true, regs);
				irq = irq_find_mapping(domain, hwirq);
				generic_handle_irq(irq);
					generic_handle_irq_desc(desc);
						desc->handle_irq(desc);
				//执行irq domain映射IRQ number时绑定的irq_desc中的handle,处理中断。最终调用action中的handler通知驱动,处理中断数据。

四、驱动程序申请中断

int request_threaded_irq(unsigned int irq, 
                        irq_handler_t handler,
                        irq_handler_t thread_fn, 
                        unsigned long irqflags,
                        const char *devname, 
                        void *dev_id)             
irq            //要注册handler的那个IRQ number。
handler        //当中断发生时,调用这个handle,如果handler == NULL,thread_fn != NULL,则默认primary handler已经存在
thread_fn    //threaded interrupt handler。如果该参数不是NULL,那么系统会创建一个kernel thread,调用的function就是thread_fn
irqflags    //中断类型
devname         //中断名字,这个不重要
dev_id        //如果您的中断是共享的,那么在释放中断时必须传递一个非空的dev_id。


irqflags:
IRQF_SHARED         //允许在多个设备之间共享irq
IRQF_PROBE_SHARED    //由调用者在期望发生共享不匹配时设置
IRQF_TIMER            //标记此中断为计时器中断
IRQF_PERCPU            //中断为每个cpu
IRQF_NOBALANCING    //从irq平衡中排除此中断的标志
IRQF_IRQPOLL        //中断用于轮询(出于性能原因,只考虑在共享中断中首先注册的中断)
IRQF_ONESHOT        //在hardirq处理程序完成后,不重新启用中断。用于线程中断,它需要在线程处理程序运行之前禁用irq中                                        断线。
IRQF_NO_SUSPEND        //在挂起期间不要禁用此IRQ。不保证此中断将系统从挂起状态唤醒
IRQF_FORCE_RESUME    //即使设置了IRQF_NO_SUSPEND,也要在恢复时强制启用它
IRQF_NO_THREAD        //中断不能线程化
IRQF_EARLY_RESUME     //在syscore期间尽早恢复IRQ,而不是在设备恢复时。
IRQF_COND_SUSPEND    //如果IRQ与NO_SUSPEND用户共享,那么在挂起中断之后执行这个中断处理程序。对于系统唤醒                                                设备,用户需要在中断处理程序中实现唤醒检测。

对于request_threaded_irq申请中断简析

request_threaded_irq
	desc = irq_to_desc(irq);
	action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
	if (!action)
		return -ENOMEM;
	action->handler = handler;
	action->thread_fn = thread_fn;
	action->flags = irqflags;
	action->name = devname;
	action->dev_id = dev_id;	
	retval = __setup_irq(irq, desc, action);	
__setup_irq
	nested = irq_settings_is_nested_thread(desc);
	if (nested) {//如果是nested
		if (!new->thread_fn) {
			ret = -EINVAL;
			goto out_mput;
		}
		/*
		 * Replace the primary handler which was provided from
		 * the driver for non nested interrupt handling by the
		 * dummy function which warns when called.
		 */
		new->handler = irq_nested_primary_handler;//重新设置handler
	} else {
		if (irq_settings_can_thread(desc)) {//判断是否可以线程化
			ret = irq_setup_forced_threading(new);
			if (ret)
				goto out_mput;
		}
	}
/*
	 * Create a handler thread when a thread function is supplied
	 * and the interrupt does not nest into another interrupt
	 * thread.
	 */
	if (new->thread_fn && !nested) {
		ret = setup_irq_thread(new, irq, false);//创建线程
		if (ret)
			goto out_mput;
		if (new->secondary) {
			ret = setup_irq_thread(new->secondary, irq, true);
			if (ret)
				goto out_thread;
		}
	}
	setup_irq_thread
		irq_thread
			while (!irq_wait_for_interrupt(action)) {//等待中断
			}

五、s3c2440按键中断实例

1、不使用设备树

加载编译好的buttons.ko ,在后台运行测试程序buttons_test ,    cat     /proc/interrupts

这里测试了IRQ_EINT0、IRQ_EINT2、IRQ_EINT11、IRQ_EINT19四个中断,虚拟中断号跟arch/arm/mach-s3c24xx/include/mach/irqs.h中定义的是一致的,这充分说明了之前的分析,在为引入设备树时,处理器的虚拟中断号,是查阅datasheet后直接定义在arch/arm/mach-s3c24xx/include/mach/irqs.h,再调用irq domain接口,完成irq domain相应操作。

2、使用设备树

使用设备树时,硬件中断号在dts文件中指定,内核在解析设备树时,会根据dtb文件,来进入irq domain动态映射虚拟中断号。

如下所示的dts文件,指定了中断的父中断,中断号,中断产生的触发方式。GPIO控制器这里也属于一个中断控制器。

buttons {
        compatible = "jz2440_button";
        eint-pins  = <&gpf 0 0>, <&gpf 2 0>, <&gpg 3 0>, <&gpg 11 0>;
        interrupts-extended = <&intc 0 0 0 3>,
                              <&intc 0 0 2 3>,
                              <&gpg 3 3>,
                              <&gpg 11 3>;
    };

可以查看到虚拟中断号跟之前固化在.h文件中不同。结合之前的分析,这个映射是动态的,若果实现注册几个其他的中断,再来注册按键中断,虚拟中断号又不一样了。

还可以通过irq domain的log分析:

直达CPU的中断,会以硬件中断号为下标,查找irq_desc数组,若HW interrupt number这一项已经被占用时,就会继续寻找下一项。

通过 父中断控制器级联的中断,会根据父中断硬件中断号查找到irq_desc数组中HW interrupt number这一项,当然这里假设这一项没有被占用。根据irq desc的handle读取具体是哪一个中断源产生的中断,并将irq_desc数组中HW interrupt number这一项的下一项没有被占用的数组标号设置为当前硬件中断源的虚拟中断号。

irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 28, hwirq = 28
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 32, hwirq = 32
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 33, hwirq = 33
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 9, hwirq = 9
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 59, hwirq = 59
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 30, hwirq = 30
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 8, hwirq = 8
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 24, hwirq = 24
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 26, hwirq = 26
irq_create_mapping, domain's name = gpf, virq = 7, hwirq = 7
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 16, hwirq = 16
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 31, hwirq = 31
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 41, hwirq = 41
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 42, hwirq = 42
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 17, hwirq = 17
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 18, hwirq = 18
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 19, hwirq = 19
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 20, hwirq = 20
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 1, hwirq = 0
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 2, hwirq = 2
irq_create_mapping, domain's name = gpg, virq = 3, hwirq = 3
irq_create_mapping, domain's name = gpg, virq = 15, hwirq = 11

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《linux内核源代码情景分析》(非扫描电版本) 第1章 预备知识 1.1 Linux内核简介 1.2 Intel X86 CPU系列的寻址方式 1.3 i386的页式内存管理机制 1.4 Linux内核源代码中的C语言代码 1.5 Linux内核源代码中的汇编语言代码 第2章 存储管理 2.1 Linux内存管理的基本框架 2.2 地址映射的全过程 2.3 几个重要的数据结构和函数 2.4 越界访问 2.5 用户堆栈的扩展 2.6 物理页面的使用和周转 2.7 物理页面的分配 2.8 页面的定期换出 2.9 页面的换入 2.10 内核缓冲区的管理 2.11 外部设备存储空间的地址映射 2.12 系统调用brk() 2.13 系统调用mmap() 第3章 中断、异常和系统调用 3.1 X86 CPU对中断的硬件支持 3.2 中断向量表IDT的初始化 3.3 中断请求队列的初始化 3.4 中断的响应和服务 3.5 软中断与Bottom Half 3.6 页面异常的进入和返回 3.7 时钟中断 3.8 系统调用 3.9 系统调用号与跳转表 第4章 进程与进程调度 4.1 进程四要素 4.2 进程三部曲:创建、执行与消亡 4.3 系统调用fork()、vfork()与clone() 4.4 系统调用execve() 4.5 系统调用exit()与wait4() 4.6 进程的调度与切换 4.7 强制性调度 4.8 系统调用nanosleep()和pause() 4.9 内核中的互斥操作 第5章 文件系统 5.1 概述 5.2 从路径名到目标节点 5.3 访问权限与文件安全性 5.4 文件系统的安装和拆卸 5.5 文件的打开与关闭 5.6 文件的写与读 5.7 其他文件操作 5.8 特殊文件系统/proc 第6章 传统的Unix进程间通信 6.1 概述 6.2 管道和系统调用pipe() 6.3 命名管道 6.4 信号 6.5 系统调用ptrace()和进程跟踪 6.6 报文传递 6.7 共享内存 6.8 信号量 第7章基于socket的进程间通信 7.1系统调用socket() 7.2函数sys—socket()——创建插口 7.3函数sys—bind()——指定插口地址 7.4函数sys—listen()——设定server插口 7.5函数sys—accept()——接受连接请求 7.6函数sys—connect()——请求连接 7.7报文的接收与发送 7.8插口的关闭 7.9其他 第8章设备驱动 8.1概述 8.2系统调用mknod() 8.3可安装模块 8.4PCI总线 8.5块设备的驱动 8.6字符设备驱动概述 8.7终端设备与汉字信息处理 8.8控制台的驱动 8.9通用串行外部总线USB 8.10系统调用select()以及异步输入/输出 8.11设备文件系统devfs 第9章多处理器SMP系统结构 9.1概述 9.2SMP结构中的互斥问题 9.3高速缓存与内存的一致性 9.4SMP结构中的中断机制 9.5SMP结构中的进程调度 9.6SMP系统的引导 第10章系统引导和初始化 10.1系统引导过程概述 10.2系统初始化(第一阶段) 10.3系统初始化(第阶段) 10.4系统初始化(第三阶段) 10.5系统的关闭和重引导
【目  录】: 第1篇系统篇 第1章嵌入式系统概述3 1.1嵌入式系统的定义和特点3 1.1.1嵌入式系统的定义3 1.1.2嵌入式系统和通用计算机比较4 1.1.3嵌入式系统的特点5 1.2嵌入式系统的硬件6 1.2.1嵌入式处理器7 1.2.2嵌入式存储器15 1.2.3嵌入式I/O设备18 1.2.4嵌入式I/O接口18 1.3嵌入式系统的软件21 1.3.1无操作系统的嵌入式软件22 1.3.2带操作系统的嵌入式软件24 1.4嵌入式系统的分类27 1.4.1按硬件(嵌入式处理器)划分27 1.4.2按软件复杂度划分27 1.4.3按实时性划分28 1.4.4按使用对象划分28 1.5嵌入式系统的应用28 1.5.1国防军事28 1.5.2工业控制29 1.5.3消费电30 1.5.4办公自动化产品30 1.5.5网络和通信设备30 1.5.6汽车电31◆嵌入式系统原理及应用目录1.5.7金融商业31 1.5.8生物医学32 1.5.9信息家电32 1.6本章小结34 习题134 第2章嵌入式系统开发35 2.1嵌入式系统的开发环境、开发工具和调试方式35 2.1.1嵌入式系统的开发环境35 2.1.2嵌入式系统的开发工具37 2.1.3嵌入式系统的调试方式43 2.2嵌入式系统的开发语言50 2.2.1嵌入式硬件开发语言50 2.2.2嵌入式软件开发语言51 2.3嵌入式系统的开发过程53 2.3.1需求分析54 2.3.2系统设计55 2.3.3系统实现61 2.3.4系统测试70 2.3.5系统发布73 2.4嵌入式开发工程师之路74 2.4.1嵌入式行业和人才的现状分析74 2.4.2嵌入式开发工程师的能力要求74 2.4.3嵌入式开发工程师的进阶之路75 2.5本章小结77 习题278 第2篇内核篇 第3章ARM CortexM3处理器81 3.1ARM CortexM3组成结构81 3.1.1CortexM3内核82 3.1.2调试系统84 3.2ARM CortexM3总线接口86 3.2.1CortexM3总线接口类型87 3.2.2CortexM3总线连接方案88 3.3ARM CortexM3编程模型89 3.3.1工作状态89 3.3.2数据类型89 3.3.3寄存器89 3.3.4指令系统93 3.3.5操作模式和特权分级96 3.3.6异常和中断98 3.3.7双堆栈机制105 3.4ARM CortexM3存储器系统107 3.4.1存储器映射107 3.4.2位带操作110 3.4.3存储格式112 3.5ARM CortexM3的低功耗模式113 3.6本章小结114 习题3115 第4章基于ARM CortexM3的STM32微控制器117 4.1从CortexM3到基于CortexM3的MCU117 4.2基于CortexM3的STM32系列微控制器概述118 4.2.1产品线118 4.2.2命名规则124 4.2.3生态系统125 4.2.4开发方法131 4.2.5学习之路134 4.3STM32F103微控制器基础136 4.3.1概述136 4.3.2主系统结构137 4.3.3功能模块139 4.3.4引脚定义140 4.3.5存储器组织141 4.4STM32F103微控制器的最小系统145 4.4.1电源电路145 4.4.2时钟电路148 4.4.3复位电路149 4.4.4调试和下载电路150 4.4.5其他151 4.5STM32F103微控制器的时钟系统153 4.5.1输入时钟153 4.5.2系统时钟155 4.5.3由系统时钟分频得到的其他时钟155 4.5.4STM32F10x时钟系统相关库函数157 4.6STM32F103微控制器的低功耗模式162

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值