LV.12 D18 中断处理 学习笔记

news2024/9/24 3:19:47

一、ARM的异常处理机制及工程代码结构

1.1异常概念

        处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生     这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件     异常事件处理完成之后再返回到被异常打断的点继续执行程序。

1.2异常处理机制

        不同的处理器对异常的处理的流程大体相似,但是不同的处理器在具体实现的机制上有所不同;比如处理器遇到哪些事件认为是异常事件遇到异常事件之后处理器有哪些动作、处理器如何跳转到异常处理程序如何处理异常、处理完异常之后又如何返回到被打断的程序继续执行等我们将这些细节的实现称为处理器的异常处理机制.

1.3ARM异常源

概念:导致异常产生的事件称为异常源

ARM异常源

FIQ                                快速中断请求引脚有效            

IRQ                                外部中断请求引脚有效    

Reset                             复位电平有效    

Software Interrupt          执行swi指令    

Data Abort                      数据终止    

Prefetch Abort                指令预取终止    

Undefined Instruction    遇到不能处理的指令

1.4异常模式

        在ARM的基本工作模式中有5个属于异常模式,即ARM遇到异常后会切 换成对应的异常模式

1.5ARM异常响应

ARM产生异常后的动作(自动完成)
        1.拷贝CPSR中的内容到对应异常模式下的SPSR_<mode>

        2.修改CPSR的值

                2.1.修改中断禁止位禁止相应的中断

                2.2.修改模式位进入相应的异常模式   

                2.3.修改状态位进入ARM状态

        3.保存返回地址到对应异常模式下的LR_<mode>

        4.设置PC为相应的异常向量(异常向量表对应的地址)

1.6异常向量表

异常向量表
    > 异常向量表的本质是内存中的一段代码

    > 表中为每个异常源分配了四个字节的存储空间

    > 遇到异常后处理器自动将PC修改为对应的地址

    > 因为异常向量表空间有限一般我们不会再这里写异常处理程序,而是在对应的位置写一条跳

      转指令使其跳转到指定的异常处理程序的入口

    注:ARM的异常向量表的基地址默认在0x00地址但可以通过配置协处理器来修改其地址 
    

1.7异常返回

ARM异常返回的动作(自己编写)

        1.将SPSR_<mode>的值复制给CPSR使处理器恢复之前的状态

        2.将LR_<mode>的值复制给PC使程序跳转回被打断的地址继续执行 

1.8 IRQ异常举例

 注:整个过程CPSR保存的永远是当前程序运行状态,SPSR只是异常时对原来的CPSR进行备份

二、工程模板代码结构分析 

common:老师写好的库函数文件,里面实现了很多功能,比如把所有的寄存器封装,比如手搓了一个printf。

interface.c:  我们自己要实现的c源文件

start:  启动文件,任何芯片一上电执行的第一个程序一定是汇编程序,要初始化栈,初始化芯片,将异常向量表基地址位置改变,打开FIQ、IRQ然后跳转到C程序。

makefile:  编译规则

map.lds:   链接脚本,这个工程模板内有很多的C文件S文件H文件,他们编译链接后只生成一个.bin文件写入开发板,哪个文件放入哪个位置,我们写好的文件在内存中的位置都由它决定
 

三、中断处理框架搭建

LR保存的是被打断的下一条指令的地址(指的是汇编指令,一条c语言可能会编译成很多条汇编指令)

在遇到IRQ时会跳转到以_start:为基地址偏移0x18。

 

然后我们在 b main 后面来写这个中断服务程序,因为b main之前的代码都是启动代码,一开始就会执行。而 irq_handler是异常处理程序,芯片刚启动时我们不希望它执行,我们希望它遇到异常的时候再执行。

但是我们不能直接在这里写,因为IRQ模式下有很多寄存器都是和USER模式共用的如果在这里写,可定会用到一些寄存器,这样就会覆盖掉寄存器中本来的内容,返回主程序就不能正确返回这个状态了

