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

news2024/9/21 2:37:59

板型:正点原子 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,都无法正常使用,即便添加到了键盘里,也仍不会触发。

        如果你也使用双拼(小鹤),那么可以参考这篇博客ubuntu安装小鹤双拼输入法,只不过要注意里面的路径,~/home/user需要改成/home/【自己的用户名】

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

        如果你要使用kali,那么需要注意以下几点,不过建议还是安装Ubuntu

:下载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 

补充:

2023.8.23

        桥接时出现问题,需要重装VMware,结果刚卸载VMware笔记本键盘便不能用了(可能是当初勾选了那个增强型键盘驱动)。之后就试了很多教程也没解决。只好按照老方法重装系统,结果这次捅了个大篓子,手生了没控制好把所有数据全部删除了

        没办法,只能当做重新整理了一下电脑吧,只是可怜了我的学习资料(×﹏×)。

        重装后,安装这个VMware,桥接确实没问题了(心痛(x_x))。这次重生我选择安装Ubuntu2024版,倒是没有发生那么多奇怪的问题了,如果黑屏取消勾选图形加速就行了。不过发现了一点,无论是kali还是Ubuntu,每重启两次就会死机一次,这就 (°ー°〃)

 ③开发工具的选择

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

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

        此时,虽然暂时不必使用vim编辑器,但是下方的命令行是不可或缺的

 补充:

2023.8.24

        嘘~,总算解决了提示的key is invalid

在进行一系列必要操作后,然后找到下面文件

        然后,对这些文件的末尾各添加三行内容,至于添加的内容想必很熟悉(不熟悉的话,可以参考一些教程,比如这个),路径需改成自己的。最后重启,再次尝试

--add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED
--add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED

-javaagent:/home/fairy/Embedded/software/clion/Crack/ja-netfilter.jar=jetbrains

        如此Linux的开发与Windows差不多了,同时还兼具强大的命令行,算是重生有重生的好处吧。只不过始料未及的是例程代码竟然会报错

2,Linux的体验

①命令行   

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

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

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

        不过在编译链接时要小心,因为视频教程里用的是-O2级优化,那么这意味着有时会发生意想不到的问题。比如定义一个局部变量,结果被编程器优化了,然后卡死了。加之这一阶段并没有掌握在线调试的方法,连判断是否卡死都得花不少功夫,完全是盲盒实验。如果发生卡死的情况,要关注是不是变量被优化导致的,尤其是结构体,倘若你没有进行手动内存对齐的话,极易在-O2级别下卡死,比如例程里的LCD结构体,得改成下面这样,把大字节放在上面。如果你不想改结构体成员顺序,那么需要在结构体变量定义和声明前面加上volatile

struct tftlcd_typedef {
    uint32_t framebuffer; /* LCD显存首地址   	  */
    uint32_t forecolor;   /* 前景色 */
    uint32_t backcolor;   /* 背景色 */
    uint32_t id;          /*	屏幕ID */
    uint16_t height; /* LCD屏幕高度 */
    uint16_t width;  /* LCD屏幕宽度 */
    uint16_t vspw;
    uint16_t vbpd;
    uint16_t vfpd;
    uint16_t hspw;
    uint16_t hbpd;
    uint16_t hfpd;
    uint8_t pixsize; /* LCD每个像素所占字节大小 */
};

         还比如触摸屏章节的内容,如果你不给temp数组加volatile的话,它会卡在【打印固件版本】的那个串口显示。

②文件传输

        进行主机与虚拟机之间的文件传输一般会用Filezilla,使用FTP协议传输时,需要注意以下几步骤:

1:先确保虚拟机使用的是桥接网络

        这里面的坑其实不少,多见于VMware卸载不彻底,解决办法就是彻底卸载,网上有教程。

 2,获取主机与虚拟机各自的ip地址

        主机:使用ipconfig命令在cmd或者powershell里查看IPV4地址

        虚拟机(Ubuntu):使用ifconfig。第一次使用时可能会提示没有这种命令,然后让你下载net tools什么的,照着下载即可。

3,主机与虚拟机确保能互相ping

        主机与虚拟机都可以使用ping命令,在主机的cmd或者powershell,使用ping 【虚拟机ip地址】,看看是否正常。同理,在虚拟机里使用ping 【主机ip地址】,观察是否正常。如果主机可以ping虚拟机,虚拟机无法ping主机,那么多半是防火墙的问题,把防火墙关闭即可。

