Linux裸机开发|GPIO中断

一、Cortex-A7中断详解

1.1 中断向量表

中断向量表是在代码的最前面,中断向量表里面都是中断服务函数的入口地址。

Cortex-A7内核有8个中断,其中断向量表如下示
在这里插入图片描述

Cortex-M内核的中断向量表列举出了芯片所有的中断向量,包括芯片外设中断

Cortex-A内核只列出了8个中断,所有芯片外设中断都属于IRQ中断

1.2 GIC控制器

GIC接收众多的外部中断,然后对其进行处理,最终只通过四个信号报给ARM内核
在这里插入图片描述
下面以IRQ信号为例,介绍GIC是如何工作的。GICV2的逻辑图如下示
在这里插入图片描述

  • SPI(Shared Peripheral Interrupt,共享中断 ):所有core共享的中断(中断ID号为 32-1019)
  • PPI(Private Peripheral Interrupt,私有中断 ):每个核独有的中断(中断ID号为 16-31)
  • SGI(Software Generated Interrupt,软件中断 ):软件触发的中断(中断ID号为 0-15)

每个中断源都有一个唯一的中断ID,一个CPU最多支持1020个中断ID,中断ID号为ID0~ID1019,这1020个中断ID又分为以上三类。具体到哪个ID对应哪个中断源就由芯片厂商定义了

IMX6U使用了128个中断ID,加上属于PPI和SGI的32个ID,IMX6U的中断源共有160个,其ID号对应的中断源如下表示
在这里插入图片描述

NXP官方SDK的MCIMX6Y2C.h文件中定义了一个枚举类型IRQn_Type,枚举出了IMX6U的所有中断

GIC架构分为两个逻辑块,GIC结构体在core_ca7.h文件中定义

  • 分发器端:Distributor,负责处理各个中断事件的分发,也就是中断事件应该发送到哪个CPU Interface上去;分发器收集所有的中断源,可以控制每个中断的优先级,并总是将优先级最高的中断事件发送到CPU接口端

分发器端主要工作:

  1. 全局中断使能控制
  2. 控制每一个中断的使能或关闭
  3. 设置每个中断的优先级、目标处理器列表
  4. 设置每个外部中断的触发模式
  5. 设置每个中断属于组0还是组1
  • CPU接口端:CPU Interface,是分发器和CPU Core之间的桥梁

CPU接口端主要工作:

  1. 使能或关闭发送到CPU核的中断请求信号
  2. 应答中断
  3. 通知中断处理完成
  4. 设置优先级掩码;定义抢占策略
  5. 选择优先级最高的中断通知给CPU核
1.3 CP15协处理器

CP15协处理器一般用于存储系统管理,中断中也会使用到,CP15协处理器共有16个32位寄存器。其读写是通过MRC(读寄存器数据)/MCR(写寄存器数据)指令来完成

