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

嵌入式软件/硬件工程师面试题集

### 1. C语言的编译过程原理

C语言的编译过程主要分为四个阶段:

1. **预处理 (Preprocessing)**

    * **任务**:处理源代码中的预处理指令,以`#`开头。

    * **操作**:展开头文件(`#include`)、宏替换(`#define`)、条件编译(`#ifdef`, `#ifndef`)、删除注释等。

    * **输出**:生成一个纯C代码的`.i`文件。

 

2. **编译 (Compilation)**

    * **任务**:将预处理后的C代码进行词法分析、语法分析、语义分析和优化,翻译成特定平台的**汇编代码**(Assembly Code)。

    * **输出**:生成对应的`.s`汇编文件。

 

3. **汇编 (Assembly)**

    * **任务**:使用汇编器(Assembler)将汇编代码翻译成机器可以执行的**二进制指令**,即目标文件(Object File)。

    * **输出**:生成`.o`或`.obj`文件。该文件包含机器码,但地址尚未最终确定。

 

4. **链接 (Linking)**

    * **任务**:将一个或多个目标文件以及所需的库文件(如C标准库)合并成一个最终的可执行文件(如`.exe`或`.elf`)。

    * **操作**:**地址重定位**:将各个目标文件中的符号(变量名、函数名)引用与它们的定义地址关联起来,解决跨文件的函数调用和变量访问问题。

    * **输出**:生成最终的可执行文件。

 

**总结**:`.c` -(预处理)-> `.i` -(编译)-> `.s` -(汇编)-> `.o` -(链接)-> `可执行文件`

 

---

 

### 2. void*类型指针作用是什么,对比char*、int*等有什么优势

 

* **作用**:`void*`是一种**泛型指针**(通用指针),它可以指向任何类型的数据。它本身不包含所指向数据的类型信息。

* **优势**:

    1. **通用性**:用于编写可以处理任意数据类型的通用函数。例如内存操作函数`memcpy`, `memset`,它们使用`void*`作为参数,可以拷贝或设置任何类型的内存块。

    2. **隐藏实现细节**:在数据结构(如链表、队列)或API设计中,使用`void*`来存储用户数据,可以避免暴露内部实现,提高模块的抽象性和复用性。

* **注意事项**:

    * `void*`指针不能直接进行解引用(`*ptr`)或算术运算(`ptr++`),因为编译器不知道它指向的数据类型和步长。在使用前必须进行**强制类型转换**,转换为具体的指针类型(如`int*`, `char*`)。

 

**对比**:`char*`、`int*`等是具体类型的指针,编译器知道其指向的数据类型和大小,可以直接进行解引用和指针算术运算,但缺乏通用性。

 

---

 

### 3. 大端模式和小端模式,内存对齐

 

* **大端模式 (Big-endian)**:数据的**高字节**存储在内存的**低地址**处。

* **小端模式 (Little-endian)**:数据的**低字节**存储在内存的**低地址**处。

 

例如,存储`0x12345678` (32位int):

| 内存地址 | 0x1000 (低) | 0x1001 | 0x1002 | 0x1003 (高) |

| :--- | :--- | :--- | :--- | :--- |

| **大端模式** | `0x12` | `0x34` | `0x56` | `0x78` |

| **小端模式** | `0x78` | `0x56` | `0x34` | `0x12` |

 

常见的X86、ARM处理器通常为**小端模式**。网络字节序(Network Byte Order)规定为**大端模式**。

 

* **内存对齐 (Memory Alignment)**:计算机系统要求数据的地址必须是某个值(通常是其自身大小)的整数倍。这可以提高CPU访问内存的效率。

* **结构体`struct { char a; int b; char c; }`在32位系统中的大小**:

    1. `char a`:占1字节,地址偏移0。

    2. **对齐**:下一个`int b`(4字节)需要从4的倍数地址开始。因此在`a`之后需要插入**3字节的填充(Padding)**。

    3. `int b`:占4字节,地址偏移4~7。

    4. `char c`:占1字节,地址偏移8。

    5. **结构体总大小对齐**:整个结构体的大小必须是其最宽成员(`int`,4字节)的整数倍。目前是9字节,所以需要在末尾补充**3字节的填充**,使总大小变为12字节。

    * **答案:占用12个字节。**

 

