Win-AVR是流行的GNU编译器在AVR平台上的移植。安装完毕后会在桌面上出现一下七个快捷方式,它们的作用如下:
|
Programmers Notepad
源文件的编辑软件,可提供应用程序接口
AVR Insight
GDB Debug的前端工具,用于仿真调试
TKInfo
GNU超文本格式的图形浏览器,用于浏览各种说明文档
Avr-libc Manual
AVR-GCC编译器的C语言函数库
GNU Manuals Online
GNU在线帮助手册
MFile
Makefile编辑软件
README
WinAVR的readme文件
Ø IDE (IntegratedDevelopment Environment)集成开发环境
Ø ICE (In CircuitEmulator) 在线仿真器
Ø JTAG (Joint Test Action Group) 联合测试行为组织
Ø *.hex 文件用来向单片机下载的16进制文件
Ø *.cof文件用来利用AVRstudio进行在线仿真和调试的文件
l AVR COFF(AVR Studio 3.x)
l AVR “Extended” COFF(AVR Studio 4.07+)
*.hex和*.cof这两个文件不能由AVR-GCC本身生成,需要其他辅助工具来完成这一步,这个工具叫avr-objcopy。
Makefile文件就是一个描述性质的文件,告知make各文件之间的依赖关系,不需要扩展名。
一般在MFile这个程序中生成对应的makefile文件,然后将它复制到PN源文件的文件夹下,就可以进行make all的命令来实现对源文件的编译了。一般而言,makefile文件只需要修改一下几个地方。Main file name (不要带扩展名)和MCU type 以及Default make target,然后点file-》save as保存到要编译的文件的文件夹下。
C语言小总结
Ø C语言的标识符是区分大小写的,标识符必须以字母或者下划线开头,下划线开头的标识符一般是编译器定义的。
Ø 一般而言,如果不是需要使用负整数,尽量使用无符号数整数来表示,这样可以减少系统处理符号的工作,从而提高程序的执行效率。
Ø 凡是耳目运算符,都可以和赋值符一起组成复合赋值符,C语言中规定可使用的10中复合赋值运算符:+=;-=;*=;/=;%=;<<=;>>=;&=;^=;|=
Ø 逗号运算符是C语言的一种特殊的运算符,其作用是将两个表达式连接起来,C语言对两个表达式分别计算,并将后一个表达式的值作为逗号表达式的值。运算优先级最低。例如:a=3*6,4*8;其结果为a=32;
Ø C语言规定了六种关系运算符:<;<=;>;>=;==;!=其中前四个的优先级比较高,后两个的优先级比较低。
Ø C语言规定的位运算共有一下几种:
& 按位与
| 按位或
^ 按位异或 (XOR) 1^0=1 1^1=0
~ 取反
<< 左移
>> 右移
位翻转:令其和一个相应位为1,其他位位0的常量做异或运算。
位置一:令其和一个相应位为1,其他为为0的常量做或运算。PORTA|=(1<<7)
位置零:令其和一个相应位为0,其他位为1的常量做与运算。PORTA&=~(1<<7)
Ø 一般的变量和数组均存储在系统的RAM中,AVR-GCC还支持存储在ROM的数组和字符串,该字符串使用PROGMEM关键字进行声明。如:
Const char str[] PROGMEM =”hello” ; // 声明了一个存储在ROM中的字符串
Ø 字符串是不能在程序中用赋值符直接赋值的,但可以借助循环语句,一个变量一个变量的进行拷贝,如下:
char a[5]=”hello” ;
char b[5];
b=a; /*这样的操作是非法的*/
for(i=0;i<5;i++)
{
b=a;
} //利用循环语句一个变量一个变量的拷贝
Ø 函数的声明用于函数的定义部分在函数实际调用之后,声明的格式如下(必须加分号):
函数返回值类型 函数名称(类型名形式参数1,类型名形式参数2,……);
例如:int add(int var1,int var2);
Ø 指针变量存储的是地址数据,因此通过指针操作,程序可以对内存等系统中编址的设备进行控制,尤其是对经常要对外部设备进行操作的单片机系统就显得非常重要。C语言里,字符串相当于一个字符型数组,作为一个数组,当然能够和指针建立联系,因此,字符串也能够用指针变量实现。
例如: charstring1[ ]=”GNU” ;
Char *string2=”GNU” ;
事实上,二者的声明在本质上是一样的,在第二行的声明中,C语言同样要为string2开辟一个4个变量的存储区域。但是用指针方式操作字符串更加灵活,这是因为只要给字符串开辟了足够的空间,字符串指针就可以在需要的时候赋值。
例如: char string1[ ]=”GNU” ;
char string2[4];
char string3[4];
string2[4]=”GNU”; //错误,不能给数组赋值一个字符串
string3=”GNU”; //正确,按照指针方式赋值
函数指针:函数生成代码时也具有地址,因此也可以定义函数指针,定义形式如下:
函数返回值数据类型 (*函数指针标识符)();
函数指针的引用形式为: (*函数指针标识符)(实参列表)
例如:
int test(intstatus); //声明一个函数
intmain(void)
{
int (*p) (); //声明一个函数指针
p=test; //令p指向test函数
(*p)(0x0098); //调用指针指向的函数
……
return 0;
}
指针数组:如果一个数组里面的元素是指针,则该数组就是指针数组,其声明形式为:
数据类型 *数组名[常量表达式]
例如: int *array[10]; //声明了含有10各整型指针的指针数组
指针数组可以用来存储字符串,在字符串之间的长度相差很大时可以使用最小的内存。如果使用二维字符数组进行存储,则数组的宽度必须与最长的字符串相同。
例如: char *string[ ]={“hello”,” it isa dog”,” this”}; //存储了三个字符串
Ø C与与语言可以构造以下四种数据类型:
l 结构体:将一些变量组合在一起,作为一个整体进行使用,用于表达某种逻辑关系
l 共同体:几种不同的数据类型共用一片地址
l 枚举类型:规定变量的取值只有有限的情况,不能取其他值
l 用户自定义类型:用户对已有的类型赋予新的类型标识符
Ø 结构体:
struct 结构体名
{
数据类型成员1;
数据类型成员2;
…………
数据类型成员n;
}机构体变量名;
例如:
struct student
{
char name[20];
char sex;
float score;
};
struct student student1,student2,student3[10];*student4;
student1.score=95.5;
*(student1.name)=”xiaoli” //给结构体变量的成员赋值
student1.sex=’M’;
student4=&student2;
student4->sex=’M’;
student3[1].sex=’M’;
student3[2].score=95.5;
Ø 共同体:共同体的所有成员都占有相同的空间,共同体占用的空间的大小等于共同体内占用空间最大的变量所占用的空间。共同体类型声明形式如下:
union 共同体名
{
数据类型 成员名1;
数据类型 成员名2;
…………
数据类型 成员名n;
};
共同体的变量声明形式为:union 共同体名 共同体变量名
共同体变量引用的形式为:共同体变量.名成员;
注意共同体的数据是存储在一个存储空间的,因此共同体变量所存储的值等于最后一次对共同体变量所赋的值。
不能对共同体变量名赋值,也不能在定义共同体变量时对其初始化,对共同体的操作必须指明其成员。不能把共同体变量作为函数的参数和返回值。
例如:
unionlength_union
{
Unit16_t word;
Unit8_t byte[2];
}length;
//定义了一个共同体,有两个成员,一个是16位的无符号整数,
另外一个是一个包含两个无符号8位整数的数组,二者共用存储空间。
在上例中,如果按照length_union..word来操作时,则作为一个16位无符号整数,如果作为length_union.byte[0]和length_union.byte[1]操作,则访问的是该存储空间的低位字节和高位字节。
Ø 枚举类型
如果变量只有几个可能的取值,则可以用枚举类型表示该变量。枚举类型定义形式为:
enum 枚举类型标识符 {枚举列表};
枚举变量的定义为:enum 枚举类型 枚举变量名;
例如: enum color {red,green,blue};
C语言将枚举类型中的枚举元素定义为常量,因此枚举元素是有值的,C语言自动按照值的顺序定义值为0,1,2,……,枚举元素的值可以改变
enumcolor{red=5,green,blue}; //red代表5,后面的未定值依次加一
例如:
enumcolor{red,green,blue};
enumcolor crt;
crt=red;
crt=2; //错误
crt=(enumcolor)2; //将枚举元素中的值为2的元素赋值给crt变量,强制转换。
枚举变量的赋值必须用枚举元素,如果直接用整数赋值是不行的,必须采用强制转换。
Ø 用户自定义类型
用户可以对已存在的C语言类型名重新进行定义,从而方便使用,用户定义类型一般为: typedef 类型名 标识符
使用这些自定义类型是,必须包含inttypes.h头文件。
例如:
#include<inttypes.h>
typedefsigned char int8_t //八位有符号数 (无分号)
typedefunsigned char uint8_t //八位无符号数
Ø 预处理
C语言的预处理功能主要有一下三方面:
l 宏定义
l 文件包含
l 条件编译
Ø 宏定义
宏定义的作用是用指定的标识符代表一个字符串,宏可以参数,也可以不带参数,不带参数的宏定义的一般形式为: #define 标识符 字符串
带参数的宏定义声明形式为: #define 宏名(参数表) 字符串
例如:
define S(a,b) a*b //定义了一个带参数的宏
…………
int main(void)
{
int i;
……
i=S(5,6); //该语句与i=5*6完全一样
……
return 0;
}
Ø 文件包含
文件包含的作用是将一个文件的内容包括到另一个文件之中,文件包含的形式为:
#include “文件名”
#include <文件名>
第一种首先在当前文件的所在目录中寻找包含的文件,如果找不到再到系统指定的包含文件的目录去寻找,第二种则直接在系统指定的包含目录中去寻找,一般为了保险起见,尽量使用双引号形式的include指令。
Ø 条件编译
条件编译可以根据用户定义的不同条件,选择使用不同的语句,这在编写可移植的程序时特别有用。条件编译有以下几种形式:
l 第一种
#ifdef 标识符
程序段 1
#else
程序段 2
#endif
该语句的作用是,如果在此语句前用“#define标识符”定义了标识符,则只在程序中包含程序段1,否则只包含程序段2.
l 第二种
#ifndef 标识符
程序段1
#else
程序段2
#endif
该语句的作用是,如果在此语句前没有用“#define 标识符”定义了标识符,则只在程序中包含程序段1,否则只包含程序段2
l 第三种
#if 表达式
程序段1
#else
程序段2
#endif
该语句的作用是,如果表达式的值为真,则值在程序中包含程序段1,否则,若表达式的值为0,则只在程序中包含程序段2.
Ø AVR-LIBC的中断处理函数
对于不同的编译器有不同的方法处理中断,这是因为C语言的目标就是与处理器的细节无关,因此每个编译器的作者都不得不使用自己的方法为编译器添加对中断的支持。
在AVR-LIBC的环境中,中断向量表已经预先固定指向具有特定名字的函数,这些函数用来执行中断操作。使用这些特殊名字的函数可在相应中断发生时被调用。
AVR-LIBC中,对中断处理函数进行了封装,通过使用两个参数的宏INTERRUPT()和SIGNAL(),程序可正确的处理不同的中断操作。
#include “avr/signal.h”
INTERRUPT(SIG_ADC)
{
//用户代码
}
或者为
#include “avr/signal.h”
SIGNAL(SIG_ADC)
{
//用户代码
}
Ø 全局中断标志操作函数:
#define sei() _asm_ _volatile_(“sei” ::)
使用中断处理函数,必须加入下面的包含语句:
#include <avr/interrupt.h>
sei(); //设置全局中断标志位允许全局中断
#define cli() _asm_ _volatile_(“cli” ::)
使用中断处理函数,必须加入下面的包含语句:
#include <avr/interrupt.h>
cli(); //清楚全局中断标志位来禁止全局中断
上面的两个函数实际上只生成一条汇编指令,不会增加用户程序的负担。
Ø 使用算术运算函数
avr-libc提供了算术运算函数,使用数学的数学运算函数请在程序的头部加入包含语句:
#include “math.h”
为了使用数学运算函数,需要链接库libm.a,通常该库是不链接到用户的程序中的,为了链接库,请在自己的GCC编译选项的结尾加入-lm选项。即用PN打开makefile文件的第130行,MATH_LIB= —lm
Ø 特殊功能寄存器的操作方式:
在单片机系统中,经常要对寄存器或者外围设备进行操作,这些操作都是通过一组特殊功能寄存器的操作实现的。
AVR单片机中,提供了两种方法完成操作。一种是独立的I/O地址空间,通过特殊的I/O操作指令,可以利用特殊的I/O指令操作部分或全部的I/O空间。另外一种是I/O地址也被映射到单片机的内存空间中,因此也可以用通常的内存操作指令完成I/O的控制,I/O地址加上0x20的偏移量就是I/O映射到内存空间中的地址。AVR单片机支持这两种操作,一般使用后者,而且这些操作已经封装好,不需要编程者干预。因此,编程者可以使用特殊函数如outb()操作I/O。
#include<avr/io.h>
outb(PORTA,0x33);
或者直接向内存中映射的地址写数据 PORTA=0x33;
编译器会选择合适的指令生成操作代码访问I/O端口,与编程人员书写的代码无关。因此,即使编程者使用内存映射的方式书写C代码,例如: PORTA|=0x44,编译器也会在优化时自动使用直接I/O地址访问的方式生成汇编代码,并且尽量可能使用位操作方式(如sbi指令)。
使用内存映射方式操作特殊功能寄存器,可以使C程序更方便地移植到其他AVR的C编译器中,同时也可以部分提高程序的可读性。
在不优化大的情况下,编译器按照表达式的形式生成内存映像方式的代码,但是在打开编译器优化的选项后,编译器会根据操作的内容自动使用更快的in/out指令生成代码。