所以我们需要先压栈保护现场

这个是时候就可以写了吗,还是不能,我们来复习一下LR寄存器 

R14(LR,Link Register)
    链接寄存器,一般有以下两种用途:

    > 执行跳转指令(BL/BLX)时,LR会自动保存跳转指令下一条指令的地址程序需要返回时将LR的值复制到PC即可实现。

    > 产生异常时,对应异常模式下的LR会自动保存被异常打断的指令的下一条指令的地址,异常处理结束后将LR的值复制到PC可实现程序返回。

原理

    当执行跳转指令或产生异常时,LR寄存器中不会凭空产生一个返回地址。其原理是当执行跳转指令或产生异常时,处理器内部会将PC寄存器中的值拷贝到LR寄存器中,然后再将LR寄存器中的值自减4。

BL

    当执行BL指令时,指令执行过程中处理器内部就会将PC寄存器的值拷贝到LR寄存器,然后再将LR寄存器中的值自减4, 所以LR寄存器中保存的就是BL指令下一条指令的地址。

该时刻PC=N+8 LR=N+4

 

 IRQ中断

    当执行一条指令时产生了一个IRQ中断,执行这条指令过程中处理器不会保存返回地址,而是执行完成后才会保存,但执行完成后PC的值又会自动增4,所以对于IRQ来说LR中保存的是被中断打断的指令的下下条指令的地址。

该时刻PC=N+12 LR=N+8

 

因为产生IRQ异常后自动保存到LR寄存器中的返回地址是被IRQ打断的指令下一条在下一条指令,所以需要我们人为的修复一下。

那么为什么不直接让他返回一个正确的呢,因为ARM是精简指令集,要想直接返回正确的必须要再加一个电路,这样会增加硬件成本,所以不如软件修复一条指令就解决了。

由于这是一个非叶子函数,在这段程序中可能还会有跳转,所以我们干脆把LR也压栈保护一下。
 

 异常处理程序既可以用汇编写,也可以通过混合编程用C语言写,但前面修改LR和压栈两条指令,只能用汇编写。

四、中断处理程序编程

interface.c

#include "exynos_4412.h"

//异常处理程序
void do_irq(void)
{
	printf("Key2 pressed\n");
}

void Delay(unsigned int Time)
{
	while(Time--);
}

int main()
{
	/*外设层次 —— 让外部的硬件控制器产生一个中断信号并发送给中断控制器*/
	/*将GPX1_1设置成中断功能*/
	GPX1.CON = GPX1.CON | (0xF << 4);
	/*设置GPX1_1中断触发方式:下降沿触发*/
	EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
	/*使能GPX1_1的中断功能*/
	EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));	

	/*中断控制器层次 —— 让中断控制器接收外设发来的中断信号并对其进行管理然后再转发给一个合适的CPU去处理*/
	/*全局使能中断控制器,使其能够接收外部设备产生的中断信号并转发给CPU接口*/
	ICDDCR = ICDDCR | 1;

	/*在中断控制器中使能57号中断,使中断控制器在接收到57号中断后,能将其进一步转发到CPU接口*/
	ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);

	/*选择CPU0来处理57号中断*/
	ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0x1 << 8);

	/*将中断控制器和CPU0之间的接口使能,使得中断控制器转发的信号能够到达CPU0*/
	CPU0.ICCICR = CPU0.ICCICR | 1;

	GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);

	while(1)
	{
		/*点亮LED2*/
		GPX2.DAT = GPX2.DAT | (1 << 7);
		/*延时*/
		Delay(1000000);
		/*熄灭LED2*/
		GPX2.DAT = GPX2.DAT & (~(1 << 7));
		/*延时*/
		Delay(1000000);
	}

	return 0;
}

 start.S

.text
.global _start
_start:
	/*
	 * Vector table
	 */ 
	b reset
	b .
	b .
	b .
	b .
	b .
	//从异常向量表再跳转到IRQ的异常处理程序
	b irq_handler
	b .

