【嵌入式】总结指南——Linux下的裸机驱动开发

news2024/12/28 8:56:12

板型:正点原子 I.MX6UL MINI

屏幕:7寸 1024*600

立意:既是这一段学习的总结,也可作为入门指南的参考,不过并不能作为教程来看,实际学习还是要找相应的视频或文章教程。

一、历程

        应该和使用这块板子的大部分人一样,看的是正点原子的教程,刚学完里面的裸机驱动,趁着还有记忆就总结一下吧。此前学的是stm32,用的是stm32f407,嗯,很经典的一块板子。但是在学习过程中,不可避免地遇到了瓶颈。

        去学ARM架构吧,可它既枯燥又晦涩,可视的实用价值太低。去学习一些工具的使用吧,可仿真工具、调试工具、IDE只能懂一些简单的用法,教程零散且需要不少软件方面的知识,不容易学习。去学习软件编程吧,接触过RT-thread,但能上手的就FreeRTOS和LVGL,只晓得怎么调用,源码很难看懂什么。去开发项目吧,但翻来覆去就是一些外设驱动,在协议与寄存器之间打转(仪表方向,很少接触网络方面的),除了一些基本的CV操作外,偶尔也就翻看一下数据手册,很难去真正提升什么。

        于是就被这样困了许久,向底层太晦涩难懂,向上层又缺乏太多的软件知识。总之,学得既不广,又不精,还缺乏一条可视的提升路径。不过好在学过RTOS,知道裸机和操作系统的区别,那么自然不可避免地被引进Linux里(不过嘛,现在学的还很浅)。于是就准备买一块板子,在京东上搜索了嵌入式Linux开发板,也就入手了现在这块,感到既庆幸又有些遗憾。

        庆幸的是用的是正点原子的,一是够大众够开源,这意味着你遇到的问题,基本上会有人遇到过,不会在踩坑之后爬不上来。二是,教程很多,作为一名CV员,对此有着天然的亲切感。遗憾的是买的是MINI板,7寸屏幕太大了,放不下。如果下次有机会,一定要事先查看一下板子和屏幕的尺寸,买个大板子和大屏幕。

         现在谈谈NXP的这块imx6ul板,刚一接触时让我直接惊为天人。原本挣扎在256KB的小内存,尤其是还经历了半个多月TI板32KB内存噩梦般的洗礼。对内存管控有着病态般的执着,能用uint8_t就绝不会用uint16_t,1B的浪费都是一种罪过。结果imx6ul直接就是512,MB!并且还是DDR3。想到电脑上插着的是DDR4,这让我有一种相当不真实的感觉。尤其是在学习过程中,很难想象自己可以用一块16GB的SD卡,通过插拔就可以让代码运行在板子上。学习时钟时,更是难以置信,在单片机中可以接触到GHz这一单位,PLL里可以直接达到1.5GHz,同时这块板子可以超频到八九百MHz。诸如此类的震撼还有不少

二、踩坑

        前期学习自然是手把手地跟着学,但同样的把戏不能玩第二遍,到中后期就需要“浏览”了,重点是看操作的流程,而非操作的细节。可以先开倍速过一遍听个响,拿本子或者平板什么的记录一些关键步骤或知识点,体会这个开发流程,然后再自己尝试独立做一遍,从翻看数据手册“考究”寄存器的作用,到配置寄存器、规范化编程。遇到过不去的坎,就去拿正点原子给的例程对照一遍,如此才能有质的提升。

        裸机驱动这一阶段的学习,无外乎开发环境搭建、Linux的体验、脚本的使用、外设驱动的配置这几个内容。对于这一阶段的学习,你会发现与之前有很大的不同,那就是开始不会给你官方库,一切都需要手动去敲、去造轮子,还需要反复查阅数据手册,弄懂里面讲的什么。比如,对着密密麻麻的时钟树,来理清所需时钟如何产生、选择。以前虽然也会翻数据手册,但更多的是翻看、浏览,看个时序和引脚。

 1,开发环境的搭建

①环境选择 

        这一步需要你有一个Linux环境,方案有装双系统、虚拟机、WSL2,但可行性较高的只有虚拟机这一种。 因为装双系统不利于文件传输,且办公、娱乐、开发离得太远,除非有两台电脑。WSL2虽然开销小文件传输更为方便,但并不适合开发,基本上是要啥缺啥。即便照着网上教程做,也很难搞定,比如图形界面我至今还是白屏,下载了相应工具后设备依旧不能挂载到设备树上等等,总之各种麻烦,很容易入门就劝退。

