23、嵌入式C编程:库管理、符号处理与实用工具

嵌入式C编程:库管理、符号处理与实用工具

1. 库文件的创建与链接

1.1 ranlib的作用

在创建库文件 libsquare.a 时,需要指定其组成部分,例如:

libsquare.a(square_unsigned.o) 
ranlib libsquare.a

前两行告知 make 命令 libsquare.a 由哪些组件构成,第三行 ranlib libsquare.a 则是在安装所有组件后,对归档文件运行 ranlib 程序,为归档文件创建目录表。

早期的链接器存在一些问题,例如当程序需要 b.o 中的函数,但 b.o 又依赖 a.o 中的函数时,链接器按顺序查找归档文件,找到 b.o 后不会再回头查找 a.o ,可能导致链接失败。为解决这个问题,引入了 ranlib ,它为归档文件添加目录表,使组件可以随机加载。

加载组件的新算法如下:
1. 查看未定义符号列表(如程序使用 b_funct ,则该符号未定义)。
2. 打开归档文件。
3. 检查目录表中是否有未定义的符号,若有则加载。
4. 重复上述步骤,直到该库无法满足更多符号需求。

1.2 链接库与程序

使用以下命令将库与程序进行链接:

square: square.o libsquare.a 
    $(CC) $(CFLAGS) -o square square.o -L. -lsquare

-L. 标志告诉链接器在当前目录查找库文件, -lsquare 指定要链接的库为 libsquare.a 。链接器会先在当前目录查找,然后在系统目录查找。

1.3 完整的makefile示例

CFLAGS=-g -Wall -Wextra 

all: square 

square: square.o libsquare.a 
    $(CC) $(CFLAGS) -o square square.o -L. -lsquare 

libsquare.a: libsquare.a(square_int.o) \ 
    libsquare.a(square_float.o) 
    libsquare.a(square_unsigned.o) 
    ranlib libsquare.a 

libsquare.a(square_int.o): square_int.o 
    ar crU libsquare.a square_int.o 

libsquare.a(square_float.o): square_float.o 
    ar crU libsquare.a square_float.o 

libsquare.a(square_unsigned.o): square_unsigned.o 
    ar crU libsquare.a square_unsigned.o 

square_int.o: square_int.h square_int.c 

square_float.o: square_float.h square_float.c 

square_unsigned.o: square_unsigned.h square_unsigned.c 

square.o: square_float.h square.h square_int.h 
    square_unsigned.h square.c

由于测试程序未调用 square_unsigned square_unsigned.o 模块不会被链接到程序中,这展示了链接器不会链接不需要的目标文件。

1.4 确定性与非确定性库

理想情况下,运行 make 命令生成的二进制文件应始终相同。最初,库文件不存储组件的创建者和创建时间信息,但这给 make 程序带来了困难,因为它无法判断归档文件中的版本与新编译版本的新旧。

ar 命令经过修改,可以存储这些信息,但为了不破坏旧功能,存储信息变为可选。指定 D 选项时,不存储修改时间,生成确定性归档文件;指定 U 选项时,每次生成不同的二进制文件,但更受 make 程序青睐,默认使用 D (旧格式)。

2. 弱符号的使用

2.1 弱符号的概念

在C语言中,GCC和大多数编译器支持弱符号。弱符号告诉链接器:“如果没有其他人定义这个符号,就使用我”。

2.2 STM中断表中的应用

在STM中断表中,必须为每个可能的中断定义函数,但有些中断可能永远不会发生。STM固件为所有中断定义了中断处理程序,若这些处理程序被调用,系统会停止,方便使用调试器排查问题。

例如,USART2中断的处理程序定义如下:

void USART2_IRQHandler(void) { 
    while(true) 
        continue; 
}

如果我们自己定义了该处理程序,固件库中的版本将被忽略。

2.3 代码示例

以下是使用弱符号的代码示例:

main.c
#include "sub.h" 

int main() 
{ 
    sub1(); 
    sub2(); 
    return (0); 
}
sub.c
#include "sub.h" 

void sub2(void) __attribute__((weak)); 

void sub1(void) {} 
void sub2(void) {} 
sub.h
#ifndef __SUB_H__ 
#define __SUB_H__ 
extern void sub1(void); 
extern void sub2(void); 
#endif // __SUB_H__
sub2.c
#include <stdio.h> 
#include "sub.h" 

void sub2(void) { 
    printf("The non-weak sub2\n"); 
}

如果链接 main.c sub.c ,将链接弱定义的 sub2 ;如果链接 main.c sub.c sub2.c ,则使用 sub2.c 中定义的非弱版本的 sub2

弱符号在中断例程等场景中非常有用,允许提供备用或默认版本。

3. 编程问题

3.1 几何形状面积计算库