reset:
	/*
	 * Set vector address in CP15 VBAR register
	 */ 
	ldr	r0, =_start
	mcr	p15, 0, r0, c12, c0, 0	@Set VBAR

	/*
	 * Set the cpu to SVC32 mode, Disable FIQ/IRQ
	 */  
	mrs r0, cpsr
	bic r0, r0, #0x1f
	orr	r0, r0, #0xd3
	msr	cpsr ,r0

	/*
	 * Defines access permissions for each coprocessor
	 */  
    mov	r0, #0xfffffff
    mcr	p15, 0, r0, c1, c0, 2  	

	/*
	 * Invalidate L1 I/D                                                                                                                   
	 */
	mov	r0, #0					@Set up for MCR
	mcr	p15, 0, r0, c8, c7, 0	@Invalidate TLBs
	mcr	p15, 0, r0, c7, c5, 0	@Invalidate icache
	
	/*
	 * Set the FPEXC EN bit to enable the FPU
	 */ 
	mov r3, #0x40000000
	fmxr FPEXC, r3
	
	/*
	 * Disable MMU stuff and caches
	 */
	mrc	p15, 0, r0, c1, c0, 0
	bic	r0, r0, #0x00002000		@Clear bits 13 (--V-)
	bic	r0, r0, #0x00000007		@Clear bits 2:0 (-CAM)
	orr	r0, r0, #0x00001000		@Set bit 12 (---I) Icache
	orr	r0, r0, #0x00000002		@Set bit 1 (--A-) Align
	orr	r0, r0, #0x00000800		@Set bit 11 (Z---) BTB
	mcr	p15, 0, r0, c1, c0, 0

	/*
	 * Initialize stacks                                                                                                                  
	 */
init_stack:     
	/*svc mode stack*/
	msr cpsr, #0xd3
	ldr sp, _stack_svc_end

	/*undef mode stack*/
	msr cpsr, #0xdb
	ldr sp, _stack_und_end

	/*abort mode stack*/	
	msr cpsr,#0xd7
	ldr sp,_stack_abt_end

	/*irq mode stack*/	
	msr cpsr,#0xd2
	ldr sp, _stack_irq_end
	
	/*fiq mode stack*/
	msr cpsr,#0xd1
	ldr sp, _stack_fiq_end
	
	/*user mode stack, enable FIQ/IRQ*/
	msr cpsr,#0x10
	ldr sp, _stack_usr_end

	/*Call main*/
	b main

//IRQ的异常处理程序
irq_handler:
	//因为产生IRQ异常后自动保存到LR中的返回地址是被IRQ打断指令的
	//下一条再下一条指令的地址,所以我们需要人为的去修复一下
	sub lr, lr, #4

	//因为IRQ模式下使用的R0-R12寄存器和USER模式下使用的是同一组
	//所以在处理异常之前需要先将之前USER模式下寄存器的值压栈保护
	stmfd sp!,{r0-r12}

	//处理异常
	bl do_irq

	//异常返回
	//1.将R0-R12寄存器中的值出栈,使其恢复到被异常打断之前的值
	//2.将SPSR寄存器中的值恢复到CPSR,使CPU的状态恢复到被异常打断之前的状态
	//3.将栈中LR寄存器中的值出栈给PC,实现程序的返回
	ldmfd sp!,{r0-r12,pc}^

_stack_svc_end:      
	.word stack_svc + 512
_stack_und_end:      
	.word stack_und + 512
_stack_abt_end:      
	.word stack_abt + 512
_stack_irq_end:      
    .word stack_irq + 512
_stack_fiq_end:
    .word stack_fiq + 512
_stack_usr_end:      
    .word stack_usr + 512

.data
stack_svc:      
	.space 512
stack_und:
	.space 512
stack_abt:      
	.space 512
stack_irq:      
	.space 512