4,进行FTP明文传输

        上述前提条件准备妥当时,可以直接在上方的框里填写对应信息,然后点击快速连接。(这当中可能会出现问题,往下翻)

或者新建一个站点

         如果在信息没有输错的情况下,依然出现无法连接的情况,那么可能是Ubuntu里的FTP没有配置好,此时可以选择配置Ubuntu的FTP或者使用SSH协议进行传输。参考博客如下:filezilla 连接不上虚拟机ubuntu(终极解决方案)_filezilla无法连接到ubuntu-CSDN博客

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-gnueabihf-gcc main.c -o main.o

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

LianJie: main.o
    arm-linux-gnueabihf-ld main.o -o main.elf

         然后就会根据这个依赖去匹配相应的规则,比如这次需要的就是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还不错,挺方便的,更像一门新语言,并且不需要怎么注意缩进。而这个makefile感觉上有点点像python,需要注意缩进。虽说cmake更加强大,但后面的学习中用到Makefile的地方也有不少,比如Uboot,所以至少得掌握Makefile。

        在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 V7三级指令流水线、工作模式、ARM汇编等等。此时,不妨降低一下心理预期,会添加使用中断、处理中断标志就行了,重在应用嘛,所见即所得的正反馈。

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

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

        (好像有点糊,但最大只能上传5MB,这里只是起到一个背景作用)

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

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

相关文章

基于ESP32驱动LAN8720以太网收发器

文章目录 一、LAN8720A简介二、引脚说明芯片管脚配置示例演示 一、LAN8720A简介 LAN8720A是低功耗的10/100M以太网PHY芯片&#xff0c;支持通过RMII接口和MAC层通信。它包含一个全双工10-BASE-T/100BASE-TX收发器&#xff0c;支持10Mbps和100Mbps操作。可以自动协商以自动确定最…

深度学习与OpenCV:解锁计算机视觉的无限可能

在科技日新月异的今天&#xff0c;计算机视觉作为人工智能领域的一颗璀璨明珠&#xff0c;正以前所未有的速度改变着我们的生活与工作方式。而《深度学习》与OpenCV&#xff0c;作为这一领域的两大重要工具&#xff0c;更是为计算机视觉的入门与深入探索铺设了坚实的基石。本文…

Python集成学习和随机森林算法使用详解

概要 集成学习是一种通过组合多个模型来提高预测性能的机器学习方法。它通过将多个弱学习器的结果结合起来,形成一个强学习器,从而提升模型的准确性和稳健性。随机森林(Random Forest)是集成学习中一种非常流行且有效的算法,特别适用于分类和回归任务。本文将详细介绍Pyt…

【图论】Tarjan算法(强连通分量)

一、Tarjan算法简介 Tarjan算法是一种由美国计算机科学家罗伯特塔杨&#xff08;Robert Tarjan&#xff09;提出的求解有向图强连通分量的线性时间的算法。 二、强连通分量的概念 在有向图 G G G 中&#xff0c;如果任意两个不同的顶点相互可达&#xff0c;则称该有向图是强…

平价电容笔排行榜:2024开学季五大高人气电容笔测评推荐 !

随着开学季的到来&#xff0c;无纸化学习再次成为热议话题&#xff0c;而电容笔作为支持这一趋势的重要配件&#xff0c;自然也备受瞩目。面对市场上琳琅满目的品牌选择&#xff0c;找到最适合自己需求的那一款就成了一个不小的挑战。不用担心&#xff0c;作为一名资深的数码产…

基于FPGA的SD NAND Flash数据读写实现

1、存储芯片分类 目前市面上的存储芯片&#xff0c;大致可以将其分为3大类&#xff1a; ① EEPROM EEPROM (Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器&#xff0c;是一种掉电后数据不丢失的存储芯片。EEPROM 可以在电脑上或专用设备…

【初阶数据结构】复杂度

b站复杂度链接 另一个复杂度链接 复杂度笔记

利用大型语言模型协作提升甲状腺结节超声诊断的一致性和准确性| 文献速递-基于深度学习的癌症风险预测与疾病预后应用

Title 题目 Collaborative Enhancement of Consistency and Accuracy in US Diagnosis of Thyroid Nodules Using Large Language Models 利用大型语言模型协作提升甲状腺结节超声诊断的一致性和准确性 Background 背景 Large language models (LLMs) hold substantial …

git仓库删除某个历史提交