编写一个库来计算几何形状的面积(如矩形面积、三角形面积等),每个函数应放在单独的目标文件中,并将所有面积函数组合到一个库中。同时编写一个主程序对这些函数进行单元测试。

3.2 串口输出程序重构

重写之前章节创建的串口输出程序,将所有与UART相关的代码放在一个单独的模块中。

3.3 弱符号测试

测试以下三种情况:
1. 定义两个弱符号和一个强符号。
2. 定义两个弱符号,无强符号。
3. 定义两个强符号。

4. 专业编程的建议

4.1 学习写作

专业程序员最重要的技能之一是良好的写作能力,编程过程中需要撰写多种文档,如提案、需求文档、设计文档、用户文档和销售资料等。可以参加创意写作课程,接受他人的反馈。

4.2 学习阅读

通过“阅读”技术文档可以学到很多知识,但这里的“阅读”并非指通读全文,而是扫描文档以找到所需信息。例如,在准备过程中可能会用到ISO/IEC9899:2017、RM0360参考手册等大量文档,但通常只使用其中的一小部分。可以熟练使用 find 命令定位感兴趣的文档部分。

4.3 协作与借鉴

与他人合作并相互审查工作,可以扩展自己和他人的知识。例如Linux内核的开发,数千人共享代码、接受反馈、改进代码,最终形成了强大的操作系统。查看公开可用的代码,了解其他程序员的做法也很有帮助,如STM32 Workbench附带的HAL固件。

5. 实用开源工具

5.1 Cppcheck

Cppcheck 是一个静态程序检查器,用于查找编译器未发现的程序问题。它能找出一些有趣的代码问题,例如:

if (flag) { 
    //(removed) processMessage() 
} else { 
    //(removed) processMessage() 
}

Cppcheck 产生的误报较少,如果它标记了代码中的某一行,建议仔细检查。

5.2 Doxygen

Doxygen 通过程序中的结构化注释为程序创建文档,但实际使用中存在一些问题,例如需要在代码中添加注释,且生成的文档看起来像是自动生成的。 Cppcheck 使用 Doxygen 进行内部文档编写。

5.3 Valgrind

Valgrind 是一套用于动态检查程序错误的工具,其中最常用的 memcheck 工具可以查找内存错误,如数组越界、释放后使用指针等,能有效检测大多数简单的运行时错误。

5.4 SQLite

SQLite 是一个适用于小型数据库(最多100,000条记录)的嵌入式数据库库,虽然不是最快的数据库,但对于不希望有大型数据库开销的小型嵌入式系统来说很适用。使用它需要学习SQL语言。

6. 项目创建清单

6.1 原生C项目

如果要创建在PC上运行的程序,可按以下步骤操作:
1. 启动STM32 Workbench,必要时选择工作空间。
2. 在主窗口中,选择 File ▶New ▶C/C++ Project
3. 在 C/C++ Project 窗口中,选择 C Managed Build 并点击 Next
4. 在 C Project 窗口中:
- 填写项目名称(无空格和特殊字符)。
- 在 Project Type 列中,选择 Executable ▶Empty Project
- 在 Toolchains 列中,选择适合本地系统的工具链,如Windows上的Visual C++,Linux上的GNU gcc。
- 点击 Next
5. 在 Configurations 窗口中:
- 取消勾选 Release
- 保持 Debug 勾选。
- 点击 Finish
6. 在主窗口中:
- 在 Project Explorer (左列)中选择项目。
- 选择 File ▶New ▶Source File
- 在 Source File 窗口中:
- 在 Source File 字段中输入程序文件名(以 .c 结尾)。
- 点击 Finish
7. 在主窗口(文件显示在编辑面板中):
- 输入程序代码。
- 选择 File ▶Save All
- 选择 Project ▶Build Project
- 选择 Run ▶Run Configuration
8. 在 Create, Manage, and Run Configurations 窗口中:
- 在左列中选择 C/C++ Application
- 点击 New
- 在 C/C++ Application 下点击 Browse
- 在文件对话框中:
- 进入工作空间并打开项目文件夹。
- 在项目文件夹中打开 Debug 目录。
- 选择程序的可执行文件。
- 点击 OK
9. 在 Create, Manage, and Run Configurations 窗口中:
- 点击 Apply
- 点击 Close

此时可以运行和调试程序。

6.2 STM32 Workbench嵌入式项目