stack_fiq:      
	.space 512
stack_usr:      
	.space 512

 只按了一次按键,就会一直打印Key2 pressed。

中断挂起寄存器, EXT_INT41_PEND[1]对应GPX1_1引脚,会自动置1,置1就会把这个中断挂。当你处理完中断,返回到main函数,此时EXT_INT41_PEND[1]依旧是1,不会自动清零,所以还会给中断控制器发送中断信号,中断控制器还会将中断信号转发给CPU,还会触发中断。所以我们需要在CPU处理完中断后,把挂起位清零。

中断挂起寄存器比较特殊,写1才会清零,写0则保持不变。

我们在异常处理程序中对中断挂起寄存器的第一位进行修改,则按一次按键,只会产生一次中断。

但是有个新问题所有的IRQ异常都会跳到这里,那么我们需要区分一下,但是CPU不知道是谁发来的,所以需要询问中断控制器。

ICCIAR寄存器,后面[31:10]位与本次实验无关,我们只看[9:0]位,中断控制器把几号中断转给CPU,就会往这个寄存器的[9:0]位写几。所以我们可以让CPU在处理中断之前先读取这个寄存器的值,来写不同的中断处理程序。

#include "exynos_4412.h"

//异常处理程序
void do_irq(void)
{
	unsigned int IrqNum = 0;
	/*从中断控制器中获取当前中断的中断号*/
	IrqNum = CPU0.ICCIAR & 0x3FF;

	switch(IrqNum)
	{
		case 0:
			//0号中断的处理程序
			break;
		case 1:
			//1号中断的处理程序
			break;
			/*
		 	* ......
		 	*/
		case 57:
			printf("Key2 pressed\n");
			/*清除GPIO控制器中的中断挂起位*/
			EXT_INT41_PEND = (1 << 1);
			break;
			/*
			 * ......
			 */
		case 159:
			//159号中断的处理程序
			break;
		default:
			break;
	}

}

void Delay(unsigned int Time)
{
	while(Time--);
}

int main()
{
	/*外设层次 —— 让外部的硬件控制器产生一个中断信号并发送给中断控制器*/
	/*将GPX1_1设置成中断功能*/
	GPX1.CON = GPX1.CON | (0xF << 4);
	/*设置GPX1_1中断触发方式:下降沿触发*/
	EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
	/*使能GPX1_1的中断功能*/
	EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));	

	/*中断控制器层次 —— 让中断控制器接收外设发来的中断信号并对其进行管理然后再转发给一个合适的CPU去处理*/
	/*全局使能中断控制器,使其能够接收外部设备产生的中断信号并转发给CPU接口*/
	ICDDCR = ICDDCR | 1;

	/*在中断控制器中使能57号中断,使中断控制器在接收到57号中断后,能将其进一步转发到CPU接口*/
	ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);

	/*选择CPU0来处理57号中断*/
	ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0x1 << 8);

	/*将中断控制器和CPU0之间的接口使能,使得中断控制器转发的信号能够到达CPU0*/
	CPU0.ICCICR = CPU0.ICCICR | 1;

	GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);

	while(1)
	{
		/*点亮LED2*/
		GPX2.DAT = GPX2.DAT | (1 << 7);
		/*延时*/
		Delay(1000000);
		/*熄灭LED2*/
		GPX2.DAT = GPX2.DAT & (~(1 << 7));
		/*延时*/
		Delay(1000000);
	}

	return 0;
}

此时,我们又发现按键只有第一次有效,之后再按按键就没有反应。

因为中断控制器不知道CPU0已经处理完中断处理程序了,所以并没有将新的中断信号发送给CPU0。

 ICCEOIR寄存器,本次实验只看[9:0]位,CPU处理完中断后,会把中断号写入该寄存器。

将当前中断的中断号写回到中断控制器,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其他中断

#include "exynos_4412.h"

