2025秋招:嵌入式开发20万的Offer,问哪些问题?

情况介绍

说说我们公司的情况吧:中型企业,位于二线省会,主要是做飞行器总体,技术已经成熟,865,周末双休,嵌入式系统设计不算复杂,总包20万。
老实说这个薪资没有太大竞争力,但相应的能力要求也会放低,一般就问些比较基础的问题,但是因为要求放低,所以要求软硬件都要会(可以偏软或偏硬)
本文会比较长,一次写不完,后面不定期更新,有兴趣的记得收藏,防止再找的时候迷路。

招聘流程

第一轮:HR先筛选一遍学校、成绩、项目经历等;
第二轮:技术负责人约十几分钟的电话面试;
第三轮:HR沟通薪资待遇等。

招聘需求

专业需求:嵌入式技术与应用、软件技术、电子科学与技术、自动化、仪器仪表等相关专业
学历要求:硕士及以上学历

岗位要求

(1)熟悉模电、数电、C语言等相关知识,具有嵌入式系统设计经验;
(2)精通ARM、DSP等硬件平台;
(3)具备电路原理图设计,PCB制作及调试能力,嵌入式程序编写能力。

面试重点

面试重点
图1 面试重点

软件常见问题

1、#ifndef在头文件的作用

#ifndef(如果未定义)预处理指令在头文件(header file)中的作用主要是防止头文件被重复包含(include)多次所导致的编译错误或警告。这是一个常用的编程技巧,用于处理C、C++(以及C#等支持条件编译的语言)中的头文件保护问题。

当一个头文件被多个源文件或多个其他头文件包含时,如果没有适当的保护措施,可能会导致该头文件中定义的变量、函数原型、类型定义等被多次声明,从而引发编译错误。例如,如果在两个不同的源文件中都包含了同一个头文件,而该头文件中定义了一个全局变量,那么编译器会因为变量被重复定义而报错。

2、#if在C文件的作用

在C语言(以及C++等支持预处理指令的语言)中,#if 预处理指令用于在编译之前根据指定的条件来包含或排除代码段。这与在运行时进行的条件判断(如使用 if 语句)不同,因为预处理指令是在编译之前由预处理器处理的,它们不会改变程序的逻辑流程,而是控制哪些代码会被编译进最终的程序中。

#if 指令后面通常跟随一个或多个预处理表达式,这些表达式由宏定义、整数常量、已定义的运算符(如 defined、&&、|| 和 !)等组成。如果表达式的计算结果为非零(即真),则编译器会包含 #if 和后续 #endif 之间的代码。如果表达式的结果为零(即假),则这部分代码会被忽略。

3、volatile关键字的作用

  1. 告诉编译器不要优化
    编译器在编译代码时,会进行各种优化以提高代码的执行效率。然而,对于被volatile修饰的变量,编译器会避免对其进行优化,如避免将其存储在寄存器中,而是每次直接从内存中读取其值。这样可以确保变量的值在程序执行过程中能够实时反映其最新状态。

  2. 应用于硬件编程
    在硬件编程中,volatile关键字常用于修饰硬件寄存器的地址或中断服务例程中的变量。由于硬件寄存器的值可能会随时被外部设备或中断服务例程修改,使用volatile可以确保程序在访问这些变量时能够获取到最新的值,从而避免了由于编译器优化导致的错误。

4、Align关键字的作用

Align关键字的作用主要涉及到内存或数据边界的对齐,它在不同的上下文和环境中可能有细微的差别,但核心目的是优化性能和数据访问的效率。FFT函数:在计算FFT(快速傅里叶变换)时,某些FFT函数要求分配的内存地址必须按照特定的对齐方式(如N*2的地址对齐)进行对齐,这时就需要使用Align关键字来确保内存对齐。嵌入式系统和实时系统:在这些系统中,内存和资源的分配更加严格,使用Align关键字可以更好地控制内存布局,满足系统的实时性和稳定性要求。