---

 

### 4. SPI的四种模式,通信协议帧,一主多从

 

* **四种模式**:由时钟极性(CPOL)和时钟相位(CPHA)组合而成。

    * **CPOL**:时钟空闲状态。0-低电平,1-高电平。

    * **CPHA**:数据采样时刻。0-第一个时钟边沿,1-第二个时钟边沿。

    * **Mode 0**: CPOL=0, CPHA=0 (最常用)

    * **Mode 1**: CPOL=0, CPHA=1

    * **Mode 2**: CPOL=1, CPHA=0

    * **Mode 3**: CPOL=1, CPHA=1

 

* **通信协议帧**:

    SPI通信是**全双工**的,没有严格的“帧”概念,以字节为单位进行交换。

    * **过程**:主设备拉低对应从设备的**SS**线 -> 主设备产生时钟**SCK** -> 在SCK的每个周期,主设备通过**MOSI**线发送1bit数据,同时从设备通过**MISO**线返回1bit数据 -> 8个时钟周期后,一个字节交换完成 -> 主设备拉高**SS**线结束通信。

    * 通信的数据内容(命令、地址、数据)完全由主从设备双方自行定义协议。

 

* **一主多从的实现**:

    * **方法**:为每个从设备分配一条独立的**片选线(SS/NSS)**。

    * **过程**:主设备需要与哪个从设备通信,就拉低对应的那条SS线,其他SS线保持高电平。这样,只有被选中的从设备会响应SCK时钟,其他从设备忽略总线状态。

    * **优点**:简单、稳定。

    * **缺点**:占用主设备较多的I/O口。

 

---

 

### 5. I2C通信协议数据帧,硬件连接

 

* **硬件连接**:只需要两条线。

    * **SDA (Serial Data)**:双向数据线。

    * **SCL (Serial Clock)**:时钟线,由主设备产生。

    * 两条线都需要通过**上拉电阻**接到正电源VCC,形成“线与”逻辑。

 

* **数据帧格式**:

    * **起始条件 (Start Condition)**:SCL为高时,SDA出现一个下降沿。

    * **从设备地址 (Slave Address)**:7位或10位地址。紧跟在起始条件后发送,包含1bit的**读写位**(0-写,1-读)。

    * **应答位 (ACK/NACK)**:每传输完一个字节(8bit),接收方需要在第9个时钟周期拉低SDA(ACK)表示应答,或释放SDA(NACK)表示非应答。

    * **数据 (Data)**:地址后的数据内容,也是以字节为单位传输,每个字节后都跟一个ACK/NACK。

    * **停止条件 (Stop Condition)**:SCL为高时,SDA出现一个上升沿。

 

* **指定地址写 (Write to a specific register)**:

    `[Start] + [Slave Addr | 0] + [ACK] + [Reg Addr] + [ACK] + [Data] + [ACK] + ... + [Stop]`

    1. 主机发送起始条件。

    2. 发送从机地址(写方向)。

    3. 发送要写入的寄存器地址。

    4. 发送要写入的一个或多个数据字节。

    5. 主机发送停止条件。

 

* **当前地址读 (Read from current address)**:

    `[Start] + [Slave Addr | 1] + [ACK] + [Data] + [NACK] + [Stop]`

    1. 主机发送起始条件。

    2. 发送从机地址(读方向)。

    3. 从机直接返回当前地址指针指向的数据。

    4. 主机发送NACK表示读取结束。

    5. 主机发送停止条件。

    *(通常需要先执行一个“伪写”操作来设定内部地址指针,然后再发起读操作)*

 

---

 

### 6. I2C通信挂死排查(软件、硬件)

 

**现象**:SCL或SDA被意外拉低,总线停滞,无法进行后续通信。

 

