本节书摘来自华章出版社《嵌入式C编程:PIC单片机和C编程技术与应用》一书中的第3章,第3.3节,作者 [美]马克·西格斯蒙德(Mark Siegesmund),更多章节内容可以访问“华章计算机”公众号查看
3.3 非标准编译指示
3.3.1 #warning
它和#error相似,但只抛出警告信息,不会停止编译。
3.3.2 #use delay
该指令告诉编译器,硬件使用的是什么类型的晶振。编译器根据它为芯片设置正确的熔丝位,同时还会生成一些控制时间的内置函数。我们已经见过这类函数了,如delay_ms(time),其中的#use delay在文件e3.h中。这里给出了一些例子:
CCS C编译器的#use...指令驱使编译器在编译阶段根据程序员的指定选项生成新的内置函数。这些#use库包括RS-232、I2C、触摸板等,具体细节将会在本书后面部分介绍。
对于大多数非嵌入式的传统C编译器来说,通常都将像delay_ms这样的通用函数封装成函数库。但是由于PIC MCU的内存比较紧张,在编译时生成这些函数会更有效率,这样只有在程序员真正需要的时候才会被包含进来。
该指令对于整个程序都非常重要,因为有时编译器为了满足特定器件接口的需求,需要加入延时。例如,PIC的寄存器在设置和读取一个比特之间需要稍作延时。编译器从该指令中获取到时钟信息,从而知道如何实现延时。
有些芯片有双晶振以及其他的时钟特性。该指令能够处理所有器件的各种选项。
3.3.3 关于频率
频率的单位是赫兹(hertz),简写为Hz。1Hz表示每秒钟发生一次;2Hz表示每秒钟重复出现两次。下面给出了频率的常用单位:
Hz ???每秒1次
kHz ??每秒1000次
Mhz 每秒1?000?000次
gHz ??每秒1?000?000?000次
3.3.4 #use rs232(options)
该指令会创建出一组用于接收和发送串口异步数据的函数,有很多选项。下面给出个简单的例子:
该指令会生成putc()和getc(),分别用来从C6、C7引脚以9600 Baud的波特率传输和接收数据。如果使用的是PIC的UART引脚,那么通信由硬件完成。否则,编译器会创建一个函数来实现bit-bang模式。第22章将会详细介绍异步通信和#use rs232指令。
3.3.5 #fuses options
options会根据硬件不同而变化,device.h文件中列出了相关硬件的选项。
PIC微控制器有一块非易失性内存用来存放配置信息。这块区域称为熔丝位或配置位,可以设置晶振类型,开关各种硬件特性,有的还可以设置某些引脚的功能。在下载程序到芯片上时,可以修改熔丝位。只有重新下载程序才能改变熔丝位的设置。不同芯片的设置内容不同。在IDE的VIEW>FUSES中,可以找到相应芯片支持哪些设置。使用#use delay指令,CCS C编译器会自动设置所需的晶振熔丝位。下面是所有芯片通用的两个常用熔丝位:
在程序写入芯片之后,该熔丝位用来防止从芯片内存中读取程序。通常用于产品中,用来防止终端客户从芯片中复制程序。
该熔丝位用于激活芯片的看门狗定时器(watchdog timer)。在该定时器没有达到预设值时,芯片正常工作。一旦定时器到期,芯片将会被复位。正常情况下,程序将会周期性地重置定时器(复位到起始状态)。只要定时器不停地被重置,程序就会一直运行下去。如果程序卡在某个地方,看门狗定时器得不到重置,就会在到期之后重启程序。函数setup_wdt()用来设置定时器时间,restart_wdt()用来重置定时器。
3.3.6 #locate id = address
id 一个有效的C标识符
address PIC微控制器数据内存中的地址
通常情况下,定义一个C变量之后,编译器在RAM中寻找一个空位置来存放该变量。该指令能够强制将变量放置到内存中的某个特殊位置。我们可以使用它将C变量指向PIC的特定功能寄存器。例如,E3板子上的PIC控制器的状态寄存器位于RAM中的4056位置。下面这段代码创建了一个变量指向该位置:
那么,后面的这行代码就可以将状态寄存器清零:
CCS C编译器中的getenv()宏可以用于获取PIC寄存器的位置。下面这条指令和前面的#locate功能一样,并且可以用于所有的芯片:
虽然C语言的ANSI标准中不允许这么声明,但是C语言还有个嵌入式扩展。在该扩展标准中,对于嵌入式程序,我们将其称为named register(有名寄存器)。CCS C编译器支持该扩展,而且,很少有编译器支持它。下面是使用named register的另外一种方法:
下划线前的名字(register)就是寄存器的地址。
3.3.7 #byte id=x和#word id=x
id 一个有效的C标识符
x 一个C变量或常量
该指令功能和#locate一样,但除了定位变量,它还声明了一个C变量。#byte用于声明8位的变量,#word则是16位。下面这行指令和前面的#locate功能一样:
3.3.8 #bit id=x.y
id 一个有效的C标识符
x 一个C变量或常量
y 可以是0~7中的任意一个数
该指令功能和#byte类似,但是它只能声明一位的变量。C变量的每个字节都可以对应到内存中的每位,Bit0就是一个字节的最低位。例如:
3.3.9 #reserve address
该指令用于在RAM中预留空间,编译器不会使用这段空间。当同一个芯片中还存在其他程序(如bootloader)时,我们会用到该指令。
3.3.10 引导加载程序
引导加载程序(bootloader)是位于程序存储器中的一段独立的程序,通常用于向程序存储器中加载应用程序。引导加载程序使用串口、USB口,或者一些其他手段来加载程序。通常,在重启的时候会启动bootloader,用于检测是否需要加载新程序或者程序是否开始运行。有时,bootloader也提供一些基础功能给应用程序使用。以本书的E3板子为例,bootloader用于加载应用程序并提供了USB相关函数,以帮助应用程序和PC之间传输数据。
3.3.11 #rom address={data}
该指令用于在芯片内存的特定位置写入数据。最常见的用法是初始化芯片上的EEPROM。许多PIC微控制器中都有一个内部数据存储器,在掉电之后仍然能够保存数据。编译器为我们提供了函数来读写这些存储器;然而,有时在程序启动前需要在内存中写入某些数据。该存储器地址在每个芯片上都不一样,预编译宏可以帮助我们在预编译阶段获取到这些地址。getenv()可以返回很多信息,本例中用它来获取EEPROM的数据起始地址:
对于PIC16F887,该指令相当于:
rom还有个特性,能够计算整个程序寄存器的校验和并将其存储到某个位置。在应用程序中使用该指令周期性地计算程序寄存器的校验和并与最初的值相比,从而保证内存中的内容没有变动。如果想将校验和存储在内存的最后位置,可以这么做:
因为地址是从0开始的,所以这里需要-1。如果芯片上有1024字的内存,它的地址范围就是0~1023。
这个校验和计算的是除存放校验和的内存之外的整个内存。
rom的另一个应用是在指定地址中存放数据,因为这些数据可能会根据外部条件发生变化。例如,某些特殊客户可能需要在产品中使用加密代码。#rom还可以用于指定设备的配置数据,如产品中所用引脚的数量。在程序中可以通过函数read_program_memory()读取#rom写入的数据,该函数使用程序存储器的绝对地址来读取数据。
3.3.12 #id data
许多芯片上还有一块特殊内存(可能只有一个字长),程序员可以在其中添加某种类型的标识。即使程序存储器由于读保护而不能读的情况下,这块区域仍然可以读取。因此,它可以用来存储固件版本或者存放校验和:
3.3.13 其他编译指示
下面是其他一些常用的预编译指令。在编译器参考手册的预编译器小节中可以找到完整的指令列表。