5、C指针有哪些类型分别有什么作用

  1. 基本类型指针
    整型指针(int*):指向整型数据的指针。可以用来访问和操作整型变量在内存中的值。
    字符型指针(char*):指向字符型数据的指针。常用于处理字符串,因为字符串在C语言中实际上是以字符数组的形式存在,而字符数组的首地址(即数组名)就是字符型指针。
    浮点型指针(float*):指向浮点型数据的指针。用于访问和操作浮点型变量。
    双精度浮点型指针(double*):指向双精度浮点型数据的指针。与浮点型指针类似,但用于访问和操作双精度浮点型变量。
  2. 结构体和联合体指针
    结构体指针(struct*):指向结构体类型的指针。结构体是C语言中一种复合数据类型,可以包含多个不同类型的成员。结构体指针用于访问和操作结构体变量的成员。
    联合体指针(union*):指向联合体类型的指针。联合体是另一种复合数据类型,与结构体不同的是,联合体中的所有成员共享同一块内存空间。联合体指针用于访问和操作联合体变量的成员。
  3. 数组指针和指针数组
    数组指针(指向数组的指针):其本质是指针,但指向的是一个数组的首地址。通过数组指针,可以遍历数组中的元素。
    指针数组:数组的每个元素都是指针类型。这种类型常用于存储多个变量的地址,或者作为函数指针数组的基础。
  4. 函数指针
    函数指针:指向函数类型的指针。函数指针可以存储函数的地址,并通过这个地址调用函数。函数指针在回调函数、中断服务例程等场景中非常有用。
  5. 通用指针和特殊指针
    通用指针(void*):可以指向任意类型数据的指针。由于其灵活性,通用指针在需要处理不同类型数据但又不希望进行显式类型转换的场景中非常有用。
    常量指针和指针常量:常量指针是指指向常量数据的指针,即指针指向的数据不能被修改;指针常量则是指指针本身是常量,即指针的值(即所指向的地址)不能被修改。
    指向指针的指针:这种指针用于存储指针的地址,可以用于创建多级指针,以访问和操作更深层次的内存结构。

6、C结构体Struct和联合体Union区别

C语言中的结构体(struct)和联合体(union)是两种不同的数据结构,它们各自具有独特的特点和用途。以下是结构体和联合体之间的主要区别:

  1. 内存占用
    结构体(struct):结构体中的每个成员都占用独立的内存空间,其总内存大小是所有成员大小之和(还需考虑字节对齐)。例如,一个结构体包含了一个整型(int)、一个字符型(char)和一个双精度浮点型(double),那么该结构体的总大小将是这三个类型大小之和(加上可能的字节对齐填充)。
    联合体(union):联合体中的所有成员共享同一块内存空间,因此联合体的总大小等于其最大成员的大小(同样需要考虑字节对齐)。这意味着在同一时间内,只能使用联合体的一个成员,因为所有成员都存储在相同的内存位置。
  2. 成员互斥性
    结构体:结构体中的成员是独立的,可以同时存在并访问。每个成员都占用自己的内存空间,互不影响。
    联合体:联合体中的成员是互斥的,即在同一时间内只能有一个成员是有效的。当给联合体的一个成员赋值时,会覆盖掉其他成员的值,因为所有成员共享同一块内存。
  3. 用途
    结构体:结构体通常用于表示具有多个属性的复杂数据类型,如学生信息、员工记录等。结构体使得这些相关的数据能够作为一个整体被处理,提高了代码的可读性和可维护性。
    联合体:联合体通常用于节省内存空间,或者在需要在不同数据类型之间切换的场合。例如,可以使用联合体来表示一个变量可以是整型、浮点型或字符型中的任意一种,但每次只能使用其中一种类型。
  4. 初始化
    结构体:结构体可以对其所有成员进行初始化,每个成员都可以被赋予一个初始值。
    联合体:由于联合体成员共享内存,因此在初始化时通常只对其中的一个成员进行初始化。如果尝试对多个成员进行初始化,可能会导致编译错误或未定义行为。
  5. 访问方式
    结构体和联合体都使用点运算符(.)或箭头运算符(->,对于指针)来访问其成员。但是,由于联合体的成员是互斥的,所以在访问时需要确保当前正在使用的成员是有效的。

7、什么是回调函数,为什么要使用

