当前位置: 首页 > news >正文

I.MX6ULL裸机的EPIT实验

0.参考资料

参考手册:正点原子用户手册,IMX6ULL参考手册

其他参考资料:正点原子提供

虚拟机平台:Ubuntu20.04

开发板:正点原子阿尔法开发板。

工程样式:BSP,需要创建:bsp,project,imx6ull,obj,四个文件夹来存放文件。bsp存放的是源码文件,project存放的是主程序和启动文件,obj存放的是编译生成的.o文件,imx6ull存放的是imx6ull的SDK,这个直接用正点原子的,官方的需要修改很麻烦。

1.实验思路分析以及编写步骤

1.1.启动文件

        裸机开发需要自己编写启动文件,这一部分理解就行不需要死记硬背,不然会浪费非常多的时间,每次写新的实验回头看一下,每行理解一下就行了。

@ 汇编文件
.global _start
_start:ldr pc, =Reset_Handler   /*复位中断服务函数*/ldr pc, =Undefined_Handler /*未定义指令中断服务函数*/ldr pc, =SVC_Handler       /*SVC*/ldr pc, =PreAbort_handler  /*预取终止*/ldr pc, =DataAbort_Handler /*数据终止*/ldr pc, =NotUsed_Handler   /*未使用*/ldr pc, =IRQ_Handler       /*IRQ中断*/ldr pc, =FIQ_Handler       /*FIQ中断*/
/*复位中断服务函数*/
Reset_Handler:cpsid i         /* 关闭IRQ *//* 关闭 I,D Cache和MMU *//* 需要使用到一个叫CP15寄存器*//* 修改SCTLR寄存器,采取读,改,写的方式 */MRC p15, 0, r0, c1, c0, 0       /* 读取SCTLR寄存器的数据到r0寄存器里面 */bic r0, r0, #(1 << 12)          /* 关闭 I Cache */bic r0, r0, #(1 << 11)          /* 关闭分支预测 */bic r0, r0, #(1 << 2)           /* 关闭D Cache */bic r0, r0, #(1 << 1)           /* 关闭对齐 */bic r0, r0, #(1 << 0)           /* 关闭MMU */    MCR p15, 0, r0, c1, c0, 0       /* 将r0寄存器里面的数据写入到SCTLR里面 */
#if 0/* 设置中断向量偏移 */ldr r0, =0x87800000dsbisbMCR p15, 0, r0, c12, c0, 0      /* 设置VBAR寄存器=0x87800000 */dsbisb
#endif@ 设置处理器进入IRQ模式 mrs r0, cpsr        /* 读取cpsr到r0 */bic r0,r0, #0x1f    /*清除cpsr的bit4-0*/orr r0, r0, #0x12   /* 使用IRQ模式 */msr cpsr, r0        /* 将r0写入到cpsr */ldr sp, =0x80600000 /* 设置IRQ模式下的sp指针 */@ 设置处理器进入SYS模式 mrs r0, cpsr        /* 读取cpsr到r0 */bic r0,r0, #0x1f    /*清除cpsr的bit4-0*/orr r0, r0, #0x1f   /* 使用SYS模式 */msr cpsr, r0        /* 将r0写入到cpsr */ldr sp, =0x80400000 /* 设置SYS模式下的sp指针 */@ 设置处理器进入SVC模式 mrs r0, cpsr        /* 读取cpsr到r0 */bic r0,r0, #0x1f    /*清除cpsr的bit4-0*/orr r0, r0, #0x13   /* 使用SVC模式 */msr cpsr, r0        /* 将r0写入到cpsr */ldr sp, =0x80200000 /* 设置SVC模式下的sp指针 */cpsie i             /* 打开IRQ */b main /* 跳转到c语言main函数 */
/*未定义指令中断服务函数*/
Undefined_Handler:ldr r0, =Undefined_Handlerbx r0
/*SVC*/
SVC_Handler:ldr r0, =SVC_Handlerbx r0
/*预取终止*/  
PreAbort_handler:ldr r0, =PreAbort_handlerbx r0
/*数据终止*/
DataAbort_Handler:ldr r0, =DataAbort_Handlerbx r0
/*未使用*/
NotUsed_Handler:ldr r0, =NotUsed_Handlerbx 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_Handlerbx r0

