ARM架构与编程6--重定位(基于百问网ARM架构与编程教程视频)

2023-05-16

一、启动程序流程

我们之前讲过,单片机有根据boot的不同,有三种启动方式:

boot0boot1启动模式
0Xflash启动
10系统存储器
11内置SRAM

单片机上电复位后,运行main函数。以STMF103ZE芯片、flash启动为例。

首先在keil中可以看到芯片默认的ROM和RAM地址:
在这里插入图片描述

可以看到,ROM也就是flash起始地址是0x08000000,大小是512k:RAM起始地址是0x20000000,大小是64k。

下面以串口实验为例,看一下程序的反汇编代码。分析一下系统的启动流程。
在这里插入图片描述

程序被下载到flash中,首地址就是0x08000000,而且内核规定了,第一条指令必须是初始化栈指针的,第二条指令必须是初始化PC指针,PC = Rest_Handle,并且跳转过去执行,然后在Rest_Handler中调用main函数,这样程序就会执行我们自己写的函数了。注意这个函数是我们自己编写的,不是官方的,这里为了让大家更加清晰的看到程序的执行过程,简化了这个函数。

这里用mymain代替main函数,也是要简化系统启动的流程。 查看MDK的文档,会发现有这么一句说明:It is automatically created by the linker when it sees a definition of main()。就是说,当发现定义了main函数,那么就会自动创建__main,这个函数是编译器自动创建的,会在里面去进行一些初始化设置。

这样,我们就可以正常运行一个简单的程序,下面以串口实验为例:

在这里插入图片描述

可以看到,现象正常,说明我们自己的启动文件是可以运行的。

二、段

启动文件像上面那样设置是不是就没问题了?我们做个实验,还是以串口为例。

char Temp = 'O';
int mymain()
{	
    usart_init();
	putchar('1');
	putchar('1');
	putchar(Temp);
	putchar('1');
	putchar('1');
	while(1);
}

定义一个全局变量,并赋值。然后通过串口打印输出。

在这里插入图片描述

我们可以看到,输出的结果明显不对,Temp应该是输出字母O,但是结果明显不是。下面大红框是数据的16进制表示,31、32是1、2的ASCII码值,中间应该是O的,应该是4F。而且每一次复位之后,输出的也不一样。说明全局变量Temp的值不是确定的,每次上电后都发生变化。

下面定义一个const修饰的全局变量,可以看到,Temp2对应的16进制是41,也就是A,说明Temp2的值是确定的,Temp1的值是不确定的。

在这里插入图片描述

下面看一下这两个遍全局变量的地址。
在这里插入图片描述

可以看到,Temp1地址是0x2000 0000,Temp2地址是0x0800 0144,这两个地址是不是有些眼熟。没错,正是对应芯片的RAM和ROM。

在这里插入图片描述

也就是说,虽然都是全局变量,但是他们在程序运行时,保存的位置是不一样的。

对于全局变量Temp1,他是可读可写的,所以在运行时放在RAM中。Temp2是只读的,所以放在ROM(也就是Flash)中。根据程序数据存放位置的不同,内存会被分为不同的段。

1、可读可写数据段(RW-data):存放初始值不为0的全局变量、静态变量。烧录在ROM上,使用之前需要从ROM上复制到内存。

2、只读数据段(RO-data):存放不可以被修改的变量,如const修饰的变量或者字符串,烧录在ROM上,不需要复制到内存。

3、BSS或ZI段:初始值为0和未初始化的全局变量或静态变量,放在RAM中。

4、堆:一块空闲空间,存放进程运行中被动态分配的内存段,使用malloc函数来管理它。

5、栈:存放局部变量和函数调用时的参数

上面是存放数据的段,还有存放代码的段。

代码段(Code):就是目标文件的所有程序代码,不会被修改,烧录在Flash中。

烧录时:

Code+RO+RW全在flash上,RI段全是0没必要烧录,只需要在程序运行前全部清零即可。

运行时:

RW-data会搬运到RAM中,链接器会把RW的地址映射到RAM中的一片区域内,访问RW-data实际上访问的是RAM中的地址。

接下来我们分析一下,为什么上面的全局变量Temp1的值是不确定的。