回调函数(Callback Function)在编程中是一个常见的概念,它指的是一个函数作为参数传递给另一个函数,并在特定条件满足或特定事件发生时被调用执行的函数。基于回调的API使用者会编写一个函数,该函数作为参数被传入API中。API的提供者(称为调用者)在其函数体中的某个点调用(或执行)这个被传入的函数。调用者负责向回调函数中传入正确的参数,并可能期望从回调函数中获得一个特定的返回值,该值用于指示调用者的进一步行为。

为什么要使用回调函数
回调函数在编程中有多种使用场景和重要性,主要体现在以下几个方面:

异步编程:
在许多编程场景中,尤其是涉及I/O操作(如网络请求、文件读写等)时,程序需要等待某个操作完成才能继续执行。为了避免程序阻塞(即“挂起”并等待),通常会使用异步编程。回调函数是异步编程中处理结果的一种方式。当异步操作完成时,它会调用一个预先定义的函数(即回调函数),并将结果作为参数传递给该函数。这样,程序可以继续执行其他任务,而不必等待异步操作完成。
事件驱动编程:
在事件驱动编程中,程序的行为由外部事件触发。当某个事件发生时(如按钮点击、鼠标移动等),程序会调用与该事件相关联的回调函数来处理该事件。这种方式使得程序更加灵活和可扩展,因为可以轻松地添加或修改事件处理程序(即回调函数),而无需修改程序的其余部分。
函数式编程:
在函数式编程中,函数可以像其他数据类型一样传递和使用。回调函数正是这种思想的体现之一。通过将函数作为参数传递给其他函数(即高阶函数),可以实现更加灵活和模块化的代码结构。
解耦合和复用:
使用回调函数可以将程序的不同部分解耦合。通过将某个操作的结果传递给回调函数处理,可以使得操作的执行和结果的处理分离开来。这种解耦合可以提高代码的可读性和可维护性,使得程序更加模块化和可测试。同时,将一些通用的操作封装成回调函数,可以在不同的地方重复使用,减少代码的重复编写,提高代码复用性。
实现动态行为:
回调函数可以根据不同的条件或状态执行不同的操作,实现动态的行为。例如,可以根据用户的输入或系统的状态来决定调用不同的回调函数。
提升程序性能和效率:
特别是在处理异步操作和I/O操作时,使用回调函数可以避免程序阻塞,从而提高程序的响应性和效率。

8、串口轮询接收和DMA接收区别

串口轮询接收和DMA(Direct Memory Access,直接存储器存取)接收是串口通信中两种常见的接收方式,它们在实现原理、资源占用、效率和适用场景等方面存在显著差异。

一、实现原理
串口轮询接收:

在轮询接收中,CPU会周期性地查询串口接收缓冲区,以检查是否有新的数据到达。
当串口接收到新的数据时,会存储在接收缓冲区中,CPU通过不断查询这个缓冲区来读取数据。
DMA接收:

DMA方式下,数据传送时,数据不通过CPU,而是直接在存储器和高速外设(如串口)间进行交换。
DMA控制器(DMAC)负责数据的传送、地址的修改、传送结束信号的发送等任务,从而节省了大量CPU的时间。
二、资源占用与效率
串口轮询接收:

CPU需要不断地查询串口接收缓冲区,这会占用大量的CPU资源,降低系统的实时性和效率。
在数据接收和发送过程中,CPU可能无法执行其他任务,导致程序阻塞。
DMA接收:

DMA接收方式下,CPU几乎不参与数据的实际传输过程,因此可以节省CPU资源,提高系统的整体效率。
DMA方式适合用于需要高速数据传输和实时性要求较高的场景。
三、适用场景
串口轮询接收:

适用于数据传输量较小、实时性要求不高的简单应用场景。
在某些嵌入式系统或微控制器中,由于资源有限,可能更倾向于使用轮询接收方式。
DMA接收:

适用于需要高速、大量数据传输的场合,如视频、音频等多媒体数据的传输。
在需要同时处理多个任务和保证系统实时性的复杂系统中,DMA接收方式具有明显优势。

9、怎么解决串口正在DMA发送时,新的该串口的DMA发送请求问题

使用缓冲队列:
当串口接收到新的DMA发送请求时,可以将这些请求放入一个缓冲队列中。DMA控制器在完成当前发送任务后,会自动从队列中取出下一个请求进行处理。这种方式可以确保所有的发送请求都能得到有序处理,同时避免了数据冲突和丢失。
任务调度:
在软件中实现一个任务调度器,根据任务的优先级和当前系统的资源状况来调度DMA发送任务。这可以确保关键任务得到及时处理,同时避免系统资源的过度占用。

