嵌入式笔试准备
引用与指针的区别
引用是变量的一个别名,用&符号定义,例如int &ref = var;定义时必须初始化,但一旦绑定,就无法改变引用指向的对象。
指针是一个变量,存储另一个变量的内存地址,int*ptr = &var;可以在定义时初始化,也可以之后再指向别的变量。
引用本身不占用额外的内存,它只是现有变量的一个别名。因此,引用与其所引用的对象共享相同的内存地址。
指针是一个变量,因此指针占用一块独立的内存。
引用:一旦初始化后,引用就无法再引用其他变量。它始终指向初始化时绑定的那个变量。
指针:指针可以在程序执行过程中修改,指向不同的变量或对象。
引用在定义时必须绑定到有效的对象或变量上,因此不能有空引用。
指针可以为空,表示它没有指向任何有效的对象。
引用没有类似指针的算术运算,无法对其进行加减操作来访问相邻的内存地址。
指针可以进行算术运算,允许遍历数组。
引用:通常用于函数参数传递,以及函数返回值。引用使得调用者和被调用者共享变量,避免了不必要的拷贝。
指针适合于需要动态内存管理、数组操作和需要改变指向的场景。
引用不能再引用,引用一旦初始化,就绑定到一个对象上,不能再绑定到另一个引用上。
SPI实验
SPI:串行外设设备接口(Serial Peripheral Interface),是一种高速的,全双工,同步通信总线。
SPI同步、串行、全双工,IIC同步、串行、半双工。
总线接口:SPI——MOSI、MISO、SCL、CS。IIC——SDA、SCL。
SPI:一主多从、一主一从
通过片选引脚选择。
数据位可以是8位/16位。
传输顺序:MSB/LSB。
SPI接口主要应用在存储芯片、AD转换器以及LCD中。
也支持半双工和单工。
时钟极性:没有数据传输时时钟线的空闲状态电平。
- 0:SCK在空闲状态保持低电平
- 1:SCK在空闲状态保持高电平
时钟相位:时钟线在第几个时钟边沿采样数据
- 0:SCK的第一边沿进行数据位采样,数据在第一个时钟边沿被锁存。
- 1:SCK的第二边沿进行数据位采样,数据在第二个时钟边沿被锁存。
SPI_HandleTypeDef
SPI_TypeDef *Instance;
SPI_InitTypeDef Init
SPI_InitTypeDef
Mode 主机
Direction 全双工
DataSize 8位
CLKPolarity CPOL = 0时钟极性
CPHA 0
NSS 软件
波特率预分频值
FirstBit 数据传输顺序(MSB)先发送高位
NOR FLASH
FLASH是常用的用于存储数据结构的半导体器件,具有容量大,可重复擦写,按扇区/块擦除,掉电后数据可以继续保存的特性。
FLASH是有一个物理特性:只能写0,不能写1,写1靠擦除。
FLASH有NOR FLASH和NAND Flash两种类型,NOR和NAND是两种数字门电路。
NOR FLASH基于字节读写,读取速度快,独立地址/数据线,无坏块,支持XIP,适用于程序ROM、25Qxx。
NM25Q128,串行闪存器件,属于NOR FLASH中的一种,容量为128Mb。擦写周期可达10W次。
LCD操作原理
在Linux系统中通过FrameBuffer驱动程序来控制LCD。
Frame是帧的意思,buffer是缓冲的意思。
Framebuffer就是一块内存,里面保存着一帧图像。
Framebuffer中保存着一帧图像的每一个像素颜色值。
LCD操作原理:
- 根据程序设置好LCD控制器:根据LCD的参数设置LCD控制器的时序、信号极性;根据LCD分辨率、BPP分配Framebuffer。
- APP使用ioctl获得LCD分辨率、BPP。
- APP通过mmap映射Framebuffer,在Framebuffer中写入数据。
LCD控制器周而复始地从Framebuffer中逐一取出每个像素的颜色值发送给LCD。
程序运行的一些基础知识
编译程序时去哪找头文件?
系统目录,也就是交叉编译工具链的某个include目录,也可以自己指定,编译时用-I dir选项指定。
链接时去哪里找库文件?
系统目录:也就是交叉编译工具链里的某个lib目录。
也可以自己指定:链接时使用-L dir选项指定。
运行时去找哪些库文件?
系统目录:就是板子上的/lib、/usr/lib目录,也可以自己指定:运行程序用环境变量LD_LIBRARY_PATH指定。
hexdump是一个常用的命令行工具,主要用于将给定文件的内容以十六进制格式输出到标准输出。
POLL/SELECT方式
POLL机制、SELECT机制是完全一样的,只是APP接口函数不一样。
简单地说,它们就是定个闹钟,在调用poll、select函数时可以传入超时时间,在这段时间内,条件合适时就会立刻返回,否则等到超时时间结束时立刻返回错误。
tslib
要使用tslib,首先要交叉编译tslib,缺点工具链中头文件、库文件目录。
把头文件、库文件放入工具链目录里,编译程序要用。
把库文件放到单板上,运行程序要用。
grep是一个强大的文本搜索工具,用于在文本文件中查找匹配特定模式的行。
-n:显示匹配的行号。
-w:匹配整个单词。
-r:递归搜索。
GCC全称是GNU Compiler Coleection,是GNU项目的一部分,主要是一套编译器工具集,支持多种编程语言。
glibc,是C语言标准库。
POSIX标准的主要目的是促进应用软件与多种类型的操作系统之间的兼容性。通过遵循POSIX标准,开发人员可以编写能够在各种不同系统上运行的程序,而无需对程序进行大量修改。
这包括Unix、Linux、MacOS以及其它类Unix系统。
预处理命令
在C语言编译过程中,预处理是其中的第一个阶段,它的主要目的是处理源代码文件中的预处理指令。
预处理主要包含宏替换、文件包含、条件编译、注释移除等几种任务。预处理后的文件通常会比原始文件大。
编译
编译阶段,编译器会将经过预处理的源代码文件转换成汇编代码。
在这个阶段,包括词法分析、语法分析、语义分析和优化等过程。
ARM中断服务程序最后一条执行指令
ARM架构的中断服务程序(ISR)的最后一条指令是BX LR。
在ARM架构中,Link Register(LR)寄存器通常用来保存子程序返回地址。当发生中断时,硬件会自动将当前PC的值保存到LR中,然后跳转到中断向量表中对应的中断服务程序入口。
BX是ARM指令集中的一个分支指令,根据指定的寄存器中的值来确定跳转的目标地址。通过将LR寄存器作为BX指令的操作数,就可以实现从ISR返回到中断发生前的指令处。
内联函数
内联函数是一种特殊的函数,通过在函数声明前添加inline关键字来定义。
内联函数在编译时,编译器会将函数体直接嵌入到调用它的代码处,而不是生成函数调用指令。
由于省去了函数调用的过程(如参数传递、返回地址保存等)内联函数可以显著提高程序的执行效率,尤其适用于那些函数体较小且频繁调用的函数。
优点:提高程序执行效率、简化代码。
缺点:过度使用可能导致代码膨胀,编译器不一定总是按照程序员的意愿进行内联展开。
STM32启动模式(自举模式)
- 从地址0x0000 0000处取出堆栈指针MSP的初始值,该值就是栈顶地址。
- 从地址0x0000 0004处取出程序计数器PC的初始值,该值是复位向量。
芯片厂商可能会把0x0000 0000和0x0000 0004地址映射到其它的地址。
在系统复位后,SYSCLK的第4个上升沿,BOOT引脚的值将被锁存。
- 初始化MSP 从0x0800 0000处获取
- 初始化PC 从0x0800 0004处获取
- 设置堆栈大小
- 初始化中断向量表
- 调用初始化函数,可选的,如调用systemInit函数
- 调用__main,标准C库函数,执行一系列设置,最终调用main函数
复位中断服务函数
32位单片机有32根地址线(每根地址线有两种状态:导通或不导通)。
单片机内存地址访问的存储单元是按字节编址的。
STM32寻址大小 2的32次方=4G(字节)。
存储器本身没有地址信息,对存储器分配地址的过程称为存储器映射。
objdump是一个强大的命令行工具,主要用于反汇编目标文件或可执行文件。将机器指令转化为人类可读的汇编代码。
常见的section
- .data:包含了已经初始化的全局变量和静态,这些变量在程序开始执行前就已经被赋予了初始值。
- .bss段:包含未初始化的全局变量和静态变量。bss段并不真正占用文件空间,它仅仅是一个占位符,指示程序启动时需要分配多少空间并将其清零。
- .rodata:包含只读数据,比如字符串常量和其他程序中用到的不可修改的数据。
objdump -d main.o反汇编
链接
链接阶段,由链接器完成。
链接器将各个目标文件以及可能用到的库文件进行链接,生成最终的可执行程序。
C语言的链接共有三种方式:静态链接、动态链接和混合链接。
静态链接
将所有目标文件和所需的库在编译的时候一并打包进最终的可执行文件。
库的代码被复制到最终的可执行文件中,使得可执行文件变得自包含,不需要在运行时查找或加载外部库。
gcc -static mian.o hello.o -o main。
-static:该参数指示编译器进行静态链接,而不是默认的动态链接。
动态链接
库在运行时被加载,可执行文件包含了需要加载的库的路径和符号信息。
动态链接的可执行文件比静态链接的小,因为它们共享系统级的库代码。
默认执行动态链接。
我们也可以将自己编写的代码处理为动态库。
gcc -fPIC -shared -o libhello.so hello.o
-fPIC:这个选项告诉编译器为位置无关码生成输出。在创建共享库时很重要,因为它允许共享库被加载到内存中的任何位置,而不影响其执行。
因为位置无关码使用相对地址而非绝对地址进行数据访问和函数调用,使得库在被不同程序加载时能够灵活映射到不同地址空间。
混合链接
自己写的某些库使用静态链接,其它库使用动态链接。
系统调用是操作系统内核提供给应用程序,使其可以间接访问硬件资源的接口。
exit和_exit()
_exit()是系统调用,立即终止一个进程,确保进程立即退出,不执行任何清理操作。
_exit()在子进程终止时特别有用,防止子进程的终止影响到父进程。(比如,防止子进程以外刷新了父进程未写入的输出缓冲区)
exit()是库函数。
exit()通常在父进程中使用,确保程序退出前能够执行清理操作,比如关闭文件和刷新输出。
在子进程中,推荐使用_exit()确保子进程的快速退出。
挂载
如何将存储设备连接到系统的文件系统树上,以便用户能够访问其内容。
在Linux中,所有文件和目录都组织在一个称为文件系统树的单一层次结构中。
挂载点:挂载点是一个已经存在的目录,通常是空的。挂载操作会将一个存储设备或分区的文件系统连接到这个目录上,之后用户就可以通过这个目录来访问该存储设备上的文件。
mount [选项] <设备路径> <挂载点>
设备路径是要挂载的设备或分区的路径,通常位于/dev目录下,比如/dev/sda1。
挂载点是把设备挂载到的目录。
mount /dev/sdb1 /mnt/usb
查看所有挂载的设备可以使用mount、df -h。
卸载
将已经挂载的设备从文件系统中端口。
sudo umount <挂载点或设备路径>
sudo umount /mnt/usb
临时挂载:使用mount命令挂载,系统重启后挂载点消失。
持久挂载:修改/etc/fstab文件。
通过挂载,可以将不同的存储设备和分区整合到一个统一的文件系统树中进行管理和访问。
当我们打开一个文件时,操作系统会提供一个文件描述符。
每个文件描述符都关联到内核一个struct file类型的结构体数据。
文件描述符是一个非负整数,其值实际上就是关联的struct file在fd指向的数组的下标。