因为链接器只负责映射地址,不负责数据的拷贝,所以对于RAM中的0x20000000这个地方,是没有数据的,或者说这里面的数据是随机的,因为没有对这快内存进行赋值。我们的程序上电之后,执行Rest_Handler程序,设置栈指针,然后运行main函数,使用串口打印Temp,这些流程中,并没有对0x20000000这块内存进行赋值。这就是为什么定义Temp1时候,即使赋值了,但是仍然打印不出来正确的数据初始化赋的值保存在了ROM中,而程序运行时访问的时RAM中的地址,RAM中的空间并没有被初始化。对于Temp2,CPU访问的时候访问的是ROM区域,而Temp2正是保存在这里,所以CPU可以直访问。

在这里插入图片描述

2.1 加载地址和链接地址

由上可知,程序在烧录和运行时分布是不同的。这些程序中所有初值要保存只可能存在ROM中,但是在使用的时候,访问的却是RAM。所以这中间肯定有这样一种机制 : 在上电以后把ROM中存储的这些变量初值来重新初始化到对应的RAM地址,以便后续程序指令访问。

在程序烧录时,所有数据都加载都ROM中,在ROM中为代码和数据分配内存空间,加载地址就是程序保存在ROM中的地址。

链接地址:程序在RAM中执行时的地址。执行这条指令时,PC值应该等于这个地址,也就是说,PC等于这个地址时,这条指令应该保存在这个地址内。链接地址由链接脚本文件指出,链接的时候确定

2.1.2 重定位

重定位就是把目标重新放在一个新的地址上,PC访问的时候是去新地址上访问。简单地说就是用链接地址来代替加载地址成为程序被访问时的地址。保存在ROM上的全局变量的值,在使用前要复制到RAM,这就是数据段重定位。若想把代码移动到其他位置,就是代码重定位。

假若程序不位于链接地址时程序会出现什么问题?

去访问某些全局变量时就会出错,因为访问这些全局变量时用的是它的链接地址,我去链接地址访问你,你就必须位于链接地址上。

现在我们应该知道了为什么上面的Temp1打印出来乱码,是因为Temp1的链接地址上的数据没有被赋值。

下面我们看一下官方提供的启动文件中的Rest_Handler函数中做了些什么:

在这里插入图片描述

SystemInit中主要负责初始化STM的时钟系统。

__main中的__scatterload函数负责设置内存,而rt_entry函数负责设置运行时的环境。__scatterload中负责把RW(非零)输出段从装载域地址复制到运行域地址(执行代码和数据复制、解压缩),并完成ZI段运行域数据的0初始化工作。然后跳到__rt_entry设置堆栈和堆、初始化库函数和静态数据。然后,__rt_entry跳转到应用程序的入口main()。主应用程序结束执行后,__rt_entry将库关闭,然后把控制权交换给调试器。main()函数的存在强制链接器链接到__main__rt_entry中的代码。如果没有标记为main()的函数,则没有链接到初始化序列,因而部分标准C库功能得不到支持。

到现在我们知道了程序的启动流程应该是:复位->设置堆栈->执行Reset_Handler->SystemInit(可以没有)->__main(重定位RW-data数据段、清楚ZI段)->进入真正的main函数。

可以看到,我们自己的启动函数缺少了SystemInit__main两部分,对于SystemInit是配置时钟的,如果不初始化,芯片默认使用HSI-8MHz的时钟。所以不是必须的;但是数据段的重定位是必须的,所以我们要加上这部分。

三、重定位

是否需要重定位,根据数据的加载地址和链接地址确定。若加载地址和链接地址相等,则不需要重定位,比如保存在ROM中的RO-data。

否则就需要重定位,比如RW-data。BSS(ZI)段:不需要重定位,在可执行文件中没有他,只需要在把他对应的空间清零即可。

对于重定位,还需要知道两个概念:位置无关码、位置有关码。

3.1 位置无关码

该段代码无论放在内存的哪个地址,都能正确运行。因为他使用程序当前运行的PC值进行相对跳转,PC=PC+offset,没有使用绝对地址,都是相对地址,无论代码在哪,总能达到指令的正常目的,也就是说这段代码扔在任何位置都可以运行。类似于文件的相对路径,可以把程序放在任何文件夹下面,编辑器均可以根据工程文件路径找到其他每一个文件。

例如:

bl main

3.2 位置有关码

它的地址与代码处于的位置相关,是绝对地址。如PC=0x08000000。他不依赖当前PC值,而是直接把要跳转的地址赋值给PC。这样赋给PC的地址里面,必须有正确的指令才能正常运行,否则会出现各种错误。类似绝对路径,一旦文件夹发生变动,基本上就是定位不到具体的文件了。