MCR {cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
/* cond		: 指令执行的条件码,如忽略表示无条件执行
 * opc1		: 协处理器要执行的操作码
 * Rt		: ARM源寄存器
 * CRn		: CP15协处理器的目标寄存器
 * CRm		: CP15协处理器中附加的目标或源寄存器,如不需要附加就设置为C0
 * opc2		: 可选的协处理器特定操作码,如不需要就设置为0
 */

简单总结,不同的CP15协处理器寄存器可以获取到不同的信息

  • 通过c0寄存器可获取到处理器内核信息
  • 通过c1寄存器可使能或禁止MMU、I/D Cache等
  • 通过c12寄存器可设置中断向量偏移
  • 通过c15寄存器可获取GIC基地址
1.4 中断使能

中断使能包括两部分,一个是IRQ或者FIQ总中断使能,另一个就是ID0~ID1019这1020个中断源的使能

  • IRQ和FIQ总中断使能和禁止
    在这里插入图片描述

  • ID0~ID1019中断使能和禁止

GIC寄存器GICD_ISENABLERn用来完成外部中断的使能

GIC寄存器GICD_ICENABLERn用来完成外部中断的禁止

Cortex-A7内核只使用零512个中断ID,一个bit控制一个中断ID的使能,因此需要512/32=16个GICD_ISENABLER寄存器来完成中断的使能。GICD_ISENABLER0的0 ~ 15位对应ID0 ~ ID15的SGI中断,16 ~ 31位对应ID16 ~ ID31的PPI中断,剩下的寄存器就是控制SPI中断的

1.5 中断优先级

Cortex-A7的中断优先级分为抢占优先级和子优先级。GIC控制器最多可以支持256个优先级,数字越小,优先级越高。Cortex-A7选择了32个优先级,在使用中断的时候需要初始化GICC_PMR寄存器,此寄存器的低八位用来决定使用几级优先级
在这里插入图片描述

抢占优先级和子优先级各占几位是由GICC_BPR寄存器的低三位来决定的
在这里插入图片描述
中断优先级由寄存器D_IPRIORITYR来设置,Cortex-A7使用了512个中断ID,每个中断ID配有一个优先级寄存器,所以共有512个D_IPRIORITYR寄存器。Cortex-A7优先级个数为32,所以使用D_IPRIORITYR寄存器的bit4~bit7来设置优先级(即实际的优先级要左移3位)

//例如设置中断源ID40的中断优先级为5
GICD_IPRIORITYR[40] = 5 << 3;

综上,优先级设置主要分三部分:

  • 设置GICC_PMR寄存器,配置优先级个数
  • 设置GICC_BPR寄存器,配置抢占优先级和子优先级
  • 设置D_IPRIORITYR寄存器,配置外设优先级

二、程序编写

2.1 移植SDK包相关文件

core_ca7.h文件中定义了GIC结构体以及GIC相关API函数,将文件复制到相关文件夹,并包含到相应的文件中
在这里插入图片描述

2.2 启动文件start.S
.global _start  				/* 全局标号 */

/* _start函数,首先是中断向量表的创建 */
_start:
	ldr pc, =Reset_Handler		/* 复位中断 			  */	
	ldr pc, =Undefined_Handler	/* 未定义中断 			  */
	ldr pc, =SVC_Handler		/* SVC(Supervisor)中断    */
	ldr pc, =PrefAbort_Handler	/* 预取终止中断 			 */
	ldr pc, =DataAbort_Handler	/* 数据终止中断 			 */
	ldr	pc, =NotUsed_Handler	/* 未使用中断			  */
	ldr pc, =IRQ_Handler		/* IRQ中断 				*/
	ldr pc, =FIQ_Handler		/* FIQ(快速中断)未定义中断 	*/

Reset_Handler:
	cpsid i						/* 关闭全局中断 */
	/* 关闭I,DCache和MMU,采取读-改-写的方式 */
	mrc     p15, 0, r0, c1, c0, 0     /* 读取CP15的C1寄存器到R0中 */
    bic     r0,  r0, #(0x1 << 12)     /* 清除C1寄存器的bit12位(I位),关闭I Cache */
    bic     r0,  r0, #(0x1 <<  2)     /* 清除C1寄存器的bit2(C位),关闭D Cache	*/
    bic     r0,  r0, #0x2             /* 清除C1寄存器的bit1(A位),关闭对齐 */
    bic     r0,  r0, #(0x1 << 11)     /* 清除C1寄存器的bit11(Z位),关闭分支预测 */
    bic     r0,  r0, #0x1             /* 清除C1寄存器的bit0(M位),关闭MMU	*/
    mcr     p15, 0, r0, c1, c0, 0     /* 将r0寄存器中的值写入到CP15的C1寄存器中 */
	
#if 0
	/* 汇编版本设置中断向量表偏移 */
	ldr r0, =0X87800000
	dsb
	isb
	mcr p15, 0, r0, c12, c0, 0
	dsb
	isb
#endif
    
	/* 设置各个模式下的栈指针,
	 * 注意:IMX6UL的堆栈是向下增长的!
	 * 堆栈指针地址一定要是4字节地址对齐的!!!
	 * DDR范围:0X80000000~0X9FFFFFFF
	 */
	/* 进入IRQ模式 */
	mrs r0, cpsr
	bic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/
	orr r0, r0, #0x12 	/* r0或上0x13,表示使用IRQ模式					*/
	msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/
	ldr sp, =0x80600000	/* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB */

	/* 进入SYS模式 */
	mrs r0, cpsr
	bic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/
	orr r0, r0, #0x1f 	/* r0或上0x13,表示使用SYS模式					*/
	msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/
	ldr sp, =0x80400000	/* 设置SYS模式下的栈首地址为0X80400000,大小为2MB */

	/* 进入SVC模式 */
	mrs r0, cpsr
	bic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/
	orr r0, r0, #0x13 	/* r0或上0x13,表示使用SVC模式					*/
	msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/
	ldr sp, =0X80200000	/* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */

	cpsie i				/* 打开全局中断 */

#if 0
	/* 使能IRQ中断 */
	mrs r0, cpsr		/* 读取cpsr寄存器值到r0中 			*/
	bic r0, r0, #0x80	/* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */
	msr cpsr, r0		/* 将r0重新写入到cpsr中 			*/
#endif

	b main				/* 跳转到main函数 			 	*/

Undefined_Handler:
	ldr r0, =Undefined_Handler
	bx r0

SVC_Handler:
	ldr r0, =SVC_Handler
	bx r0

PrefAbort_Handler:
	ldr r0, =PrefAbort_Handler	
	bx r0

DataAbort_Handler:
	ldr r0, =DataAbort_Handler
	bx r0
	
NotUsed_Handler:
	ldr r0, =NotUsed_Handler
	bx r0

/* IRQ中断!重点!!!!! */
IRQ_Handler:
	push {lr}					/* 保存lr地址 */
	push {r0-r3, r12}			/* 保存r0-r3,r12寄存器 */

	mrs r0, spsr				/* 读取spsr寄存器 */
	push {r0}					/* 保存spsr寄存器 */

	mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中
								* 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49
								* Cortex-A7 Technical ReferenceManua.pdf P68 P138
								*/							
	add r1, r1, #0X2000			/* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
	ldr r0, [r1, #0XC]			/* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,								 				 * GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据这个中断号来									 * 绝对调用哪个中断服务函数*/
	push {r0, r1}				/* 保存r0,r1 */
	
	cps #0x13					/* 进入SVC模式,允许其他中断再次进去 */
	
	push {lr}					/* 保存SVC模式的lr寄存器 */
	ldr r2, =system_irqhandler	/* 加载C语言中断处理函数到r2寄存器中*/
	blx r2						/* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */

	pop {lr}					/* 执行完C语言中断服务函数,lr出栈 */
	cps #0x12					/* 进入IRQ模式 */
	pop {r0, r1}				
	str r0, [r1, #0X10]			/* 中断执行完成,写EOIR */

	pop {r0}						
	msr spsr_cxsf, r0			/* 恢复spsr */

	pop {r0-r3, r12}			/* r0-r3,r12出栈 */
	pop {lr}					/* lr出栈 */
	subs pc, lr, #4				/* 将lr-4赋给pc */
	
/* FIQ中断 */
FIQ_Handler:
	ldr r0, =FIQ_Handler	
	bx r0							
2.3 通用中断驱动文件

启动文件start.S中,中断服务函数IRQ_Handler中调用了system_irqhandler函数来处理具体的中断,此函数有一个参数,就是中断号。在通用中断驱动文件中要实现system_irqhandler函数的具体内容。IMX6U有160个中断源,所以需要160个中断处理函数,可以将这些中断处理函数放到一个数组里面,标号对应中断号。当中断发生后system_irqhandler函数根据中断号从数组中找到对应的中断处理函数并执行即可

新建int文件夹,并编写通用中断驱动文件bsp_int.c和bsp_int.h

/* 中断服务函数形式 */ 
typedef void (*system_irq_handler_t) (unsigned int giccIar, void *param);
 
/* 中断服务函数结构体*/
typedef struct _sys_irq_handle
{
    system_irq_handler_t irqHandler; /* 中断服务函数 */
    void *userParam;                 /* 中断服务函数参数 */
} sys_irq_handle_t;

/* 函数声明 */
void int_init(void);
void system_irqtable_init(void);
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam);
void system_irqhandler(unsigned int giccIar); 
void default_irqhandler(unsigned int giccIar, void *userParam); 
/* 中断嵌套计数器 */
static unsigned int irqNesting;

/* 中断服务函数表 */
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];

//中断初始化函数
void int_init(void){
	GIC_Init(); 						/* 初始化GIC */
	system_irqtable_init();				/* 初始化中断表 */
	__set_VBAR((uint32_t)0x87800000); 	/* 中断向量表偏移,偏移到起始地址 */
}
//初始化中断服务函数表 
void system_irqtable_init(void){
	unsigned int i = 0;
	irqNesting = 0;	
	/* 先将所有的中断服务函数设置为默认值 */
	for(i = 0; i < NUMBER_OF_INT_VECTORS; i++){
		system_register_irqhandler((IRQn_Type)i,default_irqhandler, NULL);
	}
}
/* @description			: 给指定的中断号注册中断服务函数 
 * @param - irq			: 要注册的中断号
 * @param - handler		: 要注册的中断处理函数
 * @param - usrParam	: 中断服务处理函数参数
 * @return 				: 无                            */
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam){
	irqTable[irq].irqHandler = handler;
  	irqTable[irq].userParam = userParam;
}

