[本文来源于网络]
---------------------------------------------------------------------------------------
基本和特殊类型
本部分描述什么是基本数据类型和特殊数据类型和变量如何用这些类型定义。在CCS的C必须在使用之前被定义。变量可以在子函数里被定义,也可以全局定义。这个影响变量的使用范围和生命力。
基本类型:
Basic Types <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
Type-Specifier | |
int1 | Defines a 1 bit number |
int8 | Defines an 8 bit number |
int16 | Defines a 16 bit number |
int32 | Defines a 32 bit number |
char | Defines a 8 bit character |
float | Defines a 32 bit floating point number |
short | By default the same as int1 |
Int | By default the same as int8 |
long | By default the same as int16 |
void | Indicates no specific type |
注意:所有的类型,除去浮点(float),默认的是无符号(unsigned);然而, 可能由unsigned或signed在之前。Short和long要有关键字INT跟在后面没有影响。同样查看#TYPE来改变默认的大小。
SHORT是一种特殊类型用来产生非常有效的位操作和I/O排列的位 (INT1 或SHORT)代码在RAM 是支持的 。指向位的指针是不允许的。
Type-Qualifier | |
static | Variable is globally active and initialized to 0. Only accessible from this compilation unit. |
auto | Variable exists only while the procedure is active. This is the default and AUTO need not be used. |
double | Is a reserved word but is not a supported data type. |
extern | External variable used with multiple compilation units. No storage is allocated. Is used to make otherwise out of scope data accessible. there must be a non-extern definition at the global level in some compilation unit. |
register | Is allowed as a qualifier however, has no effect. |
_fixed(n) | Creates a fixed point decimal number where n is how many decimal places to round to. |
特殊类型
Enum 枚举类型:产生一个整形常量的列表。
Emum [id] {[id[=cexpr]]}
一个或多个逗号隔开
id在ENUM后面创造一个类型要足够大来放最大的常量在列表里。id在列表中每个创建一个常量。默认的第一个id是设置成0随后的是递增1的。如果a =cexpr跟在一个id后面id将有一个常量表达值并且接下来的列表将加一。
如下例:
enum colors{red, green="2",blue}; // red will be 0, green will be 2 and blue will be 3
Struct 结构体类型:创建一个或多个变量的集合,或者是不同的类型,组合成一个单元。
如下例:
struct data_record {
int a [2];
int b : 2; /*2 bits */
int c : 3; /*3 bits*/
int d;
}; // data_record is a structure
Union 联合体类型:集合不同类型和大小的对象,编译器跟踪大小和队列要求。他们提供一种操作不同类型的数据在同一个单独的存储区的方法。
union
一个或多个用分号分隔 0或多
如下例:
union u_tag {
int ival;
long lval;
float fval; }; // u_tag is a union type that can hold a float
如果typedef被用在基础或特殊的类型他创建一个新的定义后能被用的类型名。标识不分配空间而是被用来作为其他数据定义的指定类型。
typedef [type-qualifier] [type-specifier] [declarator];
如下例:
typedef int mybyte;// mybyte can be used in declaration to specify the int type typedef short mybit; // mybyte can be used in declaration to specify the int type typedef enum // colors can be used to declare variables of this enum type
{red, green="2",blue}colors;
__ADDRESS__: 预定义符号 __ADDRESS__用来指定的类型必须保留程序区地址。
如下例:
___ADDRESS__ testa = 0x1000 //will allocate 16 bits for testa and initialize to 0x1000
一个声明详细说明一种类型的限制和类型的详细说明,后面跟着一个或多个这种类型的变量。
如下例:
int a,b,c,d;
mybit e,f;
mybyte g[3][2];
char *h;
colors j;
struct data_record data[10];
static int i;
extern long j;
变量也可以声明在特殊类型的后面。
如下例:
enum colors{red, green="2",blue}i,j,k; // colors is the enum type and i,j,k are variables of that type
Non-RAM Data Definitions
CCS C 编译器同样提供用户限定地址模式能在RAM, program eeprom, data eeprom 或者外部存储空间设定存储范围。 地址模式代替了以前的类型模式(用不同的语法)。
使用方法:
addressmod (name,read_function,write_function,start_address,end_address);
这里的read_function和write_function应该为RAM空间,或者为其他的存储器应该是如下原型:
// read procedure for reading n bytes from the memory starting at location addr
void read_function(int32 addr,int8 *ram, int nbytes){
}
//write procedure for writing n bytes to the memory starting at location addr
void write_function(int32 addr,int8 *ram, int nbytes){
}
Example:
void DataEE_Read(int32 addr, int8 * ram, int bytes)
{
int i; for(i=0;i<=bytes;i++,ram++,addr++)
*ram=read_eeprom(addr);
}
void DataEE_Write(int32 addr, int8 * ram, int bytes)
{
int i; for(i=0;i<=bytes;i++,ram++,addr++)
write_eeprom(addr,*ram);
}
addressmod (DataEE,DataEEread,DataEE_write,5,0xff); // would define a region // called DataEE between //0x5 and 0xff in the //chip data EEprom.
void main(void)
{
int DataEE test;
int x,y; x="12";
test=x; // writes x to the Data EEPROM
y=test; // Reads the Data EEPROM
}
注意:如果是在RAM空间定义那么读写函数不是必须的,变量分配的存储区根据地址来分配在正确的表达式作为常规的变量。任意的结构或数据类型都可以地址指派。指针也可以指向这种数据类型。#type 指示用于为变量分配默认的存储空间。
语法如下:
#type default="addressmodname" // all the variable declarations that
// follow will use this memory region
#type default= // goes back to the default mode
Type default="emi" //emi is the addressmod name defined
char buffer[8192];
#include <memoryhog.h>
#type default=
Using Program Memory for Data
CCS C 编译器提供一些不同的方式在程序区储存数据。详细比较如下:
Constant Data:
Const限定词将在程序区中代替变量。语法是const type specifier id [cexpr] ={value} 如果在标示符之前有一个关键字CONST,标示符就被作为一个常量。常量可以初始化但是在运行时不能改变。这样很容易创建一个查找表格。
如下例:
const int table[16]={0,1,2...15}
在ROM中代替一个字符串
const char cstaring[6]={"hello"}
同样可以创建一个常量的指针
const char *cptr; cptr = string;
#org前缀用于为常量指定地址空间。
如下例:
#ORG 0x<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />1C00, 0x1C0F
CONST CHAR ID[10]= {"123456789"};
这个ID将在1C00空间。
注意:一些额外的代码将产生123456789。新方法允许使用指向ROM的指针。新关键字编辑模式CCS4和ANSI是ROM和其他模式的是_ROM。这种方法不包括在开始处的结构代码。
如:
char rom commands[] = {“put|get|status|shutdown”};
函数label_address 用于获得常量的地址。在代码中可以获取常数的变量。这个是很好的方法在大量的程序中用来保存常量数据。变量长度常量字符串可以被存储到程序空间。
PIC18 芯片编译器允许非标准c的特点来执行常量数组的可变长度串。
语法:
const char id[n]
这里n是任意的id是表格标识
例如:
const char colors[]
#ROM 标识:
另外的方法是用#rom给数据分配程序空间,使用方法是#rom address ={data, data,..,data}.
例如:
#rom 0x1000={1,2,3,4,5} // will place 1,2,3,4,5 to rom addresses starting at 0x1000
可以用字符串#rom address={"hello"} // 字符串以null结尾。这种方法只用于初始化程序空间。
Built-in-Functions内建函数:
编译器也提供程序区的内建函数:
? write_program_eeprom(address,data)- 写16位数据到程序区
? write_program_memory(address, dataptr, count ); writes count bytes of data from dataptr to address in program memory.
请参考帮助来获得更多的关于这些函数的细节关于他们的使用方法和限制。这些函数只用于可以写程序区的芯片。编译器擦除flash和写程序来执行这些功能。
用以上的三种方法读在程序区的用户代码:
? read_program_eeprom(address)- reads 16 bits data from the address in program memory.
? read_program_memory((address, dataptr, count ) -Reads count bytes from program memory at address to RAM at dataptr.
这些函数只用于那些允许读程序区的芯片。编译器用flash空间来读程序执行这些函数。
PCB,PCM和PCH编译器是不同的,PCB用于12位单片机,PCM用于14位单片机,PCH用于16位pic单片机
例如:
#if defined(__PCB__)
#include <12C508.h>
#fuses INTRC,NOWDT,NOPROTECT, NOMCLR
#use delay(clock=4000000)
#define GP0 PIN_B0
#define GP1 PIN_B1
#define GP2 PIN_B2
#define GP3 PIN_B3
#define GP4 PIN_B4
#define GP5 PIN_B5
#elif defined(__PCM__)
#include <12C671.h>
#fuses INTRC, NOWDT, NOPROTECT, NOMCLR,NOLVP
#use delay(clock=4000000)
#define GP0 PIN_A0
#define GP1 PIN_A1
#define GP2 PIN_A2
#define GP3 PIN_A3
#define GP4 PIN_A4
#define GP5 PIN_A5
#endif
全局注释:制定的注释在你源代码的最前面。
如:
//*PURPOSE This program implements a Bootloader.
//*AUTHOR John Doe
“//”后跟着“*”是要告诉编译器以下是注释。
多行注释在“*”的后面加上“:”
如:
/**:CHANGES 05/16/06
Added PWM loop 05/27.06 Fixed Flashing problem */
#use_fast_io(port)
Port是A~G
影响编译器产生跟在后面的输入输出代码。这种作用直到另外一个use xxxx_io时结束。这种快速的方法做的I/O不用设置方向寄存器。用户必须确保正确用set_tris_x()设置方向寄存器。当链接多文件时,方向设置只适用于当前文件。
#use_fixed_io
#ORG
语法:
#org start, end
or
#org segment
or
#org start, end {}
or
#org start, end auto="0"
#ORG start,end DEFAULT
or
#ORG DEFAULT
原理:
开始用第一个ROM的地址 (字的地址) ,结束用最后一个ROM的地址, 段开始于先前的ROM的地址
目的:
这个标识将定位后面的函数或者是常量在ROM中明确的空间。先前定义的段的末端可能会被忽略如果你只想增加另一个函数到段里。 在ORG的后面跟着{}部插入内容只保留空间。ORG函数的RAM空间可能从低位开始所以局部变量和临时变量应该放在低地址。这只被用于ORG无返回的时候。使用过的RAM 和主程序的RAM会交叠。增加一个AUTO=0在#ORG行的结尾处。
如果使用了关键字DEFAULT 这个地址范围用于用户自定义函数并且编译器产生范围直到另外一个#ORG DEFAULT出现(没有地址范围)。
当链接多单元时,要清楚这个方法是否适合于目标文件。任意的#org 交叠都是错误的除非#org正确匹配。
例如:
#ORG 0x1E00, 0x1FFF
MyFunc() {
//This function located at 1E00
}
#ORG 0x1E00
Anotherfunc(){
// This will be somewhere 1E00-1F00
}
#ORG 0x800, 0x820 {}
//Nothing will be at 800-820
#ORG 0x1C00, 0x1C0F
CHAR CONST ID[10}= {"123456789"};
//This ID will be at 1C00
//Note some extra code will
//proceed the 123456789
#ORG 0x1F00, 0x1FF0
Void loader (){
.
.
.
}
这些指示标识以下的函数的中断函数。中断函数没有参数。不是所有的指示都用于所有的芯片,参考设备的.h文件看芯片有效的中断或者在PCW用下拉VIEW | Valid Ints查看。 编译器会产生检测到中断发生跳过的代码。它将产生保存和恢复机器状态,并且清中断标志位。为了标志位不被清除在#INT_xxxx的后面加上NOCLEAR。
应用程序必须调用 ENABLE_INTERRUPTS(INT_xxxx)来开中断,开中断之前ENABLE_INTERRUPTS(GLOBAL)来使能中断。关键字HIGH和FAST用于 PCH的编译器来标志中断的优先级。高优先级的中断可以中断其它的中断。中断前有FAST标记执行时不保存和恢复任何寄存器。要做的尽可能少可以保存需要的寄存器值。有HIGH标记的中断可正常使用。具体可参见#DEVICE。
如下中断子程序
例子:
#INT_TIMER1 // This function is called every time the RTCC (timer1) overflows (65535->0).
void clock_isr() {
counter++;
if (counter >= 200 ) // 200 * 10000uS = 2 seconds
{ printf("\r\n\ Timer1 interrupt(2 seconds) action ...\r\n");
counter = 0;
}
set_timer1(seconds); // Generate 1.001Khz square wave.
}
中断子程序前要加上相应的中断名。在此为#int_timer1。
语法:
#asm
Or
#asm ASIS
Code
#endasm
目的:
在#asm和#endasm之间的代码是汇编语言。允许在任何地方使用。
例:
int find_parity (int data)
{
int count;
#asm
movlw 0x8
movwf count
movlw 0
loop:
xorwf data,w
rrf data,f
decfsz count,f
goto loop
movlw 1
awdwf count,f
movwf _return_
#endasm
}
delay_cycles()
语法:
delay_cycles(count)
参数:count是一个1-255的常数。无返回值。
作用:产生一个程序延时,一个指令周期相当于四个时钟周期。
例:
Delay_cycles(1); //相当于一个nop指令
Delay_cycles(25); //在20mhz的频率时,相当于5us的延时
#bit D4 = 0x08.4 D4 = 1;
PORTD = PORTD | 0x10;
PORTD|= 0x10;
bit_set(PORTD, 4);
output_high(PIN_D4)
这些写法的效果完全相同,编译出来的汇编码都是 BSF PORTD,4。
// EX_7.C 为什么写入 PIN_D4 竟然会造成 PIN_D5 被改变的副作用?
//
// 外部电路: PIN_D5 与地线间接0.1 uF电容
//
#include <16f877.H>
#use delay ( clock = 20000000 )
#define TRISD *(int8 *)(0x88)
#byte PORTD = 0x08
#bit D4 = 0x08.4
#bit D5 = 0x08.5
void main()
{
PORTD = 0;
TRISD = 0;
D5 = 1; // PIN_D5 与底线间接 0.1 uF电容
// delay_us( 50 );
D4 = 1; // 有这行,但沒有 delay_us(),PIN_D5 就不能输出高电位!?
while(1);
}
解答: 這是 RMW (Read-Modify-Write) 操作所造成的副作用。D4 = 1; 和 PORTD = PORTD | 0x10; PORTD|= 0x10; bit_set(PORTD, 4);等三种写法的效果完全相同,编译出来的汇编码都是 BSF PORTD,4。也就是说,设定D4时就要先读回整个PORT,所以,有可能会因为接脚的外部状态而影响到该脚的后续状态在本例中,若设定 D4=1 时,D5 脚的电压因电容作用而尚未升高到逻辑 1 的电压则读回 0,接着就写回 0,结果 D5 脚就永远不能输出高电位。
对F877 而言, output_high(PIN_D4) 基本上与 D4 = 1 相同;
对F458 而言, output_high(PIN_D4) 则读写Data_Latch暂存器,因此,若用 F458并改用output_high()就不会有这种副作用了。简言之,CCS的 output_high 指令虽然看起来不如 D4=1简洁,但是安全性与移植性较高,再配合#use fast_io() 使用就有更大的弹性了。与PORT I/O 相关的 RMW 指令包含BSF, BCF,CLRF, BTG 等...
文章评论(0条评论)
登录后参与讨论