Exynos_4412——中断处理(中断学习结尾篇)

2023-05-16

目录

一、ARM的异常处理机制

1.1异常概念

1.2异常处理机制

1.3ARM异常源

1.4异常模式

1.5ARM异常响应

1.6异常向量表

1.7异常返回

1.8IRQ异常举例

二、工程模板代码结构

三、中断处理框架搭建

四、中断处理程序

五、用key3再试一试


前景提要:

Exynos_4412——轮询与中断_宇努力学习的博客-CSDN博客

Exynos_4412——中断控制器_宇努力学习的博客-CSDN博客

一、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.8IRQ异常举例

 

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

二、工程模板代码结构

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

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

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

makefile:  编译规则

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

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
    . = 0x40008000;
    . = ALIGN(4);
    .text      :
    {
        start/start.o(.text)
        *(.text)
    }
    . = ALIGN(4);
    .rodata : 
    { *(.rodata) }
    . = ALIGN(4);
    .data : 
    { *(.data) }
    . = ALIGN(4);
    .bss :
    { *(.bss) }
}

三、中断处理框架搭建

在执行IRQ中断程序时保存的指令是汇编指令,C语言一条指令转化成汇编可能是很多条

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

然后我们在 b main 后面来写这个中断服务程序

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

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

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

R14(LR,Link Register)

    链接寄存器,一般有以下两种用途:

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

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

原理

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

如果不清楚下面用BL指令举例

BL

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

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

 

在遇到IRQ中断时还不太一样

IRQ中断

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

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

 

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

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

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

之后就可以处理异常了。写汇编就直接接着写,写C呢跳转一下

四、中断处理程序

.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:
    //跳转修复
    sub lr, lr, #4

    //R0-R12 IRQ模式和USER模式是同一组
    stmfd sp!, {r0-r12,lr}
    //处理异常
    bl do_irq

    //异常返回
    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

 //异常返回
 ldmfd sp!,{r0-r12,pc}^

有人可能会好奇我们不是要把spsr写入cpsr把LR写入pc

!的作用是在取元素过程中SP发生变化时将变化写入SP

^  的作用是将栈中元素取出放入寄存器,存的时候存的LR读的时候用pc就已经完成了把LR写入PC

而使用^还有一个功能就是自动将spsr写入cpsr所以完美的解决了这两个问题。

ARM寄存器组织与异常处理_宇努力学习的博客-CSDN博客

#include "exynos_4412.h"
void do_irq()
{
    printf("KEY3 pressed\n");
}

void delay(unsigned int time)
{
	while(time --);
}

int main()
{
    GPX1.CON = GPX1.CON | (0xF << 4);
    EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
    EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));
    
    ICDDCR = ICDDCR | 1;
    ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);
    ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0x01 << 8);
    CPU0.ICCICR = CPU0.ICCICR | 1;

	GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
	GPX1.CON = GPX1.CON & (~(0xF)) | (0x1);
	GPF3.CON = GPF3.CON & (~(0xFF << 16)) | (0x11 << 16);

	while(1)
	{
		GPX2.DAT = GPX2.DAT | (1 << 7);
		delay(1000000);
		GPX2.DAT = GPX2.DAT & (~(1 << 7));
		delay(1000000);
		GPX1.DAT = GPX1.DAT | 1;
		delay(1000000);
		GPX1.DAT = GPX1.DAT & (~1);
		delay(1000000);	
		GPF3.DAT = GPF3.DAT | (1 << 4);
		delay(1000000);
		GPF3.DAT = GPF3.DAT & (~(1 << 4));
		delay(1000000);
		GPF3.DAT = GPF3.DAT | (1 << 5);
		delay(1000000);
		GPF3.DAT = GPF3.DAT & (~(1 << 5));
		delay(1000000);	
			
	}

	return 0;
}

一直发怎么回事呢,想下之前说的我们少干了一件事清除中断标志位,就是清除挂起位

#include "exynos_4412.h"
void do_irq()
{
    printf("KEY3 pressed\n");
    //清除中断挂起
    EXT_INT41_PEND = (1 << 1);
}

void delay(unsigned int time)
{
	while(time --);
}

