查看: 3257|回复: 1

从文件角度看Cortex-M开发(1) - 源文件

[复制链接]
  • TA的每日心情
    开心
    2025-7-11 08:53
  • 签到天数: 301 天

    连续签到: 2 天

    [LV.8]以坛为家I

    3946

    主题

    7567

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    40297
    最后登录
    2025-9-12
    发表于 2020-8-19 08:58:31 | 显示全部楼层 |阅读模式
    从文件角度看Cortex-M开发(1) - 源文件


    大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家讲的是嵌入式开发里的source文件。

    众所周知,嵌入式开发属于偏底层的开发,主要编程语言是C和汇编。所以本文要讲的source文件主要指的就是c文件和汇编文件。

    尽管在平常开发中,我们都只会关注自己创建的.c/.h/.s源文件,但实际上我们不知不觉中也跟很多不是我们创建的源文件在打交道,那么问题来了,一个完整的嵌入式工程(以基于ARM Cortex-M控制器的工程为例)到底会包含哪些source文件呢?

    现在就到了痞子衡的show time了,痞子衡将这些文件按来源分为五类十种,下面痞子衡按类别逐一分析这些文件:

    第一类:Provided by Committee
    第一类文件由C标准委员会提供,该类文件伴随着标准的发布而逐渐壮大。该类文件主要就是一种,即C标准库。

    1. C standard Library
      大家都知道C语言是有标准的,常见的C标准有ANSI C(C89)、C99、C11,而C标准函数库(C Standard library)就是所有符合C标准的头文件的集合,以及常用的函数库实现程序。C标准库由Committee制订发布,通常会被包含在IDE里。列举一些常见文件和函数如下,是不是觉得似曾相识?

    1. /* 常用文件 */ assert.h,stdio.h,stddef.h,stdint.h,string.h ...
    2. /* 常用定义 */ bool,NULL,uint8_t,uint16_t,uint32_t...
    3. /* 常用函数 */ assert(),printf(),memset(),memcpy()...
    复制代码
    第二类:Provided by IDE(Compiler)
    第二类文件由IDE提供,C语言是编译型语言,需要编译器将C程序汇编成机器码,所有便有了一些跟编译器特性相关的函数库。

    2. Compiler Library
      我们在开发嵌入式应用时需要借助集成开发环境(IDE),常见的IDE有GCC(GNUC),Keil MDK(ARMCC),IAR EWARM(ICCARM),这些IDE都有配套的C编译器,这些编译器是各有特色的,为了充分展示各编译器特色,配套的函数库便应运而生。

    编译器函数库是因IDE而异的,此处仅讲一个例子以供参考,需要了解更多需查看各IDE手册。

    以IAR EWARM里的DLib_Product_string.h文件为例,该文件中重定义了memcpy的实现:

    1. #define _DLIB_STRING_SKIP_INLINE_MEMCPY
    2. #pragma inline=forced_no_body
    3. __EFF_NENR1NW2R1 __ATTRIBUTES void * memcpy(void * _D, const void * _S, size_t _N)
    4. {
    5.   __aeabi_memcpy(_D, _S, _N);
    6.   return _D;
    7. }
    复制代码
    第三类:Provided by ARM
    第三类文件由ARM提供,嵌入式程序的执行靠的是控制器内核(此处指的内核便是ARM内核),ARM公司在设计内核时,提供了一些内核模块的接口,开发者可以通过这些接口访问内核资源,CMSIS header里就是这些内核模块资源的接口。

    3. CMSIS header
      完整的CMSIS header目录应该是下面这个样子,而必须要关注的只有\CMSIS\Include下面的core_cmx.h文件

    1. \CMSIS
    2.      \Core      
    3.      \DAP            /* ARM debugger实现 */
    4.      \Driver         /* ARM统一的常用外设driver API */
    5.      \DSP_Lib        /* ARM优化实现的DSP Lib */
    6.      \Include        /* ARM内核资源接口 */
    7.             \arm_xx.h
    8.             \cmsis_xx.h
    9.             \core_cmx.h
    10.      \Lib            /* ARM优化实现的标准Lib */
    11.      \Pack
    12.      \RTOS           /* ARM推出的RTOS- RTX */
    13.      \RTOS2
    14.      \SVD
    15.      \Utilities
    复制代码
    core_cmx.h文件里定义了内核资源接口,里面最常用的三大模块是SCB,SysTick,NVIC,一个嵌入式开发的老手看到这些模块应该要向痞子衡挥手示意,来,让痞子衡看见你们的双手~~~

    第四类:Provided by Chip Producer
    第四类文件是由ARM芯片生产商提供,我们在选型一个ARM芯片时,除了看ARM内核类型外,还得看芯片内部外设资源,是这些外设导致了ARM芯片差异,于是便有了各大ARM厂商争奇斗艳,比如NXP(Freescale), ST, Microchip(Atmel),ARM厂商赋予了ARM芯片各种外设资源,同时也会提供这些外设资源的接口。该类别下文件有四种:

    4. device.h:芯片头文件,主要包含中断号定义(xx_IRQn)、外设模块类型定义(xx_Type) 、外设基地址定义(xx_BASE)。

    1. /////////////////////////////////////////////////////
    2. // 中断号定义
    3. typedef enum IRQn {
    4.   NotAvail_IRQn                = -128,
    5.   /* Core interrupts */
    6.   NonMaskableInt_IRQn          = -14,
    7.   HardFault_IRQn               = -13,
    8.   ...
    9.   SysTick_IRQn                 = -1,
    10.   /* Device specific interrupts */
    11.   WDT0_IRQn                = 0,
    12.   ...
    13. } IRQn_Type;
    14. ////////////////////////////////////////////////////
    15. // 外设寄存器定义
    16. typedef struct {
    17.   __IO uint32_t MOD;
    18.   ...
    19.   __IO uint32_t WINDOW;
    20. } WWDT_Type;
    21. #define WWDT_WINDOW_WINDOW_MASK       (0xFFFFFFU)
    22. #define WWDT_WINDOW_WINDOW_SHIFT      (0U)
    23. #define WWDT_WINDOW_WINDOW(x)         (((uint32_t)(((uint32_t)(x)) << WWDT_WINDOW_WINDOW_SHIFT)) & WWDT_WINDOW_WINDOW_MASK)
    24. ////////////////////////////////////////////////////
    25. // 外设基地址定义
    26. #define WWDT0_BASE                    (0x5000E000u)
    复制代码
    5. startup_device.s:芯片中断向量表文件,主要包含中断向量表定义(DCD xx_Handler) ,以及各中断服务程序的弱定义(PUBWEAK)。Note:该文件因编译器而异。

    1. ;;基于IAR的startup_device.s文件
    2.         MODULE  ?cstartup
    3.         ;; Forward declaration of sections.
    4.         SECTION CSTACK:DATA:NOROOT(3)
    5.         SECTION .intvec:CODE:NOROOT(2)
    6.         PUBLIC  __vector_table
    7.         PUBLIC  __Vectors_End
    8. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    9. ; 中断向量表定义
    10.         DATA
    11. __vector_table
    12.         DCD     sfe(CSTACK)
    13.         DCD     Reset_Handler
    14.         DCD     NMI_Handler
    15.         DCD     HardFault_Handler
    16.         ...
    17.         DCD     SysTick_Handler
    18.         ; External Interrupts
    19.         DCD     WDT0_IRQHandler
    20.         ...
    21. __Vectors_End
    22. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    23. ; 中断服务程序弱定义
    24.         THUMB
    25.         PUBWEAK WDT0_IRQHandler
    26.         PUBWEAK WDT0_DriverIRQHandler
    27.         SECTION .text:CODE:REORDER:NOROOT(2)
    28. WDT0_IRQHandler
    29.         LDR     R0, =WDT0_DriverIRQHandler
    30.         BX      R0
    31. WDT0_DriverIRQHandler
    32.         B .
    33.         END
    复制代码
    6. system_device.c/h:芯片系统初始化文件,主要包含全局变量SystemCoreClock定义(提供芯片内核默认工作频率)、SystemInit()函数定义(完成最基本的系统初始化,比如WDOG初始化,RAM使能等,这部分因芯片设计而异)。

    7. device SDK Library:官方提供的芯片外设SDK driver包文件,有了这个SDK包可以直接使用片内外设设计自己的应用,而不需要查看芯片手册里的外设模块寄存器去重写外设驱动。当然并不是每个厂商都有完善的SDK包,这取决于各厂商对软件服务的重视程度。
    1. // 来自于NXP SDK的WWDT driver API
    2. void WWDT_GetDefaultConfig(wwdt_config_t *config);
    3. void WWDT_Init(WWDT_Type *base, const wwdt_config_t *config);
    4. void WWDT_Deinit(WWDT_Type *base);
    5. void WWDT_ClearStatusFlags(WWDT_Type *base, uint32_t mask);
    6. void WWDT_Refresh(WWDT_Type *base);
    复制代码
    第五类:Created by Developer
    第五类文件是开发者自己创建,用于实现开发者自己的嵌入式应用,分为应用系统启动文件,应用系统初始化文件,应用文件。其中应用系统启动和初始化文件属于main函数之前的文件,一般可以通用,大部分开发者并不关心其具体内容,但是了解其过程可以加深对嵌入式系统结构的理解。

    8. reset.s:应用系统复位启动文件,了解ARM原理的都知道,image前8个字节数据分别是芯片上电的初始SP, PC,其中PC指向的便是本文件里的Reset_Handler,这是芯片执行的第一个函数入口,该函数主要用于完成应用系统初始化工作,包含应用中断向量表重定向、调用芯片系统初始化、ARM系统寄存器rx清零、初始化应用程序各数据段、初始化ARM系统中断、跳转main函数。

    1. // 一段经典的startup code
    2.         SECTION .noinit : CODE
    3.         THUMB
    4.         import SystemInit
    5.         import init_data_bss
    6.         import main
    7.         import CSTACK$Limit
    8.         import init_interrupts
    9.         EXTERN __vector_table
    10.         REQUIRE __vector_table
    11. #define SCB_BASE            (0xE000ED00)
    12. #define SCB_VTOR_OFFSET     (0x00000008)
    13.         PUBLIC  Reset_Handler
    14.         EXPORT  Reset_Handler
    15. Reset_Handler
    16.         // Mask interrupts
    17.         cpsid   i
    18.         // Set VTOR register in SCB first thing we do.
    19.         ldr     r0,=__vector_table
    20.         ldr     r1,=SCB_BASE
    21.         str     r0,[r1, #SCB_VTOR_OFFSET]
    22.         // Init the rest of the registers
    23.         ldr     r2,=0
    24.         ldr     r3,=0
    25.         ldr     r4,=0
    26.         ldr     r5,=0
    27.         ldr     r6,=0
    28.         ldr     r7,=0
    29.         mov     r8,r7
    30.         mov     r9,r7
    31.         mov     r10,r7
    32.         mov     r11,r7
    33.         mov     r12,r7
    34.         // Initialize the stack pointer
    35.         ldr     r0,=CSTACK$Limit
    36.         mov     r13,r0
    37.         // Call the CMSIS system init routine
    38.         ldr     r0,=SystemInit
    39.         blx     r0
    40.         // Init .data and .bss sections
    41.         ldr     r0,=init_data_bss
    42.         blx     r0
    43.         // Init interrupts
    44.         ldr     r0,=init_interrupts
    45.         blx     r0
    46.         // Unmask interrupts
    47.         cpsie   i
    48.         // Set argc and argv to NULL before calling main().
    49.         ldr     r0,=0
    50.         ldr     r1,=0
    51.         ldr     r2,=main
    52.         blx     r2
    53. __done
    54.         B       __done
    55.         END
    复制代码
    9. startup.c:应用系统初始化文件,该文件里主要包含两个初始化函数,init_data_bss()、  init_interrupts(),data, bss段数据的初始化是为了保证嵌入式系统中所有全局变量能有一个开发者指定的初值。由于data,bss段的位置是在链接阶段确定的,所以此处需要配合linker文件才能找到正确的data,bss位置,linker文件是因IDE而异的,所有本文件要想做到通用,必须增加各IDE条件编译,此处仅以IAR下的实现为例:

    1. //基于IAR的startup.c文件
    2. #if (defined(__ICCARM__))
    3. #pragma section = ".intvec"
    4. #pragma section = ".data"
    5. #pragma section = ".data_init"
    6. #pragma section = ".bss"
    7. #pragma section = "CodeRelocate"
    8. #pragma section = "CodeRelocateRam"
    9. #endif
    10. void init_data_bss(void)
    11. {
    12. #if defined(__ICCARM__)
    13.     uint8_t *data_ram, *data_rom, *data_rom_end;
    14.     uint8_t *bss_start, *bss_end;
    15.     uint8_t *code_relocate_ram, *code_relocate, *code_relocate_end;
    16.     uint32_t n;
    17. // 初始化data段 .data section (initialized data section)
    18.     data_ram = __section_begin(".data");
    19.     data_rom = __section_begin(".data_init");
    20.     data_rom_end = __section_end(".data_init");
    21.     n = data_rom_end - data_rom;
    22.     if (data_ram != data_rom)
    23.     {
    24.         while (n--)
    25.         {
    26.             *data_ram++ = *data_rom++;
    27.         }
    28.     }
    29. // 初始化bss段 .bss section (zero-initialized data)
    30.     bss_start = __section_begin(".bss");
    31.     bss_end = __section_end(".bss");
    32.     n = bss_end - bss_start;
    33.     while (n--)
    34.     {
    35.         *bss_start++ = 0;
    36.     }
    37. // 初始化CodeRelocate段 (执行在RAM中的函数(由IAR指定的__ramfunc修饰的函数)).
    38.     code_relocate_ram = __section_begin("CodeRelocateRam");
    39.     code_relocate = __section_begin("CodeRelocate");
    40.     code_relocate_end = __section_end("CodeRelocate");
    41.     n = code_relocate_end - code_relocate;
    42.     while (n--)
    43.     {
    44.         *code_relocate_ram++ = *code_relocate++;
    45.     }
    46. #endif
    47. }
    48. void init_interrupts(void)
    49. {
    50.     NVIC_ClearEnabledIRQs();
    51.     NVIC_ClearAllPendingIRQs();
    52. }
    复制代码
    10. application.c/h:应用文件,此处便是主函数以及各功能函数的集合了,嵌入式老司机们,请开始你的表演~~~

    1. void taskn(void)
    2. {
    3.     // Your task code
    4. }
    5. int main(void)
    6. {
    7.     printf("hello world\r\n");
    8.     taskn();
    9.     ...
    10.     return 0;
    11. }
    复制代码
    至此,嵌入式开发里的各种来源的source文件痞子衡便介绍完毕了,掌声在哪里~~~




    文章出处:痞子衡嵌入式
    qiandao qiandao
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2025-8-8 16:43
  • 签到天数: 1504 天

    连续签到: 1 天

    [LV.Master]伴坛终老

    97

    主题

    4693

    帖子

    12

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    10105
    最后登录
    2025-9-11
    发表于 2020-8-20 17:20:41 | 显示全部楼层
    学习了。
    看来平时我们只编写了十分之一的文件数量呀
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /3 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2025-9-12 17:55 , Processed in 0.087186 second(s), 20 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

    快速回复 返回顶部 返回列表