* **硬件排查**:

    1. **检查上拉电阻**:阻值是否合适(通常4.7kΩ~10kΩ)?是否已焊接?

    2. **测量总线电平**:用万用表或示波器测量SCL和SDA线的电压。如果一直被拉低(接近0V),说明有设备故障。

    3. **逐一排查**:**断开所有从设备**,看总线能否恢复。然后逐一连接从设备,定位故障芯片。

 

* **软件排查**:

    1. **看门狗(Watchdog)**:确保看门狗启用,能在程序跑飞时复位整个系统。

    2. **超时机制**:在I2C驱动程序的关键步骤(如等待BUSY标志、等待ACK)中加入超时检测。一旦超时,**软件模拟发送几个SCL时钟**并尝试发送停止条件来**主动恢复总线**,然后进行错误处理。

    3. **中断保护**:在操作I2C总线时,必要时禁用中断,防止高优先级中断打断时序敏感的I2C操作。

    4. **逻辑分析仪**:使用逻辑分析仪抓取I2C波形,精确分析挂死在哪一步。

 

---

 

### 7. I2C上拉电阻选择?中间电平原因?

 

* **上拉电阻选择**:

    * **计算公式**:`Rp < (VCC - V OL) / I OL` 和 `Rp > tr / (0.8473 * C b)`

    * **考虑因素**:

        1. **总线电容(C b)**:线越长,连接的设备越多,电容越大。电容大会导致上升沿变缓,因此Rp不能太大(典型值100~400pF)。

        2. **驱动能力(I OL)**:主设备的拉低电流能力限制了Rp的最小值,Rp太小会导致电流过大。

        3. **电源电压(VCC)**:电压越高,相同Rp下提供的上拉电流越大,边沿越陡。

    * **典型值**:在VCC=3.3V,标准模式(100kHz)下,常用**4.7kΩ**。在快速模式(400kHz)下,常用**2.2kΩ**。

 

* **出现中间电平(如0.4V, 2.6V)的原因**:

    * **根本原因**:总线发生了**线与**冲突,即多个设备同时试图驱动总线(一个要拉低,一个要输出高)。

    * **具体场景**:

        1. **从设备故障**:某个从设备的I2C接口内部损坏,将SDA或SCL线持续拉低或半拉低。

        2. **软件错误**:主设备或从设备的驱动程序编写错误,在不该驱动总线的时候驱动了。

        3. **电源问题**:设备供电异常,导致I/O口电平异常。

        4. **上拉电阻开路或虚焊**,导致上拉能力不足。

 

---

 

### 8. I2C多主多从模式介绍,总线仲裁

 

* **介绍**:I2C协议支持多主模式,即总线上可以有多个主设备,它们都可以发起通信。

 

* **总线仲裁 (Arbitration)**:

    * **过程**:仲裁发生在SDA线上。当两个主设备同时发起起始条件时,它们会继续发送数据并同时监听SDA线。

    * **原理**:基于“线与”逻辑。如果一个主设备发送**高电平(1)**(即释放SDA线),但它检测到SDA线实际是**低电平(0)**,说明另一个主设备正在发送0。那么这个发送1的主设备就**仲裁失败**,立即停止驱动总线并转为从模式监听总线。

    * **特点**:

        1. 仲裁不会破坏赢得仲裁的主设备正在发送的数据。

        2. 仲裁过程可以持续多位,直到地址和数据都比较出结果。

        3. SCL线上的时钟同步(高电平周期由时钟最慢的设备决定,低电平周期由时钟最低的设备决定)为仲裁提供了基础。

 

---

 

### 9. 单链表和数组的区别,单链表操作

 

| 特性 | 数组 | 单链表 |

| :--- | :--- | :--- |

| **内存分配** | 静态连续内存 | 动态非连续内存(节点分散) |

| **大小** | 固定大小 | 可动态增长/缩小 |

| **访问方式** | 随机访问,O(1) | 顺序访问,O(n) |

| **插入/删除** | 效率低,需移动元素,O(n) | 效率高,只需修改指针,O(1)(已知位置) |