int main()
{
    GPX1.CON = GPX1.CON | (0xF << 4);
    EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
    EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));
    
    ICDDCR = ICDDCR | 1;
    ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);
    ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0x01 << 8);
    CPU0.ICCICR = CPU0.ICCICR | 1;

	GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
	GPX1.CON = GPX1.CON & (~(0xF)) | (0x1);
	GPF3.CON = GPF3.CON & (~(0xFF << 16)) | (0x11 << 16);

	while(1)
	{
		GPX2.DAT = GPX2.DAT | (1 << 7);
		delay(1000000);
		GPX2.DAT = GPX2.DAT & (~(1 << 7));
		delay(1000000);
		GPX1.DAT = GPX1.DAT | 1;
		delay(1000000);
		GPX1.DAT = GPX1.DAT & (~1);
		delay(1000000);	
		GPF3.DAT = GPF3.DAT | (1 << 4);
		delay(1000000);
		GPF3.DAT = GPF3.DAT & (~(1 << 4));
		delay(1000000);
		GPF3.DAT = GPF3.DAT | (1 << 5);
		delay(1000000);
		GPF3.DAT = GPF3.DAT & (~(1 << 5));
		delay(1000000);	
			
	}

	return 0;
}

 我们在试试

这次没毛病了,按一下打印一行

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

#include "exynos_4412.h"
void do_irq()
{
    unsigned int IrqNum;
    IrqNum = CPU0.ICCIAR & 0x3FF;
    switch(IrqNum)
    {
        case 0:
            //0号中断处理程序
            break;
        case 1:
            break;
        case 57:
    		printf("KEY3 pressed\n");
    		//清除中断挂起
   	 		EXT_INT41_PEND = (1 << 1);
            break;
        case 159:
            break;
        default:
            break;
    }

}

void delay(unsigned int time)
{
	while(time --);
}

int main()
{
    GPX1.CON = GPX1.CON | (0xF << 4);
    EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
    EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));
    
    ICDDCR = ICDDCR | 1;
    ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);
    ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0x01 << 8);
    CPU0.ICCICR = CPU0.ICCICR | 1;

	GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
	GPX1.CON = GPX1.CON & (~(0xF)) | (0x1);
	GPF3.CON = GPF3.CON & (~(0xFF << 16)) | (0x11 << 16);

	while(1)
	{
		GPX2.DAT = GPX2.DAT | (1 << 7);
		delay(1000000);
		GPX2.DAT = GPX2.DAT & (~(1 << 7));
		delay(1000000);
		GPX1.DAT = GPX1.DAT | 1;
		delay(1000000);
		GPX1.DAT = GPX1.DAT & (~1);
		delay(1000000);	
		GPF3.DAT = GPF3.DAT | (1 << 4);
		delay(1000000);
		GPF3.DAT = GPF3.DAT & (~(1 << 4));
		delay(1000000);
		GPF3.DAT = GPF3.DAT | (1 << 5);
		delay(1000000);
		GPF3.DAT = GPF3.DAT & (~(1 << 5));
		delay(1000000);	
			
	}

	return 0;
}

只有第一次好使,后面都不好使了

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

#include "exynos_4412.h"
void do_irq()
{
    unsigned int IrqNum;
    IrqNum = CPU0.ICCIAR & 0x3FF;
    switch(IrqNum)
    {
        case 0:
            //0号中断处理程序
            break;
        case 1:
            break;
        case 57:
    		printf("KEY2 pressed\n");
    		//清除中断挂起
   	 		EXT_INT41_PEND = (1 << 1);
            //将当前中断号写回中断控制器
            //告知中断处理完成
            CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | 57;
            break;
        case 159:
            break;
        default:
            break;
    }

}

void delay(unsigned int time)
{
	while(time --);
}

int main()
{
    GPX1.CON = GPX1.CON | (0xF << 4);
    EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
    EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));
    
    ICDDCR = ICDDCR | 1;
    ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);
    ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0x01 << 8);
    CPU0.ICCICR = CPU0.ICCICR | 1;

	GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
	GPX1.CON = GPX1.CON & (~(0xF)) | (0x1);
	GPF3.CON = GPF3.CON & (~(0xFF << 16)) | (0x11 << 16);

	while(1)
	{
		GPX2.DAT = GPX2.DAT | (1 << 7);
		delay(1000000);
		GPX2.DAT = GPX2.DAT & (~(1 << 7));
		delay(1000000);
		GPX1.DAT = GPX1.DAT | 1;
		delay(1000000);
		GPX1.DAT = GPX1.DAT & (~1);
		delay(1000000);	
		GPF3.DAT = GPF3.DAT | (1 << 4);
		delay(1000000);
		GPF3.DAT = GPF3.DAT & (~(1 << 4));
		delay(1000000);
		GPF3.DAT = GPF3.DAT | (1 << 5);
		delay(1000000);
		GPF3.DAT = GPF3.DAT & (~(1 << 5));
		delay(1000000);	
			
	}

	return 0;
}

 

这次没问题了。

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

FIQ为什么比IRQ快:

1)FIQ的优先级比IRQ高