10、什么是共享临界区数据,怎么保证共享安全

共享临界区数据是计算机科学中的一个重要概念,它涉及并发编程和资源共享的复杂问题。下面从几个方面详细解释这一概念:

  1. 临界区的定义
    临界区(Critical Section)是指一段代码或程序片段,其中包含了对共享资源的访问或修改操作。这段代码在同一时间内只能被一个线程(或进程)执行,以防止多个线程同时访问共享资源导致的竞态条件(Race Condition)和数据不一致性问题。

  2. 共享资源的概念
    共享资源是指可以被多个线程或进程同时访问的资源,如全局变量、共享数据结构、文件等。这些资源中,如果有一部分被划分为临界区,那么这部分资源就被称为共享临界区数据。

  3. 共享临界区数据的特性
    互斥性:在任何给定时间,只有一个线程或进程可以进入临界区执行,以确保对共享资源的独占访问。
    原子性:临界区内的操作通常被视为不可分割的单个操作,以防止在执行过程中被其他线程或进程干扰。
    同步需求:为了确保临界区的互斥性和原子性,通常需要使用同步机制(如互斥锁、信号量、条件变量等)来控制对临界区的访问。

  4. 同步机制的作用
    同步机制的主要作用是确保在任意时刻只有一个线程能够进入临界区执行。当线程尝试进入临界区时,如果已经有其他线程在执行临界区内的代码,则当前线程必须等待,直到临界区被释放(即当前执行线程退出临界区)后才能进入。

  5. 注意事项
    避免死锁:在设计同步机制时,需要避免死锁的发生。死锁是指多个线程或进程因相互等待对方释放资源而无法继续执行的情况。
    性能考虑:过多的锁定和解锁操作可能会影响程序的性能。因此,在确定临界区的范围时,需要权衡保护和性能之间的平衡。
    动态性:临界区不是一直存在的,它是在程序执行期间动态创建和销毁的,具体取决于多线程或多进程程序的逻辑和需要。

11、举例说明编译优化造成的代码被误删除问题

while(a<b)
{
	if(a>=b)
	{
	//此处可能被优化
	}
}

由于在While循环判断条件(true)内再次if判断(false),导致编译器将flase条件内的代码全部优化不再参与编译。

硬件问题

1、无源晶振和有源晶振区别

无源晶振和有源晶振区别:
无源晶振:

  • 谐振频率稳定,与供电电压无关。
  • 不需要外部电源,仅通过机械振动即可产生输出信号,因此具有低功耗、节能的优势。
  • 输出幅值较小,可能需要外部放大器来增加输出信号的幅度。
  • 设计简单,成本较低。
    有源晶振:
  • 通常能提供更高的频率范围和更高的频率精度。
  • 需要外部电源供电,但设计上可以提供更大的输出幅值。
  • 具有多种控制和调节功能,如频率调谐、相位锁定等,可以根据需求调整输出信号的特性。
    获得稳定的时钟信号。

2、三级管和MOS管的区别

  • 三极管:三个引脚:基极(B)、集电极(C)和发射极(E)。
  • MOS管:三个引脚:栅极(G)、漏极(D)和源极(S)。
  • 三极管:基于电流放大效应,通过控制基极电流来改变集电极电流,实现信号的放大和开关功能。三极管是电流控制型器件。
  • MOS管:基于场效应原理,通过调节栅极电压来控制沟道中的载流子浓度,从而改变漏极和源极之间的电流。MOS管是电压控制型器件。
  • 三极管:具有较高的放大倍数,但速度相对较慢,适用于低频、小信号放大。
  • MOS管:具有较高的开关速度和较低的功耗,适用于高频、高速和大电流应用。MOS管在开关状态下几乎没有功耗,且输入输出电阻高,抗干扰能力强。
  • 三极管:常用于数字电路中的开关控制,以及模拟电路中的小信号放大。
  • MOS管:广泛应用于数字电路中的高速开关,以及高频电路、大电流场合和电源管理电路中。

(未完待续)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值