例如:

ldr pc, =main    

3.3 重定位实质

重定位的实质就是数据拷贝,把加载地址中的数据搬运到链接地址中去。

对于移动数据,我们要知道三个重要因素:、

1、源:数据来源,也就是加载地址中的数据。

2、目的:数据最终要保存的地址,也就是链接地址。

3、长度:要搬运的数据的长度。

在keil中,用散列文件描述以上三个方面。

3.4 散列文件

在编译过程中有多个.o文件,而最后生成的只是一个可执行文件,那么这些文件要怎么以什么方式生成一个文件呢----链接,在Keil-MDK下就是使用散列文件来指导链接的。

在这里插入图片描述

如上图所示,进行设置,然后重新编译,就会在Objects文件夹中得到一个.sct文件,他就是散列文件。

在这里插入图片描述

打开文件,可以看到下图中的代码。

在这里插入图片描述

下面我们分析一下这个散列文件中的代码。

首先看一下keil中散列文件的基础知识。

在这里插入图片描述

根据上图操作,打开文档找到第八章Scatter File Syntax。

在这里插入图片描述

根据上图可知,一个散点文件包含一个或多个加载域, 一个加载域可以包含一个或多个执行域,每一个执行域包含一个或多个Input section。 加载域中描述二进制文件中一共有哪些东西。

加载域语法

在这里插入图片描述

执行域分布:

在这里插入图片描述

.sct文件:
在这里插入图片描述

LR_IROM1 0x08000000 0x00080000  {;加载域地址从0x08000000开始,大小是0x00080000
  ER_IROM1 0x08000000 0x00080000  {  ; 执行域ER_IROM1的链接地址是0x08000000,与加载地址相同,大小也相同,所以他不需要重定位
   *.o (RESET, +First)	;所有的.o文件里的RESET段抽取出来放在最开始的位置,一般只有启动文件中有RESET段
   *(InRoot$$Sections)	;所有的文件包括库,keil添加的可执行文件,看不到源码,可以去掉
   .ANY (+RO)			;等同于*,优先级比*低,这里表示所有的只读数据段-RO
   .ANY (+XO)			;这里表示所有的只可执行段
  }
  RW_IRAM1 0x20000000 0x00010000  {  ; 执行域RW_IRAM1的链接地址是0x20000000,大小是0x00010000,他需要重定位
   .ANY (+RW +ZI)		;所有的文件的可读可写数据段和ZI段,但是ZI段并不会存在bin可执行文件里。
  }
}

上面两个执行域包含了整个工程的所有信息,两个执行域,一个把内容存放在ROM,一个把内存放在了RAM。由上面也看到了,RO-data保存在了ROM上,RW-data和ZI段放在了RAM。

实际上,对于在STM32F103这类资源紧缺的单片机芯片中:

1、代码段保存在Flash上,直接在Flash上运行(当然也可以重定位到内存里)。

2、数据段暂时先保存在Flash上,然后在使用前被复制到内存里(只读数据段不复制)。

3.4.1 确定三要素

根据上面的内容可以分析出执行域的源和目的。

目的
ER_IROM10x0800 00000x0800 0000
RW_IRAM1紧随ER_IROM1之后0x2000 0000

上面的内容是根据已知的文件生成的,但是对于任意的、未知的执行域来说,如何查找呢?

还是看上面的keil手册6.3节。

在这里插入图片描述

在这里插入图片描述

上面知道了三要素:

1、目的:Image$$region_name$$Base

2、长度:Image$$region_name$$Length

3、源:Load$$region_name$$Base

3.5 重定位代码

知道数据传输三要素之后,我们就可以自己编写重定位代码了。注意:重定位是在调用main函数之前。

先写一个数据拷贝函数memory_copy

void memory_copy(void * dest,void * src,unsigned int len)
{
	unsigned char * pcDest;
	unsigned char * pcSrc;
	while(len--)
	{
		*pcDest = *pcSrc;
		pcSrc++;
		pcDest++;
	}
}

3.5.1 RW-data重定位

ldr r0, = |Image$$RW_IRAM1$$Base|
ldr r1, = |Load$$RW_IRAM1$$Base|
ldr r2, = |Image$$RW_IRAM1$$Length|
bl memory_copy

重定位之后,再次执行之前的实验代码:

在这里插入图片描述

可以看到,全局变量Temp1已经可以正常打印出来了。