2. 选择时钟源与使能时钟

这个之前的时钟实验就有写,但是我还没有把笔记总结出来,今天就一起总结一下。

2.1.时钟树

图2-1

         由图2-1所示,时钟树包含了三个部分,分别是 CLOCK SWITCHER , CLOCK ROOT GENERATOR和SYSTEM CLOCKS,第一部分就是时钟源,第三部分就是我们需要使用到的各个时钟,中间部分则是选择时钟源的各种方式,也就是各种寄存器。

2.2.编写步骤

        看我们的标题就知道,我们需要选择时钟源,所以先对第一部分下手,这一部分可以去阅读一下参考手册,内容很多就不一一赘述了,这个开发板的时钟来源于两个部分,而我们只是用到了24Mhz的晶振,另外一个不管,而24M中呢,又有7路PLL,但是这里没有用完,只需要修改主频PLL1以及两个带PFD的也就是PLL2和PLL3,其他PLL没有使用到不用管。

图2-2 PLL1时钟的切换

 由图可知,我们默认选择主频是996M,然后又被2分频了,就是498M,但是该开发板的时钟频率还是非常强大的只用这样么一点不太够,我们可以手动的进行调频,具体的时钟范围,阅读参考手册时钟源那一章节。

    /* ============================================================================== *//* 由图可知,CCM.CCSR寄存器的PLL1_SW_CLK_SEL控制着时钟源的切换 *//* 这一步,先将CPU主频调整为24,让CPU在我们修改主频的时候依然有时钟可用 *//* 先判断CCSR的PLL1_SW_CLK_SEL位是否为0 */if(((CCM->CCSR) >> 2) == 0){/* 是的话就在图中从下往上修改,先将频率选择为24,在修改主频 *//* 先将CCSR的step_sel位切换为0 */CCM->CCSR &= ~(1 << 8);/* 再将CCSR的pll1_sw_clk_sel置为1 */CCM->CCSR |= (1 << 2);}/* 下面就是修改PLL1的主频时钟源了 *//* ((88 << 0) & 0x7f这个是只取7位,防止高位污染 */CCM_ANALOG->PLL_ARM |= (1 << 13) | ((88 << 0) & 0x7f);/* 然后分频调整为2分频 */CCM->CACRR = 1;/* 在将主频从24修改回来 */CCM->CCSR &= ~(1 << 2);/* 以上就可以得到1056/2=528的时钟频率 *//* ============================================================================== */

接下来就是修改PLL2_PFD和PLL3_PFD

    /* 创建一个变量用以接受PFD的值 */unsigned int reg = 0;/* 将reg等于PLL2 */reg = CCM_ANALOG->PFD_528;/* 先将源配置清零 */reg &= ~(0x3f3f3f3f);/* 根据官方的每个PFD的频率来赋值,注意官方会虚标 *//* 计算公式 X=528*18/PFDn */reg |= (32 << 24);reg |= (24 << 16);reg |= (16 << 8);reg |= (27 << 0);/* 在将reg赋值给CCM_ANALOG->PFD_528 */CCM_ANALOG->PFD_528 = reg;reg = 0;/* reg又等于480 */reg = CCM_ANALOG->PFD_480;reg &= ~(0x3f3f3f3f);// PFD3_FRACreg |= 19 << 24;reg |= 17 << 16;reg |= 16 << 8;reg |= 12 << 0;CCM_ANALOG->PFD_480 = reg;

上面的步骤将时钟树左边的时钟源就配置完成了,然后就要配置右边的外设时钟。

图2-3 外设时钟配置

 图2-3是节选了一点不是整张图纸,详细的可以查看imx6ull参考手册的CCM章节。

图2-4 外设时钟配置

