博主现在正值大四,2023届自动化本科专业,写好简历之后抱着投一家公司看看简历有没有回应的态度投递了一家很不错的公司(业务是汽车的方向),记录一下一面的流程以及面试的问题。
fristly,首先投递了简历之后过了很荣幸通过了简历的初筛,与hr预定了第二天的线上视频面试。告诉hr能准时参加之后,hr发了一封面试的邮件通知,邮件内容有三点内容。
内容一:填写一张面试登记表,表的内容是自己的一些详细基本信息。
内容二:做了大概100道的性格测试,很多大企业都有行测这个环节。
内容三:确定同意第二天下午2点的腾讯会议视频面试。
然后就是第二天下午,我提前15分钟进入了腾讯会议,面试官也提前进入了会议,只有一个人。我本来以为一面一般是问一些比如是自我介绍之内的面试,不涉及技术,没想到第一面就开始技术面试,也包括了一些除技术以外的其他问题。具体内容如下 :
开始面试,面试官提问现在可以开始面试了吗?我回答可以,然后面试官开始了以下问题。
问题一:请先做一下自我介绍?
然后我做了一个自我介绍,内容包括我的信息,然后应聘哪一个岗位,学习的历程。。。最后我的自我结束完毕。
问题二:开始询问我做过哪些项目?
问题三:开始从我的项目入手,死扣项目的内容以及知识点。
(1)我项目中的按键键盘模块的是怎么实现的,用到了哪些技术。
(2)spi的时序?
(3)static关键字的作用
(4) const和宏定义
(5)udp/tcp协议的区别,tcp的三次握手四次挥手
(6)epoll,poll,select多路复用
(7)A/D转换模块的精度问题
(8)系统移植的步骤
(9)我对嵌入式的个人理解
然后具体就问了以上技术知识点,其他的问题是和技术无关的:
(1)想要工作的地点
(2)女朋友是哪里的?
(3)期望的薪资?
(4)未来的规划?
(5)公司有两个岗位需求,一是数据结构,算法相关 二是驱动开发(我选的驱动)
(6)然后问我还有没有什么问题问面试官的。
我反问了如下问题:
如果通过本次面试还有第二次面试吗?回答如果通过,还有一次更具体的技术面试,然后我也没有什么问题了。
然后是我个人的反思,我认为我的面试还没有准备的特别好,一些问题没有足够完美的回答出来,可能是因为有一些紧张的原因,毕竟也是我的第一次面试吧。如果接到通知一面能通过,那么我应该更努力的回顾知识点,积极准备第二次面试。如果本次面试没有通过,那么我也不会灰心,仔细归纳本次的面试经验,以及复习技术问题,努力准备下一次面试。
关于技术问题的知识点总结
1.我项目中的按键键盘模块是怎么是实现的?
这个项目是我大学期间大创立项的一个国家级项目,也是我最拿得出手的一个项目。是基于stm32f103和多个传感器实现的。
关于按键模块:
按键功能:
本次设计采用四个独立式按键实现时间调整,步数清除,界面切换功能。四个按键分为功能键(SET1),增加键(UP1),减少键(DOWN1),确认键(ENTER1)。设置功能键的功能是按下进入设置参数界面,温度界面,湿度界面,烟雾浓度界面和空气质量界面等功能,设置加键是用来对各种环境参数的值进行加法,设置减键是用来对环境参数的值进行减法。加在主界面的情况下,按下设置增加减少键是不起作用的。功能键是对界面切换键 ,可实现对主界面和其他参数界面进行切换。
键盘电路设计原理:
图中四个按键一端接公共端3.3V的Vcc,另一端分别接单片机的I/O引脚。三个按键接口分别接单片机的B5、B6、B7、B8引脚,当单片机的相应引脚检测到高电平时,说明该引脚被触发,从而实现相应的动作。
键盘子程序设计:
按键在本系统中主要起到增阈值、减阈值、确认、切换界面等,在软件下,首先将STM32的相应I/O设置为上拉输入,上拉输入取决于电路硬件公共端接地线。工作流程如下。
首先系统上电后,在正常工作状态下,判断是否有按下功能切换键,进入设置菜单界面,OLED显示模块温湿度、烟雾浓度、火焰系数、空气质量参数,通过设置加键、设置减键实现对各个参数阈值的修改,并在显示模块显示出来。判断加键是否被按下,若被按下,执行加1的操作,否则执行下一步操作。判断是否是减键按下,若被按下,执行减1操作,否则执行下一步操作。进入显示值设置界面,并判断确认键是否被按下,如被按下保存设置的阈值,如果当下确认键并未被按下,系统则会返回到最初的设置界面并结束当前子程序的循环运行。
2.spi协议的时序?
模式0(CPOL=0,CPHA=0)
CPOL=0;时钟线空闲时是低电平,第一个跳变沿是上升沿,第2个跳变沿是下降沿。
模式1(CPOL=0 CPHA=1)
CPOL=0;空闲时是低电平,第一个跳变沿是上升沿,第二个跳变沿是下降沿
CPHA=1;数据在第2个跳变沿(下降沿)采样的时序图如下:
模式2(CPOL=1CPHA=0)
CPOL=1;空闲时是高电平,第一个跳变沿是下降沿,第二个跳变沿是上升沿
CPHA=0;数据在第一个跳变沿(下降沿)采样的时序图如下:
模式3(CPOL=1CPHA=1)
CPOL=1;空闲时是高电平,第一个跳变沿是下降沿,第二个跳变沿是上升沿;
CPHA=1;数据在第二个跳变沿(上升沿)采样时序图如下:
3.static关键字的作用?
static修饰的对象:
static修饰后的作用:
1.改变了生存周期;就是一个变量,函数从分配内存去表示到回收内存的过程
static修饰不同对象时的作用:
局部变量:局部变量就是在函数内定义的变量,普通的局部变量,生存周期是随着函数的结束而结束,每次函数重新执行,局部变量都是新的值,不会保留上次的值。当用static修饰后,局部变量的生存周期就是当程序结束才会结束。再次调用函数时,用static修饰的变量会保留上一次的值。
应用:在函数内,我们想保留某些变量上一次的值,就可以用static去修饰该变量。比如:想统计该函数被执行的次数时,就可以定义被static修饰的int型变量,每执行一次该变量就++。
总结:用static修饰的局部变量,改变了生存周期,但是没有改变其作用域。改变其生存周期的原因是被static修饰的局部变量被存放在.bss段或者.data段,而普通的局部变量是存放在栈上的。
全局变量:全局变量用static修饰改变了作用域,没有改变生存周期。普通的全局变量是可以被其他的.c文件引用的,一旦被static修饰,就只能被定义该全局变量的.c文件引用,使得该全局变量的作用范围减小。
作用:当一个全局变量不想被其他.c文件引用时,可以用static修饰,这样其他的文件就不能通过extern的方式去访问,这样主要是为了数据安全。
总结:改变其作用域,没有改变生存周期。
函数:函数用static修饰,改变了作用域。普通的函数是可以通过头文件声名的方式被其他文件调用,被static修饰后就只能在本文件里被调用,这样是为了数据的安全。
作用:有些函数并不想对外提供,只需要在本文件里调用,这时候就可以用static去修饰。
总结:改变了作用域,没有改变其生存周期。
补充:用static修饰全局变量和函数,除了上面说的数据安全,防止被误引用,还有一个作用是解决重名问题。当用static修饰了全局变量和函数后,其他文件里再定义同名的全局变量和函数也是可以的。一般来说,如果不是要对外提供的函数和全局变量,最好都用static修饰。
4.const和宏定义?
const:
1. 修饰变量
具有常属性,可以在定义数组的时候用该变量定义,每次取值从寄存器中取,在编译过后,直接将对应的值,替换到当前变量的位置。与之相对的是volatile。被这个关键字修饰的话,代表告诉了编译器,这个变量时随时可能被修改的。防止编译器优化,每次读取该值时,从内存中读取。而不是从编译器优化的寄存器中读取。C++中,被const修饰的变量,会在编译期间将对应的变量,直接替换为该变量的值。但是C语言中不会。
2. 修饰指针int * const p ;表示指针变量本身不能被修改,只能指向这一个地址,这与引用比较类似,一个变量的引用,在生命周期内只能引用一个对象。但是这个指针所指向的内容是可以改变的。
const int *p这表示p所指向的空间内容不可修改。C语言中int *p = 1; 可以正常运行。因为发生了隐式类型转换会出现警告。但是C++中有严格的类型检测,不允许这样进行赋值。必须显式的给出强制转换。强行转换之后对对应地址内容进行修改,C中输出 *p与m值相同,C++中 *p是修改后的值,m则是定义时的值,因为C++中被const修饰的变量,会在编译时期进行替换。
3. 修饰函数放在返回值前 const int add(int a, int b)无意义,因为返回的值本就是一个临时变量。所以const修饰也没有任何作用
放在参数列表前修饰。用来保护传进来的参数,保证尽可能的不被修改。
放在函数后面。用来修饰类的成员函数中的this所指向的,也就是保护类成员(非静态成员除外)不被修改。同时,没有const修饰的函数可以调用const修饰的成员函数,但是被const修饰的成员函数不可以调用非const修饰的成员函数。而且非const定义出来的对象可以调用const与非const函数,但是const定义出来的对象只能调用const修饰的函数,不能调用非const函数,因为他有可能对对象进行修改。
类成员函数中,后面加与不加const也可以形成重载。const修饰的对象调用的是const修饰函数,非const修饰的对象调用的是非const函数。
4. 修饰类成员变量const成员变量必须在类的构造函数的初始化列表中初始化,因为此时类并没有进行实例化(创建对象),因此也没有分配内存。因为类中变量只是声明时候用的cosnt来修饰,const要修饰一个变量不能被改变,总不能这个变量都没有值,就让他不能被改变吧。所以要在这个变量被创建之前就给他定义一个值。类的构造函数的初始化列表中就可以在对象创建之前做到这一点。如果不在构造函数初始化列中给出值,有可能在构造函数体中,这个const成员已经指向了一个随机的初始值。这样就不切合实际了。
如果是static const 同时修饰的类成员变量。可以在类的内部声明时给出初始化。同时在类外(全局作用域)进行声明。否则不会为这个变量分配空间。宏定义:
1.替换作用
#define pi 3.14159
2.替换简单函数
3.宏开关
5.udp/tcp协议的区别,tcp的三次握手四次挥手?
TCP是面向有连接的,可靠的,基于字节流的传输层协议;
UDP是面向无连接的传输层协议;
1.有连接:
有连接是指客户端和服务端之间的连接,在双方通信之前,TCP需要通过三次握手建立连接,而UDP没有;
2.可靠性
为了保持可靠性,TCP花费了很大的精力来做这些事情,主要体现在有状态,可控制;
有状态:主要体现在TCP准确记录当前发送了多少数据,那些被接收了,哪些没有被接收;
可控制:TCP会根据当前的丢包和网络环境不佳的情况下,按照具体的情况调节自己的行为,如控制发送
速度和重返;
但UDP是无状态,不可控的;
3.基于字节流
UDP数据传输是基于数据报的,这是因为UDP继承了IP层的特点,而TCP为了维持有状态,将IP包处理成了字节流传输;流量控制
作用:
解决端到端的数据发送速度的合理控制;
对于发送端和接收端来说,TCP需要把发送的数据放到发送缓存区,将接收的数据放到接收缓存区,
而流量控制就是通过接收缓存区的大小,控制发送端缓存区的大小,如果接收方的缓存区满了,就不继续发送了;
滑动窗口
服务器和客户端的缓存区的大小的相互控制;(以后详细叙述)拥塞控制:
作用:
防止过多的数据注入到网络中,可以使网络中的路由器和数据链路不过在
拥塞控制主要有四个算法:
分别为:慢启动算法,拥塞避免算法,快重传,快恢复;
慢启动算法:
初始发送拥塞窗口为cwnd=1,确认发送成功后,cwnd=2, 慢启动算法没经过一个传输轮次,拥塞窗口cwnd就加倍
拥塞避免:
为了防止拥塞窗口增加过快而导致网络拥塞,所以需要设置一个慢开始门限ssthresh:
1 cwnd<ssthresh 使用慢启动算法
2. cwnd>ssthresh 使用拥塞避免算法
3. cwnd=ssthresh 两个都可以
拥塞避免是让cwnd缓慢增加 每一个传输轮次只加1;
无论是慢启动还是拥塞避免,只要判断出现网络拥塞,就设置慢开始门限ssthresh/=2; cwnd =1;然后在使用慢启动算法
**网络拥塞:*发送方发送一些报文时,如果发送方没有在时间间隔内收到接收方的确认报文段,则认为是网络拥塞;
*快重传算法: 选择性重传(左边界和右边界)
接收方收到一个失序的报文段后就立刻就发出重复确认,如果发送方收到三个重复的ack时,意识到丢包了,于是马上继续重传,不用等待一个往返时间;
快恢复算法:
aimD:
1.ssthresh /=2;
2.cwnd=ssthresh/2;
3.拥塞避免算法;三次握手 四次挥手
三次握手
服务端先会进入到监听状态:
1.客户端向服务端发起请求syn
2.服务端接收到syn后, 发送ack 和服务端的syn;
3. 客户端接收到 ack,和服务端的syn, 发送sck给服务端;
四次挥手
1.客户端发起关闭请求fin;
2.服务端接收到fin后 ,回复给客户端ack
3.由于服务端还有数据发送,完成后发送fin给客户端
4.客户端接收到fin后,回复ack给服务端,需要等待2MSL;TCP的keep-alive
TCP 并不是一个轮询的协议,当一方网络发生故障或者其他原因连接失败失效,在下一个数据到达之前是不知道的;
keep-alive, 它的作用就是探测对端的连接有没有失效。解决方案:一般是一方定时给另一方发送保活的数据包,保持连接和判断对方的状态;
TCP粘包问题
由于TCP是基于字符流传送数据的,所以可能会导致多个数据包粘结成一个包。
6.epoll,poll,select多路复用?
epoll、poll、select都是一种I/O多路复用技术。I/O多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
epoll、poll、select本质是同步I/O,都是I/O多路复用,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
1、select
select的几大缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是10242、poll
poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
3、epoll
(1)poll和select的增强版,event poll,更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
(2)两种工作模式:LT(level trigger)和ET(edge trigger)LT模式水平触发(默认模式):当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET模式边沿触发:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。(3)操作需要三个接口
epoll_create、eploo_ctl、epoll_wait。7.A/D转换模块精度问题?
AD转换中的主要概念
(1)位数,AD转换后转出来的二进制数由几位二进制来表示。位数越多,越细腻。
(2)量程,AD转换器可以接受的模拟量的范围。
(3)精度,简单理解就是转出来到底有多准。
(4)分辨率,AD转换器转出来的二进制数,每一格表示多少。
(5)转换速率(转换时间)。
举个栗子:
输入电压范围0-5V,AD转换输出位数是10,精度是0.01V,则:量程为0-5V,
分辨率为:(5-0)/2exp(10)=0.00488V
譬如一次AD转换后得到的数据是1010101010,则对应的电压值为:3.328V,考虑精度后为3.33V8.系统移植的步骤?
第一步环境搭建
第二部bootloader引导
第三内核移植
第四步根文件系统
9.我对嵌入式的理解?
我的回答是:从我最开始接触plc说起,然后再到单片机,然后再到芯片。。。这个自由发挥吧
最后最值得我反省的一点:我自己本身投递的是嵌入式软件开发岗位,但是由于面试官问的问题感觉都偏硬件底层知识,而且我硬件部分答的不错,最后提出如果通过面试,让我对两个岗位的选择,一个是算法,一个是驱动,我选择了驱动,可能在这时候,面试会觉得我的目的性不明确,这是一个很严重的问题,相当于搞了半天还不知道自己到底想干什么。