目录 问题情况1情况2 问题 如果我们在开发过程中&#xff0c;存在一些验证性的提交或者失误性的提交&#xff0c;那么这些提交我们不想要了&#xff0c;怎么办&#xff1f; 情况1 如果是想要删除某个commitid之后的所有提交 那么git reset 可以满足你 git reset --hard 你要…

2001-2023年上市公司数字化转型年报词频统计(吴非、赵宸宇、甄红线等300+个关键词)

2001-2023年上市公司数字化转型年报词频统计&#xff08;吴非、赵宸宇、甄红线&#xff09; 1、时间&#xff1a;2001-2023年 2、来源&#xff1a;上市公司年报 3、参考文献&#xff1a;企业数字化转型与资本市场表现——来自股票流动性的经验证据&#xff08;吴非&#xff…

电脑浏览器打不开部分网页

电脑浏览器打不开部分网页 时间: 2024-08-25 问题描述: 电脑突然打不开部分网页 例如腾讯文档 夸克网盘 但其他网页能够正常打开 原因 可能为域名解析问题 更改DNS即可解决 解决办法 控制面板–> 网络和Internet—>网络连接—> WLAN----> 属性 —> Interne…

spring security怎么生成JWT返回前端,以及怎么自定义JWT认证过滤器

怎么生成JWT返回前端 1.先写一个类,里面含有jwt的生成解析验证过期时间的方法 package com.lzy.util;import io.jsonwebtoken.*; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.…

7-6 分段函数2

计算分段函数&#xff0c;测试数据分别是-1、5、12。 输入格式: 输入一个数。 输出格式: 直接输出保留6位小数的结果&#xff0c;没有其它任何附加字符&#xff0c;没有宽度控制。 输入样例: 11输出样例: 0.999912输入样例: 7输出样例: 8.000000 #include <stdio.h…

单片机裸机程序——程序架构

目 录 程序架构等同于思想体系一、前后台顺序法二、时间片轮询法 程序架构等同于思想体系 建一栋楼房&#xff0c;地基要先设计好&#xff0c;而不是马上砌砖&#xff0c;地基和布局都合理&#xff0c;房子就住得舒服&#xff0c;也不会闹心。 写一段程序也一样&#xff0c;程…

c++,python实现网络爬虫

前言&#xff1a; 社交网络中用户生成的海量数据&#xff0c;社交网络数据的多样性和复杂性 如何高效地从海量的数据中获取和处理我们需要的信息资源&#xff1f; 该微博爬虫能够从社交网络平台中地提取文本、图片和用户之间的转发关系&#xff0c;并将这些数据结构化存储到…

Python的Windows GUI自动化之Pywinauto(四)

引言&#xff1a; 我们上章节中打开了一个应用程序后&#xff0c;并打印了所有的控件信息&#xff0c;这些对于工具无法定位到的控件有很好的协助作用&#xff08;当然这个可以作为主要的查找控件的用法&#xff0c;也可以辅助使用&#xff0c;我一般是把这个作为辅助使用&…

【C++】初识C++模板与STL

C语法相关知识点可以通过点击以下链接进行学习一起加油&#xff01;命名空间缺省参数与函数重载C相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C内存管理 本章将简单分享C模板与STL相关知识&#xff0c;与之相关更多知识将留到下次更详细地来分享给大家 &#x1f3…

MySQL与ES数据实时同步,双写一致

一、简介 在项目的开发与运维过程中&#xff0c;MySQL 是业务数据库的核心角色&#xff0c;以其强大的事务处理能力和数据完整性保障&#xff0c;支撑着系统的稳定运行。随着数据量的急剧增长和查询复杂度的不断提升&#xff0c;单一依赖 MySQL 进行高效的数据检索显得日益吃力…

centos安装软件

1.centos 安装 unrar 提示找不到 使用EPEL仓库&#xff1a; 首先&#xff0c;你需要安装EPEL仓库&#xff1a; yum install epel-release 然后&#xff0c;尝试再次安装unrar&#xff1a; yum install unrar 编译安装&#xff1a; 下载源代码&#xff1a;wget http://www.rarla…

对耳朵伤害最小的耳机类型是哪种?五款口碑绝佳机型安利!

​目前来说&#xff0c;开放式耳机应该算是对耳朵伤害最小的耳机了。当今耳机市场上&#xff0c;开放式耳机以其舒适的佩戴和创新的非入耳设计赢得了众多消费者的喜爱。这种耳机让你在聆听音乐的同时&#xff0c;还能清晰地感知周围环境的声音&#xff0c;便于与人交流&#xf…