②Linux发行版选择

        这个就见仁见智吧,理论上使用Ubuntu更适合开发嵌入式。但是我本人的电脑可能有什么心事,即便是重装一遍系统后,安装Ubuntu24还是会失败,于是就换成了Ubuntu22,但是使用过程中有着诸多不便,比如无法输入中文、主机与虚拟机之间不能传文件(即便下载了VMwareTools)等等。

        于是就换成了kali purple,主机与虚拟机间传文件都不需要别的操作,直接右键复制粘贴。至于无法输入中文,这个可以通过下载对应的输入法,比如搜狗输入法(网上有不少教程)、谷歌输入法。但我个人经验是用谷歌输入法(两行代码的事),搜狗输入法下过很多次,无论是在Ubuntu还是在kali,都无法正常使用,即便添加到了键盘里,也仍不会触发。

        除此之外呢,kali有不少优点,它这个命令行很好用,除了一般的使用Tab键将命令补全外,还可以使用方向键↑、↓、→,并且命令补全时它会显示出来,这点真的很贴心。同时呢,紫色背景尽显高贵,没事了还能学学网安,一举多得

        如果你要使用kali,那么需要注意以下几点

:下载kali镜像时,不能使用多线程下载,否则会下载失败
:阿里源是可以正常使用的,有些源并不可用

:kali命令行有两个,一个是sh,一个是zsh,如果你要添加环境变量,那么两个都需要添加,否则有些情况下不会生效。这个网上有相关解决办法,很简单

deb http://mirrors.aliyun.com/kali kali-rolling main non-free contrib 
deb-src http://mirrors.aliyun.com/kali kali-rolling main non-free contrib 

 ③开发工具的选择

        默认选用VS Code就行了。虽然之前用CLion,那代码补全让人爱不释手,但我尝试了在Linux下安装,但无论是Ubuntu还是kali都无法ᗜ ‸ ᗜ     。有空的话,试一下桥接吧

        至于VS Code,下载时要上官网,最好不要在Linux发行版里的应用市场下载(会被阉割)。此外,插件方面的选择,可以使用通义灵码,输入拼音“Tong"就能搜索到了,这个算是弥补了VS Code代码补全不够强劲的缺点。

        此时,你虽然暂时不必使用vim编辑器,但是下方的命令行还是摆脱不掉的

2,Linux的体验

        使用就是最好的磨练。初见命令行时,可能会有一种抵触心理。虽然Windows下也有PowerShell、cmd,但用的场景并不多,可能在游戏崩溃、电脑蓝屏时会用一下。使用Linux命令行时,你要把它当做一个工具,不必弄清它是如何做到的,更应该关心的是如何去使用它,用造好的轮子去简化你的开发流程。

        像一些常见命令如cd、find、pwd、rm、mkdir、echo、touch等,自己手动尝试创建几次目录、文件也就自动学会了,并不需要死记硬背。

        用一段时间,自然就熟练了。在这一阶段的学习中,其实真正用到Linux的地方并不多,主要工作还是在VS code里面完成,最多是使用VS Code下面的终端来make(自动化编译链接的命令)一下

        不过在编译链接时要小心,因为视频教程里用的是-O2级优化,那么这意味着有时会发生意想不到的问题。比如定义一个局部变量,结果被编程器优化了,关键是优化之后还卡死了。如果发生卡死的情况,要注意是不是局部变量被优化导致的。

3,脚本的使用

①shell脚本

        说实话,这一阶段要掌握的shell命令其实并不多,这里所说的shell命令其实就是你在命令行里输入的命令,二者并没有什么区别。只不过作为脚本时,你可以在一个后缀名为.sh的文件里输入很多行命令,与写一个C文件没有太大区别。在这一阶段,并没怎么用到shell脚本

