1. volatile
是否可以修饰const
?
- 可以。
volatile
和const
可以同时修饰一个变量。 volatile
表示变量可能会被意外修改(例如硬件寄存器),编译器不应优化对该变量的访问。const
表示变量是只读的,不能被修改。- 示例:
这里volatile const int *p = (volatile const int *)0x1234;
p
指向一个硬件寄存器,该寄存器的值可能会被硬件改变,但程序不能修改它。
2. 如何快速一行代码操作硬件寄存器?
- 使用指针直接访问硬件寄存器地址。例如:
*(volatile unsigned int *)0x1234 = 0x5678; // 将0x5678写入地址0x1234
volatile
确保编译器不会优化掉该操作。
3. 如何最快比较两组寄存器里有多少位不同?
- 使用异或操作(
^
)和位计数。例如:int diff_bits = __builtin_popcount(reg1 ^ reg2);
reg1 ^ reg2
:异或操作,结果中为1的位表示不同。__builtin_popcount
:GCC内置函数,计算二进制中1的个数。
4. 如何降低功耗?
- 硬件层面:
- 使用低功耗模式(如睡眠模式)。
- 关闭未使用的外设时钟。
- 降低CPU频率。
- 软件层面:
- 减少CPU运行时间(如使用中断代替轮询)。
- 优化算法,减少计算量。
- 进入低功耗模式(如
WFI
或WFE
指令)。
5. 什么时候会用到do{}while(0)
?
- 主要用于宏定义中,确保宏在多行代码中正确展开。例如:
#define SAFE_FREE(p) do { free(p); p = NULL; } while(0)
- 这样可以避免宏在
if-else
语句中展开时出现问题。
6. GPIO有几种状态?
- 输入状态:
- 高电平(1)。
- 低电平(0)。
- 高阻态(浮空)。
- 输出状态:
- 推挽输出(高电平或低电平)。
- 开漏输出(只能拉低或高阻态)。
7. 如何用软件处理硬件管脚抖动?
- 去抖动算法:
- 多次采样,确认电平稳定后再处理。例如:
if (read_pin() == HIGH) { delay_ms(10); // 延时 if (read_pin() == HIGH) { // 确认高电平 } }
- 多次采样,确认电平稳定后再处理。例如:
8. 如何高效处理中断?
- 快速处理:
- 中断服务程序(ISR)尽量简短,只做必要操作。
- 延迟处理:
- 在ISR中设置标志位,在主循环中处理耗时任务。
- 避免阻塞:
- 不要在ISR中调用可能阻塞的函数(如
printf
)。
- 不要在ISR中调用可能阻塞的函数(如
9. delay
和sleep
的区别?
delay
:忙等待,CPU一直运行。sleep
:让出CPU,进入低功耗状态。
10. 中断时可否睡眠?
- 不可以。在中断上下文中睡眠会导致系统崩溃。
11. 如何设计RAM和Flash的验证工具?
- RAM验证:
- 写入特定模式(如
0xAA
、0x55
),然后读取验证。
- 写入特定模式(如
- Flash验证:
- 写入数据后读取并校验CRC或哈希值。
12. 如何合理高效静态分配内存?
- 使用固定大小的数组或内存池。
- 避免内存碎片化。
13. 如何跟踪内存泄漏?
- 使用工具(如Valgrind)。
- 在代码中记录内存分配和释放。
14. 如何实现一个ring buffer
以及用途?
- 实现:
#define BUFFER_SIZE 1024 uint8_t buffer[BUFFER_SIZE]; int head = 0, tail = 0; void push(uint8_t data) { buffer[head] = data; head = (head + 1) % BUFFER_SIZE; } uint8_t pop() { uint8_t data = buffer[tail]; tail = (tail + 1) % BUFFER_SIZE; return data; }
- 用途:用于数据缓冲(如串口通信)。
15. DMA和FIFO的区别?
- DMA:直接内存访问,用于高速数据传输。
- FIFO:先进先出队列,用于数据缓冲。
16. 如何做到统一API对接不同外设驱动?
- 使用抽象接口(如函数指针或虚函数)。
- 示例:
struct Device { void (*read)(void); void (*write)(void); }; void UART_Read() { /* UART读取 */ } void SPI_Read() { /* SPI读取 */ } struct Device uart = { UART_Read, NULL }; struct Device spi = { SPI_Read, NULL };
17. 如何合理设计Flash分区表?
- 分区表应包括:
- Bootloader区。
- 应用程序区。
- 配置区。
- 日志区。
18. 正常非掉电重启是否要释放内存?
- 不需要,系统重启时会自动释放。
19. 正常掉电关机流程是否要释放内存?
- 不需要,掉电后内存数据会丢失。
20. 非掉电异常如何处理?
- 记录异常信息。
- 重启系统。
21. 如何实现异常后的dump
?
- 保存寄存器状态和堆栈信息到Flash或日志。
22. 非正常掉电如何保护?
- 使用掉电检测电路。
- 快速保存关键数据到非易失性存储器(如Flash)。
23. 如何设计一个简单的profiling
工具?
- 使用高精度计时器记录函数执行时间。
- 示例:
#define START_TIMER() uint32_t start_time = get_timer() #define STOP_TIMER() printf("Time: %lu\n", get_timer() - start_time)
24. 低功耗深睡眠如何唤醒后继续之前工作?
- 保存状态到非易失性存储器。
- 唤醒后恢复状态。
25. RTOS不能断点和打印的时候如何调试?
- 使用日志系统。
- 使用硬件调试器(如JTAG)。
26. 什么是交叉编译?
- 在一种平台上编译另一种平台的代码。
27. 如何保证Makefile
的增量编译?
- 使用依赖规则:
target: dependency command
28. 如何用一套代码支持不同硬件?
- 使用条件编译:
#ifdef HW_TYPE_A // 硬件A的代码 #else // 硬件B的代码 #endif
29. 不同代码编译后的存放区域有何不同?
- 代码段(
.text
)、数据段(.data
)、BSS段(.bss
)。
30. release
和debug
编译的区别?
debug
:包含调试信息,未优化。release
:优化代码,去除调试信息。
31. ARM多核之间有多少通讯机制及优缺点?
- 共享内存:速度快,但需要同步。
- 消息传递:灵活,但开销较大。
32. 两个线程之间不同锁的区别是什么?
- 互斥锁:同一时间只有一个线程访问资源。
- 自旋锁:忙等待,适合短时间锁定。
33. 如何理解收益边界?
- 投入与产出的平衡点,超过该点后收益递减。
34. 代码优化经验
- 使用高效算法。
- 减少内存访问。
- 利用编译器优化选项。
35. 代码移植经验
- 抽象硬件相关代码。
- 使用条件编译支持不同平台。