打开反汇编文件,查看Temp1和Temp2的地址。可以看到,Temp1的地址是0x2000 0000,Temp2位于RO-data段,地址是0x0800 0164。

在这里插入图片描述

3.5.2 ZI段清零

ZI段不被烧录到ROM中,也不会放入bin文件中,否则也太浪费空间了。在使用ZI段里的变量之前,把ZI段所占据的内存清零就可以了。

查看散列文件可知,ZI段在可执行域RW_IRAM1中。

在这里插入图片描述

根据手册可以看到ZI段的基地址和长度。

在这里插入图片描述

知道了ZI段的基地址和长度,我们就可以对这块空间进行清除了。

ZI段保存的是初始值为0或没有初始值的全局变量和静态变量。

int Temp1[16] = {0};
int Temp2[16];
int mymain()
{	
	static int Temp3[16] = {0};
	static int Temp4[16];
	usart_init();
	put_s_hex("Temp1 is: ",Temp1[0]);
	put_s_hex("Temp2 is: ",Temp2[0]);
	put_s_hex("Temp3 is: ",Temp3[0]);
	put_s_hex("Temp4 is: ",Temp4[0]);
	while(1);
}

输出结果:

Temp1 is: 0x6BF87F8B
Temp2 is: 0x126577A9
Temp3 is: 0xC82AA8DC
Temp4 is: 0xE5EC5EF7

可以看到,上面四个变量的值是随机的,打开keil生成的.map文件(Listings目录下),可以看到,他们都是位于BSS段。

在这里插入图片描述

由于没有进行RAM中BSS段的清零,所以他们初值是随机的。

注意:若本属于BSS段的数据比较少,编译器会进行优化,把数据放在.data段。

int Temp1 = 0;
int Temp2;
int mymain()
{	
	static int Temp3 = 0;
	static int Temp4;
	usart_init();
	put_s_hex("Temp1 is: ",Temp1);
	put_s_hex("Temp2 is: ",Temp2);
	put_s_hex("Temp3 is: ",Temp3);
	put_s_hex("Temp4 is: ",Temp4);
	while(1);
}

在这里插入图片描述

ZI段清零:

还是在启动文件中添加ZI段清零的代码:

IMPORT |Image$$RW_IRAM1$$ZI$$Base|	;ZI段基地址
IMPORT |Image$$RW_IRAM1$$ZI$$Length|	;ZI段长度
IMPORT	memory_set

ldr r0, = |Image$$RW_IRAM1$$ZI$$Base|
mov r1, 0
ldr r2, = |Image$$RW_IRAM1$$ZI$$Length|
bl memory_set

输出结果:

Temp1 is: 0x00000000
Temp2 is: 0x00000000
Temp3 is: 0x00000000
Temp4 is: 0x00000000

可以看到,ZI段已经被清零了。

3.5.3 代码重定位

一般情况下,代码都是下载到Flash中,但是有时候为了提高执行速度,也会把代码拷贝到RAM中运行。

首先设置Keil,使用自定义的散列文件。

在这里插入图片描述

然后修改Objects文件夹下的.sct文件。

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x20000000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 + 0  {  ; RW data,紧跟着ER_IROM1后面
   .ANY (+RW +ZI)
  }
}

这样,代码的链接地址就处于RAM中了。

在汇编中,使用不同的指令可以在Flash或RAM中跳转函数。

LDR PC,=main	;使用链接地址,运行RAM中代码
BL main	;运行Flash中代码

所以运行RAM中的代码,要使用第一种跳转方法,同时必须进行代码重定位,否则会跟RW-data重定位出现的问题一样,运行函数的之前,函数的链接地址中没有相应的指令,程序就会崩溃。

代码段的链接地址(基地址)、长度,使用下面的符号获得:

在这里插入图片描述

代码段的加载地址,使用下面的符号获得:

在这里插入图片描述

代码重定位的汇编代码与之前的RW-data重定位类似,

LDR R0, = |Image$$ER_IROM1$$Base|
LDR R1, = |Load$$ER_IROM1$$Base|  
LDR R2, = |Image$$ER_IROM1$$Length|
BL memory_copy
LDR  R0, =mymain
BLX  R0

注意注意:要把启动文件的第二个指令的地址改一下,否则程序上电后第二步执行Reset_Handler时,是去RAM中执行,但此时RAM中并没有相应的Reset_Handler代码,所以要先执行ROM中的Reset_Handler,就是地址0x08000008。