②Makefile脚本

        这里重点要掌握的是Makefile脚本。前期学习中,会讲怎么把C文件一步步变成单片机里面可以运行的代码。简单来说,就是通过交叉编译工具链一步步实现,先是gcc把.c文件转为.o文件,然后是ld把.o文件链接成.elf文件(其实就是Linux下的可执行文件,与Windows下的.exe一样),然后由obcopy把这个.elf文件提取出.bin文件,这个就是一般意义上的二进制文件,理论上是可以直接让CPU执行的。最后由官方提供的烧写工具,在这个bin前面加上前缀信息,然后烧录到存储介质中,以便芯片可以从存储介质中把代码加载到内存中。如果需要的话,可以使用objdump把elf文件反汇编为.dis文件,可以在这个.dis文件里查看反汇编的代码

        接下来就简单谈一下Makefile的语法。Makefile就是由一系列规则构成,这里的规则你完全可以理解为命令,就是Linux命令行的那种命令。在Makefile里面编写规则,其实就是相当于你在自定义属于自己的“Linux命令”。

        比如说在编译完项目后,我还想要有一个可以自动清理编译文件的命令,假设编译的文件都放在Debug路径下,那么就可以在Makefile里这样写道

clean:
	rm -rf ./Debug

        当你在命令行里使用make clean时,make工具会自动找到名为clean的这条规则,然后执行它。你也完全可以换个名字,比如就叫它sc(删除),想要调用这个命令就make sc

sc:
	rm -rf ./Debug

        不过要注意的是,直接使用make会默认执行在Makefile里编写的第一条规则。所以第一条规则往往是编译文件用的,此时名字可以任意去取。

规则构成

        规则,就是用来执行的。规则们都有一个统一的形式,可以表述为下面这种

【规则名】:【依赖】
    【执行内容1】
    【执行内容2】
    ... ...

【规则名】其实可以不是一个名字,还可以是一个文件,比如main.o:

main.o: main.c
    arm-linux-gnueabi-gcc main.c

【依赖】指的就是要完成这条规则,需要准备哪些东西,可以省略。比如说我要这个规则要编译main.c文件,于是就可以把规则写成下面这样。make会先执行第一条内容,它会判断这个main.o在不在,如果不在就会到依赖里面去找

BianYi: main.o
    arm-linux-gnueabi-ld main.o

         然后就会根据这个依赖去匹配相应的规则,比如这次需要的就是main.o,那么它就会先去执行main.o那个规则

【执行内容x】也可以省略。这里面既可以是shell命令(因为make就是运行在shell环境里的),也可以是Makefile自身的命令。不过要注意的是,【执行内容x】与【规则名】所处的位置不同,它需要一个Tab键来产生缩进,不能使用空格代替。

函数变量

         这个其实是为规则服务的,变量定义跟Python一样,不需要指定类型,当然这个变量实际上是字符串替换,Makefile是不能直接执行计算的。变量定义时,需要注意,赋值符需要与变量隔开,不然会认为是一体的。而赋值符除了:=,还有?=、+=等,各有各的作用

Debug_PATH := ./Debug

        变量定义后,如果要使用,得用$(xxx)括起来

    rm -rf $(Debug_PATH)

        至于这里的函数,与c语言里的大大不同,这里的函数(也可以说是Makefile内置命令)有一个统一的格式

$(函数名  参数1,参数2,......)

         单个参数里面可以使用空格来隔开,而逗号是隔开不同参数的

比如一个去除目录的函数$(notdir xxx),下面命令可以把CFiles里面带有路径的文件变为

main.c 和led.c

CFiles    :=    ./Application/main.c ./Drivers/LED/led.c

CFiles_NO_PATH :=  $(notdir    $(CFiles) )

自动化编译链接

         刚用CLion时,一上手就是cmake,当时觉得cmake还不错,挺方便的,更接近c语言,不需要怎么注意缩进。而这个makefile感觉上更像python,需要注意缩进。不过都是自动化编译工具,哪个用的顺手就用哪个,但至少得保证能熟练运用一个。

        在Makefile中,虽然不建议使用shell命令,能尽量用Makefile内置命令就使用。只不过,有些shell命令实在太方便,比如find命令,可以递归查找文件。虽然理论上使用Makefile也能完成,比如用递归,但实际上并不好用,一用就容易卡死。而使用find命令可以让你的效率成倍提升,比如头文件路径,往往需要自己手动添加,每创建一个文件夹,就得添加一个路径。使用find后,可以这样做

        ①先搜索所有的.h文件,$(shell  xxxxx)意思就是用shell去执行xxxxx,防止与Makefile内置命令冲突。

        $(INCDIRS)是个变量,存放的就是四个路径,因为我习惯上把项目分成四个部分,比如驱动文件在Driver里创建相应目录并放置

                    Application/    
                    Driver/
                    FounctionModule/
                    Library/