2)FIQ可以打断IRQ

3)FIQ在异常向量表的最末,别的中断处理程序要跳一下,但是FIQ可以直接在这里接着写

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

r0-r7还是要压栈保护现场的。

32有中断优先级的概念,但是三星的猎户座只有FIQ和IRQ这两个级别

五、用key3再试一试

 

58号 

还是下降沿触发 

把咱们的INT1【2】打开 

 由于KEY2已经开了外部中断就不在写了

然后找到58号将58号打开

 58-32=26

 每个寄存器确定4个中断

58/4 = 14……2    (0-159,所以是第三个位置第2号就是左移16位)

同样中断控制器和CPU之间的开关只需要闭合一次因为都是CPU0

读取哪个中断

清除2号中断挂起,已经发生了就不会在挂起

告知中断控制器中断58处理完成

#include "exynos_4412.h"
unsigned int flag = 1;
void do_irq()
{
    unsigned int IrqNum;
    IrqNum = CPU0.ICCIAR & 0x3FF;
    switch(IrqNum)
    {
        case 0:
            //0号中断处理程序
            break;
        case 1:
            break;
        case 57:
    		printf("KEY2 pressed\n");
    		//清除中断挂起
   	 		EXT_INT41_PEND = (1 << 1);
            //将当前中断号写回中断控制器
            //告知中断处理完成
            CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | 57;
            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;
			}
			EXT_INT41_PEND = (1 << 2);
			CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | 58;
			break;
        case 159:
            break;
        default:
            break;
    }

}

void delay(unsigned int time)
{
	while(time --);
}