//异常处理程序
void do_irq(void)
{
	unsigned int IrqNum = 0;
	/*从中断控制器中获取当前中断的中断号*/
	IrqNum = CPU0.ICCIAR & 0x3FF;

	switch(IrqNum)
	{
		case 0:
			//0号中断的处理程序
			break;
		case 1:
			//1号中断的处理程序
			break;
			/*
		 	* ......
		 	*/
		case 57:
			printf("Key2 pressed\n");
			/*清除GPIO控制器中的中断挂起位*/
			EXT_INT41_PEND = (1 << 1);
			/*将当前中断的中断号写回到中断控制器,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其他中断*/
			CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | 57;
			break;
			/*
			 * ......
			 */
		case 159:
			//159号中断的处理程序
			break;
		default:
			break;
	}

}

void Delay(unsigned int Time)
{
	while(Time--);
}

int main()
{
	/*外设层次 —— 让外部的硬件控制器产生一个中断信号并发送给中断控制器*/
	/*将GPX1_1设置成中断功能*/
	GPX1.CON = GPX1.CON | (0xF << 4);
	/*设置GPX1_1中断触发方式:下降沿触发*/
	EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
	/*使能GPX1_1的中断功能*/
	EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));	

	/*中断控制器层次 —— 让中断控制器接收外设发来的中断信号并对其进行管理然后再转发给一个合适的CPU去处理*/
	/*全局使能中断控制器,使其能够接收外部设备产生的中断信号并转发给CPU接口*/
	ICDDCR = ICDDCR | 1;

	/*在中断控制器中使能57号中断,使中断控制器在接收到57号中断后,能将其进一步转发到CPU接口*/
	ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);

	/*选择CPU0来处理57号中断*/
	ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0x1 << 8);

	/*将中断控制器和CPU0之间的接口使能,使得中断控制器转发的信号能够到达CPU0*/
	CPU0.ICCICR = CPU0.ICCICR | 1;

	GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);

	while(1)
	{
		/*点亮LED2*/
		GPX2.DAT = GPX2.DAT | (1 << 7);
		/*延时*/
		Delay(1000000);
		/*熄灭LED2*/
		GPX2.DAT = GPX2.DAT & (~(1 << 7));
		/*延时*/
		Delay(1000000);
	}

	return 0;
}

这次代码我们需要实现按一次按键就产生一次中断,但个人原因,没有实现,也没找出问题,后续再解决。

五、中断编程补充

中断和轮询:轮询是CPU主动去查看硬件有没有异常产生,而中断是硬件主动通知CPU。中断的效率更高一些,用的较多。

真正开发时有操作系统,我们其实只需要写中断程序,然后打开对应中断就可以

FIQ为什么比IRQ快:

1)FIQ的优先级比IRQ高

2)FIQ可以打断IRQ

3)FIQ在异常向量表的最末,别的中断处理程序需要跳转,而FIQ可以直接往后写

4)FIQ有四组直接独有的寄存器,如果只需要r8-r12的话,他不需要压栈保护现场。但是如果用到了r0-r7还是要压栈保护现场的。


作业

1.使用中断的方式检测Key3按键的状态,实现按一次按键,LED2点亮,再次按下,LED2熄灭

 

 

  

  EINT[10]的中断号是58。

 本次实验依旧采用下降沿触发方式,GPX1_2对应EXT_INT41_CON[2]。

EXT_INT41_MASK[1]对应GPX1_1的开和关。0x0打开中断,0x1关闭中断。我们把它打开。

我们把ICDDCR寄存器写1 ,监控所有的外部中断,并将挂起的中断转发到CPU的接口

ICCDCR寄存器相当于GIC(中断控制器)的总开关。

 ICDISER_CPU寄存器的作用:寄存器接收到中断信号,通过配置该寄存器对应的位,控制该中断信号发送或不发送给CPU。 

 下面这个寄存器是ICDIPTR_CPU,它的作用是为每一个中断选择处理他的CPU。 