图2-4就是各个时钟的范围,我们直接拉最大。

    /*=================================================================*//* 这里时钟树左边的时钟源就配置完成了,接下来该配置右边的外设时钟了 *//* 这里正常应该配置三个,分别是AXI,AHB和IPG,但是AXI默认是够使用的,所以只需要配置AHB和IPG *//* 查看时钟树,就会发现,AHB和AXI,IPG都有关系,所以先配置AHB是没错的 *//* AHB最大是132 所以得选择PLL2_PFD2=396,分频设置为3分频*//* 先清除原先的数值 */CCM->CBCMR &= ~(3 << 18);CCM->CBCMR |= (1 << 18);/* CBCDR寄存器的bit25置为0 */CCM->CBCDR &= ~(1 << 25);/* 设置3分频 */CCM->CBCDR |= (2 << 10);/* 等待握手完成 */while(CCM->CDHIPR & (1 << 1));/* IPG最大频率为66,即是132/2 所以设置成2分频*/CCM->CBCDR &= ~(3 << 8);CCM->CBCDR |= (1 << 8);//设置 PERCLK_CLK_ROOT 时钟 CSCMR1//PERCLK_CLK_SEL(bit6)为0是ipg,为1是oscCCM->CSCMR1 &= ~(1 << 6);//设置为1分频CCM->CSCMR1 &= ~(7 << 0);

以上,更换时钟源的设置就全部配置结束了。

接下来就是使能时钟,这个可以根据GPIO的需求去使能对应的GPIO时钟,但是为了图方便直接采取全部使能。

void clk_init(void)
{CCM->CCGR0 = 0xFFFFFFFF;CCM->CCGR1 = 0xFFFFFFFF;CCM->CCGR2 = 0xFFFFFFFF;CCM->CCGR3 = 0xFFFFFFFF;CCM->CCGR4 = 0xFFFFFFFF;CCM->CCGR5 = 0xFFFFFFFF;CCM->CCGR6 = 0xFFFFFFFF;
}

这个大家可以看看手册,这个很简单,跟写32的步骤差不多,我之前试过但是写新的GPIO老是把这一步搞忘了,索性全部使能,毕竟CPU的性能还是很强大的,只使用裸机不会造成多大影响。

3.编写中断函数

       第一步中的启动文件,有一个IRQ_Handler,这个就是常用的外部中断了,里面有一个跳转到C语言的system_irqhandler, 这个就是中断处理函数。由于启动文件中有这一步,所以我们在做分步测试的时候虽然没有写具体的中断,但是不写这个函数,就编译不过,所以配置了时钟以后就将中断写好。

3.1.定义中断处理函数格式

        定义一个中断处理函数的格式,往后的中断函数都依照这个格式写。

typedef void (*system_irq_handler_t)(unsigned int gicciar,void*pagram);

参数1:gicciar就是中断号,中断表中的中断号是多少就是多少。

参数2:pagram传递的参数。

3.2.定义中断处理函数结构体

typedef struct _sys_irq_handle
{system_irq_handler_t irqhandler;void*userParam;
}sys_irq_handle_t;

第一个元素就是中断处理函数,第二个是传递的参数。

3.3.定义中断处理函数表和初始化中断处理函数表

        CPU有很多中断,我们将其放置在一个数组中,方便根据下标快速查找。

/* 定义中断处理函数表 */
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];/* 下面这个就是官方定义的了 */
#define NUMBER_OF_INT_VECTORS 160 
/* 一共有160个中断 */

        然后我们要将这个表初始化,因为是结构体组成的数组,里面引用了指针,为了避免野指针的风险还是要赋初值,中断处理函数就要先写一个默认的中断处理函数,这个函数里面啥都不干,死循环就行了。

/* 默认中断处理函数 */
void default_irqhandler(unsigned int gicciar,void *userParam)
{while(1){}
}

        然后就是写初始化中断处理函数表的函数了

/* 初始化中断处理函数表 */
void system_irqtable_init(void)
{unsigned int i = 0;/* 初始化中断嵌套的值 */irqNesting = 0;for(i = 0; i < NUMBER_OF_INT_VECTORS; i++){/* 赋值为默认中断处理函数 */irqTable[i].irqhandler = default_irqhandler;irqTable[i].userParam = NULL;}
}

        注意代码中同样需要初始化一下中断嵌套的值,所以需要先定义一下中断嵌套

/* 中断嵌套 */
static unsigned int irqNesting;

        中断嵌套这个变量就是方便CPU的对当前中断嵌套的层级进行追踪,便于系统进行管理以及保护,定义一个中断嵌套计数还是很有必要的。