HEADERS := $(shell find $(INCDIRS) -name "*.h")

         ②然后再用$(dir   xxx)对这个头文件取路径,之后再用$(sort  xxx)进行排序(同时会去除重复路径)

HEADER_DIRS		:=$(sort $(dir $(HEADERS)) $(INCDIRS)) 

        两行代码就可以找到所有头文件路径了

        ③至于这一步,就是把每个头文件前面加个前缀“-I”,因为这个头文件路径是给gcc用的,让gcc找相应头文件

INCLUDE			:= $(patsubst %, -I %, $(HEADER_DIRS))

         至于编译链接,就是在能自动找到文件的情况下,添加各种编译、链接标志完成相应文件的输出。这里需要注意的是VPATH变量,这个变量是会被make识别出来的,make会根据这个VPATH里包含的路径来搜索%.c或者%.h什么的(%相当于通配符*),如果不设置这个VPATH,那么后面用%.o : %.c时就会识别不出来

自定义规则

         使用make其实就是为了简化操作,有些麻烦的操作流程能省就省,比如烧录这一操作,视频教程里总是会在命令行里输入./imxdownload xxx.bin /dev/sdx,这样做就显得很麻烦,因为自己开发过程中,挂载到设备树上的sd设备往往是同一个,序号不会变。于是可以自己定义一个规则,专门用于烧录,每次需要烧录时,直接make download。如果觉得还是太长,可以把规则名改短一点

download:
	./imxdownload ./Debug/$(TARGET).bin /dev/sdb

 

Makefile示例

         下面这个是我编写的Makefile,主要功能是自动地把

                Application    Driver   FounctionModule   Library 路径下所有的.c、.s文件编译链接成对应的.o文件,然后存放到Debug路径下,并且路径名不变。此外会单独找到start.s这个启动文件,去除掉路径,直接存放为./Debug/start.o,这是为了链接脚本文件可以正确找到启动文件,不必关心start.s处于哪个路径。

         编译后产生的.elf、.dis、.bin文件会自动放在./Debug目录下

TARGET		  	?= test

#我的交叉编译器可能有些不同,arm-linux-gnueabi后面没有hf,因为版本比较新
CROSS_COMPILE 	:= arm-linux-gnueabi-
CC 				:= $(CROSS_COMPILE)gcc
LD				:= $(CROSS_COMPILE)ld
OBJCOPY 		:= $(CROSS_COMPILE)objcopy
OBJDUMP 		:= $(CROSS_COMPILE)objdump

# 烧录工具
DownloadTool   := ./imxdownload

# 编译标志
# -Wall 用于开启警告  -nostdlib 不使用标准库,与pg冲突  -pg用于生成额外调试信息
CFLAGS = -Wall -c -O2 -fno-builtin -Wa,-mimplicit-it=thumb
LDFLAGS = -Timx6u.lds 
RM = rm -rf

# Debug目录路径  链接库路径
Debug_PATH = ./Debug
LIBPATH			:= -lgcc -L /usr/local/arm/gcc-linaro-gnueabi/lib/gcc/arm-linux-gnueabi/7.5.0


#头文件路径
INCDIRS 		:= Application/	\
					Driver/\
					FounctionModule/\
					Library/\

#源文件路径,会自动递归搜索其下的所有目录				   			   
SRCDIRS			:= Application/ \
					Driver/\
					FounctionModule/\
					Library/


#--------------------------------路径处理--------------------------------------				   
#使用find命令来自动递归匹配
CFILES := $(shell find $(SRCDIRS) -name "*.c")
SFILES := $(shell find $(SRCDIRS) -name "*.s")
#为了方便自动匹配头文件,获取头文件所在目录,并去除重复
HEADERS := $(shell find $(INCDIRS) -name "*.h")
HEADER_DIRS		:=$(sort $(dir $(HEADERS)) $(INCDIRS)) 

# 使用 filter 找出 start.s,是以区分并供链接脚本引用
START_FILE 		:= $(filter %start.s, $(SFILES))
NO_START_SFILES	:= $(filter-out %start.s, $(SFILES))
START_FILE_NDIR	:= $(notdir  $(START_FILE))

