问题的引入
在单片机的开发过程中,往往涉及到驱动的移植,但是移植的过程中又会去添加和修改主逻辑的驱动引脚初始化或时钟初始化,这里面就会存在一个问题就是:改动的地方太多了,容易影响到其它功能模块。所以能不能做成独立模块的东西,在模板内部去声明xxx,让程序主动来加载我们写的模块或驱动呢?如果你也有这个困扰的话,请阅读此篇内容。
前言
- 参考RT-Thread源码的链接文件
- 参考Linux驱动开发的驱动注册
- 参考网上一些对链接器脚本的说明文档
- 通过STM32CubeMx生成的链接器脚本文件,在此基础上进行修改.
正文
创建项目
- 使用stm32cubemx生成一个基本的工程项目即可,这里打开串口1,方便观察
简单重写一下串口打印
源码
static char os_tx_buf[512];
void os_ps(char *format, ...) {
va_list v_args;
va_start(v_args, format);
// 如果成功,则返回写入的字符总数,否则返回一个负数。
int len = vsnprintf((char *) &os_tx_buf[0], (size_t)
sizeof(os_tx_buf), (char const *) format, v_args);
va_end(v_args);
if (len > 0) {
HAL_UART_Transmit(&huart1,(uint8_t *)os_tx_buf,len,1000);
}
}
测试一下项目工程(保证工程正常运行)
链接文件的修改
步骤01: 在链接文件中添加硬件驱动初始化段
/* 添加驱动初始化段名 */
.driver.init :
{
/*展示当前驱动初始化起始地址*/
__driver_init_start = .;
/*这里对驱动进行排序(这里就是所有注册驱动的地址,在生成的时候自动分配地址(可以是结构体或函数的指针))*/
KEEP (*(SORT(.driver_init*)))
/*展示当前驱动初始化结束地址*/
__driver_init_end = .;
. = ALIGN(4);
} >FLASH
步骤02: 编写自定义段加载规则模板(这里使用函数指针)
代码
#ifndef TEST_SECTION_TEST_H
#define TEST_SECTION_TEST_H
#include "main.h"
/* 宏定义一下 编译器的特性 */
#define sys_section(x) __attribute__((section(x)))
#define sys_used __attribute__((used))
/* 类型定义 驱动初始化函数类型 */
typedef void (*driver_init_fn)(void);
/* 宏定义驱动模块导出宏定义 (偷懒)*/
#define DRIVER_EXPORT(dr_name, init_fn) sys_used const driver_init_fn __fn_##dr_name sys_section(".driver_init."#dr_name) = init_fn
#endif //TEST_SECTION_TEST_H
步骤03:编写一个测试驱动
代码
#include "section_test.h"
/**
*@brief 模拟串口初始化
*
* */
void serial_driver_init(void) {
}
/*这句话就是将驱动导出到段中*/
DRIVER_EXPORT(serial_init, serial_driver_init);
步骤04:编译生成,查看map文件中,自定义段
步骤05:根据起始和结束地址编写统一的初始化函数
步骤06:在主函数中调用统一初始化函数
步骤07:调试运行看是否会主动运行serial_driver_init函数
- 说明:此函数并没有和任何地方进行耦合。
步骤08:结论
1. 我们自己写的代码只有这个驱动初始化需要在主逻辑里面执行,其它的都没有进行一个耦合.
2. 这里就可以做到和模块之间的耦合不再是在编写中进行一个耦合,而是把这个过程交给链接器去执行这个关联的逻辑
结语
- 本篇涉及到的链接器相关的知识,请自行去上网搜索