(嵌入式面经)第11章 20+公司面经杂谈(三):腾讯、科大讯飞、阳光电源、蔚来

本篇涉及的所有问题概要:大家可以试试在看参考答案前,提前尝试解答,以便明确自身知识点的不足部分!

1.用过交叉编译器吗,简单介绍一下?

2.说一下memcpy()和strcpy()的区别?

3.你写代码时用过多条件if else if else吗,如何进行优化呢?

4.在STM32里,串口接收哪几种模式,你比较常用什么模式,为什么呢?它是接受断如何判断数据接受结束的?

5.IIC接口地址位有几位,只有7位地址模式吗,理论上能接多少外设,实际上能接多少,是什么因素影响的?

6.说一说FreeRTOS如何去实现任务调度的?

7.如果实际中,你碰到数据传入速度大于数据处理速度,你会怎么解决?

8.在你的项目中,是如何考虑控制的实时性?

9.看你有涉及Linux跟FreeRTOS,说一说这两个操作系统的区别吧?

10. 专栏订阅奖励(支持模仿)——个人创新点问答:说一说你所设计的FreeRTOS PLUS中是如何在应对小数据量存储下,增加Flash单扇区存储的使用寿命的?

---------------------------------------------------------------------------------------------------

1.用过交叉编译器吗,简单介绍一下?

1. 交叉编译器(Cross Compiler)介绍

交叉编译器 是指在 一种 CPU 架构(如 x86)上编译可在 另一种 CPU 架构(如 ARM、RISC-V)上运行的代码的编译器。它主要用于 嵌入式系统操作系统移植跨平台开发

2. 交叉编译的应用场景

嵌入式开发(如 STM32、ESP32、ARM Linux)

Linux 内核编译(使用 arm-linux-gcc 交叉编译 ARM 设备的 Linux 内核)

Android 开发(Android NDK 使用交叉编译器生成 ARM 兼容的应用)

操作系统移植(移植 FreeRTOS、U-Boot 等)

3. 交叉编译器的常见工具链

4. 如何使用交叉编译器

(1)使用交叉编译器编译 C 代码

使用 arm-none-eabi-gcc 编译:

arm-none-eabi-gcc -o hello.elf hello.c

编译 ARM Linux 可执行文件:

arm-linux-gnueabihf-gcc -o hello_arm hello.c

然后可以拷贝到 ARM 设备(如树莓派)运行:

scp hello_arm user@raspberrypi:/home/user/

ssh user@raspberrypi ./hello_arm

(2)编译 FreeRTOS 代码

arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -o main.elf main.c

  • -mcpu=cortex-m4 指定 Cortex-M4 处理器
  • -mthumb 使能 Thumb 指令集
  • 5. 交叉编译 vs 本地编译

    6. 交叉编译的挑战

    库兼容性问题(如 glibc 版本不同)

    调试难度高(需借助 GDB 远程调试)

    架构指令集差异(如 ARM vs RISC-V

    解决方案:

  • 使用 qemu 模拟目标环境
  • 在目标设备上 远程调试
  • 使用 静态编译 避免动态库兼容性问题
  • 总结

    🚀 交叉编译器嵌入式开发多平台支持 方面至关重要。

    常见工具链如 arm-none-eabi-gccarm-linux-gnueabihf-gcc 可用于不同场景。

    通过合理的工具配置,我们可以在 PC 端开发代码,并将其部署到 ARM、RISC-V 设备,极大提高开发效率!

    2.说一下memcpy()和strcpy()的区别?

    memcpystrcpy 都是用于内存操作的 C 语言库函数,但它们的功能、用法和适用场景有明显的不同。

    1. memcpy 与 strcpy 的主要区别

    2. memcpy 详解

    函数原型

    void *memcpy(void *dest, const void *src, size_t n);

  • dest:目标地址
  • src:源地址
  • n:拷贝的字节数
  • 注意:不会自动添加 \0 结束符
  • 适用场景

    ✅ 适用于 二进制数据、结构体、数组、内存块 拷贝,例如:

  • 复制结构体
  • 复制固定长度的数据
  • 复制非字符串数据(如 intdoublechar 数组)
  • 示例

    ⚠️ memcpy 可能会导致溢出,如果 n 超过目标缓冲区大小,就会引发 缓冲区溢出,影响程序稳定性。

    3. strcpy 详解

    函数原型

    char *strcpy(char *dest, const char *src);

    • dest:目标字符串
    • src:源字符串(必须以 \0 结尾)
    • 注意:会自动拷贝 \0 结束符

    适用场景

    ✅ 适用于 字符串(char 数组)拷贝,例如:

  • 复制字符串
  • 初始化 char 数组
  • 处理 C 风格字符串
  • 示例

    ⚠️ strcpy 可能导致缓冲区溢出,如果 dest 的大小 小于 src,则会发生 越界写入,可能导致程序崩溃。

    4. 关键区别示例

    memcpy 可拷贝二进制数据,而 strcpy 仅适用于字符串

    memcpy(dest1, src, 5);

    • 仅仅复制 "Hello"的 5 个字节,但不会自动添加 \0 结尾
    • dest1 之前的内容是 "XXXXXXXXXX",所以输出可能会出现乱码,或者包含之前的内容。
    • 如果目标数组中原本有数据,则 \0 终止符不会自动加上,可能导致不受控的输出!

    strcpy(dest2, src);

    • strcpy会自动拷贝 \0 结束符,因此 dest2 仅包含 "Hello" 并正确终止。
    • 不会残留 XXXXXX,保证输出是正确的字符串!

    memcpy(dest3, src, 6);

    • 这里 memcpy手动指定 6 个字节拷贝,包含了 \0 终止符,因此 dest3 也是正确的字符串。
    • 但如果 src 不是字符串,而是结构体或二进制数据,memcpy 仍然不会做 \0 处理。

    5. 区别总结

    6. 何时使用?

    使用 memcpy

    • 复制 非字符串(二进制数据、结构体、数组)
    • 需要拷贝 精确的字节数
    • 数据大小已知,且不会重叠

    使用 strcpy

    • 复制字符串
    • 保证目标有足够空间
    • 需要 自动拷贝 \0 结尾

    🚨 避免常见错误

    • memcpy不会 添加 \0,要手动处理
    • strcpy不检查 目标空间,可能 越界
    • 推荐 strncpy 限制 strcpy 的拷贝大小

    结论

    • memcpy 更通用,适用于结构体、二进制数据
    • strcpy 适用于字符串,但要注意越界
    • 优先使用 strncpymemmove 以提高安全性

    3.你写代码时用过多条件if else if else吗,如何进行优化呢?

    if 语句包含多个条件时,优化代码不仅能提升可读性,还能提高执行效率。

    1. 提前 return,让主干代码更清晰

    适用于: 代码逻辑有多个分支,但主要执行路径明确的情况。

    示例:未优化代码

    优化后(提前 return):

  • 代码主干更清晰,减少嵌套,提高可读性。
  • 代码更符合短路逻辑,避免不必要的分支执行。
  • 2. 使用 && 和 || 组合条件

    适用于: 条件逻辑较多但可以合并的情况。

    示例:未优化代码

    优化后(合并条件):

    • 逻辑更直观,减少 if 嵌套。

    3. 使用 switch 代替多个 if-else

    适用于: 多个 if-else 语句用于匹配固定值的情况。

    示例:未优化代码

    优化后(switch 替换 if-else):

  • switch 比多个 if-else 更高效(编译器可能优化为跳转表)。
  • 可读性更强,适合固定值判断的情况。
  • 4. 使用 三目运算符 使代码更简洁

    适用于: 简单 if-else 赋值逻辑。

    示例:未优化代码

    优化后(? : 三目运算符):

    • 代码简洁,可读性更强。

    5. 逻辑表驱动法

    适用于: 复杂的 if-else 判断逻辑,可用数据结构存储匹配规则。

    示例:

    • 便于扩展,可减少 if-else 的复杂性。

    总结

    4.在STM32里,串口接收哪几种模式,你比较常用什么模式,为什么呢?它是接受断如何判断数据接受结束的?

    STM32 的串口(USART/UART)支持 三种数据接收模式

    1. 轮询模式(Polling Mode)
    2. 中断模式(Interrupt Mode)
    3. DMA 模式(Direct Memory Access Mode)

    1. 轮询模式(Polling Mode)

    工作方式:

  • CPU 主动轮询 USART_SR 寄存器的 RXNE(接收缓冲区非空标志位),如果该位被置 1,表示有数据可读取,否则继续轮询。
  • 读取数据后,RXNE 位置 0,等待下一次接收。
  • 代码示例:

    优点:

    • 代码逻辑简单,不需要额外的中断管理。

    缺点:

    • CPU 需要不断检查状态,占用 CPU 资源,效率较低。
    • 适用于低速率、少量数据的场景。

    2. 中断模式(Interrupt Mode)

    工作方式:

    • 启用 USART 接收完成中断(USART_IT_RXNE)。
    • 当数据到达时,触发中断,在中断服务函数中读取数据。

    代码示例(基于 HAL 库):

    优点:

    • 释放 CPU 资源,不需要频繁轮询。
    • 适用于数据量适中的场景,如 命令控制、低速数据传输

    缺点:

    • 当数据量较大时,会频繁进入中断,影响系统性能。

    3. DMA 模式(DMA Mode)

    工作方式:

    • DMA(直接存储器访问) 负责数据搬运,CPU 无需干预。
    • 串口接收到数据后,自动存入 指定的内存缓冲区
    • 可通过 DMA 完成中断定时器 触发数据处理。

    代码示例(基于 HAL 库):

    优点:

    • 适用于大数据量传输,如传感器数据流、日志记录
    • CPU 负担最小,提高系统实时性。

    缺点:

    • 代码实现较复杂,需要配置 DMA。
    • 需要 额外的方式判断数据接收完成。

    4. 如何判断数据接收结束?

    不同模式下,数据接收完成的判断方式不同

    5. DMA 模式:不定长数据的接收终止

    方法 1:启用 IDLE 中断

    USART IDLE 中断(空闲中断):当串口在一个帧的最后一个字节接收完成后,RX 线在一定时间内未收到新的数据,触发 IDLE 中断,可用于判断数据传输结束。

    示例代码:

  • 适用于变长数据接收,无需固定大小的缓冲区。
  • 减少 CPU 处理负担,相比于定时器超时判断更加高效。
  • 方法 2:使用定时器超时

    如果设备不支持 IDLE 中断,可以在接收到数据后启动一个定时器,如果超时时间内未接收到新的数据,则认为数据接收完成。

  • 适用于没有 IDLE 中断的 MCU(如部分低端 STM32)。
  • 灵活,可以适应不同的串口波特率。
  • 6. 常用模式选择

    常用选择:

    • 中断模式:适用于 短数据包通信(如 AT 指令、短指令)。
    • DMA + IDLE 中断:适用于 高频率/长数据包(如 UART 传感器、日志传输)。
    • DMA + 定时器:适用于 硬件不支持 IDLE 的 MCU

    总结

    STM32 串口支持轮询、中断、DMA 三种模式:

  • 轮询:简单但占用 CPU。
  • 中断:适合短数据包。
  • DMA:适合高吞吐数据传输,如传感器数据流。
  • 如何判断数据接收完成?

  • 轮询模式:查询 RXNE 标志位。
  • 中断模式:USART_IRQHandler() 触发。
  • DMA 模式:
  • 固定长度数据:使用 RxCpltCallback
  • 变长数据:推荐使用 IDLE 中断,若不支持则用定时器超时判断
  • 5.IIC接口地址位有几位,只有7位地址模式吗,理论上能接多少外设,实际上能接多少,是什么因素影响的?

    I²C(Inter-Integrated Circuit)是一种多主多从的串行通信协议,它支持7位和10位地址模式,并且理论上可以连接多个从设备,但受限于多种实际因素。

    1. I²C 地址位有几种模式?

    I²C 设备地址是用于标识不同从设备的,常见的地址模式有两种:

  • 7位地址模式(最常见)
  • 10位地址模式(用于需要更多设备的应用)
  • (1) 7位地址模式

    • 设备地址占7位,数据传输时再加上 1 位 R/W 位(读写标志位)。
    • 地址范围:0x00 ~ 0x7F(0~127)
    • 理论可连接设备数量:127 个(但 0x000x7F 为保留地址,实际可用 0x01 ~ 0x7E,共 126 个)

    示例:

    • 设备 A 地址:0x1A
    • 设备 B 地址:0x2F
    • 通信帧格式(读):0x1A << 1 | 1(发送 0x35,即 00110101
    • 通信帧格式(写):0x1A << 1 | 0(发送 0x34,即 00110100

    (2) 10位地址模式

    • 设备地址占 10 位,可扩展设备地址范围。
    • 地址范围:0x0000 ~ 0x03FF(0~1023)
    • 理论可连接设备数量:1023
    • 使用两个字节传输地址,前 5 位为 11110,后 10 位存放设备地址。

    示例:

    • 设备地址 0x1F3(十进制 499
    • 通信帧格式(写):11110XXX 0XXXXXXX
  • 第一个字节 11110000(高 5 位固定 11110,低 3 位 000
  • 第二个字节 01111111
  • 数据帧后续传输与 7 位模式相同
  • 2. 理论上能连接多少个设备?

    3. 实际上能接多少个设备?影响因素有哪些?

    虽然 I²C 理论上可以连接上百个设备,但在实际应用中受到以下因素的限制

    (1) 地址冲突

    • 原因:如果多个 I²C 设备具有相同的 7 位地址,则会发生冲突,无法正常通信。
    • 解决方案
  • 使用可配置地址的设备(
  • 剩余60%内容,订阅专栏后可继续查看/也可单篇购买

    作者简介:仅用几个月时间0基础天坑急转嵌入式开发,逆袭成功拿下华为、vivo、小米等15个offer,面试经验100+,收藏20+面经,分享求职历程与学习心得。 专栏内容:这是一份覆盖嵌入式求职过程中99%问题指南,详细讲解了嵌入式开发的学习路径、项目经验分享、简历优化技巧、面试心得及实习经验,从技术面,HR面,AI面,主管面,谈薪一站式服务,助你突破技术瓶颈、打破信息差,争取更多大厂offer。

    全部评论

    相关推荐

    评论
    9
    19
    分享

    创作者周榜

    更多
    牛客网
    牛客企业服务