#find命令不区分后缀名大小写,gcc寻找文件不区分后缀名大小写
SOBJS			:= $(patsubst %, $(Debug_PATH)/%, $(filter  %.o ,$(NO_START_SFILES:.s=.o)))
START_SOBJS		:= $(patsubst %, $(Debug_PATH)/%, $(START_FILE_NDIR:.s=.o))
COBJS			:= $(patsubst %, $(Debug_PATH)/%, $(CFILES:.c=.o))
OBJS			:= $(SOBJS) $(COBJS)

# 设置VPATH以包含所有源文件目录,这样%.c 文件会自动匹配到相应的目录
VPATH 			:= $(sort $(dir $(CFILES)))
# 头文件路径
INCLUDE			:= $(patsubst %, -I %, $(HEADER_DIRS))
#--------------------------------路径处理--------------------------------------		


# 创建Debug目录
Debug_ALL_PATH := $(sort $(dir $(OBJS)))
$(foreach path, $(Debug_ALL_PATH), $(shell mkdir -p $(path) 2>/dev/null))


#----------------------------------编译规则------------------------------------------
all:$(Debug_PATH)/$(TARGET).elf $(Debug_PATH)/$(TARGET).bin $(Debug_PATH)/$(TARGET).dis
	@echo -e '\t'
	@wc -c  $(Debug_PATH)/$(TARGET).bin

#elf文件
$(Debug_PATH)/$(TARGET).elf : $(OBJS) $(START_SOBJS)
	@echo -e '\t'
	$(LD) $(LDFLAGS) -o $@ $^ $(LIBPATH)

#bin文件
$(Debug_PATH)/$(TARGET).bin : $(Debug_PATH)/$(TARGET).elf 
	$(OBJCOPY) -O binary -S $< $@


#反汇编文件
$(Debug_PATH)/$(TARGET).dis : $(Debug_PATH)/$(TARGET).elf 
	$(OBJDUMP) -D -m arm $< > $@

#obj文件
$(SOBJS) : $(Debug_PATH)/%.o : %.s
	$(CC) $(CFLAGS) $(INCLUDE)  -o $@ $<

$(START_SOBJS):$(START_FILE)
	$(CC) $(CFLAGS) $(INCLUDE) -o $@ $<

$(COBJS) : $(Debug_PATH)/%.o : %.c
	$(CC) $(CFLAGS)  $(INCLUDE) -o $@ $<
#----------------------------------编译规则------------------------------------------


# 清理规则,删除所有编译生成的文件
.PHONY: clean
clean:
	@$(RM) $(Debug_PATH)/  *.imx

#烧录
.PHONY: download
download:
	@echo  '-------------------------------------------'
	$(DownloadTool) $(Debug_PATH)/$(TARGET).bin /dev/sdb

#重新构建
.PHONY: rebuild
rebuild:
	@make clean
	@make

#获取下载权限,一次获取即可
.PHONY: getDownload
getDownload:
	chmod 777 $(DownloadTool)

#检查SD卡
.PHONY: check
check:
	@ls /dev/sd*

#生成调试信息
.PHONY: print
print:
	@echo -e "CFILES = $(CFILES)\n"


# 性能分析,暂时不可以使用
.PHONY: profile
profile: $(Debug_PATH)/$(TARGET).elf
	gprof $(Debug_PATH)/$(TARGET).elf > $(Debug_PATH)/profile.txt
	

4,外设驱动的配置

 ①引脚

         首先要讨论的自然是引脚,这里的IO引脚配置与以前学的GPIO有很大的不同。这里的引脚先是有IOMUXC来分配(MUX意为多路选择器),并配置电气属性、复用为哪个功能,其中有一个功能才是GPIO,此时才可以配置GPIO的输入输出、高低电平、中断触发方式。而上下拉、速度、驱动强度等都是由IOMUXC控制的。

        重要的事情再次强调一遍,这里的引脚是由IOMUXC来控制,GPIO只是IOMUX中复用功能的一种,与I2C、UART复用功能属于同一级

  IOMUXC包含了三类寄存器:

 