int main()
{
    GPX1.CON = GPX1.CON | (0xF << 4);//KEY2
    EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
    EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));

	GPX1.CON = GPX1.CON | (0xF << 8);//KEY3
	EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 8)) | (0x2 << 8);
	EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 2));

	//57    
    ICDDCR = ICDDCR | 1;
    ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);
    ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0x01 << 8);
    CPU0.ICCICR = CPU0.ICCICR | 1;

	//58
	ICDDCR = ICDDCR | 1;
	ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 26);
	ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 16)) | (0x01 << 16);
	CPU0.ICCICR = CPU0.ICCICR | 1;


	GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);//LED2
	GPX1.CON = GPX1.CON & (~(0xF)) | (0x1);//LED3
	GPF3.CON = GPF3.CON & (~(0xFF << 16)) | (0x11 << 16);//LED4 LED5

	while(1)
	{
		GPX2.DAT = GPX2.DAT | (1 << 7);
		delay(1000000);
		GPX2.DAT = GPX2.DAT & (~(1 << 7));
		delay(1000000);
		GPX1.DAT = GPX1.DAT | 1;
		delay(1000000);
		GPX1.DAT = GPX1.DAT & (~1);
		delay(1000000);	
		GPF3.DAT = GPF3.DAT | (1 << 4);
		delay(1000000);
		GPF3.DAT = GPF3.DAT & (~(1 << 4));
		delay(1000000);
		GPF3.DAT = GPF3.DAT | (1 << 5);
		delay(1000000);
		GPF3.DAT = GPF3.DAT & (~(1 << 5));
		delay(1000000);	
			
	}

	return 0;
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Exynos_4412——中断处理(中断学习结尾篇) 的相关文章

  • Arduino——GY39大气压、温湿度、光照模块

    GY39模块 文章目录 GY39模块前言一 模块介绍二 arduino代码 前言 前几天买东西的时候买了一个GY39 xff0c 这个模块集成了温湿度 xff0c 大气压 xff0c 海拔 xff0c 光照一体 xff0c 使用起来很方面
  • Arduino通过NRF24L01实现双机无线通信

    双机无线通信 文章目录 双机无线通信前言一 接线二 Arduino代码1 主机2 从机 总结 前言 无线通信对于做各种项目来说都很加分 xff0c 今天使用这个nrf模块进行无线通信 我原本是想用两个蓝牙的 xff0c 但是蓝牙有个缺点 x
  • STM32+ESP8266+机智云+DHT11数据上传

    机智云 文章目录 机智云前言一 工程的修改二 数据的上传1 标识符2 数据处理3 数据上传 三 app控制 前言 今天搞了一下机智云 xff0c 就想把温湿度发到app上去 xff0c 然后能够控制灯的开关 之前从来没有用过这个玩意 xff
  • 数据结构——线性结构(二)

    数据结构 文章目录 数据结构前言一 线性结构1 线性表2 线性表的特点 二 线性结构的存储形式1 顺序结构2 链式结构 前言 很早之前提到了数据结构 xff0c 上一篇博客简单介绍了什么是线性结构 xff0c 这篇博客简单做一个补充 常见的
  • 数据结构——顺序表(三)

    数据结构 文章目录 数据结构一 什么是顺序表二 顺序表的创建1 静态顺序表2 动态数据表 三 顺序表的初始化 销毁四 顺序表的插入1 尾插2 头插3 任意插入 总结 一 什么是顺序表 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线
  • PHP new mysqli()连接

    1 首先在mysql命令控制台新建数据库 mysql gt create database test Query OK 1 row affected 0 04 sec mysql gt use test Database changed m
  • 数据结构——链表(五)

    数据结构 96 文章目录 数据结构前言一 什么是链表二 实现单链表1 单链表的结构2 单链表的初始化3 单链表的插入4 遍历链表5 链表长度 总结 前言 接下来学习一下链表 xff0c 链表比数组用的更多 一 什么是链表 概念 xff1a
  • 数据结构——双向循环链表(八)

    数据结构 文章目录 数据结构前言一 双向循环链表初始化二 双向循环链表的插入遍历 三 双向链表的删除总结 前言 双向循环链表用的次数是最多的 xff0c 下面我们看一下双向循环链表的增删改查 一 双向循环链表初始化 双向循环链表不能出现NU
  • 数据结构——栈(九)

    数据结构 文章目录 数据结构前言一 栈的介绍二 栈的实现1 栈的结构2 栈的初始化3 进栈4 出栈5 栈的判断6 取栈顶元素7 栈的清空8 栈的销毁 总结 前言 栈是一种特殊的线性表 xff0c 只允许在固定的一端进行插入和删除元素的操作
  • 数据结构——队列(十)

    数据结构 文章目录 数据结构一 什么是队列二 队列的实现1 队列的结构2 队列的初始化3 入队4 出队6 队头队尾的获取 总结 一 什么是队列 队列 xff1a 只允许在一端进行插入数据操作 xff0c 在另一端进行删除数据操作的特殊线性表
  • ESP8266通过MQTT协议连接onenet云平台

    中国移动onenet平台 文章目录 中国移动onenet平台前言一 onenet平台二 ESP82661 完整代码2 联网代码3 连云代码4 数据处理 总结 前言 最近在弄onenet平台 xff0c 用arduino结合esp8266 x
  • Arduino串口提取数字(整型和浮点型)

    数据提取 文章目录 数据提取前言一 提取整型数据二 提取浮点型数据 前言 之前需要用32和ESP进行通信上传数据 xff0c 一直都用的都是数据上传然后处理成整型数据 xff0c 今天需要处理成浮点型数据所以就查了一下 xff0c 于是就记
  • vins-fusion环境配置、安装与测试

    本文主要介绍如何搭建vins fusion的运行环境 xff0c 以及解决vins fusion编译运行时遇到的环境冲突问题 xff0c 并在此基础上实现例程的运行 目录 一 安装OpenCV 3 4 111 1 配置依赖环境1 2 下载O
  • JS和JQuery监听滚动条事件

    网上查了一下 xff0c 找到两种js监听滚轮事件的方法 xff08 1 xff09 window onscroll 61 function xff08 2 xff09 document addEventListener 34 onscro
  • STM32使用中断及串口通信

    1 中断模式编程控制LED 采用中断模式编程 xff0c 当开关接高电平时 xff0c LED亮灯 xff1b 接低电平时 xff0c LED灭灯 单片机除了基本的连线外 xff0c 我们另外只接一只LED灯 使用外部中断的基本步骤如下 x

