MPLAB C32编译器介绍
第1 章 语言相关信息
1.1 简介
本章讨论MPLAB C32 C 编译器命令行的使用、属性、pragma 伪指令和数据表示。
1.2 数据存储
1.2.1 MPLAB C32 C 编译器以小尾数格式存储多字节值。即,最低有效字节存储在最低地址。
1.2.2 整型表示
MPLAB C32 C 编译器中的整型值以2 的补码形式表示,长度为8 到64 位。这些值可在通过limits.h 编译后的代码中使用。
1.2.3 有符号和无符号字符类型
默认情况下,不带修饰符的char 类型的值是有符号值。根据C 标准,这是由实现定义的操作,有一些环境将不带修饰符的char 值定义为无符号值。
1.2.4 浮点型表示
MPLAB C32 C 编译器使用IEEE-754 浮点格式。float.h 中提供了对于翻译单元的实现限制的详细信息。
1.2.5 指针
MPLAB C32 C 编译器中的指针长度均为32 位。
1.2.6 limits.h
limits.h 头文件定义了可以使用整型表示的值的范围。
1.3 预定义宏
1.3.1 MPLAB C32 C 编译器宏
MPLAB C32 C 编译器定义了许多宏,大多数都带有前缀“_MCHP_”,它们定义了各种目标特定选项、目标处理器和主机环境其他方面的特性。
1.3.2 SDE 兼容性宏
MIPS SDE(软件开发环境)定义了许多宏,大多数带有前缀“_MIPS_”,它们定义了各种目标特定选项的特性,其中一些由命令行选项决定(例如, -mint64)。在适用时,这些宏将由MPLAB C32 C编译器定义,以方便将应用程序和中间件从SDE移植到MPLAB C32 C编译器。
1.4 属性和PRAGMA 伪指令
1.4.1 函数属性
always_inline
如果函数声明为inline,则始终内联函数,即使是未指定任何优化级别。
longcall
始终通过以下方式调用函数:首先将其地址装入一个寄存器,然后使用该寄存器的内容进行调用。这使得可以调用位于直接调用指令28 位寻址范围之外的函数。
far
在功能上等价于longcall。
near
始终使用绝对调用指令来调用函数,即使是指定了-mlong-calls 命令行选项。
mips16
以MIPS16 指令集为函数生成代码。
nomips16
始终以MIPS32 指令集为函数生成代码,即使是编译带有-mips16 命令行选项的翻译单元。
interrupt
为用作中断处理程序的函数生成序言(prologue)和尾声(epilogue)代码。
vector
在所指示的异常向量(其目标为一个函数)处生成一条转移指令。
at_vector
将函数体放到所指示的异常向量地址处。
naked
不为函数生成序言或尾声代码。
section (“name”)
将函数放入由“name”指定的段。
例如,
void __attribute__ ((section (“.wilma”))) baz () {return;}
函数baz 将被放入.wilma 段。
-ffunction-sections 命令行选项对使用section 属性定义的函数不起作用。
unique_section
将函数放入唯一指定的段中,就如同指定了-ffunction-sections。如果函数还具有section 属性,那么将使用相应的段名作为前缀来生成唯一的段名。
例如,
void __attribute__ ((section (“.fred”), unique_section) foo (void) {return;}
函数foo 将被放入.fred.foo 段。
noreturn
向编译器指示,函数将永不返回。在一些情况下,这将使编译器可以在调用函数中生成效率更高的代码,因为在执行优化时可以无需考虑函数确实会返回时的行为。声明为noreturn 的函数的返回类型应始终为void。
noinline
始终不考虑将函数内联。
pure
如果某个函数除了对其返回值之外没有任何其他副面影响,并且返回值仅依赖于参数和/ 或(非易变)全局变量,那么对于该函数的调用,编译器可以执行更积极的优化。此类函数可以使用pure 属性指示。
const
如果一个pure 函数仅根据其参数决定其返回值(即,不检查任何全局变量),那么可以将其声明为const,以允许更积极的优化。请注意,对指针参数进行解引用的函数不属
于const,因为指针解引用使用了不属于参数的值,虽然指针本身是一个参数。
format (type, format_index, first_to_check)
format 属性指示函数采用printf、scanf、strftime 或strfmon 样式的格式字符串和参数,编译器应当根据格式字符串对那些参数进行类型检查,方法与针对标准库函数时一样。
type 参数是printf、scanf、strftime 或strfmon 的其中之一(可以选择在两端附加双下划线,例如, __printf__),决定格式字符串的解释方式。
format_index 参数指定哪个函数参数是格式字符串。函数参数从最左端的参数开始,从1 开始编号。
first_to_check 参数指定根据格式字符串检查的第一个参数的编号。如果first_to_check 为0,那么不执行类型检查,编译器仅检查格式字符串的一致性(例如, vfprintf)。
format_arg (index)
format_arg 属性指定函数处理printf 样式的格式字符串,编译器应检查格式字符串的一致性。用作格式字符串的函数属性使用index 标识。
nonnull (index, ...)
向编译器指示,传递给函数的一个或多个指针参数必须为非空指针。如果编译器确定
有空指针作为值传递给非空参数,并且指定了-Wnonnull 命令行选项,那么它会发出警告诊断信息。
如果未为nonnull 属性提供任何参数,那么函数的所有指针参数均标记为非空。
unused
向编译器指示,函数可能不使用。如果该函数未被使用,编译器将不会发出警告。
used
向编译器指示,将始终使用该函数,即使编译器无法检查到对该函数的引用,也必须为函数生成代码。例如,行内汇编代码是到某个静态函数的唯一引用。
deprecated
在使用指定为deprecated 的函数时,会产生警告。
warn_unused_result
如果调用程序未使用所指示函数的返回值,将会发出警告。
weak
weak 符号指示,如果有另一个版本的相同符号可用,那么应使用另一个版本。例如,要使已实现的库函数可以由用户编写的函数覆盖,可以使用该属性。
malloc
所指示函数的任何非空指针返回值将不会赋给函数返回时有效的任何其他指针。这使编译器可以改善优化。
alias (“symbol”)
指示函数是另一个符号的别名。例如,
void foo (void) { /* stuff */ }
void bar (void) __attribute__ ((alias(“foo”)));
符号bar 被视为是符号foo 的别名。
1.4.2 变量属性
aligned (n)
具有该属性的变量将在下n 字节边界处对齐。
aligned 属性还可以用于结构成员。此类成员将在结构内对齐到所指示的边界处。如果省略了对齐值n,那么变量的对齐值设置为8 (基本数据类型的最大对齐值)。请注意, aligned 属性是用于增大变量的对齐值,而不是减小它。要减小变量的对齐值,请使用packed 属性。
cleanup (function)
指示当具有该属性的自动函数作用域变量超出作用域时要调用的函数。
所指示的函数应只具有一个参数,即指向与具有该属性的变量的类型兼容的指针,返回类型应为void。
deprecated
在使用指定为deprecated 的变量时,将会产生警告。
packed
具有该属性的变量或结构成员将具有所可能的最小对齐值。即,将不为声明分配任何对齐填充存储空间。与aligned 属性联合使用时, packed 可以用于设置任意的对齐限制,即大于或小于变量或结构成员的类型所具有的默认对齐值。
section (“name”)
将函数放入由“name”指定的段。
例如,
unsigned int dan __attribute__ ((section (“.quixote”)))
变量dan 将被放入.quixote 段。
除非同时也指定了unique_section,否则-fdata-sections 命令行选项对使用section 属性定义的变量不起作用。
unique_section
将变量放入唯一指定的段中,就如同指定了-fdata-sections。如果变量还具有section 属性,那么将使用相应的段名作为前缀来生成唯一的段名。
例如,
int tin __attribute__ ((section (“.ofcatfood”), unique_section)
变量tin 将被放入.ofcatfood 段。
transparent_union
如果联合类型的函数形参带有transparent_union 属性,那么传递相应的实参时,它的类型视同为联合的第一个成员的类型。
unused
向编译器指示,变量可能不使用。如果该变量未被使用,编译器将不会发出警告。
weak
weak 符号指示,如果有另一个版本的相同符号可用,那么应使用另一个版本。
1.5 Pragma 伪指令
#pragma interrupt
将一个函数标记为中断处理程序。函数的序言或尾声代码将执行范围更广的现场保护。
#pragma vector
在所指示的异常向量(其目标为一个函数)处生成一条转移指令。
#pragma config
#pragma config 伪指令指定要由应用程序使用的特定于处理器的配置设置(即,配
置位)。
1.5 命令行选项
MPLAB C32 C 编译器提供了许多控制编译的选项,它们都是区分大小写的。
• 针对PIC32MX 器件的选项
• 控制输出类型的选项
• 控制C 语言的选项
• 控制警告与错误的选项
• 调试选项
• 控制优化的选项
• 控制预处理器的选项
• 汇编选项
• 链接选项
• 目录搜索选项
• 代码生成约定选项
1.6 通过命令行编译单个文件
在DOS 提示符下输入如下命令行来编译该程序:
C:\\> pic32-gcc -o ex1.out ex1.c
命令行选项-o ex1.out 命名输出可执行文件(如果未指定-o 选项,则输出文件名为a.out)。可执行文件可加载到MPLAB IDE 中。
如果需要hex 文件,例如要装入器件编程器中,可以使用下面的命令:
C:\\> pic32-bin2hex ex1.out
这样就生成了一个名为ex1.hex 的Intel hex 文件。
1.7 通过命令行编译多个文件
在DOS 提示符下输入如下命令行来编译这两个文件:
C:\\> pic32-gcc -o ex1.out ex1.c add.c
这个命令编译模块ex1.c 和add.c。编译的模块和编译器库文件链接,并生成可执行文件ex1.out。
第2 章 库环境
2.1 简介
本章讨论MPLAB C32 C 库的使用。
2.2 标准I/O
标准输入/ 输出库函数支持两种操作模式:简单和完全。简单模式通过用于stdout、stdin 和stderr 的单字符设备上的双函数接口来支持I/O。完全模式支持所有标准I/O函数。如果应用程序调用fopen,那么库将使用完全模式,否则将使用简单模式。
简单模式使用4 个函数 _mon_puts、_mon_write、_mon_getc 和_mon_putc 来
执行I/O,即执行原始设备I/O。_mon_getc 的默认实现始终返回失败(即,默认情况下,字符输入不可用)。_mon_putc 的默认实现会向UART2 写一个字符。它假定应用程序已经对UART 执行了所有必要的初始化。_mon_puts 和_mon_write 的默认实现都是简单地迭代调用_mon_putc。所有4 个函数都定义为弱(weak)函数,所以如果需要不同的功能,用户应用程序可以覆盖它们。
使用完全模式的应用程序必须提供标准的低级POSIX I/O 函数open、read、write、lseek 和close。未提供任何默认实现。
2.3 弱函数
标准库提供了低级接口的一些弱函数实现。使用该功能的用户应用程序通常会实现这些函数的更完整版本。
如上所述,标准I/O 库函数使用一组弱函数进行简单输出:_mon_write、_mon_putc、_mon_puts 和_mon_getc。标准启动代码会直接调用如下一些弱函数,并提供弱处理程序来处理引导异常和一般异常:_on_reset、_nmi_handler、_bootstrap_exception_handler、_general_exception_handler 和_on_bootstrap。
标准库函数exit 在返回之前会调用弱函数_exit。
用于信号的标准库函数signal 和raise 实现为弱函数,它们始终返回失败。
用于语言环境的标准库函数setlocale 和localeconv 实现为弱函数,不执行任何操作。
用于访问环境变量的标准库函数getenv 实现为弱函数,始终返回NULL。
2.4 “HELPER”头文件
2.4.1 sys/attribs.h
对于许多常用属性,提供了一些宏,以便提高用户代码可读性。
2.4.2 sys/kmem.h
系统代码可能需要在虚拟和物理地址之间,以及在内核段地址之间转换。提供了一些宏,帮助更方便地进行这些转换和确定地址所处的段。
2.5 MULTILIB
使用multilib,目标库将使用一组置换选项构建多次。Multilib 是使用这些选项进行构建所产生的一组目标库。当调用编译器shell 来编译并链接应用程序时, shell 会选择使用相同选项构建的目标库的版本。
第3 章 中断
3.1 简介
中断处理对于大多数单片机应用来说都是很重要的一个方面。中断用来使软件操作与实时发生的事件同步。当发生中断时,软件的正常执行流程被打断,调用专门的函数来处理事件。当中断处理结束时,恢复先前的现场信息并继续正常执行流程。
PIC32MX 器件支持多个内部和外部中断源。另外,允许高优先级中断中断任何正在处理的低优先级中断。
MPLAB C32 C 编译器完全支持在C 或行内汇编代码中进行中断处理。
3.2 指定中断处理函数
中断处理函数用于实现现场保护和恢复,以确保从中断返回时,程序现场恢复进入中断前的状态。
3.2.1 处理程序函数现场保护
C 函数的标准调用约定始终会保留zero、s0-s7、gp、sp 和fp。k0 和k1 供编译器访问和保留非GPR 现场,但对它们的存取始终是以原子方式进行的(即,在禁止全局中断的序列中),所以不需要主动保存它们。除了标准寄存器之外,处理程序函数还会主动保存a0-a3、t0-t9、v0、v1 和ra 寄存器。
中断处理函数还会主动保存和恢复处理程序函数所使用的处理器状态寄存器。特别是,EPC、SR、hi 和lo 寄存器会被保留为现场。
优先级指定为7 (最高优先级)的处理程序函数将使用影子寄存器组来保留通用寄存器,使得进入处理程序函数的应用程序代码的延时可以较短。
3.2.2 将一个函数标记为中断处理程序
函数通过interrupt 属性或中断pragma 伪指令标记为处理程序函数。每种方法在功能上是彼此等价的。中断可以指定为具有特定优先级的处理中断,也可以指定为在单向量模式下工作。
# pragma interrupt function-name ipln [vector [@]vector-number
[,vector-number-list]]
# pragma interrupt function-name single [vector [@] 0
其中, n 处于范围0..7 内(且包括0 和7)。iplx 说明符可以全为大写或全为小写。使用中断pragma 伪指令指示的处理程序函数的函数定义必须紧跟在pragma 伪指令之后且与其位于同一个翻译单元中。
interrupt属性也指示函数定义是中断处理程序。它在功能上等价于中断pragma伪指令。
例如,以下两个foo 定义均指示它是优先级为4 的中断处理函数。
#pragma interrupt foo ipl4
void foo (void)
在功能上等价于
void __attribute__ ((interrupt(ipl4))) foo (void)
3.3 将中断处理函数与异常向量相关联
共有64 个异常向量,编号为0 至63。每个中断源都按器件数据手册中的规定映射到一个异常向量。默认情况下,在每个向量地址处会保留4 个字的空间,用于将中断分派给该异常源的处理程序函数。
中断处理函数可以通过以下方式与中断向量相关联:指定为位于异常向量地址处的分派函数的目标,或者直接定位到异常向量地址处。单个处理程序函数可以作为多个分派函数的目标。
处理程序函数与一个或多个异常向量地址之间的关联可以通过中断pragma 伪指令的子句、单独的向量pragma 伪指令或函数声明中的向量属性指定。
3.3.1 中断Pragma 子句
中断pragma 伪指令具有可选的vector 子句,跟随在优先级说明符之后。
# pragma interrupt function-name ipl-specifier [vector
[@]vector-number [, vector-number-list]]
目标为指定处理程序函数的分派函数将创建在指定向量号的异常向量地址处。如果所指定的第一个向量号前面带有“@”符号,那么处理程序函数本身将直接定位到该位置。
例如,以下pragma 伪指令指定函数 foo 将创建为优先级为4 的中断处理函数。foo将定位到异常向量54 的地址处。目标为foo 的分派函数将创建在异常向量34 的地址处。
#pragma interrupt foo ipl4 vector @54, 34
以下pragma 伪指令指定函数bar 将创建为优先级为5 的中断处理函数。bar 将定位到通用程序存储区(.text 段)中。在异常向量23 所在的地址处将创建目标为bar 的分派函数。
#pragma interrupt bar ipl5 vector 23
3.3.2 Vector Pragma
vector pragma 用于创建一个或多个目标为所指示函数的分派函数。对于使用interrupt pragma 指定的目标函数,它的作用就如同是使用了vector 子句。Vector pragma 的目标函数可以是任意函数,包括以汇编或其他方式实现的外部函数。
# pragma vector function-name vector vector-number [,vector-number-list]
以下pragma 伪指令定义了一个分派函数,它的目标是处于异常向量54 所在的地址处的函数foo。
#pragma vector foo 54
3.3.3 Vector 属性
处理程序函数可以通过属性与一个或多个异常向量地址相关联。at_vector 属性指示应将处理程序函数本身放在异常向量地址处。vector 属性指示应在异常向量地址处创建分派函数。
例如,以下声明指定函数foo 将创建为优先级为4 的中断程序函数。foo 将定位到异
常向量54 的地址处。
void __attribute__ ((interrupt(ipl4))) __attribute__ ((at_vector(54))) foo (void)
以下声明指定函数foo 将创建为优先级为4 的中断处理函数。并在异常向量52 和53所在的地址处定义目标为foo 的分派函数。
void __attribute__ ((interrupt(ipl4))) __attribute__ ((vector(53,52))) foo (void)
3.4 异常处理程序
PIC32MX 器件还有两个用于非中断异常的异常向量。这些异常归类为引导异常和一般异常。
3.4.1 引导异常
复位异常是在引导代码运行时(StatusBEV=1)发生的任何异常。所有复位异常的向量地址都是0xBFC00380。在该地址单元, MPLAB C32 工具链会放置一条转移指令,其目标为名为_bootstrap_exception_handler() 的函数。在标准库中,提供了该函数的默认弱版本,它只是进入一个无限循环。如果用户应用程序提供了_bootstrap_exception_handler() 的实现,那么将改为使用该实现版本。
3.4.2 一般异常
一般异常是引导代码之外的程序执行期间(StatusBEV=0)发生的任何非中断异常。
一般异常向量的地址对于EBase 的偏移量为0x180。
在该地址单元, MPLAB C32 工具链会放置一条转移指令,其目标为名为
_general_exception_context() 的函数。所提供的该函数的实现版本会保存现场、调用应用程序处理程序函数、恢复现场,以及执行从异常返回的指令。所保存的现场是hi 和lo 寄存器,以及除s0-s8 之外的所有通用寄存器, s0-s8 定义为由所有被调用函数进行保留,所以不需要在此处再次主动保存。Cause 和Status 寄存器的值会被传递给应用程序处理函数(_general_exception_handler())。如果用
户应用程序提供了_general_exception_context() 的实现,那么将改为使用该实现版本。
void _general_exception_handler (unsigned cause, unsigned status);
标准库中提供了_general_exception_handler() 弱版本的默认实现,该实现只是进入一个无限循环。如果用户应用程序提供了_general_exception_handler() 的实现,那么将改为使用该实现版本。
第4 章 低级处理器控制
4.1 简介
本章讨论PIC32MX 器件的低级寄存器和配置的访问。
4.2 通用处理器头文件
通用处理器头文件是C 文件,它根据使用-mprocessor 命令行选项指定的处理器而
包含正确的特定于处理器的头文件。通用处理器头文件位于
c:\\Program Files\\Microchip\\MPLAB C32\\pic32mx\\include ;其中,
c:\\Program Files\\Microchip\\MPLAB C32 是MPLAB C32 工具链的安装目录。
除了包含正确的特定于处理器的头文件之外,通用处理器头文件还提供了#define 伪指令,使得可以使用来自汇编语言文件的约定寄存器名称。
要包含通用处理器头文件,请在源代码中使用以下语句:
#include 包含通用处理器头文件使得可以为MPLAB C32 工具链所支持的任意处理器编译源代码,而无需更改要包含的文件。 4.3 处理器支持头文件 特定于处理器的头文件是一些包含了在C 或汇编语言中使用的特殊功能寄存器 (Special Function Register, SFR)的外部声明的文件。依照约定,每个SFR 都使用数据手册中的相同名称进行命名——例如, WDTCON 代表看门狗定时器控制寄存器。如果寄存器含有可能关注的一些个别位,那么还会有一个为该SFR 定义的结构typedef ;其中,结构typedef 的名称是附加了bits_t 的寄存器的名称——例如, __WDTCONbits_t。这些个别位(或位域)在结构中使用数据手册中的名称进 行命名。例如,在PIC32MX360F512L 的特定于处理器的头文件中,供C 使用的 WDTCON 寄存器声明为: extern volatile unsigned int WDTCON __attribute__((section(\"sfrs\"))); typedef union { struct { unsigned WDTCLR:1; unsigned :1; unsigned SWDTPS0:1; unsigned SWDTPS1:1; unsigned SWDTPS2:1; unsigned SWDTPS3:1; unsigned SWDTPS4:1; unsigned :8; unsigned ON:1; }; struct { unsigned :2; unsigned WDTPSTA:5; unsigned :1; unsigned PWRTPSTA:3; }; struct { unsigned w:32; }; } __WDTCONbits_t; extern volatile __WDTCONbits_t __attribute__((section(\"sfrs\"))); WDTCONbits asm (\"WDTCON\") 注: 符号WDTCON 和WDTCONbits 指代相同的寄存器,解析为相同的地址,这从WDTCONbits 的声明就可以看出。 用于汇编语言时, WDTCON 寄存器声明为:.extern WDTCON。 特定于处理器的头文件位于 c:\\Program Files\\Microchip\\MPLAB C32\\pic32mx\\include\\proc;其中, c:\\Program Files\\Microchip\\MPLAB C32 是MPLAB C32 工具链的安装目录。 要包含特定于处理器的头文件,建议您包含通用处理器头文件;但是,如果您希望特别调用特定于处理器的头文件,请在源文件中使用以下语句(示例假定是为PIC32MX360F512L 包含特定于处理器的头文件): #include 4.4 外设库函数 随编译器工具提供的外设库函数支持PIC32MX 器件的许多外设。 4.5 特殊功能寄存器访问 在应用程序中使用SFR 时,需要执行3 个步骤。 1. 包含通用处理器头文件(即, p32xxxx.h)或相应器件的特定于处理器的头文件(例 如, proc/p32mx360f512l.h)。 #include 2. 像访问任何其他C 变量一样访问SFR。源代码可以写和/ 或读SFR。例如,以下语句将Timer1 的特殊功能寄存器中的所有位清零: TMR1 = 0; 下一条语句使能看门狗定时器: WDTCONbits.ON = 1; 3. 使用默认链接描述文件进行链接,或者在项目中包含相应处理器的processor.o 文件。 4.6 CP0 寄存器访问 4.6.1 CP0 寄存器定义头文件 CP0 寄存器定义头文件(cp0defs.h)是包含CP0 寄存器及其位域的定义的文件。 此外,它还包含了用于访问CP0 寄存器的宏。CP0 寄存器定义头文件位于 c:\\Program Files\\Microchip\\MPLAB C32\\pic32mx\\include ;其中, c:\\Program Files\\Microchip\\MPLAB C32 是MPLAB C32 工具链的安装目录。 CP0 寄存器定义头文件旨在与汇编或C 文件配合使用。 CP0 寄存器定义头文件依赖于在处理器通用头文件中定义的宏。要包含CP0 寄存器定义头文件,请在源代码中使用以下语句: #include 4.6.2 CP0 寄存器定义 当从汇编文件中包含CP0 寄存器定义头文件时, CP0 寄存器定义为: #define _CP0_REGISTER_NAME $register_number, select_number 例如, IntCtl 寄存器定义为: #define _CP0_INTCTL $12, 1 当从C 文件中包含CP0 寄存器定义头文件时, CP0 寄存器和选择定义为: #define _CP0_REGISTER_NAME register_number #define _CP0_REGISTER_NAME_SELECT select_number 例如, IntCtl 寄存器定义为: #define _CP0_INTCTL 12 #define _CP0_INTCTL_SELECT 1 4.6.3 CP0 寄存器位域定义 当从汇编或C 文件中包含CP0 寄存器定义头文件时,对于每个CP0 寄存器位域,存在3 个#define。 _CP0_REGISTER_NAME_FIELD_NAME_POSITION——起始位地址 _CP0_REGISTER_NAME_FIELD_NAME_MASK——属于该位域的位置1 _CP0_REGISTER_NAME_FIELD_NAME_LENGTH——该位域占用的位数 例如, IntCtl 寄存器的向量间隔位域具有以下定义: #define _CP0_INTCTL_VS_POSITION 0x00000005 #define _CP0_INTCTL_VS_MASK 0x000003E0 #define _CP0_INTCTL_VS_LENGTH 0x00000005 4.6.4 CP0 访问宏 当从C 文件中包含CP0 寄存器定义头文件时,即定义了CP0 访问宏。每个CP0 寄 存器最多可以定义6 个不同的访问宏: 4.7 配置位访问 4.7.1 #pragma config #pragma config 伪指令指定要由应用程序使用的特定于处理器的配置设置(即,配置位)。 配置设置可以使用多个#pragma config 伪指令指定。MPLAB C32 C 编译器会验证所指定的配置设置对于它进行编译所针对的处理器是否有效。如果配置字中的给定设置未在任何#pragma config 伪指令中指定,那么与那些设置关联的位默认为未设定值。 对于使用#pragma config 伪指令指定了设置的每个配置字,编译器会生成名为.config_address 的只读数据段;其中, address 是配置字地址的十六进制表示。例如,如果为位于地址0xBFC02FFC 的配置字指定了配置设置,那么会创建名为.config_BFC02FFC 的只读数据段。 4.7.1.1 语法 pragma-config 伪指令: # pragma config setting-list setting-list: setting | setting-list, setting setting: setting-name = value-name setting-name 和value-name 是特定于器件的名称,可以使用 “PIC32MXConfiguration Settings”文档来确定。 4.7.1.2 示例 以下示例说明可以如何使用#pragma config 伪指令。示例执行以下操作: • 使能看门狗定时器, • 将看门狗后分频比设置为1:128,并 • 选择HS 振荡器作为主振荡器 #pragma config FWDTEN = ON, WDTPS = PS128 #pragma config POSCMOD = HS ... void main (void) { ... } 第5 章 编译器运行时环境 5.1 简介 本章讨论MPLAB C32 C 编译器的运行时环境。 5.2 寄存器约定 5.3 堆栈使用 MPLAB C32 C 编译器使用通用寄存器29 专门作为软件堆栈指针。所有的处理器堆栈操作(包括函数调用、中断和异常)都使用软件堆栈。堆栈从高地址到低地址向下增长。 默认情况下,堆栈的大小为1024 字节。堆栈大小可以通过在链接器命令行中使用--defsym_min_stack_size 链接器命令行选项指定大小进行更改。以下是使用命令行分配2048 字节的堆栈的示例: pic32-gcc foo.c -Wl,--defsym,_min_stack_size=2048 运行时堆栈从高地址到低地址向下增长(见下图)。编译器使用两个工作寄存器来管理堆栈: • 寄存器29 (sp)——这是堆栈指针。它指向堆栈中的下一个可用单元。 • 寄存器30 (fp)——这是帧指针。它指向当前函数的帧。在需要时,每个函数会创建一个新帧,通过该帧分配自动变量和临时变量。编译器优化可能会取消通过帧指针进行的堆栈指针引用,而将它们转换为通过堆栈指针进行的等效引用。这种优化使得帧指针可以用作通用寄存器。 5.4 堆使用 C 运行时堆是数据存储器中的未初始化段,用于使用标准C 函数库中的动态存储器管理函数calloc、malloc 和realloc 进行动态存储器分配。如果不使用以上函数,那么就不 需要分配堆。默认情况下,不会创建堆。 如果希望使用动态存储器分配,无论是通过调用一个存储器分配函数直接使用,还是通过使用以上函数的标准C 库函数间接使用,那么必须创建一个堆。堆通过在链接器命令行中使用--defsym_min_heap_size 链接器命令行选项指定其大小来创建。以下是使用命令行分配512 字节的堆的示例: pic32-gcc foo.c -Wl,--defsym,_min_heap_size=512 链接器在紧接堆栈前面的位置分配堆。 5.5 函数调用约定 堆栈指针始终在4 字节边界处对齐。 • 所有长度小于32位的整数类型首先会被转换为32位的值。参数的前4个32位通过寄存器a0-a3 传递。 • 虽然一些参数可能通过寄存器传递,但还是会在堆栈中为要传递给函数的所有参数分配空间。 • 调用函数时: - 寄存器a0-a3 用于向函数传递参数。调用函数时,不会保留这些寄存器的值。 - 寄存器t0-t7 和t8-t9 是由调用程序保存的寄存器。调用函数必须将这些值压入堆 栈,以保存寄存器的值。 - 寄存器s0-s7 是由被调用程序保存的寄存器。被调用函数必须保存这些寄存器中会被修改的寄存器的值。 - 如果优化器取消将寄存器s8 用作帧指针,那么该寄存器的值需要保存。否则, s8 是被保留的寄存器。 - 寄存器ra 中包含函数调用的返回地址。 5.6 启动和初始化 5.6.1 规定 对于运行时模型,存在以下规定: • 仅内核模式 • 仅KSEG1 • RAM函数带有属性__ramfunc__ 或__longramfunc__,表示所有RAM函数均终止于.ramfunc 段 5.6.2 PIC32MX 启动代码 PIC32MX 启动代码必须执行以下操作: 1. 在发生NMI 时跳转到NMI 处理程序 2. 初始化堆栈指针和堆 3. 初始化全局指针 4. 调用“复位时”过程 5. 清零未初始化数据段 6. 将初始化数据从程序闪存复制到数据存储器 7. 将RAM 函数从程序闪存复制到数据存储器 8. 初始化总线矩阵寄存器 9. 初始化CP0 寄存器 10. 跟踪控制2 寄存器(TraceControl2——CP0 寄存器23,选择2) 11. 调用“引导时”过程 12. 更改异常向量的位置 13. 调用主程序 5.6.2.1 在发生NMI 时跳转到NMI 处理程序 如果NMI 导致进入复位向量,那么会跳转到NMI 处理程序(_nmi_handler)。代码中提供了弱(weak)版本的NMI 处理程序,它执行一条ERET 指令。 _nmi_handler 函数必须带有属性nomips16, [ 例如,__attribute__((nomips16))],因为启动代码会跳转到该函数。 5.6.2.2 初始化堆栈指针和堆 堆栈指针(sp)寄存器必须在启动代码中初始化。要使启动代码能够初始化sp 寄存器,链接描述文件必须初始化一个指向KSEG1 数据存储器末尾位置的变量。该变量名为_stack。用户可以通过向链接器提供命令行选项--defsym _min_stack_size=N 来更改所分配的最小堆栈空间量。链接描述文件提供的 _min_stack_size 的默认值为1024。 类似地,用户可能会希望在应用程序中使用堆。虽然启动代码不需要初始化堆,但是必须让标准C 函数库(sbrk)知道堆的位置及大小。链接描述文件会创建一个变量来标识堆的开始位置。堆位于所使用的KSEG1 数据存储器的末尾位置。该变量名为_heap。用户可以通过向链接器提供命令行选项--defsym _min_heap_size=M 来更改所分配的最小堆空间量。链接描述文件提供的_min_heap_size 的默认值为0。如果在堆大小设置为0 时使用堆,那么产生的行为与堆使用量超出最小堆大小时相同。即,它溢出到为堆栈分配的空间中。 堆和堆栈使用未被分配的KSEG1 数据存储器,堆从已分配的KSEG1 数据存储器末尾位置开始,向堆栈方向向上增长,而堆栈则从KSEG1 数据存储器的末尾位置开始,向堆方向向下增长。如果根据所请求的最小堆大小和最小堆栈大小,没有足够空间可用,那么链接器会报告一个错误。 5.6.2.3 初始化全局指针 编译器工具链支持全局指针(gp)相对寻址。在位于gp 寄存器中存储的地址任意一侧的32 KB 地址范围内装入或存储数据时,可以使用gp 寄存器作为基址寄存器,在单条指令中执行该操作。在不使用全局指针的情况下,从静态存储区装入数据需要两条指令——一条指令用于装入由编译器/ 链接器计算得到的32 位常量地址的高位,另一条指令用于装入数据。 5.6.2.4 调用“复位时”过程 初始化最小的“C”现场之后,将会调用一个过程。该过程使用户可以在器件复位时立即执行一些操作。随启动代码一起,提供了该过程(_on_reset)的弱版本,其内容为空。 如果是以“C”编写该过程,那么用户需要注意一些特殊事项。最重要的是,静态分配的变量不会被初始化(需要使用指定的初始化器,或像对待未初始化变量那样赋0 值)。 5.6.2.5 清零未初始化数据段 存在两个未初始化数据段——.sbss 和.bss。.sbss 段是一个数据段,包含长度小于等于n 字节的未初始化变量,其中的n 由-Gn 命令行选项决定。.bss 段是一个数据段,包含.sbss 中未包含的未初始化变量。 因篇幅问题不能全部显示,请点此查看更多更全内容