海康BSP嵌入式开发实习面试经验
✅ IIC启动流程
I2C 启动流程概述
总线空闲状态:
在通信开始之前,I2C总线处于空闲状态,即SDA和SCL线都处于高电平。发送启动信号(START):
主设备在SCL线为高电平时,将SDA线从高电平拉低,形成启动信号。这一过程标志着通信的开始。发送设备地址和读写位:
启动信号后,主设备发送目标设备的地址(通常为7位)和读写位。读写位为0表示写操作,为1表示读操作。等待应答信号(ACK):
从设备接收到地址和读写位后,返回一个应答信号(ACK)。如果从设备未响应,主设备可以根据需要进行重试或处理错误。数据传输:
主设备根据读写位的设置,开始与从设备进行数据的读写操作。每传输一个字节后,接收方返回一个应答信号。发送停止信号(STOP):
数据传输完成后,主设备在SCL线为高电平时,将SDA线从低电平拉高,形成停止信号,标志着通信的结束。
🔹 特别注意:
IIC信号在数据传输过程中,当SCL=1(高电平)时,数据线SDA必须保持稳定状态,不允许有电平跳变。只有在SCL为低电平期间,SDA的电平状态才允许变化。
✅ C语言关键字
volatile
🔹 防止编译器优化:
编译器可能会将变量存储在寄存器中以提高访问效率。但如果该变量的值可能被外部因素(如硬件中断、其他线程或外部设备)改变,使用volatile修饰的变量会强制编译器每次从内存中读取最新值。
🔹 保证顺序性:volatile修饰的变量的读写操作不会被编译器重排序,确保操作顺序与源代码一致。
🔹 硬件寄存器访问:
在嵌入式系统中,直接访问硬件寄存器时,使用volatile可确保每次访问都读取硬件的最新值。
🔹 中断服务例程(ISR):
中断处理程序修改的变量需用volatile修饰,确保主程序读取最新值。
🔹 多线程编程:
多线程环境中,volatile可确保线程间变量的可见性(但需注意它不保证原子性)。
1 | |
在上述代码中,flag 被 volatile 修饰,确保主程序每次访问 flag 时都从内存中读取其最新值,而非使用寄存器缓存的值。
static
🔹 修饰局部变量:保持变量的持久性
当 static 用于修饰局部变量时,该变量的生命周期将持续整个程序运行期间,但其作用域仍限于定义它的函数内部。这意味着变量在函数调用之间保持其值,而不是每次调用时重新初始化。
1 | |
输出
1 | |
在上述代码中,count 变量在函数 count_calls 的多次调用之间保持其值,实现了对函数调用次数的统计。
🔹 修饰全局变量和函数:限制作用域
当 static 用于修饰全局变量或函数时,它将限制该变量或函数的作用域仅限于定义它的源文件(即翻译单元)。这意味着其他源文件无法通过 extern 关键字访问这些变量或函数,从而避免了命名冲突。
1 | |
在另一个源文件中,尝试访问 secret 或 show_secret 将导致链接错误,因为它们的作用域被限制在 file1.c 中。
🔹 修饰类成员(C++):实现类级别的共享
在 C++ 中,static 可以用于修饰类的成员变量和成员函数,使其成为类级别的成员,而非实例级别的成员。这意味着所有类的实例共享同一份静态成员。
1 | |
输出
1 | |
在上述代码中,count 是一个静态成员变量,所有 Counter 类的实例共享同一份 count 变量。
🔹 修饰函数参数(C99):指定数组的最小大小
在 C99 标准中,static 可以用于修饰函数参数,指定数组参数的最小大小。这有助于编译器进行更严格的类型检查。
1 | |
在上述代码中,data 参数被声明为至少包含 10 个元素的数组,编译器将检查传递给 process_data 函数的数组是否满足这一要求。
✅ C语言编程题
内存对齐
以下两个结构体分别占用多少内存
1 | |
🔹 student内存布局分析
no(int):占 0-3 字节(对齐到 4 字节)。
name(char):占 4 字节(对齐到 1 字节,无需填充)。
sex(short):需要对齐到 2 字节,所以必须在地址 6 开始(因为地址 5 未对齐到 2)。因此,在 name(地址 4)后插入 1 字节填充(地址 5)。sex 占 6-7 字节。
总大小:0-7 共 8 字节,且 8 是最大成员 int(4 字节)的整数倍,无需末尾填充。
🔹 teach内存布局分析
no(char):占 0 字节(对齐到 1 字节)。
name(int):需要对齐到 4 字节,所以必须在地址 4 开始。因此:在 no(地址 0)后插入 3 字节填充(地址 1-3)。name 占 4-7 字节。
sex(short):占 8-9 字节(对齐到 2 字节,地址 8 已对齐)。
总大小:当前已用 0-9 共 10 字节,但结构体总大小必须是最大成员 int(4 字节)的整数倍。因此,在 sex(地址 9)后插入 2 字节填充(地址 10-11),使总大小为 12 字节(12 是 4 的倍数)。
malloc(记不太清了,感觉不会这么简单)
以下代码输出结果
1 | |
输出 result =12345
✅ FreeRTOS任务调度
FreeRTOS是怎么进行调度的
FreeRTOS 的任务调度遵循优先级调度的原则,其中高优先级的任务优先执行。当任务调度器决定哪个任务运行时,它会选择优先级最高的就绪任务。
任务优先级
每个任务都具有一个优先级,优先级数值较大的任务优先级较高。任务的优先级是在创建任务时指定的,可以在运行时通过 vTaskPrioritySet() 函数动态修改任务的优先级。
调度器行为
🔹 抢占式调度
如果高优先级任务变为就绪状态,调度器会立即中断当前正在运行的低优先级任务,切换到高优先级任务执行。
🔹 协作式调度
任务在执行时不会被抢占,除非它显式地调用 taskYIELD() 或其他类似的 API 来让出 CPU 给其他任务。
FreeRTOS 中,任务可以通过时间片轮转的方式调度,除非某个任务被标记为“挂起”或“阻塞”,此时它不会被调度。
高优先级任务一直运行会不会霸占CPU资源
如果 FreeRTOS 中的高优先级任务长时间占用 CPU 而不主动让出控制权,低优先级任务将无法获得执行机会,导致 CPU 资源被高优先级任务“霸占”。
🔹 为什么高优先级任务会“霸占”CPU?**
在 FreeRTOS 中,任务调度遵循优先级原则:优先级高的任务优先执行。如果高优先级任务没有被挂起、阻塞或主动让出 CPU,它将持续占用 CPU,导致其他任务无法执行。
🔹 可能导致的问题
任务饥饿:低优先级任务长时间无法获得 CPU 时间,可能导致系统响应迟缓或实时性差。
系统不稳定:某些低优先级任务可能负责关键功能(如通信、数据采集等),长时间无法执行可能导致系统功能异常。
🔹 如何避免“霸占”CPU
使用 vTaskDelay() 或 vTaskDelayUntil():让任务主动延时,释放 CPU 给其他任务。
示例:
1 | |
使用 taskYIELD():在任务中适当位置调用,主动让出 CPU 给同优先级的其他任务。
示例:
1 | |
合理设计任务优先级:确保高优先级任务只处理紧急或实时性要求高的任务,其他任务使用较低优先级。
使用互斥机制:避免高优先级任务长时间占用共享资源,导致低优先级任务无法访问