②时钟 

        时钟的重要性不言而喻,对于时序电路来说这是必不可少的。任何一个外设都要配置相应的时钟来驱动它,只不过IMX6UL的时钟系统过于复杂,需要从外设需要的时钟到它的根时钟,再经过一系列分频、时钟选择逆推到锁相环PLL上。

        这里一共有7路PLL,经过分频、多路选择就形成了各个外设需要的时钟。PLL是时钟的源头,而这一切都是由CCM控制的,类比于stm32的RCC

         至于如何分频、多路选择,其实图上都标有相应的寄存器,到手册上找到这些寄存器,那里会有详细的说明。 而锁相环PLL的产生则依赖于CCM_ANALOG这个寄存器。

        至于这些寄存器怎么配置,配置在什么范围,在相应的寄存器位置和CCM的开头部分都有介绍,这需要你仔细独自阅读,就能找到“蛛丝马迹”了。当一个外设的时钟知道怎么配置后,其他外设的时钟就简单多了,因为已经有一条成功的配置路径了

 ③外设驱动编写

        每个人有自己的编写习惯,但尽量要让编写符合规范。规范真的很重要,即便不是团队开发,自己独立开发时如果东一套西一套,那么时间久了自己也会迷糊的。

        当然在实际开发中,可以有一些自己的小想法。比如高精度延时这一章节,在定义延时us函数时,产生了一个问题,那就是如果计数器溢出了怎么办,示例代码是这样解决的

void delayus(unsigned    int usdelay)
{
	unsigned long oldcnt,newcnt;
	unsigned long tcntvalue = 0;	/* 走过的总时间  */

	oldcnt = GPT1->CNT;
	while(1)
	{
		newcnt = GPT1->CNT;
		if(newcnt != oldcnt)
		{
			if(newcnt > oldcnt)		/* GPT是向上计数器,并且没有溢出 */
				tcntvalue += newcnt - oldcnt;
			else  					/* 发生溢出    */
				tcntvalue += 0XFFFFFFFF-oldcnt + newcnt;
			oldcnt = newcnt;
			if(tcntvalue >= usdelay)/* 延时时间到了 */
			break;			 		/*  跳出 */
		}
	}
}

        你可以先确定终点计数值是多少,然后等待可能的溢出,因为如果溢出了,那么此时的时钟就一定会比终点计数值end要大,那么就会在这个地方一直等待。如果不溢出,那么这个while循环会直接跳过,对下面正常情况下的延时循环没有任何影响

static inline uint32_t GPT_GetValue(GPT_Type *GPTx)
{
    return GPTx->CNT;
}



// us延时,最大延时0xFFFFFFFFus(输入0或者0xFFFFFFFF)
void delay_us(uint32_t us)
{
	uint32_t end = GPT_GetValue(GPT1) + us;
	// 等待可能的溢出
	while (GPT_GetValue(GPT1) >= end)
		;
	// 切换为正常模式
	while (GPT_GetValue(GPT1) < end)
		;
}

        不过,不得不说这个外设驱动难度差异太悬殊了,像I2C驱动和蜂鸣器驱动的开发,简直一个天一个地。本来独自看寄存器,觉得就那几个功能,以前再怎么说也编写过stm32的I2C,无论是软件I2C还是硬件I2C。但打开例程的I2C代码后才发现,脱离了库函数后,再来配置I2C控制器,是一种顶级折磨。

三、总结

        这次学习,除了“由简入深,方能深入浅出”以外。还有就是,学习一个东西要懂取舍千万不能死磕,要先追求广度,再逐步深入。就比如在stm32学习阶段(专指F4、F1系列),启动文件是从官方例程下的,链接脚本也是官方提供的,如果那时死磕汇编语言、链接脚本语言,一心想弄懂它是干什么的,那么很容易浪费大把时间,从进展缓慢到渐渐失去信心,进而停滞不前。除了路子不对以外,还有一点就是缺乏广度。

        在stm32不怎么讲的启动文件和链接脚本,在imx6ul里则是开篇就讲,很自然地就一步步衔接起来了,因为imx6ul会讲交叉编译工具链、从C语言到elf文件再到bin文件最后到镜像文件的过程。而在stm32阶段,由于使用的是IDE,这些东西很难接触到,与启动文件、链接脚本有着天然的隔阂。这并非坏事,一个阶段有着一个阶段的事要做。

        如果你在学习中遇到特别大的难点,除非必须要攻克,否则建议是先跳过这一节,到后面知识面变广,就自然攻克了。比如imx6ul这一章节的中断那一讲,如果强行要求自己把启动文件里关于中断工作的过程从头写一遍,那么非常有可能达不到你的预期,反而会让你受挫沮丧。因为那涉及到了许多关于ARM架构的前置知识,比如ARM A7三级指令流水线、工作模式、ARM汇编等等。此时,不妨降低一下心理预期,会添加使用中断、处理中断标志就行了,重在应用嘛,所见即所得的正反馈。

        像有些章节如RTC(开头很有趣哦,一定要看看),如果你觉得没什么用,那么可以先跳过,等需要时再学习印象会更深刻。虽然凡事讲究未雨绸缪,但相信看到本篇的大多数应该是自学,没有人会强迫你每个知识点都得学全。那么完全可以在循序渐进的基础上,挑自己喜欢的去学,以兴趣为导向会事半功倍。

        即便中途遇到困难,也不能停滞不前。前进,意味着还能找到补救的方案,但停止,就很难再次前行了。取舍很重要

        (好像有点糊,但最大只能上传5MB。不过脑图写的本来就凌乱,这里只是起到一个背景作用)

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

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