3.4.中断注册函数

/* 注册中断处理函数 */
void system_register_irqhandler(IRQn_Type irq,system_irq_handler_t handler, void*userParam)
{irqTable[irq].irqhandler = handler;irqTable[irq].userParam = userParam;
}

        这个函数就是设置GPIO中断时,需要写的了,他只要是将中断号,和我们自定义的中断函数,以及参数传递进来。

3.5.具体的中断处理函数

/* 具体的中断处理函数*/
void system_irqhandler(unsigned int gicciar)
{uint32_t intNum = gicciar & 0x3ff;if(intNum >= NUMBER_OF_INT_VECTORS){return;}irqNesting++;/* 根据中断ID号,读取中断处理函数,然后执行 */irqTable[intNum].irqhandler(intNum,irqTable[intNum].userParam);irqNesting--;
}

        这个就是启动文件中的了,检测到有中断就会调用该函数,不需要手动。

3.6.中断初始化

/* 中断初始化 */
void int_init(void)
{/* GIC初始化 */GIC_Init();system_irqtable_init();/* 中断向量偏移 */__set_VBAR(0x87800000);
}

        这个中断向量偏移涉及的东西就比较多了,之后把资料整理处理在详细讲述。

4.gpio函数的编写

  1. GPIO是输入还是输出,这个使用枚举的方式
  2. GPIO触发中断的形式(这个也使用枚举的方式)
    1. 无中断触发,低电平触发,高电平触发,上升沿触发,下降沿触发,上升沿下降沿(双边沿触发)。
  3. 定义GPIO结构体,有以上两个内容以及默认电平。
  4. 初始化GPIO。(GDIR)
  5. 读取GPIO的电平。(PSR/DR)
  6. 向GPIO写入电平。(DR)
  7. GPIO使能中断(IMR)
  8. GPIO禁止中断(IMR)
  9. 清除中断(ISR)
  10. 中断初始化函数。(ICR1/ICR2)

按照上面的步骤进行编写代码:

/* 定义枚举,中断触发的模式 */
typedef enum _gpio_interrupt_mode
{kGPIO_NoIntmode = 0U,kGPIO_IntLowLevel = 1U,kGPIO_IntHighLevel = 2U,kGPIO_IntRisingEdge = 3U,kGPIO_IntFallingEdge = 4U,kGPIO_IntRasingFallingEdge = 5U,
}gpio_interrupt_mode_t;
/* 定义GPIO输入或输出 */
typedef enum gpio_direction
{GPIO_INPUT = 0U,GPIO_OUTPUT = 1U,
}gpio_direction_t;
/* 定义gpio结构体 */
typedef struct gpio_pin_config{gpio_direction_t gpio_direction;uint8_t outputLogic;gpio_interrupt_mode_t InterrputMode;
}gpio_pin_config_t;
/* GPIO初始化 */
void gpio_init(GPIO_Type*base,int pin,gpio_pin_config_t*config)
{if(config->gpio_direction == GPIO_INPUT){base->GDIR &= ~(1 << pin);}else{base->GDIR |= (1 << pin);gpio_write_pin(base,pin,config->outputLogic);}/* 初始化中断 */gpio_interrupt_init(base,pin,config->InterrputMode);
}
/* 读取gpio的电平函数 */
int gpio_read_pin(GPIO_Type*base,int pin)
{return 0;
}
/* 写入gpio的电平 */
void gpio_write_pin(GPIO_Type*base,int pin,int value)
{if(value == GPIO_INPUT){base->DR |= (1 << pin);}else{base->DR &= ~(1 << pin);}
}/* gpio使能中断 */
void gpio_enable_interrupt(GPIO_Type*base, int gpio_pin)
{base->IMR |= (1 << gpio_pin);
}/* gpio禁止中断 */
void gpio_disable_interrupt(GPIO_Type*base, int gpio_pin)
{base->IMR &= ~(1 << gpio_pin);
}/* 清除中断 */
void gpio_clear_interrupt(GPIO_Type*base, int gpio_pin)
{base->ISR |= (1 << gpio_pin);
}
/* 初始化中断函数 */
void gpio_interrupt_init(GPIO_Type*base, int gpio_pin,gpio_interrupt_mode_t interrupt_mode)
{volatile uint32_t *icr;int pin = gpio_pin;base->EDGE_SEL &= ~(1U << pin);if(gpio_pin < 16){icr = &(base->ICR1);}else{icr = &(base->ICR2);pin -= 16;}switch(interrupt_mode){case kGPIO_IntLowLevel:*icr &= ~(3 << (2*pin));break;case kGPIO_IntHighLevel:*icr &= ~(3 << (2*pin));*icr |= (1 << (2*pin));break;case kGPIO_IntRisingEdge:*icr &= ~(3 << (2*pin));*icr |= (2 << (2*pin));break;case kGPIO_IntFallingEdge:*icr &= ~(3 << (2*pin));*icr |= (3 << (2*pin));break;case kGPIO_IntRasingFallingEdge:base->EDGE_SEL |= (1 << gpio_pin);break;default:break;}
}

        以上就是GPIO库函数的编写了,这个和STM32的库函数还是很像的, 可以看一下参考手册,特别是ICR这个寄存器,他有ICR1和ICR2,没两个IO表示一位pin脚的中断。