哪一位置1,中断信号就发给哪个CPU处理。但4412是一个四核的CPU,所以高四位是没有用的。

 

一共需要40个寄存器来管理这160个中断归属于哪一个CPU处理

想把58号中断交给CPU0处理,则把偏移地址为0x838的寄存器的[23:16]写为00000001即可。

 ICCICR_CPUn寄存器是中断控制器和CPU之间的接口,他就像一个开关,用哪个CPU就要打开哪个。

EXT_INT41_PEND[2]对应GPX1_2引脚,会自动置1,置1就会把这个中断挂。当你处理完中断,返回到main函数,此时EXT_INT41_PEND[2]依旧是1,不会自动清零,所以还会给中断控制器发送中断信号,中断控制器还会将中断信号转发给CPU,还会触发中断。所以我们需要在CPU处理完中断后,把挂起位清零。

中断挂起寄存器比较特殊,写1才会清零,写0则保持不变。

ICCIAR寄存器,后面[31:10]位与本次实验无关,我们只看[9:0]位,中断控制器把几号中断转给CPU,就会往这个寄存器的[9:0]位写几。所以我们可以让CPU在处理中断之前先读取这个寄存器的值,来写不同的中断处理程序。

 ICCEOIR寄存器,本次实验只看[9:0]位,CPU处理完中断后,会把中断号写入该寄存器。

将当前中断的中断号写回到中断控制器,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其他中断

#include "exynos_4412.h"

unsigned int flag = 1;

//异常处理程序
void do_irq(void)
{
	unsigned int IrqNum;
	/*从中断控制器中获取当前中断的中断号*/
	IrqNum = CPU0.ICCIAR & 0x3FF;

	switch(IrqNum)
	{
		case 0:
			//0号中断的处理程序
			break;
		case 1:
			//1号中断的处理程序
			break;
			/*
		 	* ......
		 	*/
		case 58:
			printf("Key3 pressed\n");
			if(flag == 1)
			{
				GPX2.DAT = GPX2.DAT | (1 << 7);
				flag = 0;
			}else
			{
				GPX2.DAT = GPX2.DAT & (~(1 << 7));
				flag = 1;
			}
			/*清除GPIO控制器中的中断挂起位*/
			EXT_INT41_PEND = (1 << 2);
			/*将当前中断的中断号写回到中断控制器,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其他中断*/
			CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | 58;
			break;
			/*
			 * ......
			 */
		case 159:
			//159号中断的处理程序
			break;
		default:
			break;
	}

}

void Delay(unsigned int Time)
{
	while(Time--);
}

int main()
{
	/*外设层次 —— 让外部的硬件控制器产生一个中断信号并发送给中断控制器*/
	/*将GPX1_2设置成中断功能*/
	GPX1.CON = GPX1.CON | (0xF << 8);
	/*设置GPX1_2中断触发方式:下降沿触发*/
	EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 8)) | (0x2 << 8);
	/*使能GPX1_2的中断功能*/
	EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 2));	

	/*中断控制器层次 —— 让中断控制器接收外设发来的中断信号并对其进行管理然后再转发给一个合适的CPU去处理*/
	/*全局使能中断控制器,使其能够接收外部设备产生的中断信号并转发给CPU接口*/
	ICDDCR = ICDDCR | 1;

	/*在中断控制器中使能58号中断,使中断控制器在接收到58号中断后,能将其进一步转发到CPU接口*/
	ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 26);

	/*选择CPU0来处理58号中断*/
	ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 16)) | (0x1 << 16);

	/*将中断控制器和CPU0之间的接口使能,使得中断控制器转发的信号能够到达CPU0*/
	CPU0.ICCICR = CPU0.ICCICR | 1;

	GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);//LED2

	while(1)
	{
		/*点亮LED2*/
		GPX2.DAT = GPX2.DAT | (1 << 7);
		/*延时*/
		Delay(1000000);
		/*熄灭LED2*/
		GPX2.DAT = GPX2.DAT & (~(1 << 7));
		/*延时*/
		Delay(1000000);
	}

	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1228358.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【算法】滑动窗口题单——2.不定长滑动窗口(求最长/最大)