相关文章

理解List AbstractList ArrayList

ArrayList 实现了 List 接口&#xff0c;继承了 AbstractList 抽象类。 Q: 为什么要ArrayList继承AbstractList&#xff0c;让AbstractList实现List&#xff1f;而不是让ArrayList直接实现List&#xff1f; A: 接口中全都是抽象的方法&#xff0c;而抽象类中可以有抽象方法&am…

【达梦数据库】shell脚本获取集群内确认监视器地址

目录 1、需求2、想法3、实现代码4、检验效果4.1、集群内任意节点使用非dmdba用户执行4.2、集群内任意节点使用dmdba用户执行4.2.1、数据库主备节点执行4.2.1、数据库确认监视器节点执行 4.3、非集群内节点执行 1、需求 有确认监视器的集群&#xff0c;在集群的任何一个集群上执…

Android13 app后台无法启动Abort background activity starts from

总纲 android13 rom 开发总纲说明 目录 1.前言 2.log分析 3.代码查找分析 4.修改方法 5.编译测试 6彩蛋 1.前言 Android13 用户app后台无法启动,提示Abort background activity starts from 10111 2.log分析 08-07 21:37:36.703: W/ActivityTaskManager(440): Back…

Llama3.1大模型

背景 Llama 3.1是一款由Meta&#xff08;前Facebook&#xff09;推出的先进大型语言模型。它在自然语言处理领域具有显著优势&#xff0c;为用户提供高质量的文本生成、理解和推理能力。 Transformer架构 Transformer是一种神经网络架构&#xff0c;可以处理文本、音频、视频和…

无线数传模块有啥特点?

一 、 模块特点  支持 RS485RTU 、RS232、UART 标准协议  AES加密  供电电压DC4.5V——5.5V  工作频段 410~525MHz, 免申请频段  标准配置提供多达 115信道 …

数据结构-递归算法-第四天

参考文献&#xff1a; 华为云 博客园 labuladong 的算法笔记 递归是一种编程技巧&#xff0c;一种解决问题的思维方式&#xff1b;分治算法和动态规划很大程度上是递归思想基础上的&#xff08;虽然动态规划的最终版本大都不是递归了&#xff0c;但解题思想还是离不开递归&…

数学建模之数据分析【七】:对Pandas DataFrame 进行切片

文章目录 一、切片简介二、创建Pandas数据框三、使用iloc进行切片3.1 对行进行切片3.2 对列进行切片3.3 Dataframe选中特定单元格 四、使用loc创建切片4.1 使用Python对Dataframe中的行进行切片4.2 指定单元格 五、在Python中使用布尔条件六、结论 对 Pandas DataFrames 进行切…

水战再起波澜,“怡宝”要下好怎样一盘棋?

不少投资者常把那些刚需性强、永远也不可能淘汰的产业称为“日不落产业”&#xff0c;从细分板块来看&#xff0c;水无疑具有一定代表性。农夫山泉掌门人钟晱晱曾直言&#xff1a;“我选择了一个日不落的产业&#xff0c;你永远要喝水&#xff0c;不可能不喝水。” 多年下来&a…

Python | Leetcode Python题解之第367题有效的完全平方数

题目&#xff1a; 题解&#xff1a; class Solution:def isPerfectSquare(self, num: int) -> bool:x0 numwhile True:x1 (x0 num / x0) / 2if x0 - x1 < 1e-6:breakx0 x1x0 int(x0)return x0 * x0 num