5.其他外设代码编写

5.1.led代码编写

        这个开发板只有一个led可以使用,如果有多个的话就需要使用枚举的方式表示其他led,所以这里预留

typedef enum led_num
{LED_NUM0 = 0U,LED_NUM1 = 1U,
}led_num_t;

         然后就是led初始化和电平跳转了

void led_init(void)
{gpio_pin_config_t led_config;//复用IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03,0);//电气属性IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03,0X10B0);led_config.direction = GPIO_OUTPUT;led_config.outputLogic = 0U;gpio_init(GPIO1,3,&led_config);
}void led_turn(int led_num,int state)
{switch (led_num){case  LED_NUM0:if(state == ON){gpio_write_pin(GPIO1,3,1);}else{gpio_write_pin(GPIO1,3,0);}break;default:break;}
}

5.2.蜂鸣器代码编写

void beep_init(void)
{gpio_pin_config_t beep_config;//复用IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01,0);//电气属性IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01,0X10B0);beep_config.gpio_direction = GPIO_OUTPUT;beep_config.outputLogic = 0U;gpio_init(GPIO5,1,&beep_config);
}void beep_turn(int state)
{gpio_write_pin(GPIO5,1,state);
}

5.3.按键代码编写

void key_init(void)
{//定义中断按键的值gpio_pin_config_t key_config;//复用gpioIOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0);//配置电气属性IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0XF080);key_config.gpio_direction = GPIO_INPUT;key_config.InterrputMode = kGPIO_IntFallingEdge;key_config.outputLogic = 1;gpio_init(GPIO1,18,&key_config);/* ================中断部分==================== */GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);/* 注册中断处理函数 */system_register_irqhandler(GPIO1_Combined_16_31_IRQn,(system_irq_handler_t)gpio1_io18_handler,NULL);/* 使能中断 */gpio_enable_interrupt(GPIO1,18);
}void gpio1_io18_handler (unsigned int gicciar,void *param)
{//蜂鸣器的状态static unsigned char state;delay(10);if(gpio_read_pin(GPIO1,18) == 0) //按键按下了{state = !state;beep_turn(state);}gpio_clear_interrupt(GPIO1, 18);      //清除中断标志位}

        注意这里加入之前的按键中断实验,但是在中断处理函数里面加入了延时函数(软件),这个不行后面会有更改。

5.4.epit时钟代码编写

void epit_init(unsigned int frac,unsigned int value)
{if(frac > 0XFFF)frac = 0XFFF;EPIT1->CR = 0;EPIT1->CR = (1 << 24 | frac << 4 | 1 << 3 | 1 << 2 | 1 << 1);EPIT1->LR = value;EPIT1->CMPR = 0;GIC_EnableIRQ(EPIT1_IRQn);system_register_irqhandler(EPIT1_IRQn,(system_irq_handler_t)epit_irqhandler,NULL);EPIT1->CR |= 1 << 0;
}
void epit_irqhandler(void)
{static unsigned char state = 0;state = !state;if(EPIT1->SR & (1<<0)){led_turn(LED_NUM0,state);}EPIT1->SR |= 1<<0;
}

epit配置图 

         可以去手册看一下,epit的详细配置步骤。

6.Makefile编写

CROSS_COMPULE ?= arm-linux-gnueabihf-
TARGET		  ?= epitCC 			:= $(CROSS_COMPULE)gcc
LD 			:= $(CROSS_COMPULE)ld 
OBJCOPY		:= $(CROSS_COMPULE)objcopy 
OBJDUMP		:= $(CROSS_COMPULE)objdump 
# 头文件
INCUDIRS 		:= 	imx6ull		\bsp/clk		\bsp/led 	\bsp/delay	\bsp/gpio	\bsp/int 	\bsp/beep 	\bsp/key		\bsp/epit	\project
# 源码
SRCDIRS			:=	project		\bsp/clk		\bsp/led		\bsp/gpio	\bsp/int 	\bsp/beep 	\bsp/key 	\bsp/epit	\bsp/delay	
# 给每个头文件前面加上 -I  INCLUDE = -I imx6ull -I bsp/clk -I bsp/led -I bsp/delay -I project
INCLUDE			:= $(patsubst %, -I %, $(INCUDIRS))
# 获取工程的所有.S 文件和 .c 文件
SFILES			:= $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))CFILES			:= $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))SFIEDIR 		:= $(notdir $(SFILES))
CFIEDIR			:= $(notdir $(CFILES))SOBJS			:= $(patsubst %, obj/%, $(SFIEDIR:.S=.o))
COBJS			:= $(patsubst %, obj/%, $(CFIEDIR:.c=.o))OBJS			:= $(SOBJS) $(COBJS)VPATH 			:= $(SRCDIRS)
.PHONY:clean$(TARGET).bin:$(OBJS)$(LD) -Timx6ul.lds -o $(TARGET).elf $^$(OBJCOPY) -O binary -S $(TARGET).elf $@$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis$(SOBJS) : obj/%.o : %.S$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
$(COBJS) : obj/%.o : %.c$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
clean:rm -rf $(TARGET).elf $(TARGET).bin $(TARGET).dis $(OBJS)
print:@echo INCLUDE = $(INCLUDE)@echo SFILES = $(SFILES)@echo CFILES = $(CFILES)@echo SFIEDIR = $(SFIEDIR)@echo CFIEDIR = $(CFIEDIR)@echo SOBJS = $(SOBJS)@echo COBJS = $(COBJS)@echo OBJS = $(OBJS)

 7.烧录并运行