文章目录 3. 无重复字符的最长子串1493. 删掉一个元素以后全为 1 的最长子数组904. 水果成篮1695. 删除子数组的最大得分2841. 几乎唯一子数组的最大和2024. 考试的最大困扰度1004. 最大连续1的个数 III1438. 绝对差不超过限制的最长连续子数组2401. 最长优雅子数组解法1——维…

nodejs微信小程序-实验室上机管理系统的设计与实现-安卓-python-PHP-计算机毕业设计

用户&#xff1a;管理员、教师、学生 基础功能&#xff1a;管理课表、管理机房情况、预约机房预约&#xff1b;权限不同&#xff0c;预约类型不同&#xff0c;教师可选课堂预约和个人&#xff1b;课堂预约。 目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 …

容斥 C. Strange Function改编题

补题&#xff1a; 题目详情 - 9.段坤爱取模%%% - SUSTOJ 本题或许是参考 Problem - C - Codeforces 根据题意&#xff0c;f(i)就是不能被整除的最小的一个质因子。 打表发现&#xff0c;当15个质因子相乘后&#xff0c;长度就大于18。 因此可以知道小于等于1e16内的正整数x…

(动手学习深度学习)第13章 计算机视觉---微调

文章目录 微调总结 微调代码实现 微调 总结 微调通过使用在大数据上的恶道的预训练好的模型来初始化模型权重来完成提升精度。预训练模型质量很重要微调通常速度更快、精确度更高 微调代码实现 导入相关库 %matplotlib inline import os import torch import torchvision f…

java文件压缩加密,使用流的方式

使用net.lingala.zip4j来进行文件加密压缩。 添加依赖net.lingala.zip4j包依赖&#xff0c;这里使用的是最新的包2.11.5版本。 <dependency><groupId>net.lingala.zip4j</groupId><artifactId>zip4j</artifactId><version>${zip4j.versi…

丹麦能源袭击预示着更关键的基础设施成为目标

5 月&#xff0c;22 个丹麦能源部门组织在与俄罗斯 Sandworm APT 部分相关的攻击中受到损害。 丹麦关键基础设施安全非营利组织 SektorCERT 的一份新报告描述了不同的攻击者群体利用合勤防火墙设备中的多个关键漏洞&#xff08;包括两个零日漏洞&#xff09;侵入工业机械&…

Dockerfile自定义镜像以及案例分析

文章目录 一、Dockerfile自定义镜像1.1 镜像结构1.2 Dockerfile语法 二、构建Java项目三、基于java8构建java四、小结 一、Dockerfile自定义镜像 常见的镜像在DockerHub就能找到&#xff0c;但是我们自己写的项目就必须自己构建镜像了。 而要自定义镜像&#xff0c;就必须先了…

boomYouth

上一周实在是过得太颓废了&#xff0c;我感觉还是要把自己的规划做好一下&#xff1a; 周计划 这周截至周四&#xff0c;我可以用vue简单的画完登陆注册的界面并且弄一点预处理&#xff1a; 周一 的话可以把这些都学一下&#xff1a; 父传子&#xff0c;子传父&#xff1a…

配置iTerm2打开自动执行命令

打开iTerm2&#xff0c;commado&#xff0c;打开profies->edit profies&#xff0c;点击号&#xff0c;创建一个新的profile 在新的profile中填写 name&#xff1a;随意 command&#xff1a;Login Shell Send text at start&#xff1a;执行脚本的命令&#xff0c;不想写路…

python django 小程序图书借阅源码

开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索图书&#xff0c;轮播图&#xff0…

某60区块链安全之不安全的随机数实战一