随机推荐

  • 用opencv打开图片及视频

    用opencv打开图片及视频 1 opencv的安装 参考文章http t csdn cn QO7dr 2 用opencv打开图片 建立code文件夹存放代码 xff0c 然后打开文件夹创建test1 cpp文件 在test1 cpp文件里
  • 【Keil】 Keil的搭建并配置,并编写简单的汇编程序

    Keil的搭建并配置 一 配置环境1 MDK的安装1 1 MDK5下载1 2安装 2 安装stm32 pack 二 Mdk使用配置技巧1 设置tab键为2个空格2 代码自动补齐3 语法动态错误检测4 右边距指示 三 编写 一个简单的汇编程序
  • 【stm32CubeMX】STM32F103c8t6串口通信

    stm32CubeMX STM32F103c8t6串口通信发送 39 hello windows 39 一 串口通信协议1 UART协议2 RS 2323 RS 485 二 USB转TTL三 配置CubeMX并建立工程四 串口通信实现五 k
  • 【STM32】基于SPI的OLED显示屏与DHT20温湿度采集显示数据

    STM32 基于SPI总线的OLED显示屏与DHT20温湿度采集显示数据 一 SPI通讯协议二 关于0 96英寸OLED模块三 硬件连接四 示例代码五 代码修改与撰写六 屏幕歌词滚动1 滚屏设置2 代码撰写 七 展示八 DHT20温湿度采集
  • 【STM32CubeMX】使用STM32F103C8T6输出PWM波形实现呼吸灯

    STM32CubeMX 使用STM32F103C8T6输出PWM波形实现呼吸灯 一 关于PWM二 Cube MX创建工程三 修改代码四 效果展示五 总结六 参考与代码下载 一 关于PWM 1 关于PWM 含义 PWM xff08 Pulse
  • 【STM32】基于HAL库使用最小系统板移植uCOS

    STM32 基于HAL库使用最小系统板移植uCOS 一 CubeMX建立工程模板二 下载uC OS III源码三 移植准备四 开始移植1 将uCOS文件添加到项目2 为bsp c和bsp h添加代码3 修改main c文件代码4 修改其余文
  • Ubuntu20.04打不开终端

    Ubuntu20 04打不开终端 下载xterm输入gnome terminal显示 xff1a Error constructing proxy for org gnome Terminal org gnome Terminal Fact
  • 串口DMA发送接收

    目录 一 DMA的基本介绍 1 DMA定义 2 原理 1 请求 2 响应 3 传输 4 结束 3 传送方式 1 停止CPU访问内存 2 周期挪用 3 DMA与CPU交替访问内存 4 DMA中断 二 新建cubemx项目 1 选择STM32F
  • Time Limit Exceeded的原因

    Time Limit Exceeded的原因及避免方法 荷叶田田 CSDN博客
  • GStreamer学习三(延迟)

    1 延迟 延迟 xff08 即latency xff09 是在时间戳0处捕获的样本到达接收器所花费的时间 此时间是根据流水线的时钟测量的 对于只有包含与时钟同步的 接收器 元素的流水线 xff0c latency 始终为0 xff0c 因为
  • 第一届ACC全国高校联赛

    y 竞赛 AcWing 面向全国高校同学的高校联赛 https www acwing com activity content 1173 一 1 暴力 include lt bits stdc 43 43 h gt using namesp
  • JDBC连接数据库

    个人简介 x1f496 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki x1f4c2 喜欢 xff1a x1f308 点赞 x1f308 收藏 xff01 更新Java x1f308 python
  • idea 文件夹右键新建没有Class

    个人简介 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki 喜欢 xff1a x1f308 点赞 x1f308 收藏 x1f308 一键三连 xff01 共勉 一 问题发现 xff1a 没法创建ja
  • 《关于我找了好久的bug,却没找出来的,又不小心解决了的事》

    个人简介 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki 喜欢 xff1a x1f308 点赞 x1f308 收藏 x1f308 一键三连 xff01 共勉 问题 xff1a 这是一个Spring
  • 某某科技实习日志

    个人简介 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki 喜欢 xff1a x1f308 点赞 x1f308 收藏 x1f308 一键三连 xff01 共勉 时间 2023年 4月 11日 今日任
  • XXXX实习日志

    个人简介 作者简介 xff1a 大家好 xff01 我是yukki 个人主页 xff1a yukki 喜欢 xff1a x1f308 点赞 x1f308 收藏 x1f308 一键三连 xff01 共勉 时间 2023年 4月 12日 今日任
  • STM32——中断优先级分组

    一 SCB AIRCR寄存器 首先 xff0c 对STM32中断进行分组 xff0c 0 4 同时 xff0c 每个中断设置一个抢占优先级和一个响应优先级 1 高抢占可以打断正在执行的低抢占 2 抢占相等 xff0c 高响应不能打断低响应
  • Keil报错总结(1)

    一 newline expected extra characters found c323 头文件定义有问题 ifndef define endif 他们后面的文件名与文件名不一致 xff0c 或者大小写不一致 xff0c 文件名尽量避免
  • GPIO实验

    一 GPIO简介 GPIO xff08 General purpose input output xff09 即通用型输入输出 xff0c GPIO可以控制连接在其之上的引脚实现信号的输入和输出 芯片的引脚与外部设备相连 xff0c 从而实
  • Exynos_4412——中断处理(中断学习结尾篇)

    目录 一 ARM的异常处理机制 1 1异常概念 1 2异常处理机制 1 3ARM异常源 1 4异常模式 1 5ARM异常响应 1 6异常向量表 1 7异常返回 1 8IRQ异常举例 二 工程模板代码结构 三 中断处理框架搭建 四 中断处理程