相关文章:

  • 贪心,回溯,动态规划
  • 从零发布一个 Vue 3 Button 组件到 npm(基于 Vite)
  • 更改安卓虚拟机屏幕大小
  • 计算机基础知识(第四篇)
  • 2025年上海市“星光计划”第十一届职业院校技能大赛 网络安全赛项技能操作模块样题
  • 农田水利如何「聪明」起来?Modbus转Ethernet IP破解设备互联
  • 洛谷题目:P2761 软件补丁问题 (本题简单)
  • linux下覆盖率测试总结
  • App使用webview套壳引入h5(二)—— app内访问h5,顶部被手机顶部菜单遮挡问题,保留顶部安全距离
  • 从Copilot到Agent,AI Coding是如何进化的?
  • [特殊字符] 一文了解目前主流的 Cursor AI 免费续杯工具!
  • 使用logrotate切割nginx日志
  • NX985NX988美光固态闪存NY103NY106
  • 【论文解读】MemGPT: 迈向为操作系统的LLM
  • 【如何做好应用架构?】
  • 基于WSL搭建Ubnutu 20.04.6 LTS(二)-部署Docker环境
  • 【强化学习】——03 Model-Free RL
  • 【前端】js如何处理计算精度问题
  • 并发编程 - go版
  • Go中的协程并发和并发panic处理
  • 全景网站建设/哈尔滨关键词优化报价
  • 你认为视频网站如何做推广/企业推广网络营销
  • html5企业网站建设/手机百度网页版
  • 应用网站模板/资源网站优化排名优化
  • 自己有域名如何做网站/推广方案如何写
  • 网站数据库是干什么的/新闻发稿