| **内存开销** | 无额外开销 | 每个节点有额外指针开销 |

 

* **单链表增加节点**(在节点`p`后插入新节点`s`):

    1. `s->next = p->next;`

    2. `p->next = s;`

    * **时间复杂度**:O(1)

 

* **单链表删除节点**(删除节点`p`的后继节点`q`):

    1. `p->next = q->next;`

    2. `free(q);`

    * **时间复杂度**:O(1)(已知前驱节点`p`)

 

* **链表在内存中的排布规律**:**非连续**。每个节点(数据域+指针域)可以位于内存的任意位置,依靠指针`next`来逻辑上链接到下一个节点。

 

---

 

### 10. UART通信数据帧、奇偶校验、硬件流控、异步

 

* **数据帧 (Data Frame)**:

    * **1个起始位**:总是低电平。

    * **5~9个数据位**:传输的实际数据,通常为8位。

    * **0或1个奇偶校验位**:用于简单的错误检测。

    * **1或2个停止位**:总是高电平。

    * 帧与帧之间是空闲的高电平。

 

* **奇偶校验 (Parity Bit)**:

    * **作用**:检测数据位中单个bit的错误。

    * **偶校验**:校验位使“1”的总数(数据位+校验位)为偶数。

    * **奇校验**:校验位使“1”的总数为奇数。

 

* **硬件流控 (Hardware Flow Control)**:

    * **作用**:通过额外的信号线控制数据流,防止接收方缓冲区溢出。

    * **信号线**:`RTS` (Request To Send) 和 `CTS` (Clear To Send)。

    * **过程**:发送方在发送前检查`CTS`线,如果为有效电平(通常低有效)则表示接收方准备好,可以发送;否则等待。

 

* **异步 (Asynchronous)**:UART是**异步通信**。通信双方没有统一的时钟信号(SCLK),依靠预先约定好的**波特率(Baud Rate)** 和各自的本地时钟来同步每一位数据。依靠**起始位**和**停止位**来界定一帧数据。

 

---

 

### 11. UART误码率,通信错误原因排查

 

* **误码率 (Bit Error Rate, BER)**:错误接收的比特数与总传输比特数之比。是衡量通信可靠性的指标。

 

* **通信错误原因排查**:

    * **硬件方面**:

        1. **波特率不匹配**:双方波特率设置不一致是最常见原因。

        2. **电平不匹配**:如TTL与RS232设备直接相连。

        3. **线路干扰**:长距离传输时,导线成为天线,引入噪声。

        4. **地线问题**:共地不良导致地电势差,产生巨大电流,损坏接口芯片或导致数据错误。

        5. **硬件故障**:接口芯片损坏。

    * **软件方面**:

        1. **波特率、数据位、停止位、校验位**配置错误。

        2. 驱动程序Bug,如缓冲区溢出、中断处理错误。

        3. 协议解析错误。

 

* **排查工具**:**示波器**或**逻辑分析仪**是终极工具,可以直观地看到波形、测量波特率、检查数据内容。

 

---

 

### 12. UART的三种电平协议(TTL, RS232, RS485)

 

| 特性 | TTL | RS232 | RS485 |

| :--- | :--- | :--- | :--- |

| **电平标准** | 0V ~ 0.8V为0<br>2.4V ~ VCC为1 | -15V ~ -3V为1<br>+3V ~ +15V为0 | 两线压差:<br>+2V ~ +6V为1<br>-2V ~ -6V为0 |

| **通信方式** | 全双工 | 全双工 | 半双工/全双工 |

| **传输距离** | 很短 (<1m) | 中等 (~15m) | 远 (~1200m) |

| **抗干扰能力** | 弱 | 较强(差分) | 强(差分平衡) |

| **节点数** | 1对1 | 1对1 | 1主多从(32/128等) |

| **应用场景** | MCU内部、板级器件间通信 | 计算机COM口、老式调制解调器 | 工业自动化、楼宇控制、长距离通信 |

 

---

 

### 13. CAN通信一主多从的总线仲裁

 