/* @description			: C语言中断服务函数,irq汇编中断服务函数会
 						  调用此函数,此函数通过在中断服务列表中查
 						  找指定中断号所对应的中断处理函数并执行。
 * @param - giccIar		: 中断号
 * @return 				: 无 */
void system_irqhandler(unsigned int giccIar) {
   uint32_t intNum = giccIar & 0x3FFUL;   
   /* 检查中断号是否符合要求 */
   if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS)){
	 	return;
   } 
   irqNesting++;	/* 中断嵌套计数器加一 */
   /* 根据传递进来的中断号,在irqTable中调用确定的中断服务函数*/
   irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam); 
   irqNesting--;	/* 中断执行完成,中断嵌套寄存器减一 */
}
/* @description			: 默认中断服务函数
 * @param - giccIar		: 中断号
 * @param - usrParam	: 中断服务处理函数参数
 * @return 				: 无*/
void default_irqhandler(unsigned int giccIar, void *userParam){
	while(1){
   	}
}
2.4 GPIO驱动文件

修改GPIO驱动文件,加上GPIO中断相关功能及函数

/* 枚举类型和结构体定义 */
typedef enum _gpio_pin_direction{
    kGPIO_DigitalInput = 0U,  		/* 输入 */
    kGPIO_DigitalOutput = 1U, 		/* 输出 */
} gpio_pin_direction_t;
/* GPIO中断触发类型枚举 */
typedef enum _gpio_interrupt_mode{
    kGPIO_NoIntmode = 0U, 				/* 无中断功能 */
    kGPIO_IntLowLevel = 1U, 			/* 低电平触发	*/
    kGPIO_IntHighLevel = 2U, 			/* 高电平触发 */
    kGPIO_IntRisingEdge = 3U, 			/* 上升沿触发	*/
    kGPIO_IntFallingEdge = 4U, 			/* 下降沿触发 */
    kGPIO_IntRisingOrFallingEdge = 5U, 	/* 上升沿和下降沿都触发 */
} gpio_interrupt_mode_t;	
/* GPIO配置结构体 */	
typedef struct _gpio_pin_config{
    gpio_pin_direction_t direction; 		/* GPIO方向:输入还是输出 */
    uint8_t outputLogic;            		/* 如果是输出的话,默认输出电平 */
	gpio_interrupt_mode_t interruptMode;	/* 中断方式 */
} gpio_pin_config_t;
/* 函数声明 */
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config);
int gpio_pinread(GPIO_Type *base, int pin);
void gpio_pinwrite(GPIO_Type *base, int pin, int value);
void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pinInterruptMode);
void gpio_enableint(GPIO_Type* base, unsigned int pin);
void gpio_disableint(GPIO_Type* base, unsigned int pin);
void gpio_clearintflags(GPIO_Type* base, unsigned int pin);
/* @description		: GPIO初始化。
 * @param - base	: 要初始化的GPIO组。
 * @param - pin		: 要初始化GPIO在组内的编号。
 * @param - config	: GPIO配置结构体。
 * @return 			: 无 */
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config){
	base->IMR &= ~(1U << pin);	
	if(config->direction == kGPIO_DigitalInput){ /* GPIO作为输入 */
		base->GDIR &= ~( 1 << pin);
	}
	else{										/* 输出 */
		base->GDIR |= 1 << pin;
		gpio_pinwrite(base,pin, config->outputLogic);	/* 设置默认输出电平 */
	}
	gpio_intconfig(base, pin, config->interruptMode);	/* 中断功能配置 */
}
/* @description	 : 读取指定GPIO的电平值 。
 * @param - base	 : 要读取的GPIO组。
 * @param - pin	 : 要读取的GPIO脚号。
 * @return 		 : 无*/
 int gpio_pinread(GPIO_Type *base, int pin) {
	 return (((base->DR) >> pin) & 0x1);
 }
