|
C程序是如何运行的
本帖最后由 cruelfox 于 2015-11-21 16:18 编辑
STM32F0xx 系列是ARM Cortex-M0架构,地址空间32位,也就是4G Bytes的访问范围。数据和代码使用同一编址,下图是地址空间的布局:
实际上单片机用到的资源很少,地址空间大部分都没有内容。我使用的STM32F072C8T6带有64kB的Flash ROM, 16kB的SRAM,起始分别是 0x08000000 和 0x20000000. (由于有硬件映射功能,在0x00000000也就是最低地址,还可以访问ROM或者RAM的内容). 单片机片上外设的寄存器,则分布在更高的地址空间。读写这些寄存器,在CPU看来和读写内存(RAM)操作是一样的。
所以,C语言访问设备寄存器,和访问内存中的一个变量一样。只要知道寄存器的地址,通过一个指针访问就可以实现读写。上一贴子我的程序中引用了 RCC, GPIOA, TIM6 这三个(结构)指针,它们的值(也就是地址)以及类型(代表访问的内容)定义在 stm32f0xx.h 这个头文件中。因为设备寄存器太多了哇,如果每一个都定义一个指针就太烦琐了,所以把按功能划分定义成组,每组用一个C语言的结构类型表示,写起来也更清晰。而寄存器里面的位描述也可以定义成一些宏,在读程序的时候就知道是什么意思了。如果有兴趣,可以把 stm32f0xx.h 文件和STM32F0的手册对照着阅读。
好,假设已经熟悉寄存器操作了,知道怎么配置寄存器实现想要的功能,那么就可以写C程序让STM32工作了。现在需要一个工具来将C程序翻译成机器代码——编译器,或者是叫做工具链(Tool chain)。Keil MDK-ARM 或者 IAR-EWARM 开发环境都带有各自的编译器,不过我更偏向于用开源的GCC-ARM. 在launchpad.net上可以下载到编译好的arm-gcc工具链zip包,将它解压缩,加到PATH里面就可以直接用了,很方便(很精简吧)。
OK,现在来编译上面那个mini.c文件,命令行:
arm-none-eabi-gcc -c -Os -mcpu=cortex-m0 -mthumb mini.c
gcc的参数 -c 是表示仅编译,-Os 是优化代码大小,-mcpu=cortex-m0 -mthumb 是指定指令集的,因为ARM有不同的版本。对了,include的头文件还没弄到呢。要编译通过需要把 stm32f0xx.h 这个文件找来。我的建议是下载ST提供的 "STM32F0x2 USB FS Device Library" 程序库(URL http://www.st.com/st-web-ui/static/active/en/st_prod_software_internet/resource/technical/software/firmware/stsw-stm32092.zip),把里面需要的头文件等等扒出来。在 stm32f0xx.h 中还包含了另外几个头文件,一并弄出来放到工程目录下。
如果编译成功,将得到 mini.o 目标文件。可以用 arm-none-eabi-objdump -S mini.o 反汇编看看翻译成什么代码了。
- E:\arm\test072\mini>arm-none-eabi-objdump -S mini.o
- mini.o: file format elf32-littlearm
- Disassembly of section .text.startup:
- 00000000 <main>:
- 0: 4b16 ldr r3, [pc, #88] ; (5c <main+0x5c>)
- 2: 2280 movs r2, #128 ; 0x80
- 4: 6959 ldr r1, [r3, #20]
- 6: 0292 lsls r2, r2, #10
- 8: 430a orrs r2, r1
- a: b510 push {r4, lr}
- c: 2180 movs r1, #128 ; 0x80
- e: 615a str r2, [r3, #20]
- 10: 2290 movs r2, #144 ; 0x90
- 12: 0249 lsls r1, r1, #9
- 14: 05d2 lsls r2, r2, #23
- 16: 6011 str r1, [r2, #0]
- 18: 69da ldr r2, [r3, #28]
- 1a: 2110 movs r1, #16
- 1c: 430a orrs r2, r1
- 1e: 61da str r2, [r3, #28]
- 20: 4b0f ldr r3, [pc, #60] ; (60 <main+0x60>)
- 22: 4a10 ldr r2, [pc, #64] ; (64 <main+0x64>)
- 24: 851a strh r2, [r3, #40] ; 0x28
- 26: 2290 movs r2, #144 ; 0x90
- 28: 32ff adds r2, #255 ; 0xff
- 2a: 62da str r2, [r3, #44] ; 0x2c
- 2c: 2205 movs r2, #5
- 2e: 801a strh r2, [r3, #0]
- 30: 4a0d ldr r2, [pc, #52] ; (68 <main+0x68>)
- 32: 7812 ldrb r2, [r2, #0]
- 34: 8a1c ldrh r4, [r3, #16]
- 36: 2101 movs r1, #1
- 38: 4809 ldr r0, [pc, #36] ; (60 <main+0x60>)
- 3a: 420c tst r4, r1
- 3c: d0fa beq.n 34 <main+0x34>
- 3e: 8a04 ldrh r4, [r0, #16]
- 40: 438c bics r4, r1
- 42: 8204 strh r4, [r0, #16]
- 44: 2090 movs r0, #144 ; 0x90
- 46: 2480 movs r4, #128 ; 0x80
- 48: 05c0 lsls r0, r0, #23
- 4a: 408c lsls r4, r1
- 4c: 2a00 cmp r2, #0
- 4e: d102 bne.n 56 <main+0x56>
- 50: 6184 str r4, [r0, #24]
- 52: 1c0a adds r2, r1, #0
- 54: e7ee b.n 34 <main+0x34>
- 56: 8504 strh r4, [r0, #40] ; 0x28
- 58: 2200 movs r2, #0
- 5a: e7eb b.n 34 <main+0x34>
- 5c: 40021000 .word 0x40021000
- 60: 40001000 .word 0x40001000
- 64: 0000270f .word 0x0000270f
- 68: 00000000 .word 0x00000000
复制代码
如上,其实里面就一个main函数。但是 main 的入口地址还没有确定,而且它还使用了一个static型的内存变量,地址也还没有确定。可以用 arm-none-eabi-nm mini.o 来查看模块里面的全局符号表:
- E:\arm\test072\mini>arm-none-eabi-nm mini.o
- 00000000 b a.4686
- 00000000 T main
复制代码
那么,怎么让程序放到ROM中合适的地址,并运行呢?如果熟悉C语言编程就知道还有一步——链接,才能确定符号的地址。但是,到目前为止我们还没有告诉GCC地址的布局,也就是RAM从哪里开始,代码放在哪里。因为ARM的器件很多,这并不是统一的,所以需要提供一些信息给链接程序。具体地,需要一个Linker Script, 可以从软件包中找到 STM32F072C8_FLASH.ld (或者用近似的来修改得到)
OK,下面就是链接了,使用命令 arm-none-eabi-ld mini.o -Le:\arm-2014q3\arm-none-eabi\lib\armv6-m -Le:\arm-2014q3\lib\gcc\arm-none-eabi\4.8.4\armv6-m -T STM32F072C8_FLASH.ld -o mini.elf
这里面 -L 参数是添加标准库文件的搜索路径,虽然暂时并没有用到C标准库里面的东西,但是Linker Script里面引用了标准库文件。-o 指定输出的目标文件。这么就快要得到最终的机器码了,不过好象还缺少了什么……
arm-none-eabi-ld: warning: cannot find entry symbol Reset_Handler; defaulting to 08000000
linker给了一个警告:找不到入口地址 Reset_Handler 的值,设成了默认 0x08000000. 下面再用objdump -S反汇编看一下
- E:\arm\test072\mini>arm-none-eabi-objdump -S mini.elf
- mini.elf: file format elf32-littlearm
- Disassembly of section .text:
- 08000000 <main>:
- 8000000: 4b16 ldr r3, [pc, #88] ; (800005c <main+0x5c>)
- 8000002: 2280 movs r2, #128 ; 0x80
- 8000004: 6959 ldr r1, [r3, #20]
- 8000006: 0292 lsls r2, r2, #10
- 8000008: 430a orrs r2, r1
- 800000a: b510 push {r4, lr}
- 800000c: 2180 movs r1, #128 ; 0x80
- 800000e: 615a str r2, [r3, #20]
- 8000010: 2290 movs r2, #144 ; 0x90
- 8000012: 0249 lsls r1, r1, #9
- 8000014: 05d2 lsls r2, r2, #23
- 8000016: 6011 str r1, [r2, #0]
- 8000018: 69da ldr r2, [r3, #28]
- 800001a: 2110 movs r1, #16
- 800001c: 430a orrs r2, r1
- 800001e: 61da str r2, [r3, #28]
- 8000020: 4b0f ldr r3, [pc, #60] ; (8000060 <main+0x60>)
- 8000022: 4a10 ldr r2, [pc, #64] ; (8000064 <main+0x64>)
- 8000024: 851a strh r2, [r3, #40] ; 0x28
- 8000026: 2290 movs r2, #144 ; 0x90
- 8000028: 32ff adds r2, #255 ; 0xff
- 800002a: 62da str r2, [r3, #44] ; 0x2c
- 800002c: 2205 movs r2, #5
- 800002e: 801a strh r2, [r3, #0]
- 8000030: 4a0d ldr r2, [pc, #52] ; (8000068 <main+0x68>)
- 8000032: 7812 ldrb r2, [r2, #0]
- 8000034: 8a1c ldrh r4, [r3, #16]
- 8000036: 2101 movs r1, #1
- 8000038: 4809 ldr r0, [pc, #36] ; (8000060 <main+0x60>)
- 800003a: 420c tst r4, r1
- 800003c: d0fa beq.n 8000034 <main+0x34>
- 800003e: 8a04 ldrh r4, [r0, #16]
- 8000040: 438c bics r4, r1
- 8000042: 8204 strh r4, [r0, #16]
- 8000044: 2090 movs r0, #144 ; 0x90
- 8000046: 2480 movs r4, #128 ; 0x80
- 8000048: 05c0 lsls r0, r0, #23
- 800004a: 408c lsls r4, r1
- 800004c: 2a00 cmp r2, #0
- 800004e: d102 bne.n 8000056 <main+0x56>
- 8000050: 6184 str r4, [r0, #24]
- 8000052: 1c0a adds r2, r1, #0
- 8000054: e7ee b.n 8000034 <main+0x34>
- 8000056: 8504 strh r4, [r0, #40] ; 0x28
- 8000058: 2200 movs r2, #0
- 800005a: e7eb b.n 8000034 <main+0x34>
- 800005c: 40021000 .word 0x40021000
- 8000060: 40001000 .word 0x40001000
- 8000064: 0000270f .word 0x0000270f
- 8000068: 20000000 .word 0x20000000
复制代码 现在 main() 被放到ROM最开始去了,这好象是对的?如果了解ARM Cortex-M0下就知道这样错了,因为最开始应该是中断向量表。我们还没有编写Linker Script中的 .isr_vectors 段的内容。而且,一上来初始化堆栈指针等工作都没有做就直接运行 main() 了也不合适吧?还缺少了初始化代码。
在软件包中搜刮一个 startup_stm32f072.s 汇编文件
- /**
- ******************************************************************************
- * @file startup_stm32f072.s
- * @author MCD Application Team
- ******************************************************************************
- */
- .syntax unified
- .cpu cortex-m0
- .fpu softvfp
- .thumb
- .global g_pfnVectors
- .global Default_Handler
- /* start address for the initialization values of the .data section.
- defined in linker script */
- .word _sidata
- /* start address for the .data section. defined in linker script */
- .word _sdata
- /* end address for the .data section. defined in linker script */
- .word _edata
- /* start address for the .bss section. defined in linker script */
- .word _sbss
- /* end address for the .bss section. defined in linker script */
- .word _ebss
- .section .text.Reset_Handler
- .weak Reset_Handler
- .type Reset_Handler, %function
- Reset_Handler:
- ldr r0, =_estack
- mov sp, r0 /* set stack pointer */
- /*Check if boot space corresponds to test memory*/
-
- LDR R0,=0x00000004
- LDR R1, [R0]
- LSRS R1, R1, #24
- LDR R2,=0x1F
- CMP R1, R2
- BNE ApplicationStart
- /*SYSCFG clock enable*/
- LDR R0,=0x40021018
- LDR R1,=0x00000001
- STR R1, [R0]
- /*Set CFGR1 register with flash memory remap at address 0*/
- LDR R0,=0x40010000
- LDR R1,=0x00000000
- STR R1, [R0]
- ApplicationStart:
- /* Copy the data segment initializers from flash to SRAM */
- movs r1, #0
- b LoopCopyDataInit
- CopyDataInit:
- ldr r3, =_sidata
- ldr r3, [r3, r1]
- str r3, [r0, r1]
- adds r1, r1, #4
- LoopCopyDataInit:
- ldr r0, =_sdata
- ldr r3, =_edata
- adds r2, r0, r1
- cmp r2, r3
- bcc CopyDataInit
- ldr r2, =_sbss
- b LoopFillZerobss
- /* Zero fill the bss segment. */
- FillZerobss:
- movs r3, #0
- str r3, [r2]
- adds r2, r2, #4
- LoopFillZerobss:
- ldr r3, = _ebss
- cmp r2, r3
- bcc FillZerobss
- /* Call the application's entry point.*/
- bl main
-
- LoopForever:
- b LoopForever
- .size Reset_Handler, .-Reset_Handler
- /**
- * @brief This is the code that gets called when the processor receives an
- * unexpected interrupt. This simply enters an infinite loop, preserving
- * the system state for examination by a debugger.
- *
- * @param None
- * @retval : None
- */
- .section .text.Default_Handler,"ax",%progbits
- Default_Handler:
- Infinite_Loop:
- b Infinite_Loop
- .size Default_Handler, .-Default_Handler
- /******************************************************************************
- *
- * The minimal vector table for a Cortex M0. Note that the proper constructs
- * must be placed on this to ensure that it ends up at physical address
- * 0x0000.0000.
- *
- ******************************************************************************/
- .section .isr_vector,"a",%progbits
- .type g_pfnVectors, %object
- .size g_pfnVectors, .-g_pfnVectors
- g_pfnVectors:
- .word _estack
- .word Reset_Handler
- .word NMI_Handler
- .word HardFault_Handler
- .word 0
- .word 0
- .word 0
- .word 0
- .word 0
- .word 0
- .word 0
- .word SVC_Handler
- .word 0
- .word 0
- .word PendSV_Handler
- .word SysTick_Handler
- .word WWDG_IRQHandler
- .word PVD_VDDIO2_IRQHandler
- .word RTC_IRQHandler
- .word FLASH_IRQHandler
- .word RCC_CRS_IRQHandler
- .word EXTI0_1_IRQHandler
- .word EXTI2_3_IRQHandler
- .word EXTI4_15_IRQHandler
- .word TSC_IRQHandler
- .word DMA1_Channel1_IRQHandler
- .word DMA1_Channel2_3_IRQHandler
- .word DMA1_Channel4_5_6_7_IRQHandler
- .word ADC1_COMP_IRQHandler
- .word TIM1_BRK_UP_TRG_COM_IRQHandler
- .word TIM1_CC_IRQHandler
- .word TIM2_IRQHandler
- .word TIM3_IRQHandler
- .word TIM6_DAC_IRQHandler
- .word TIM7_IRQHandler
- .word TIM14_IRQHandler
- .word TIM15_IRQHandler
- .word TIM16_IRQHandler
- .word TIM17_IRQHandler
- .word I2C1_IRQHandler
- .word I2C2_IRQHandler
- .word SPI1_IRQHandler
- .word SPI2_IRQHandler
- .word USART1_IRQHandler
- .word USART2_IRQHandler
- .word USART3_4_IRQHandler
- .word CEC_CAN_IRQHandler
- .word USB_IRQHandler
- /*******************************************************************************
- *
- * Provide weak aliases for each Exception handler to the Default_Handler.
- * As they are weak aliases, any function with the same name will override
- * this definition.
- *
- *******************************************************************************/
- .weak NMI_Handler
- .thumb_set NMI_Handler,Default_Handler
- .weak HardFault_Handler
- .thumb_set HardFault_Handler,Default_Handler
- .weak SVC_Handler
- .thumb_set SVC_Handler,Default_Handler
- .weak PendSV_Handler
- .thumb_set PendSV_Handler,Default_Handler
- .weak SysTick_Handler
- .thumb_set SysTick_Handler,Default_Handler
- .weak WWDG_IRQHandler
- .thumb_set WWDG_IRQHandler,Default_Handler
- .weak PVD_VDDIO2_IRQHandler
- .thumb_set PVD_VDDIO2_IRQHandler,Default_Handler
-
- .weak RTC_IRQHandler
- .thumb_set RTC_IRQHandler,Default_Handler
-
- .weak FLASH_IRQHandler
- .thumb_set FLASH_IRQHandler,Default_Handler
-
- .weak RCC_CRS_IRQHandler
- .thumb_set RCC_CRS_IRQHandler,Default_Handler
-
- .weak EXTI0_1_IRQHandler
- .thumb_set EXTI0_1_IRQHandler,Default_Handler
-
- .weak EXTI2_3_IRQHandler
- .thumb_set EXTI2_3_IRQHandler,Default_Handler
-
- .weak EXTI4_15_IRQHandler
- .thumb_set EXTI4_15_IRQHandler,Default_Handler
-
- .weak TSC_IRQHandler
- .thumb_set TSC_IRQHandler,Default_Handler
-
- .weak DMA1_Channel1_IRQHandler
- .thumb_set DMA1_Channel1_IRQHandler,Default_Handler
-
- .weak DMA1_Channel2_3_IRQHandler
- .thumb_set DMA1_Channel2_3_IRQHandler,Default_Handler
-
- .weak DMA1_Channel4_5_6_7_IRQHandler
- .thumb_set DMA1_Channel4_5_6_7_IRQHandler,Default_Handler
-
- .weak ADC1_COMP_IRQHandler
- .thumb_set ADC1_COMP_IRQHandler,Default_Handler
-
- .weak TIM1_BRK_UP_TRG_COM_IRQHandler
- .thumb_set TIM1_BRK_UP_TRG_COM_IRQHandler,Default_Handler
-
- .weak TIM1_CC_IRQHandler
- .thumb_set TIM1_CC_IRQHandler,Default_Handler
-
- .weak TIM2_IRQHandler
- .thumb_set TIM2_IRQHandler,Default_Handler
-
- .weak TIM3_IRQHandler
- .thumb_set TIM3_IRQHandler,Default_Handler
-
- .weak TIM6_DAC_IRQHandler
- .thumb_set TIM6_DAC_IRQHandler,Default_Handler
-
- .weak TIM7_IRQHandler
- .thumb_set TIM7_IRQHandler,Default_Handler
- .weak TIM14_IRQHandler
- .thumb_set TIM14_IRQHandler,Default_Handler
-
- .weak TIM15_IRQHandler
- .thumb_set TIM15_IRQHandler,Default_Handler
-
- .weak TIM16_IRQHandler
- .thumb_set TIM16_IRQHandler,Default_Handler
-
- .weak TIM17_IRQHandler
- .thumb_set TIM17_IRQHandler,Default_Handler
-
- .weak I2C1_IRQHandler
- .thumb_set I2C1_IRQHandler,Default_Handler
-
- .weak I2C2_IRQHandler
- .thumb_set I2C2_IRQHandler,Default_Handler
-
- .weak SPI1_IRQHandler
- .thumb_set SPI1_IRQHandler,Default_Handler
-
- .weak SPI2_IRQHandler
- .thumb_set SPI2_IRQHandler,Default_Handler
-
- .weak USART1_IRQHandler
- .thumb_set USART1_IRQHandler,Default_Handler
-
- .weak USART2_IRQHandler
- .thumb_set USART2_IRQHandler,Default_Handler
- .weak USART3_4_IRQHandler
- .thumb_set USART3_4_IRQHandler,Default_Handler
-
- .weak CEC_CAN_IRQHandler
- .thumb_set CEC_CAN_IRQHandler,Default_Handler
- .weak USB_IRQHandler
- .thumb_set USB_IRQHandler,Default_Handler
- /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
复制代码 原来是这样,中断向量表在这里进行了描述,还有设置堆栈,初始化全局变量的代码,然后跳转到 main 执行。好了,这样就该差不多了。这个汇编程序是GNU AS的语法,可以用 arm-none-eabi-gcc 来直接汇编
arm-none-eabi-gcc -c startup_stm32f072.s
链接两个目标模块
arm-none-eabi-ld mini.o startup_stm32f072.o -Le:\arm-2014q3\arm-none-eabi\lib\armv6-m -Le:\arm-2014q3\lib\gcc\arm-none-eabi\4.8.4\armv6-m -T STM32F072C8_FLASH.ld -o mini.elf
最后转换出一个 HEX 文件
arm-none-eabi-objcopy -Oihex mini.elf mini.hex
可以进行烧写了。
我这个是最简化的例子,使用最简化的软件工具,不过已经包含了基本的C语言框架。后面随着我本人的学习,我会继续分享怎么开发一个简单的 USB 设备。
|
|