区块链安全 文章目录 区块链安全不安全的随机数实战一实验目的实验环境实验工具实验原理实验内容攻击过程分析合约源代码漏洞EXP利用 不安全的随机数实战一 实验目的 学会使用python3的web3模块 学会以太坊不安全的随机数漏洞分析及利用 实验环境 Ubuntu18.04操作机 实验工…

环境配置|GitHub——解决Github无法显示图片以及README无法显示图片

一、问题背景 最近在整理之前写过的实验、项目&#xff0c;打算把这些东西写成blog&#xff0c;并把工程文件整理上传到Github上。但在上传README文件的时候&#xff0c;发现github无法显示README中的图片&#xff0c;如下图所示&#xff1a; 在README中该图片路径为&#xff1…

Unity Meta Quest 一体机开发(七):配置玩家 Hand Grab 功能

文章目录 &#x1f4d5;教程说明&#x1f4d5;玩家物体配置 Hand Grab Interactor⭐添加 Hand Grab Interactor 物体⭐激活 Hand Grab Visual 和 Hand Grab Glow⭐更新 Best Hover Interactor Group &#x1f4d5;配置可抓取物体&#xff08;无抓取手势&#xff09;⭐刚体和碰撞…

【算法】树形DP③ 监控二叉树 ⭐(二叉树染色二叉树灯饰)!

文章目录 前期知识 & 相关链接例题968. 监控二叉树解法1——标记状态贪心解法2——动态规划 相关练习题目P2458 [SDOI2006] 保安站岗⭐&#xff08;有多个儿子节点&#xff09;&#x1f6b9;LCP 34. 二叉树染色⭐&#xff08;每个节点 单独dp[k 1]数组&#xff09;LCP 64.…

时间序列预测实战(十七)利用Prophet实现电力负荷长期预测(附代码+数据集+详细讲解)

一、本文介绍 Prophet是一个由Facebook开发的开源工具&#xff0c;用于时间序列预测。这个工具特别适合于具有强季节性影响和多个历史数据季节的业务时间序列数据。Prophet的主要思想是将数据分解为如下三个部分&#xff1a;趋势、季节性、节假日和特殊事件。这个模型非常适合…

GIT无效的源路径/URL

ssh-add /Users/haijunyan/.ssh/id_rsa ssh-add -K /Users/haijunyan/.ssh/id_rsa

SQL基础理论篇(七):多表关联的连接算法

文章目录 简介Nested LoopsMerge JoinHash Join总结参考文献 简介 多表之间基础的关联算法一共有三种&#xff1a; Hash JoinNested LoopsMerge Join 还有很多基于这三种基础算法的变体&#xff0c;以Nested Loops为例&#xff0c;就有用于in和exist的半连接&#xff08;Nes…

【Android Jetpack】Hilt的理解与浅析

文章目录 依赖注入DaggerHiltKoin添加依赖项Hilt常用注解的含义HiltAndroidAppAndroidEntryPointInjectModuleInstallInProvidesEntryPoint Hilt组件生命周期和作用域如何使用 Hilt 进行依赖注入 本文只是进行了简单入门&#xff0c;博客仅当做笔记用。 依赖注入 依赖注入是一…

文档向量化工具(一):Apache Tika介绍

Apache Tika是什么&#xff1f;能干什么&#xff1f; Apache Tika是一个内容分析工具包。 该工具包可以从一千多种不同的文件类型&#xff08;如PPT、XLS和PDF&#xff09;中检测并提取元数据和文本。 所有这些文件类型都可以通过同一个接口进行解析&#xff0c;这使得Tika在…

node实战——koa实现文件上传

文章目录 ⭐前言⭐koa实现文件上传⭐foxapi测试⭐总结⭐结束⭐前言 大家好,我是yma16,本文分享关于node实战——node实战——koa实现文件上传。 本文适用对象:前端初学者转node方向,在校大学生,即将毕业的同学,计算机爱好者。 node系列往期文章 node_windows环境变量配置…