/* @description	 : 指定GPIO输出高或者低电平 。
 * @param - base	 : 要输出的的GPIO组。
 * @param - pin	 : 要输出的GPIO脚号。
 * @param - value	 : 要输出的电平,1 输出高电平, 0 输出低低电平
 * @return 		 : 无 */
void gpio_pinwrite(GPIO_Type *base, int pin, int value){
	 if (value == 0U){
		 base->DR &= ~(1U << pin); /* 输出低电平 */
	 }else{
		 base->DR |= (1U << pin); /* 输出高电平 */
	 }
}
/* @description  			: 设置GPIO的中断配置功能
 * @param - base 			: 要配置的IO所在的GPIO组。
 * @param - pin  			: 要配置的GPIO脚号。
 * @param - pinInterruptMode: 中断模式,参考枚举类型gpio_interrupt_mode_t
 * @return		 			: 无 */
void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pin_int_mode){
	volatile uint32_t *icr;
	uint32_t icrShift;
	icrShift = pin;	
	base->EDGE_SEL &= ~(1U << pin);

	if(pin < 16){ 	/* 低16位 */
		icr = &(base->ICR1);
	}else{			/* 高16位 */
		icr = &(base->ICR2);
		icrShift -= 16;
	}
	switch(pin_int_mode)
	{
		case(kGPIO_IntLowLevel):
			*icr &= ~(3U << (2 * icrShift));
			break;
		case(kGPIO_IntHighLevel):
			*icr = (*icr & (~(3U << (2 * icrShift)))) | (1U << (2 * icrShift));
			break;
		case(kGPIO_IntRisingEdge):
			*icr = (*icr & (~(3U << (2 * icrShift)))) | (2U << (2 * icrShift));
			break;
		case(kGPIO_IntFallingEdge):
			*icr |= (3U << (2 * icrShift));
			break;
		case(kGPIO_IntRisingOrFallingEdge):
			base->EDGE_SEL |= (1U << pin);
			break;
		default:
			break;
	}
}
/* @description  			: 使能GPIO的中断功能
 * @param - base 			: 要使能的IO所在的GPIO组。
 * @param - pin  			: 要使能的GPIO在组内的编号。
 * @return		 			: 无 */