如果要创建在Nucleo板上运行的程序,可按以下步骤操作:
1. 启动STM32 Workbench,必要时选择工作空间。
2. 在主窗口中,选择 File ▶New ▶C Project
3. 在 C Project 窗口中:
- 填写项目名称(无空格和特殊字符)。
- 在 Project Type 列中,选择 Executable ▶Empty Project
- 在 Toolchains 列中,选择 Ac6 STM32 MCU GCC
- 点击 Next
4. 在 Configurations 窗口中:
- 取消勾选 Release
- 保持 Debug 勾选。
- 点击 Finish
5. 在 Target Configuration 窗口中:
- 选择 STM32F0 系列。
- 选择 NUCLEO-F030R8 板。
- 点击 Next
6. 在 Project Firmware Configuration 窗口中:
- 点击 Hardware Abstraction Layer (Cube HAL)
- 下载目标固件(仅需一次)。
- 点击 Finish
7. 在主窗口中:
- 在 Project Explorer (左列)中选择项目。
- 选择 Project ▶Properties
8. 在 Properties 窗口中:
- 展开 C++ Build
- 进入 Settings
- 进入 Tool Settings 选项卡。
- 选择 MCU GCC Compiler ▶Debugging ,将调试级别设置为 Maximum (-g3)
- 选择 MCU GCC Compiler ▶Miscellaneous ,设置其他标志为 -fmessage-length=0 0Wa,adhls=$(@:%.o=%.lst) ,开启列表功能。
- 点击 Apply
- 点击 OK
9. 在主窗口中:
- 在 Project Explorer (左列)中选择项目。
- 展开项目。
- 展开 src 目录。
- 点击 main.c 进行编辑。
- 输入程序代码。
- 选择 File ▶Save All
- 选择 Project ▶Build Project
- 选择 Project ▶Run Project ▶Debug

总之,嵌入式C编程涉及库管理、符号处理、实用工具使用等多个方面,同时不断学习和实践是提升编程能力的关键。技术在不断发展,我们应保持学习的热情,探索更多的可能性。

7. 嵌入式C编程要点总结

7.1 库管理要点

  • 库创建与链接 :通过 ar 命令将目标文件打包成静态库,如 ar crU libsquare.a square_int.o ,再使用 ranlib 为库创建目录表以解决链接顺序问题。链接时使用 -L 指定库搜索路径, -l 指定库名。
  • 确定性与非确定性 ar 命令的 D U 选项可控制库的确定性,默认 D 选项生成确定性归档文件。

7.2 符号处理要点

  • 弱符号应用 :弱符号允许在没有其他定义时使用自身,在STM中断表等场景中可提供备用或默认实现,避免重复定义错误。

7.3 实用工具总结

工具名称 功能描述
Cppcheck 静态程序检查器,查找编译器未发现的问题,误报少
Doxygen 通过结构化注释生成程序文档
Valgrind 动态检查程序错误, memcheck 可检测内存错误
SQLite 适用于小型嵌入式系统的数据库库,需学习SQL语言

7.4 项目创建流程对比

项目类型 创建步骤关键差异
原生C项目 选择适合本地系统的工具链,在PC上运行
STM32 Workbench嵌入式项目 选择 Ac6 STM32 MCU GCC 工具链,配置目标板和固件,在Nucleo板上运行

8. 未来学习方向与展望

8.1 持续学习新技术

计算机技术发展迅速,嵌入式领域不断有新的芯片、框架和工具出现。例如,随着物联网的发展,对低功耗、高性能嵌入式设备的需求增加,我们需要学习新的通信协议(如LoRa、ZigBee)和低功耗设计技术。

8.2 深入研究现有技术

对于已经掌握的技术,如库管理、弱符号使用等,可以进一步深入研究其底层原理。例如,了解 ar ranlib 命令的具体实现,以及链接器在处理弱符号和强符号时的详细算法。

8.3 参与开源项目

参与开源项目是提升技术能力和扩展人脉的好方法。可以从简单的贡献开始,如修复代码中的小错误、完善文档等,逐渐参与到项目的核心开发中。例如,参与Linux内核或STM32相关的开源项目。

8.4 实践项目积累经验

通过实践项目将所学知识应用到实际中,不断积累经验。可以从简单的小项目开始,如制作一个简单的传感器数据采集系统,逐渐挑战更复杂的项目,如开发一个完整的嵌入式操作系统。

9. 总结与建议

9.1 总结

嵌入式C编程是一个综合性的领域,涉及库管理、符号处理、实用工具使用和项目创建等多个方面。掌握这些知识和技能,能够帮助我们更高效地开发嵌入式系统。

9.2 建议

  • 多实践 :通过实际项目来巩固所学知识,不断尝试新的技术和方法。
  • 学习交流 :加入技术社区,与其他开发者交流经验,分享学习心得。
  • 持续学习 :关注行业动态,不断学习新的知识和技能,适应技术的快速发展。

以下是一个简单的mermaid流程图,展示嵌入式C编程学习的基本流程:

graph LR
    A[学习基础知识] --> B[实践项目]
    B --> C[参与开源项目]
    C --> D[深入研究技术]
    D --> E[持续学习新技术]
    E --> B

总之,嵌入式C编程是一个充满挑战和机遇的领域,只要我们保持学习的热情,不断实践和探索,就能够在这个领域取得不错的成绩。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值