SpringBoot集成kafka-获取生产者发送的消息(阻塞式和非阻塞式获取)

说明 CompletableFuture对象需要的SpringBoot版本为3.X.X以上&#xff0c;需要的kafka依赖版本为3.X.X以上&#xff0c;需要的jdk版本17以上。 1、阻塞式&#xff08;等待式&#xff09;获取生产者发送的消息 生产者&#xff1a; package com.power.producer;import org.ap…

<数据集>车内视角行人识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;6470张 标注数量(xml文件个数)&#xff1a;6470 标注数量(txt文件个数)&#xff1a;6470 标注类别数&#xff1a;1 标注类别名称&#xff1a;[pedestrian] 序号类别名称图片数框数1pedestrian647029587 使用标注…

c++链表(list)

前言 链表作为一个常见的数据结构&#xff0c;在高频插入删除的场景下有独特的优势&#xff0c;在内存的使用上也极少有浪费可以按需申请。今天我们就来简单的学习一下这种数据结构&#xff0c;链表也有很多不同的实现&#xff0c;我们这里和标准库保持一致&#xff0c;实现带…

UDP通信函数补充 | TCP

UDP流程补充&#xff1a; recvfrom() 这是一个系统调用&#xff0c;用于从套接字接收数据的函数。该函数通常与无连接的数据报服务&#xff08;如 UDP&#xff09;一起使用&#xff0c;但也可以与其他类型的套接字使用。 函数原型为&#xff1a; ssize_t recvfrom(int sock…

使用Node-RED实现和部署物联网入侵检测的机器学习管道

整理自 《Implementing and Deploying an ML Pipeline for IoT Intrusion Detection with Node-RED》&#xff0c;由 Yimin Zhang 等人撰写&#xff0c;发表于 2023 年 CPS-IoT Week Workshops。以下是根据提供的 PDF 内容整理的论文的详细主要内容&#xff1a; 摘要 (Abstra…

Linux入门——09 共享内存

1.共享内存原理 OS内的每个进程都会有自己的内核结构&#xff08;task_struct&#xff09;和虚拟地址空间,通过页表与物理内存进程映射。 如果让两个不同的进程共享内存&#xff0c;首先就是在内存中申请一块空间&#xff08;共享内存&#xff09;&#xff0c; 然后将建立好…

Unity XR Interaction Toolkit 踩坑记录

1&#xff1a;按下 grap/select 键 物品直接飞到手上 2 按下 grap/select 键 物品一点点的想自己移动

《机器学习》—— AUC评估指标

文章目录 一、什么是AUC&#xff1f;1、什么是ROC曲线&#xff1f;2、ROC曲线的绘制 二、如何计算AUC的值三、代码实现AUC值的计算四、AUC的优缺点 一、什么是AUC&#xff1f; 机器学习中的AUC&#xff08;Area Under the Curve&#xff09;是一个重要的评估指标&#xff0c;特…

走进虚拟机逃逸技术之VMware Escape漏洞CVE-2023-20872复现

走进虚拟机逃逸技术之VMware Escape漏洞CVE-2023-20872复现 技术分享 技术分享 起初&#xff0c;为了学习虚拟机逃逸相关技术&#xff0c;也为了搞懂硬件虚拟化。于是请教了某巨佬后告诉我一本书&#xff0c;看完之后为了验证我理解到的硬件虚拟化及虚拟化逃逸原理是否正确&am…

图书管理系统详细设计

需求概述 按照需求分析文档中的规格要求&#xff0c;使用条形码扫描器进书、借书、还书&#xff0c;使得信息传递准确、流畅。同时&#xff0c;系统最大限度地实现易安装&#xff0c;易维护性&#xff0c;易操作性&#xff0c;运行稳定&#xff0c;安全可靠。 软件结构 系统由…

如何让虚拟机识别到宿主机的USB设备

我的实验环境&#xff1a; Windows宿主机VirtualBox虚拟化软件一个Linux虚机一个8G的USB磁盘 首先要让虚拟机能看到宿主机的USB设备&#xff0c;这是在VirtualBox中设置的。 选中虚机&#xff0c;右键选择“设置”菜单&#xff0c;再单击“USB设备”&#xff1a; 选中“启用…