void gpio_enableint(GPIO_Type* base, unsigned int pin){ 
    base->IMR |= (1 << pin);
}
/* @description  			: 禁止GPIO的中断功能
 * @param - base 			: 要禁止的IO所在的GPIO组。
 * @param - pin  			: 要禁止的GPIO在组内的编号。
 * @return		 			: 无 */
void gpio_disableint(GPIO_Type* base, unsigned int pin){ 
    base->IMR &= ~(1 << pin);
}
/* @description  			: 清除中断标志位(写1清除)
 * @param - base 			: 要清除的IO所在的GPIO组。
 * @param - pin  			: 要清除的GPIO掩码。
 * @return		 			: 无 */
void gpio_clearintflags(GPIO_Type* base, unsigned int pin){
    base->ISR |= (1 << pin);
}
2.5 按键中断驱动文件

以中断的方式编写KEY按键驱动,当按下按键后触发GPIO中断,然后在中断服务函数里控制蜂鸣器的开关

新建exit文件夹,并编写按键中断驱动文件bsp_exit.c和bsp_exit.h

void exit_init(void);						/* 中断初始化 */
void gpio1_io18_irqhandler(void); 			/* 中断处理函数 */
/* 初始化外部中断 */
void exit_init(void){
	gpio_pin_config_t key_config;
	/* 1、设置IO复用 */
	IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0);	/* 复用为GPIO1_IO18 */
	IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);
	/* 2、初始化GPIO为中断模式 */
	key_config.direction = kGPIO_DigitalInput;
	key_config.interruptMode = kGPIO_IntFallingEdge;
	key_config.outputLogic = 1;
	gpio_init(GPIO1, 18, &key_config);

	GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);			/* 使能GIC中对应的中断 */
	system_register_irqhandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL);	/* 注册中断服务函数 */
	gpio_enableint(GPIO1, 18);							/* 使能GPIO1_IO18的中断功能 */
}
/*GPIO1_IO18最终的中断处理函数 */
void gpio1_io18_irqhandler(void){ 
	static unsigned char state = 0;
	/*采用延时消抖,中断服务函数中禁止使用延时函数!因为中断服务需要
	 *快进快出!!这里为了演示所以采用了延时函数进行消抖,后面我们会讲解
	 *定时器中断消抖法!!! */
	delay(10);
	if(gpio_pinread(GPIO1, 18) == 0){	/* 按键按下了  */
		state = !state;
		beep_switch(state);
	}	
	gpio_clearintflags(GPIO1, 18); /* 清除中断标志位 */
}
2.6 main.c文件

在main.c中输入如下代码:

int main(void){
	unsigned char state = OFF;
	int_init(); 		/* 初始化中断(一定要最先调用!) */
	imx6u_clkinit();	/* 初始化系统时钟 			*/
	clk_enable();		/* 使能所有的时钟 			*/
	led_init();			/* 初始化led 			*/
	beep_init();		/* 初始化beep	 		*/
	key_init();			/* 初始化key 			*/
	exit_init();		/* 初始化按键中断			*/

	while(1){	
		state = !state;
		led_switch(LED0, state);
		delay(500);
	}
	return 0;
}
2.7 下载验证
  • 修改Makefile文件:修改TARGET为exit,追加“bsp/exit”和“bsp/int”文件夹
  • 使用imxdownload软件将bin文件下载到SD卡中
  • 烧写成功后,插入SD卡,复位后按一次按键,蜂鸣器的状态会翻转一次,LED0不断闪烁
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安迪西嵌入式

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值