1. 第二段实习的通信链路,为什么用UDP不用TCP?实际遇到过丢帧吗?
我们项目选用 UDP,主要是业务传输 IMU、图像这类实时传感器数据,对时延和抖动要求高。TCP 的重传、拥塞控制会带来不确定延迟,而且旧帧重传回来已经失效。UDP 无连接,开销小、延迟稳定,还支持组播。可靠性我们在应用层自己实现序号、校验。 实际遇到过丢帧,大多是接收缓冲区不足、瞬时流量超限、UDP 大包 IP 分片导致。处理方式:限制单包大小,调大网卡缓冲区,接收线程高优先级;传感器流丢帧不重传,关键指令上层做可选重传。
2. 描述I2C帧格式,地址是几位,如何识别读写?两根线都拉低是什么情况?如何解决?出现这种情况多吗?怎么判断连没连上?
I2C 由 SDA、SCL 两根线。帧顺序:起始 S → 从机地址字节 → ACK → 数据 + ACK → 停止 P。 常用 7 位从地址,字节最低位是读写位,0 写 1 读。 两根线同时拉低是 I2C 总线死锁,一般是通信中途异常,从机钳住 SDA 无法释放。解决方法:用 bit-bang 方式输出 9 个时钟脉冲,让从机释放总线,再发停止信号;也可以复位从机。正常稳定业务场景死锁不常发生,断电或者程序跑飞时容易触发。 判断设备是否连接:主机发送起始信号,发送从机写地址,等待 ACK;收到 ACK 代表设备在线,NACK 代表不存在或者接线故障。
3. FreeRTOS任务调度机制,时间片轮转的任务切换通过什么方式实现?调度通过什么触发?pendsv由什么触发?为什么需要pendsv?
FreeRTOS 是抢占式优先级调度,同优先级开启时间片轮转。
时间片轮转依靠 SysTick 定时器中断,时间片到期触发切换。
调度触发来源:SysTick 中断、外部中断,或者任务调用 vTaskDelay、taskYIELD、队列信号量 API。
PendSV 通过置位 NVIC 的 PendSV pending 标记触发,portYIELD 函数会置位它。
PendSV 优先级最低,用来延迟执行上下文切换;保证所有中断处理完成之后,才做任务切换,避免在中断嵌套过程中切换上下文,防止系统异常。
4. 任务有几个状态?运行态到就绪态的切换条件?
FreeRTOS 任务四个状态:运行、就绪、阻塞、挂起。 运行态转到就绪态两种情况:
- 同优先级,时间片耗尽(SysTick 中断触发轮转) 时间片到,当前任务被切走,放回就绪队列。
- 高优先级任务就绪,抢占当前任务 低优先级正在跑,高优先级任务就绪,抢占 CPU;旧任务回到就绪。
- 任务调用
taskYIELD(),主动放弃 CPU 任务在代码里主动调用taskYIELD(),自愿交出 CPU。
5. 创建任务需要提供哪些内容?返回什么?动态创建还是静态创建?栈在哪里?
1. 创建任务需要提供哪些参数(xTaskCreate)
函数原型:
BaseType_t xTaskCreate(
TaskFunction_t pxTaskCode, // 1. 任务函数入口
const char * const pcName, // 2. 任务名字(调试用,字符串)
const configSTACK_DEPTH_TYPE usStackDepth, //3. 栈深度,单位word不是byte
void * const pvParameters, //4. 传给任务函数的入参
UBaseType_t uxPriority, //5. 任务优先级
TaskHandle_t * const pxCreatedTask//6. 任务句柄输出(可以传NULL不保存)
);
需要提供内容:
- 任务函数指针(任务入口,返回 void,参数 void*)
- 任务名称字符串
- 任务栈深度(以
word为单位,不是字节) - 任务函数参数
- 任务优先级
- 任务句柄指针(用来接收句柄,不需要就填 NULL)
2. 返回什么
返回值类型 BaseType_t:
pdPASS:任务创建成功pdFALSE:创建失败(最常见:堆内存不足,栈申请失败)
注意:任务句柄是输出参数,不是返回值,用来后续操作任务(vTaskDelete、vTaskSuspend 等)。
3. 动态创建还是静态创建
- xTaskCreate:动态创建 任务控制块 TCB + 任务栈,都从 FreeRTOS 堆(
pvPortMalloc)动态分配。删除任务时,内存会在空闲任务里自动回收。 - xTaskCreateStatic:静态创建 需要用户预先定义好静态数组:任务栈数组 + 静态 TCB 结构体,内存放在全局 / 静态区,不使用堆。 优点:没有 malloc,内存确定,适合高可靠、不能动态内存的场景;缺点:内存必须提前预留,删除后需要手动管理,不能自动释放。
4. 栈在哪里
✅ 动态创建 (xTaskCreate)
任务栈是 pvPortMalloc 从FreeRTOS 堆中申请出来的一块内存。
注意:不是 MCU 的 C 语言栈!每个 FreeRTOS 任务拥有独立私有栈。
✅ 静态创建 (xTaskCreateStatic)
任务栈是你定义的全局静态数组 ,放在 MCU 的RAM(.bss/.data 段),编译时就固定分配。
系统栈(主栈 MSP):中断使用的栈,属于内核,不属于任何任务;任务运行时使用各自任务栈 PSP。
一句话总结面试背诵版
xTaskCreate需要传入任务函数、任务名、栈深度、入参、优先级、句柄指针;返回 pdPASS 成功 /pdFALSE 失败;属于动态创建,TCB 和任务栈从 FreeRTOS 堆分配;静态版本 xTaskCreateStatic 由用户提供静态内存。每个任务有独立私有栈,动态任务栈在 FreeRTOS 堆,静态任务栈在全局静态 RAM,中断使用 MSP 主栈。
6. 消息队列、信号量、互斥锁分别的作用?锁和信号量的差异?
1. 消息队列 Queue
作用:任务间传递数据(带内容),实现任务通信
- 可以存放完整数据(拷贝数据,不是传地址,也可传地址)
- 支持多任务读、多任务写;读写阻塞等待(超时)
- 典型场景:传感器采集任务把数据发给显示任务、串口收发数据。
特点:有实体数据,先进先出 FIFO;队列里可以存多条消息。
2. 信号量 Semaphore(分二值信号量、计数信号量)
作用:事件通知、资源计数,只传递 "事件有没有发生",不带数据
- 二值信号量 :只有 0/1。多用于事件通知。 例:中断收到数据,释放信号量唤醒任务去处理。
- 计数信号量 :0~N。用于多个同类资源管理。 例:3 个串口,最多 3 个任务同时使用,资源数 = 3。
特点:只传递状态,不带数据;信号量计数值。
3. 互斥锁 Mutex(互斥信号量)
作用:保护临界资源,实现独占访问,防止多个任务同时修改共享资源
- 本质是特殊二值信号量,拥有所有权:谁拿到锁,只有谁才能释放锁
- 自带优先级继承,用来缓解优先级反转问题
- 典型场景:多个任务读写同一个全局变量、操作同一个硬件外设。
特点:用于排他访问资源,不是事件通知。
二、互斥锁 vs 普通信号量(核心差异,面试高频)
- 所有权
- 互斥锁:有所有权,谁获取,只能谁释放。别的任务不能随便释放。
- 二值信号量:无所有权,任务 A 获取,任务 B 也可以直接释放。
- 优先级继承
- 互斥锁:自带优先级继承,缓解优先级反转。
- 普通二值信号量:没有优先级继承,存在优先级反转风险。
- 用途定位
- 互斥锁:保护临界区,独占资源访问。
- 二值信号量:事件通知(中断通知任务)。
- 计数
- 互斥锁:只能 0/1,不能计数。
- 计数信号量:计数值可以大于 1。
- 递归
- 递归互斥锁:支持同一个任务多次获取(需要多次释放);普通信号量不支持递归获取。
一句话背诵差异: 互斥锁有所有权、带优先级继承,用于资源互斥保护,只能持有者释放;普通信号量无所有权,任意任务可释放,多用于事件通知,没有优先级继承。
一句话总述背诵版
消息队列用于任务间传递数据 ;信号量用于事件通知或统计可用资源数量 ;互斥锁用于共享资源的独占保护。互斥锁拥有所有权,自带优先级继承,只能持有者释放;普通信号量没有所有权,任意任务都可以释放,没有优先级继承,多用于事件通知。