__Vectors       DCD     0                  
                ;DCD     Reset_Handler     ;直接跳转到RAM中执行,但是此时RAM中并没有相应的指令,所以程序崩溃
				DCD      0x08000009        ;跳转到0x08000008执行,执行Flash中的Reset_Handler

3.6 重定位之前的代码怎么运行

重定位之前的代码是使用位置无关码写的,关于位置无关码和位置有关码的区别之前介绍过,这里不再讲解。

使用位置无关码,无论程序在哪里,都可以被CPU正确访问到。

1、只使用相对跳转指令:B、BL

2、不能用绝对跳转指令:

LDR R0, =main
BLX R0

3、不访问全局变量、静态变量、字符串、数组

4、重定位完后,使用绝对跳转指令跳转到XXX函数的链接地址去

BL main         ; bl相对跳转,程序仍在Flash上运行
LDR R0, =main   ; 绝对跳转,跳到链接地址去,就是跳去内存里执行
BLX R0

四、C语言编写重定位函数

在汇编中,重定位需要的变量用|Image$$region_name$$Base|这样的格式表示。

在C语言中,对于一个变量,可以用extern关键字声明为外部变量,然后直接引用即可。

对于变量,引用的时候要加上&:

extern int Image$$ER_IROM1$$Base;
extern int Load$$ER_IROM1$$Base;
extern int Image$$ER_IROM1$$Length;
memcpy(&Image$$ER_IROM1$$Base, &Image$$ER_IROM1$$Length, &Load$$ER_IROM1$$Base);

对于数组,数组名就相当于地址,所以不用加&:

extern char Image$$ER_IROM1$$Base[];
extern char Load$$ER_IROM1$$Base[];
extern int Image$$ER_IROM1$$Length;
memcpy(Image$$ER_IROM1$$Base, Image$$ER_IROM1$$Length, &Load$$ER_IROM1$$Base);

现在把启动文件中的重定位代码删除,用C语言编写一个重定位函数relocate_c,然后在启动文件中调用。

extern int Image$$ER_IROM1$$Base;
extern int Image$$ER_IROM1$$Length;
extern int Load$$ER_IROM1$$Base;
extern int Image$$RW_IRAM1$$Base;
extern int Image$$RW_IRAM1$$Length;
extern int Load$$RW_IRAM1$$Base;
extern int Image$$RW_IRAM1$$ZI$$Base;
extern int Image$$RW_IRAM1$$ZI$$Length;	
void relocate_c(void)
{
	/*代码段重定位*/
    memcpy(&Image$$ER_IROM1$$Base, &Load$$ER_IROM1$$Base, &Image$$ER_IROM1$$Length);	
    /*RW-data重定位*/
    memcpy(&Image$$RW_IRAM1$$Base, &Load$$RW_IRAM1$$Base, &Image$$RW_IRAM1$$Length);
    /*清除ZI段*/
    memset(&Image$$RW_IRAM1$$ZI$$Base, 0, &Image$$RW_IRAM1$$ZI$$Length);
}

五、程序是否需要加载到RAM中?

4.1 单片机

