3. UART总结
3.1 UART协议 – 重要!
UART(通用异步收发传输器)用于异步通信。UART协议允许两个设备之间通过单一的通信线路进行数据传输。以下是UART协议的一些关键特性:
-
异步通信:
- UART通信是异步的,意味着发送方和接收方的时钟是独立的,不需要同步信号。
-
数据格式:
- UART通信的数据格式通常包括数据位、起始位和停止位。
- 起始位:每个字节的开始是一个低电平信号(0)。
- 数据位:可以是5到9位,常用的是8位。
- 奇偶校验位(Parity Bit):可选的,用于错误检测。
- 停止位:数据字节后的一个或两个高电平信号(1),表示数据传输的结束。
- UART通信的数据格式通常包括数据位、起始位和停止位。
-
波特率:
- 波特率(Baud Rate)是UART通信的速率,即每秒传输的信号单位数。常见的波特率有9600、19200、38400、115200等。
-
全双工通信:
- UART支持全双工通信,即可以同时进行数据的发送和接收。
-
硬件连接:
- 通常只需要两条线:TX(发送线)和RX(接收线)。
3.2 常用寄存器
以下是STC51单片机中与UART通信相关的常用寄存器:
寄存器名称 | 功能描述 |
---|---|
SCON | 串行控制寄存器,用于设定串行口的工作方式、接收/发送控制以及设置状态标志等。 |
PCON | 电源控制寄存器,包括波特率选择和帧错误控制位。 |
SBUF | 串行数据缓冲寄存器,物理上是两个独立的寄存器,但占用相同的地址。 |
TMOD | 定时器模式寄存器,用于配置定时器的工作模式,UART通信中用于控制波特率。 |
TH1 | 定时器1高8位,用于设置定时器1的初值,进而控制波特率。 |
TL1 | 定时器1低8位,用于设置定时器1的初值,进而控制波特率。 |
3.3 代码回顾
#include "reg52.h"
#include "intrins.h"
sfr AUXR = 0x8E;
void UartInit(void) {
// 9600bps @ 11.0592MHz
AUXR = 0x1;
SCON = 0x40; // 选择串口工作方式1
TMOD &= 0x0F;
TMOD |= 0x20; // 定时器1工作在8位自动重载
TH1 = 0xFD;
TL1 = 0xFD; // 9600波特率根据公式算出的初始值
TR1 = 1; // 定时器开始工作
// ES = 1;
// EA = 1;
}
void Delay1000ms() {
// @11.0592MHz
unsigned char i, j, k;
_nop_();
i = 8;
j = 1;
k = 243;
do {
do {
while (--k);
} while (--j);
} while (--i);
}
void main() {
char data_msg = 'a';
// 配置C51串口的通信方式
UartInit();
while(1) {
Delay1000ms();
// 往发送缓冲区写入数据,就完成数据的发送
SBUF = data_msg;
}
}
3.4 面试常见问题
3.4.1 简述一下UART如何传输一个字符’A’
UART传输一个字符’A’的过程如下:
- 单片机通过配置UART的相关寄存器(如SCON、TMOD、TH1等)设置波特率和通信参数。
- 将字符’A’的ASCII码值(65)写入到UART的数据缓冲寄存器SBUF中。
- UART硬件检测到SBUF非空后,自动在起始位后发送’A’的8位ASCII码数据(从最低位(LSB)开始发送,然后逐步发送到最高位(MSB))。
- 接着发送一个可选的奇偶校验位。
- 最后发送一个或两个停止位来标识传输结束。
- 接收方的UART在检测到起始位后开始接收数据,并在停止位后确认字符’A’已完全接收。
3.4.2 简述一下UART通信协议
UART通信协议是一种异步串行通信机制,它允许设备之间通过两条线(发送线TX和接收线RX)进行数据交换,数据以字符为单位传输,每个字符由起始位、一定数量的数据位(通常是5到8位)、可选的奇偶校验位和停止位组成,所有这些位都以特定的波特率(即每秒传输的符号数)进行传输,从而实现设备间的同步通信。
3.4.3 如何提升串口通信效率
提升串口通信效率可以通过以下方法:
- 优化通信参数:选择合适的波特率和数据格式。
- 使用中断或DMA:减少CPU占用,提高数据传输效率。
- 采用更高的波特率:加快数据传输速度。
- 减少不必要的通信握手和校验过程:简化通信流程。
- 合理配置缓冲区大小:平衡内存使用和数据吞吐量。
- 优化协议栈和数据处理流程:提高软件层面的效率。
3.4.4 C51和STM32对UART使用上的区别
C51单片机和STM32单片机在使用串口(UART)时的主要区别如下:
-
硬件架构:
- C51单片机:基于传统的8051架构,通常只包含一个UART接口。
- STM32单片机:基于ARM Cortex-M系列处理器,拥有更复杂的总线结构和更多的串口资源,如USART1、USART2、USART3等。
-
外设功能:
- C51单片机:外设种类较少,功能相对简单。
- STM32单片机:集成了丰富的片上外设,包括多个UART接口,以及其他高级通信接口如SPI、I2C等。
-
波特率配置:
- C51单片机:波特率配置通常涉及到定时器的设置,需要手动计算并设置波特率生成器。
- STM32单片机:波特率配置更为灵活,可以通过软件配置APB时钟和USART分频寄存器来设置。
-
编程和配置:
- C51单片机:串口配置相对简单,主要涉及SCON、PCON和TMOD等寄存器的设置。
- STM32单片机:串口配置更为复杂,需要配置GPIO、RCC(时钟控制)、USART等多个寄存器,并且可以使用CubeMX等软件工具来简化配置。
-
中断和DMA:
- C51单片机:UART通信通常使用中断方式处理数据接收和发送,DMA功能较弱。
- STM32单片机:支持更高级的中断处理和DMA数据传输,可以提高数据传输效率。
-
内存和处理能力:
- C51单片机:通常具有较小的内存容量和较低的处理能力。
- STM32单片机:具有更大的内存容量和更高的处理能力,适合处理更复杂的数据和通信任务。
3.4.5 你用串口做过哪些开发
在我之前的开发经验中,串口主要用于网络通信模块和语音模块:
- 网络模块:较多使用AT指令驱动,如WiFi模块、蓝牙模块、4G模块,还有物联网NB-IoT模块等。
- 语音模块:使用SU-06模块,通过串口数据来交互模块中的固件,通过发送特定串口数据来播报语音,或者根据接收的串口数据来判断语音模块识别结果。
3.4.6 小车中关于串口的使用
智能小车属于大学期间的比赛项目,当时串口使用有两个目的:
- 调试:如驱动电机、获取电机速度、获取小车前方障碍物距离等。
- 连接语音模块:根据语音模块的识别结果来切换小车的工作模式,如避障模式、跟随模式、手机控制模式的切换。
因为51单片机只有一个串口,在项目设计中把一个串口拆分为两个模块连接:
- 语音模块:仅在需要语音识别功能时连接到C51串口的输入口。
- 蓝牙或WiFi:需要通过蓝牙或WiFi把小车数据(如车速、温湿度采集数据)发送到上位机时,连接到C51串口的输出口。
4. I2C总结
4.1 I2C协议概述
C51单片机实现I2C通信主要涉及以下几个方面:
-
I2C总线结构:
- I2C通信使用两条线,数据线(SDA)和时钟线(SCL),所有设备共享这两条线。
-
通信时序:
- I2C通信时序包括起始信号、数据传输、应答信号和停止信号。主设备生成时钟信号并控制通信的开始和结束,从设备响应时钟信号并进行数据的发送或接收。
-
数据传输协议:
- I2C数据传输包括发送和接收字节,每个字节包含8位,从最高位开始传输。发送时,主机将数据位放在SDA线上,然后拉高SCL,从机在SCL高电平期间读取数据位。接收时,从机将数据位放在SDA线上,主机在SCL高电平期间读取数据位。
-
软件模拟I2C通信:
- C51单片机可以通过软件模拟I2C通信时序,包括起始信号、发送字节、接收字节、发送应答和接收应答等。
-
硬件接口:
- C51单片机通过两个引脚(如P21和P20)分别连接到I2C的SCL和SDA,实现与I2C设备的通信。
-
编程实现:
- 在C51单片机上,需要编写程序来控制I2C通信,包括发送起始信号、发送数据字节、接收数据字节、发送和接收应答位以及发送停止信号。
-
应用示例:
- 例如,使用C51单片机与OLED进行通信时,可以通过编写特定的I2C通信协议函数来实现数据的读写操作。
总结来说,C51单片机实现I2C通信需要理解I2C的总线结构和通信时序,并通过软件编程来模拟I2C通信协议,控制硬件接口进行数据的发送和接收。
4.2 代码回顾
#include "reg52.h"
#include "intrins.h"
sbit scl = P0^1;
sbit sda = P0^3;
void IIC_Start() {
sda = 1;
scl = 1;
_nop_();
sda = 0;
_nop_();
}
void IIC_Stop() {
sda = 0;
scl = 1;
_nop_();
sda = 1;
_nop_();
}
char IIC_ACK() {
char flag;
sda = 1; // 在时钟脉冲9期间释放数据线
_nop_();
scl = 1;
_nop_();
flag = sda;
_nop_();
scl = 0;
_nop_();
return flag;
}
void IIC_Send_Byte(char dataSend) {
int i;
for(i = 0; i < 8; i++) {
scl = 0; // scl拉低,让sda做好数据准备
sda = dataSend & 0x80; // 1000 0000 获得dataSend的最高位,给sda
_nop_(); // 发送数据建立时间
scl = 1; // scl拉高开始发送
_nop_(); // 数据发送时间
scl = 0; // 发送完毕拉低
_nop_();
dataSend = dataSend << 1;
}
}
void main() {
int a = 10;
IIC_Start();
}
4.3 面试官常问
4.3.1 说一下I2C通信协议
I2C通信协议是一种多主机、同步、串行计算机总线,用于连接微控制器和其他设备,通过两条线(数据线SDA和时钟线SCL)实现数据的传输。其特点是支持多个设备共享同一总线,且通信时序由主设备控制,从设备响应,包括起始条件、数据传输、应答位和停止条件等关键步骤,广泛应用于微控制器与外围设备如传感器、存储器等之间的数据交换。
4.3.2 描述I2C的起始信号、终止信号和停止信号
在I2C通信协议中:
- 起始信号:由SDA线在SCL保持高电平时从高电平跳变到低电平产生,表示一次新的数据传输开始。
- 停止信号:则是SDA线在SCL保持高电平时从低电平跳变到高电平,标志着数据传输的结束。
- 重复启动信号(Restart Condition):是SDA线在SCL为高电平时从高电平跳变到低电平,紧接着在SCL为低电平时回到高电平,用于在不释放SDA和SCL线的情况下开始新一轮的数据传输。
4.3.3 描述I2C总线数据传输过程
I2C总线数据传输过程开始于主设备生成的起始信号,随后主设备通过SDA线发送数据字节,每个字节后跟一个由从设备在SCL线产生的应答信号(ACK),数据传输可以包含多个字节,并通过发送应答位来确认每个字节的成功接收。传输结束后,主设备发出停止信号或重复启动信号来结束当前传输或开始新的传输周期。在整个过程中,SCL线由主设备控制,用于同步数据的发送和接收。
4.3.4 I2C通信有几种工作模式
I2C通信协议主要有以下几种工作模式:
- 单主单从模式:在这种模式下,只有一个主设备和一个从设备。主设备通过发送从设备地址选择具体的从设备进行通信。
- 单主多从模式:在这种模式中,主设备可以与多个从设备通信。每个从设备都有唯一的地址,主设备通过发送从设备地址选择特定的从设备。
- 多主多从模式:在这种模式中,多个主设备可以与多个从设备通信。I2C协议通过仲裁机制确保只有一个主设备在总线上进行数据传输。
这些模式允许I2C总线在不同的应用场景中灵活地进行数据通信。
4.3.5 I2C通信总线上拉电阻多大
I2C总线的上拉电阻通常推荐值在1kΩ到10kΩ之间。对于标准速率为100kHz的I2C通信,常见的选择是4.7kΩ或10kΩ。对于快速模式(400kHz)或更快的高速模式(3.4MHz),可能需要降低阻值到几千欧姆,以保证信号的快速上升沿。在实际应用中,常见的I2C上拉电阻阻值范围从1kΩ到10kΩ。
4.3.6 C51如何通过I2C驱动OLED屏
通过C51单片机驱动OLED显示屏,主要涉及以下几个步骤:
-
初始化I2C通信:
- 定义I2C通信所需的引脚,并初始化这些引脚的状态。例如,将P10设置为I2C时钟线(SCLK),P11设置为I2C数据线(SDA)。
-
编写I2C通信函数:
- 包括I2C的起始信号、停止信号、字节写入、等待应答等函数。这些函数通过软件模拟I2C协议的时序,控制数据线和时钟线来实现数据的发送和接收。
-
发送命令和数据:
- 通过编写的I2C通信函数,向OLED发送控制命令和显示数据。通常,命令和数据需要分别发送,命令用于设置OLED的工作模式,数据用于显示内容。
-
OLED显示控制:
- 定义OLED的显示控制函数,如清屏、显示字符、显示数字、显示字符串等。这些函数内部会调用I2C通信函数来发送具体的命令和数据。
通过以上步骤,可以在C51单片机上实现对OLED显示屏的控制。
————————
7.115200 波特率对应多少字节?
115200 波特率意味着每秒钟可以传输 115200 个位。在大多数串行通信协议中,一个字节(byte)由 8 位组成,即 ( \frac{115200}{8} ) 个字节。但在实际的串行通信中,还需要考虑起始位、停止位以及可能的奇偶校验位。
假设使用的是最常用的 8 位数据位、1 位起始位和 1 位停止位的配置(即 8N1 配置,其中 N 表示无奇偶校验位),那么一个完整的字节传输需要 10 位(1 + 8 + 1)。因此,我们可以计算每秒传输的字节数如下:
[ \text{每秒传输的字节数} = \frac{\text{波特率}}{\text{每字节的位数}} ]
对于 115200 波特率:
[ \text{每秒传输的字节数} = \frac{115200}{10} = 11520 \text{ 字节/秒} ]
总结
- 波特率:115200 波特率
- 每字节的位数:10 位(1 起始位 + 8 数据位 + 1 停止位)
- 每秒传输的字节数:11520 字节/秒
因此,115200 波特率对应的每秒传输字节数为 11520 字节。
当然,以下是更详细的回答:
面试问题及答案
1. 多线程间临界资源用了什么保证同步和互斥?
答:多线程之间的临界资源通常采用以下几种方式来保证同步和互斥:
-
信号量(Semaphore):一种计数器,用于控制多个进程对共享资源的访问。当信号量大于零时,进程可以获取该资源;否则,进程必须等待直到信号量变为正数。
-
互斥锁(Mutex):确保同一时间只有一个线程能访问特定的数据结构或资源。其他想要访问该资源的线程会被阻塞,直到持有锁的线程释放它为止。
-
条件变量(Condition Variable):与互斥锁一起使用,允许线程暂停和恢复执行,直到满足某些条件。这有助于避免“忙等待”现象,提高效率。
这些机制共同确保了多线程环境下的资源共享安全性和一致性。
2. 交叉编译是什么,为什么需要交叉编译?
答:交叉编译是指在一台机器上编写源代码,在另一台不同架构的机器上运行编译后的二进制文件。这种做法的主要原因包括:
-
目标设备资源有限:例如嵌入式系统或移动设备,它们可能没有足够的计算能力或存储空间来运行复杂的编译过程。
-
开发环境需求:开发者希望在自己的计算机上进行开发和测试,而不是在目标设备上,这样可以利用更好的开发工具和更快的反馈循环。
-
安全性考量:有些情况下,为了保护目标系统的完整性,不允许直接在其上进行编译活动。
3. Linux采取什么手段调试代码?
答:Linux提供了多种强大的工具和技术来帮助开发者调试代码,主要包括:
-
GDB(GNU Debugger):这是一个广泛使用的源码级调试器,支持C, C++, Objective-C等多种语言。GDB允许用户查看和修改程序状态,设置断点,检查变量值等。
-
Valgrind:一套开源的工具集,主要用于内存错误检测、性能分析和缓存模拟。特别适合查找内存泄漏和其他难以发现的问题。
-
strace:用于追踪系统调用和信号,可以帮助理解程序如何与操作系统交互。
-
core dump:当程序崩溃时,Linux会自动生成核心转储文件,包含程序终止时刻的内存映像,这对于诊断问题非常有用。
4. GDB常用命令有哪些?
答:GDB有许多有用的命令,这里列举一些最常见的:
-
run
:开始执行程序。 -
break
:设置断点。 -
continue
或c
:继续执行程序,直到遇到下一个断点或程序结束。 -
step
或s
:逐行执行代码,进入函数体。 -
next
或n
:逐行执行代码,但不会进入函数体。 -
list
或l
:显示当前正在执行的源代码行及其上下文。 -
info breakpoints
:列出所有的断点。 -
delete
:删除指定的断点。 -
watch
:监视表达式的值变化。 -
backtrace
或bt
:显示当前的调用堆栈。
5. 常用的是,怎么使用的?
答:GDB的基本使用步骤如下:
-
启动GDB并加载你的程序:
gdb your_program
-
设置断点:
break function_name
或b line_number
-
运行程序:
run
-
使用
next
,step
,continue
等命令逐步执行代码 -
查看变量值:
print variable_name
-
结束调试:
quit
6. 什么叫内存泄露?
答:内存泄露指的是程序在申请内存之后,无法正确地释放已经不再使用的内存块。随着时间推移,这些未被回收的内存会导致应用程序占用越来越多的物理内存,最终可能导致系统资源耗尽,甚至崩溃。
7. 段错误?
答:段错误(Segmentation Fault)是一种常见的程序错误,发生在程序尝试访问不属于其权限范围内的内存位置时。这可能是由于数组越界、空指针解引用或其他形式的非法内存访问造成的。段错误通常会导致程序立即退出,并输出错误消息。
8. char a 的取值范围?
答:字符型变量char a
的取值范围依赖于具体的编程语言和编译器。在C/C++中,默认情况下char
类型是带符号的,其取值范围从-128到+127。如果声明为unsigned char
,则取值范围是从0到255。
9. static关键字有什么作用?
答:static
关键字在C/C++中有几个不同的用途:
-
在全局作用域中,
static
修饰符使得变量仅在本文件范围内有效,不能被其他文件访问。 -
在函数体内,
static
变量具有静态存储期,即使函数返回后仍然保留其值,下次调用时保持不变。 -
对于成员函数,
static
表明它是类级别的,不需要实例化就可以调用,且对所有对象都是一样的行为。
10. 堆和栈有什么区别?
答:堆和栈是两种不同的内存管理方式,主要区别在于:
-
栈(Stack):栈是一种先进后出(LIFO)的数据结构,由编译器自动管理。栈内存用于存储函数参数、局部变量和返回地址等临时数据。栈的空间大小固定,增长速度快,但容量相对较小。
-
堆(Heap):堆内存由程序员手动分配和释放,常用于动态创建对象和大块数据。堆内存的增长速度较慢,但容量较大,适用于长时间存在的数据。
11. Git版本管理工具?
答:Git是一款分布式版本控制系统,设计目的是高效快速地处理从小到大的项目版本管理。Git的核心特点包括:
-
分布式:每个克隆的仓库都有完整的历史记录和版本库,无需连接中央服务器即可工作。
-
快速:本地操作如提交、分支切换等几乎瞬间完成。
-
安全性高:每次提交都会生成唯一的哈希标识,防止数据篡改。
-
支持非线性开发:轻松创建、合并和删除分支。
12. 说说你这个项目最大的难点,你是怎么解决的?
答:在这个项目中,我们面临的一个重大挑战是如何优化算法以适应大规模数据处理的需求。为了解决这一难题,我们采用了以下策略:
-
并行处理:利用多核处理器的优势,将任务分解成独立的部分并发执行。
-
数据压缩:通过对原始数据进行预处理,减少不必要的冗余,降低存储和处理成本。
-
缓存技术:合理运用缓存机制,减少重复计算,加快响应速度。
通过这些措施,我们成功提高了系统的整体性能和稳定性。
13. PWM IIC UART 英文?
答:PWM: Pulse Width Modulation(脉冲宽度调制)
IIC: Inter-Integrated Circuit(集成电路总线)
UART: Universal Asynchronous Receiver/Transmitter(通用异步收发器)
14. 你期望薪资是多少?
答:基于我对职位的理解和个人经验,我认为合理的薪资范围应该是…元至…元之间。不过我也很乐意听取贵公司的意见,并愿意进一步讨论。
15. 能接受加班吗?
答:我明白有时候为了完成重要任务可能会需要额外的工作时间。我会尽力安排好个人事务,以便能够在必要时投入更多精力到工作中去。同时,我也认为保持良好的工作生活平衡非常重要,所以我会尽量寻找有效的解决方案,既能按时完成任务又不影响健康。
16. 未来3到5年的规划?
答:在未来三到五年里,我希望能够深入学习和掌握更多的专业知识技能,提升自己在行业内的竞争力。同时,我也期待有机会参与更具挑战性的项目,不断积累实战经验。长远来看,我希望能成为团队中的关键人物之一,为公司的发展做出更大的贡献。
17. 你有什么优缺点?
答:我的优点是我对待工作认真负责,善于沟通协调,能够有效地解决问题。同时,我也具备较强的学习能力和适应新环境的能力。至于缺点,我觉得我在细节处理方面还有待加强,有时可能会因为追求完美而导致进度稍慢。但我相信只要给予足够的时间和机会,我一定能够克服这些问题。
18. 你还有什么想了解的?
答:我很想知道公司在接下来的一年里有哪些重要的发展计划和战略方向。另外,我还想了解一下团队的文化氛围以及员工的职业晋升路径等方面的情况。
请根据具体情况进行适当调整和补充。祝面试顺利!