CAN总线是**多主总线**,没有严格的主从概念,任何节点都可以在总线空闲时发起传输。

 

* **仲裁机制**:基于**标识符 (Identifier)** 的**非破坏性逐位仲裁**。

* **原理**:

    1. CAN总线是“线与”逻辑:**显性电平 (Dominant, 逻辑0)** 会覆盖**隐性电平 (Recessive, 逻辑1)**。

    2. 节点在发送数据的同时也在监听总线。

    3. 仲裁段期间,多个节点同时发送报文。每个节点从标识符的最高位开始发送。

    4. 如果一个节点发送了隐性位(1),但监听到总线是显性位(0),说明有另一个节点正在发送优先级更高的报文(标识符数值更小)。则该节点**立即停止发送**,转为接收模式。

    5. 仲裁失败的节点会在总线空闲时自动重发。

* **结果**:**标识符数值最小的报文**(优先级最高)赢得仲裁,继续完成发送,整个过程没有任何数据损坏。

 

---

 

### 14. volatile关键字、RTOS中volatile应用场景

 

* **`volatile`关键字**:告诉编译器,该变量的值可能会被程序之外的代理(如硬件寄存器、中断服务程序、另一个线程)意外修改。编译器必须**禁止对该变量进行任何优化**,每次访问它时都必须**直接从其内存地址中读取**,而不是使用寄存器中的缓存值。

 

* **在RTOS中的应用场景**:

    1. **内存映射硬件寄存器**:指向硬件寄存器(如状态寄存器、数据寄存器)的指针必须定义为`volatile`。因为寄存器的值会随硬件状态改变,与程序流无关。

    2. **在中断服务程序(ISR)中修改的全局变量**:主循环中判断的某个`flag`如果在ISR中被修改,该`flag`必须定义为`volatile`,否则编译器可能优化掉对它的重复读取,认为它的值不会改变。

    3. **多任务间共享的全局变量**:在两个或多个任务中共享的变量(虽然这种共享方式需非常小心,通常需配合互斥锁等机制),应使用`volatile`以防止编译器进行不安全的优化。但`volatile`**不能保证操作的原子性**。

http://www.dtcms.com/a/347306.html

相关文章:

  • 从观众席到股东席,何猷君成NBA凯尔特人新Co-owner
  • 网址账号正确,密码错误返回的状态码是多少
  • Java基础面试题(04)—Java(Java中String StringBuffer 和 StringBuilder的区别)
  • 山西某焦化厂炼焦区电气维护系统无线传输解决方案实施案例
  • Mangio RVC Fork 本地部署(Cuda12.9)
  • 蓝牙aoa仓库管理系统功能介绍
  • 有哪些Spring Boot微服务架构成功落地的案例?
  • GitHub发布革命性工具:GitHub Spark,用自然语言打造全栈智能应用
  • yolo命令行-训练篇(三)
  • Android安卓学习日志1 聊一聊安卓的历史和笔者的想法
  • 微服务统一入口——Gateway
  • 航空复杂壳体零件深孔检测方法 - 激光频率梳 3D 轮廓检测
  • 把 AI 塞进「自行车码表」——基于 MEMS 的 3D 地形预测码表
  • 基础IO
  • electron进程间通信-IPC通信注册机制
  • SAP FI 应收应付账龄分析
  • MySQL 锁的详解:从 InnoDB 到死锁诊断实战
  • Hive Metastore和Hiveserver2启停脚本
  • 爱普生打印机的使用
  • day40-tomcat
  • UE C++ 堆化
  • 【卫星通信】超低码率语音编码ULBC:EnCodec神经音频编解码器架构深度解析
  • 随机森林2——集成学习的发展
  • TCP:传输控制协议
  • JAVA核心基础篇-递归
  • C 语言标准输入输出头文件stdio.h及其常见用法
  • 【读论文】Qwen-Image技术报告解读
  • 云原生高级——K8S总概
  • ArkTS 语言全方位解析:鸿蒙生态开发新选择
  • 双指针:成最多水的容器