1、把程序从flash加载到RAM需要bootloader,(其实程序也可以直接下载到RAM中运行,只不过重启程序就没有了。

2、单片机RAM较小程序太多无法加载全部程序。

3、单片机执行分三个步骤:取指令,分析指令,执行指令。取指令任务是根据PC值从地址总线上读出指令。虽然从RAM取指令速度远大于ROM,但单片机自身运行速度不高,所以程序放在哪里没有太大影响。

4.2 运行Linux系统

linux程序比较大,很少用norflash储存程序,而是用nandFlash或sd储存,这类储存不符合CPU 的指令译码执行要求。

运行linux系统的cpu,其运行频率非常高,远大于ROM读写速度,从ROM取指会严重影响速度。故系统会把程序拷贝到RAM执行。

所有运行Linux系统时程序必须加载到RAM

六、参考文章

(7条消息) STM32裸机开发(6) — Keil-MDK下散列文件的分析_Willliam_william的博客-CSDN博客

(7条消息) STM32启动详细流程之__main_非常规自我实现的博客-CSDN博客

(7条消息) STM32代码烧写到哪里去了?是ROM?还是RAM?还是flash?它们都是啥?代码具体占了多少空间?超没超芯片的范围?KEI里如何设置芯片flash、RAM可用大小呢?_越过山丘呀的博客-CSDN博客

欢迎阅读《MDK的编译过程及文件类型全解》文档-by 秉火 — FLASH 1.0 文档 (flash-rtd.readthedocs.io)

(7条消息) 【IoT】STM32 启动代码 __main 与用户主程序 main() 的区别_产品人卫朋的博客-CSDN博客

(7条消息) 什么是重定位?为什么需要重定位?_cherisegege的博客-CSDN博客_重定位是什么意思

stm32中存在rom中的全局变量初始值是怎么copy到RAM区? (amobbs.com 阿莫电子论坛)

(7条消息) MDK __main()代码执行分析_TS_up的博客-CSDN博客___main

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

ARM架构与编程6--重定位(基于百问网ARM架构与编程教程视频) 的相关文章

  • 港科大VINS-MONO入门(一):框架入门及源码解析

    一 VINS介绍 VINS Mono是HKUST的Shen Shaojie团队开源的一套Visual Inertial融合定位算法 介绍见 https github com HKUST Aerial Robotics VINS Mono 论
  • ROS学习笔记(三):rosrun和runlaunch的用法

    一 区别 rosrun是运行一个单独节点的命令 xff0c 如果要运行多个节点 xff0c 则需要使用多次rosrun命令 roslaunch采用XML的格式对需要运行的节点进行描述 xff0c 可以同时运行多个节点 例如 xff1a lt
  • Javascript>> onmouseover用法

    lt DOCTYPE html gt lt html gt lt head gt lt title gt Window Title lt title gt lt head gt lt body gt lt p gt Test your mo
  • Python之Flask登录认证--before_request

    from flask import Flask render template request Response redirect session url for app 61 Flask name app debug 61 True 自动
  • Prometheus安装部署和node_exporter的使用

    一 环境 服务器IP系统组件192 168 0 181CentOS7 6Prometheus Server 2 18 1192 168 0 182CentOS7 6node exporter 1 0 0 下载地址为 xff1a https
  • easyui 学习总结

    1 分页折行导致显示问题 问题描述 xff1a 1 缩小datagrid的宽度 xff0c 直至分页刚刚折行 2 此时再隐藏pageList按钮和刷新按钮 此时table底部将出现一个白条 39 dg 39 datagrid data ge
  • 机会从来都是留给有准备的人,当然,也总是留给那些耐得住寂寞的人, 在别人玩的时候,静下心来学习

    席华锋 1985年出生 2004年上大学 华中科技大学 2011研究生毕业 工作蚂蚁金服 搞Ocean Base分布式数据库 八年如一日 实现从P5到P8的职业生涯三级跳 xff0c 也完成了三个阶段的成长和蜕变 https blog cs
  • JetsonTX2上安装tensorflow的心酸史

    JetsonTX2上安装tensorflow的心酸史 还是那句话 xff0c 做事情得有耐心 xff0c 有耐心 耐心 心 感觉像是给自己的一个心理暗示 tensorflow安装常见问题总结验证 tensorflow1 3 0安装 好的 x
  • MOS管开关电路应用及MOS管原理、选型

    目录 硬件基础 MOS管原理 使用 开关电路应用0 写在前面 xff1a 1 MOS管基本原理及分类1 1 MOS管分类1 2 MOS管导通原理1 3 MOS管输出特性曲线1 4 MOS管的转移特性1 5 MOS管的寄生二极管 xff1a
  • MQTT服务器搭建和ESP32实现MQTT代码

    文章目录 1 MQTT介绍 xff1a 1 1 需求介绍1 2 MQTT介绍 xff1a 2 具体实现 xff1a 2 1 库推荐2 2 配置MQTT的服务器Broker xff1a 2 3 PubSubClient库使用 xff1a 3
  • 三极管从入门到精通

    文章目录 摘要1 基础1 1 PN结1 2 三极管 2 三极管模拟电路知识2 1 I V特性曲线2 2 极限参数解释2 3 基本共射极放大电路2 4 小信号模型2 5 用小信号模型分析基本共射极放大电路 3 三极管实际模拟电路应用图3 1
  • Type-C接口简单介绍-面向单片机应用

    Type C接口简单介绍 面向单片机应用 1 绪论 用单片机做一些东西时 xff0c Type C接口逐渐替代了MicroUSB接口 但不像MicroUSB那样只有5V GND D 43 D ID五个接口 xff0c Type C接口有24
  • 嵌入式硬件:放大器电路仿真

    文章目录 说明同向放大电路反向放大电路放大器滤波电路低通滤波电路proteus仿真TINA TI仿真 窄带滤波电路preteus仿真TINA TI仿真 参考 说明 书上的放大电路图很多都是理论图 xff0c 和实际应用有所差异 比如下面这个
  • git 切换分支

    1 查看所有分支 git branch a 2 查看当前分支 号表示当前分支 git branch 3 切换分支 git checkout 39 分支名 39 4 修改代码仓库 git remote set url origin 39 仓库
  • 嵌入式安卓开发:使用Camera2获取相机

    文章目录 Camera2介绍Camera2的主要API类介绍CameraManager通过CameraManage获取Cameracharacteristics通过CameraManage获取CameraDevice从CameraDevic
  • ESP32的VSPI和HSPI

    说明 SPI共有4根线 xff0c MOSI MISO CS CLK xff0c 在ESP32中对应规则如下表 xff1a ESP32共有4个SPI xff0c 但是用户能够使用的只有2个SPI xff0c 分为VSPI和HSPI 引脚接口
  • Android Studio添加EasyPemissions

    问题描述 按照EasyPermissions主页描述的那样添加完依赖后 xff0c 在程序中使用还是报错 xff1a Failed to resolve pub devrel easypermissions 0 3 0 解决方法 首先 xf
  • ROS:话题编程 订阅者Subscriber的简单实现

    1 xff08 1 xff09 编写一个C 43 43 话题订阅者 该例程将订阅 turtle1 pose话题 xff0c 消息类型turtlesim Pose include lt ros ros h gt include 34 turt
  • 浅谈操作系统-启动过程

    前言 时光匆碌 xff0c 不知不觉都大三了 xff0c 在众多的专业课的学习中也算是找到了一些乐趣 xff0c 纸上得来终觉浅 xff0c 所以决定完整的回顾一下整个操作系统的知识 xff0c 为了理论与实践相结合 xff0c 以学校实验
  • 串口调试常见问题和排查方法

    串口UART作为嵌入式应用和通讯领域中最常用的接口之一 xff0c 接口协议虽然简单 xff0c 但在实际应用中不同设备之间的通讯也会存在各种小问题 xff0c 下面对使用中各种常见的问题做下总结和梳理 xff0c 可作为调试参考 串口可分

随机推荐

  • 3 POSIX 多任务及同步机制-拓展实验 条件变量与生产者-消费者问题

    3 POSIX 多任务及同步机制 拓展实验 条件变量与生产者 消费者问题 一 xff0e 实验目的 理解进程 线程同步问题 掌握POSIX条件变量机制的使用方法 深入理解在动态并发环境下 xff0c 进程 线程在运行过程中的资源竞争应发的问
  • 深度优先搜索DFS和广度优先搜索BFS

    相关博客链接 xff1a https www cnblogs com rjgcs p 5198467 html https blog csdn net xiaobo Clanguage article details 88085074 ht
  • 埋头努力之前得先看清方向

    埋头努力之前得先看清方向 写在工作半年 xff0c 第一次拿到绩效结果后 工作和学生时代最大的不同在于 xff0c 工作之后很多事情是消耗型的 xff0c 领导和组织看重的是输出 xff0c 而个人的成长和提升需要自己全权负责 这两者之间是
  • 周末写点轻松的吧

    一直觉得 有一段跟自己独处的时光是很幸福的事情 难得的双休日 xff0c 中午太阳很好 xff0c 照在绿萝上 xff0c 嫩嫩绿绿的叶子很漂亮 前几周去花市买了盆栀子花 xff0c 整整一大盆 xff0c 上面布满了花苞 以前见过开了花的
  • 为什么大部分的C/C++码农都成不了高级工程师?真实原因是缺少核心能力!

    一般来说技术团队的金字塔顶尖往往是技术最牛的人做底层架构师 xff08 或高级工程师 xff09 所以底层架构师在广大码农中的占比大概平均不到 20 然而80 码农干上许多年都是重复以下内容 xff0c 所以做不了架构师 xff0c 正在辛
  • kaggle邮箱不能验证+安装python的Speedml库

    注册kaggle账号遇到一些问题 下面是具体问题和解决方案 希望遇到同样问题的小伙伴不要再踩到坑啦 1kaggle邮箱不能验证You did not enter the correct captcha response Please try
  • 用程序验证生日“悖论”

    生日 悖论 其实并不是悖论 xff0c 它是说在一个人数超过23人的集体中 xff0c 至少有两个人生日在同一天的概率约为0 5 因为这个理论上的概率与人们的直觉不符 xff0c 才会被称为 悖论 我们可以用一个简单的小程序验证它哦 xff
  • python 中 defaultdict 的用法

    场景 xff1a 统计一个字符串列表中每个字符串的频数 一个明显的方法是建立一个键是字符串 xff0c 值是频数的字典 方法1 xff1a word count 61 for word in document if word in word
  • K近邻算法所面临的维数灾难问题

    K近邻算法的基本思想 K近邻算法是一种常用的监督学习方法 xff0c 其原理非常简单 xff1a 给定测试样本 xff0c 基于某种距离找出训练集中与其最靠近的K个训练样本 xff0c 然后基于这K个邻居的信息来进行预测 两个基本要素 xf
  • Android广播发送机制剖析【android广播系列二】

    上篇博客大致说了说广播的注册机制 xff0c 动态注册和静态注册广播的原理还不一样 xff0c 动态广播最后HashMap中了 xff0c 最后放到mReceiverResolver中 xff0c 以后当ActivityManagerSer
  • 观察者模式--Java设计模式

    观察者模式定义 xff1a 定义了对象之间的一对多的依赖 xff0c 这样一来 xff0c 当一个对象发生改变状态的时候 xff0c 它的所有依赖者都会收到通知并自动更新 参考如下图 xff1a 观察者设计模式也叫发布 订阅模式 也可以称作
  • Android——RuntimePermission介绍

    1 介绍 androidM版本上 xff0c 对permission的管理做了部分改动 xff0c 针对dangerous permission xff0c 不在安装的时候给予权限 xff0c 而是在运行过程中咨询用户是否给予app响应的权
  • Android中launcherMode="singleTask"详解【android源码解析六】

    android中launcherMode有4中属性 xff1a standard 默认 xff0c singleTop xff0c singleTask和 singleInstance xff1b 网上有好多例子讲解这四种关系的 xff1a
  • Android闹钟最终版【android源码闹钟解析】

    我以前写了个复杂闹钟的demo xff0c 参见 Android闹钟 复杂版 大明进化十五 但是里面的bug有一些 xff0c 好多人留言 xff0c 所以我就看看源码 xff0c 找找原因 xff1f 顺便把源码代码整理出来 xff0c
  • Smali--Dalvik虚拟机指令语言-->【android_smali语法学习一】

    最近一周在研究rom移植 xff0c 所以就对Smali语言学习了一下 xff0c Smali语言其实就是Davlik的寄存器语言 xff1b Smali语言就是android的应用程序 apk通过apktool反编译出来的都有一个smal
  • 程序员平均月薪过万,想当程序员的话,大学学那些专业会更好呢?

    在互联网时代 xff0c 程序员成为炙手可热的职业 虽然加班累成狗 xff0c 也有可能面临英年早秃的局面 xff0c 但是不得不说程序员的工资高于很多很多传统行业职位的工资 据统计 xff0c 应届程序员毕业生在一线城市平均月薪达到8k
  • android4.0自定义锁屏总结【android锁屏研究一】

    最近搬家了 xff0c 从北京 gt 深圳 xff0c 除了天气有点不同外 xff0c 其他的都差不多 xff0c 工作性质和以前也类似 xff01 纪念一下自己的迁移 题外话 转载请表明出处 xff1a http blog csdn ne
  • android系统锁屏详解【android锁屏解析二】

    谷歌的代码写的确实不错 xff0c 我很幸运 xff0c 一开始接触代码就赶上了谷歌这个开源的系统 xff0c 让我的视野开阔了很多 xff0c 也让我看到了优秀的代码工程师写到的代码 心怀感恩之心 题记 我的有篇文章说了这个锁屏 xff0
  • 单片机学习笔记8--按键和外部中断(基于百问网STM32F103系列教程)

    第八章 按键和外部中断 第一节 按键原理 GPIO内部上拉较弱 xff0c 可根据满足电路需求选择是用内部的上拉还是自己外接上拉 根据原理图可知 xff0c 当按键松开时 xff0c 单片机引脚连接在高电平上 xff0c GPIO口输入高电
  • ARM架构与编程6--重定位(基于百问网ARM架构与编程教程视频)

    一 启动程序流程 我们之前讲过 xff0c 单片机有根据boot的不同 xff0c 有三种启动方式 xff1a boot0boot1启动模式0Xflash启动10系统存储器11内置SRAM 单片机上电复位后 xff0c 运行main函数 以