310 likes | 604 Views
ARM C 语言. 一、文件包含伪指令. #include < 头文件名 .h> #include “ 头文件名 .h” #include 宏标识符. 1. #include < 头文件名 .h>. #include <stdio.h> #include <string.h>. 2 . #include “ 头文件名 .h ”. #include “2410lib.h” #include “2410b.h” #include “../LCD/lcd_test.h” #include “.. \ LCD \ lcd_tmp.h”.
E N D
一、文件包含伪指令 • #include <头文件名.h> • #include “头文件名.h” • #include 宏标识符
1.#include <头文件名.h> #include <stdio.h> #include <string.h>
2.#include “头文件名.h” #include “2410lib.h” #include “2410b.h” #include “../LCD/lcd_test.h” #include “..\\LCD\\lcd_tmp.h”
3.#include 宏标识符 #define LCDINCLUDE “c:/2410s/examble/lcd_def.h” #include LCDINCLUDE 展开后: #include “c:/2410s/examble/lcd_def.h”
二.#define 宏标识符 宏体 #define LED8ADDR (*(volatile unsigned char *) (0x2140000)) void Digit_Led_Symbol(int value) { if((value>=0)&&(value<16)) //符号显示 LED8ADDR=~Symbol[value]; /* 预处理器将LED8ADDR 替换为地址0x2140000的内存单元*/ }
#include <stdio.h> // ARM 2410S的LED程序 #include <stdlib.h> #include <fcntl.h> //ZLG7290 addr, take the addr as the cmd in the ioctl too. #define ZLG7290_ADDR_FlashOnOff 0x0c #define ZLG7290_ADDR_ScanNum 0x0d #define ZLG7290_ADDR_DpRam0 0x10 #define ZLG7290_ADDR_DpRamBase ZLG7290_ADDR_DpRam0 #define ZLG7290_ADDR_DpRam1 (ZLG7290_ADDR_DpRamBase+0x01) #define ZLG7290_ADDR_DpRam2 (ZLG7290_ADDR_DpRamBase+0x02) #define ZLG7290_ADDR_DpRam3 (ZLG7290_ADDR_DpRamBase+0x03) #define ZLG7290_ADDR_DpRam4 (ZLG7290_ADDR_DpRamBase+0x04) #define ZLG7290_ADDR_DpRam5 (ZLG7290_ADDR_DpRamBase+0x05) #define ZLG7290_ADDR_DpRam6 (ZLG7290_ADDR_DpRamBase+0x06) #define ZLG7290_ADDR_DpRam7 (ZLG7290_ADDR_DpRamBase+0x07) #define ZLG7290LED_DEV "/dev/i2c/zlg7290/0"
static int fd = -1; #define DEBUG #ifdef DEBUG #define DPRINTF(x...) printf("Debug:"##x) #else #define DPRINTF(x...) #endif struct LED_REG { unsigned char addr; //寄存器接口方式 unsigned char ctrl; }__attribute__ ((packed)) ; struct LED_MAP { char map; //显示内容 unsigned char raw; //映射段码 };
struct LED_MAP ledmap[] ={ {'0', 0xfc}, {'1', 0x60}, {'2', 0xda}, {'3', 0xf2}, {'4', 0x66}, {'5', 0xb6}, {'6', 0xbe}, {'7', 0xe0}, {'8', 0xff}, {'9', 0xfb}, }; unsigned char led_map_table[128]; unsigned char led_map(char map); int led_clear(int fd, unsigned char addr); #define CYCLE_LEFT (1) #define CYCLE_RIGHT (2)
三.函数及函数库 • 函数的定义格式 • 函数的原型说明格式
1.函数的定义格式 • [存储类说明符]类型说明符[修饰符]标识符(参数表){函数体} • 存储类说明符:static和extern • 类型说明符:void、char…… • 修饰符:interrupt、near、far、huge • 标识符:函数名、*函数名、(*函数名)、*(*函数名)……
1).存储类说明符 • static 表示在本文件定义前和非本函数定义文件中,该函数将不能使用。提高了函数的安全性。 • extern 在C中,函数是全局的,全程序可见。但在该函数定义之前调用该函数时,需要加原型说明;而且在多源文件的程序中,非定义该函数的文件中需要调用该函数时,必须在原型说明中使用extern。
2).类型说明符 • 简单类型 int、char、float long double等。 • 复合类型 struct、union • 指针类型 • 无类型 void
3).参数表 • 类型说明符 变量名 • void • 空
4).修饰符-对函数起修饰作用 • interrupt 将函数修饰为中断函数。中断函数的特点是返回值类型和参数均必须为void。经interrupt修饰的函数,编程时只需要编写中断服务程序的主体部分,中断服务的保护现场前缀段和恢复现场的后缀段,均由编译程序完成。另外,编译程序还把ret指令改为reti指令。 • near、far、huge 规定函数的地址类型。覆盖存储模式规定的函数缺省地址类型,指明函数与被调用函数的距离。near为近调用(16位段内地址);far为远调用(32位段间地址);huge为规范化远调用(32位段间规范地址)。
2.函数的原型说明格式 [存储类说明符]类型说明符[修饰符]标识符(参数表){函数体}; int led_clear(int fd, unsigned char addr); int led_clear(int , unsigned char); int led_cycle(int fd, int direction); int led_cycle(int , int);
int led_cycle(int fd, int direction); struct LED_REG reg[8]; int main(int argc, char** argv) { int i,j,k; if((fd=open(ZLG7290LED_DEV, O_RDWR))<0){ printf("Error opening %s ZLG7290 device\n", ZLG7290LED_DEV); return 1; } led_map_init(); //led 段码映射 for(i=0; i<=7; i++) { //clear all leds led_clear(fd, ZLG7290_ADDR_DpRam0+i); } for(i=0; i<=7; i++) { reg[i].addr=ZLG7290_ADDR_DpRam0+i; reg[i].ctrl=led_map(‘0’+i); //设定LED显存内容 write(fd, ®[i], sizeof(struct LED_REG)); } k=0; while(k<12) {led_cycle(fd, CYCLE_RIGHT); k++; } printf("Display over@\n");close(fd);return 0; }
int led_map_init() { int i; for(i=0;i<sizeof(ledmap)/sizeof(ledmap[0]);i++){ led_map_table[ledmap[i].map]=ledmap[i].raw; } return 0; } unsigned char led_map(char map) { return led_map_table[map]; } int led_clear(int fd, unsigned char addr) { struct LED_REG reg; reg.addr=addr; reg.ctrl=0; ioctl(fd, reg.addr, reg.ctrl); }
int led_cycle(int fd, int direction) //led 左右移位函数 { struct LED_REG temp, * preg; int i; preg=®[0]; if(direction == CYCLE_LEFT ) { preg+=7; temp = *(preg); do{ preg--; (preg+1)->ctrl = preg->ctrl ; }while(preg!=®[0]) ; preg->ctrl=temp.ctrl; } else {temp = *(preg); do{ preg++; (preg-1)->ctrl = preg->ctrl ; }while(preg!=®[7]) ; preg->ctrl=temp.ctrl; } for(i=0; i<8; i++) { write(fd, ®[i], sizeof(struct LED_REG)); } }
四.变量修饰符 • const • volatile • near、far
1. const 常量修饰符,指示被修饰的变量或指针变量是常量。在C语言中,单独开辟一个常量区域用于存放const变量。被修饰的变量必须被赋予初值,变量一旦被const修饰后值就不能再改变了。如: const int x=20; x=50;//错误被赋值 指针的修饰有下列两种方式,含义是不同的。 const int *ptr=&m;//常量指针,说明指针指向的对象是常量 int * const ptr=&n;//指针常量,说明指针本身是常量
2.volatile-易变的、反复无常的(性格) 易失性修饰符。所定义的变量或指针变量是可以被多种原因修改的(如可以在中断服务程序中被修改,会被I/O口修改),这种修改是随机的。因此使用volatile修饰符禁止对该变量的任何形式的优化,禁止该变量作为寄存器变量。防止它被随机改变。 编译器有一种技术是数据流分析,分析程序中的变量在哪里赋值,在哪里使用,在哪里失效。分析结果可以用于常量合并、常量传播等优化。当它判定你的代码没有修改变量的值时,它就可能提供上次访问变量时提供的缓存值,以提高程序的效率。但有时这种优化会带来问题,特别是对于硬件寄存器操作的程序,此时可以使用volatail关键词禁止做这些优化。
左边的代码会被优化为: int nn,mm,which_int which_int=rEXTENTPND; nn=which_int; mm=which_int; int nn,mm,which_int which_int=rEXTENTPND; nn=which_int; which_int=rEXTENTPND; mm=which_int; 但这样的优化结果可能导致错误。rEXTENTPND寄存器的内容在执行第1次读操作后可能会被外部中断写入新值,那么第2次读操作读出的内容与第1次的不同,变量mm和nn的值就会不同。但优化后的代码中mm和nn是肯定相同的。因此可以使用volatile来防止优化。正确的写法为: volatile int which_int;
volatile的作用 • 不会在两个操作之间把volatile变量缓存在寄存器中。在多任务、中断处理程序中,变量可能被其他程序改变,编译器自己无法知道。volatile就是告诉编译器不要优化,不要使用缓存值。 • 不做常量合并、常量传播等优化,所以下面的代码: volatile int i=1; if(i>0)printf(“%d”,i); if的条件不会当做无条件真。因为变量i被说明为volatile类型就是说该变量可能会被意想不到随时改变。 • 对volatile变量的读写不会被优化掉。如果一个变量赋值后但后面没有用到,编译器常常可以忽略那个赋值操作,然而对有些硬件寄存器的处理是不能这样优化的。
使用volatile变量的场合 • 硬件寄存器通常要加volatile,因为每次对它的读写都可能有不同的意义。如下列为中断屏蔽寄存器的定义: #define rINTMSK (*(volatile unsigned *)0x1e0000c) • 在中断服务程序中修改的其他程序检测的变量需要加volatile。如: volatile int which_int; • 多任务环境下各任务间共享的标志应该加volatile。在多线程访问某变量时,希望这些访问能读到变量的最新值,同时写到变量的操作能够立即实现。声明字段的时候如果加上了volatile,那么对该字段的任何请求,包括读和写操作,都会立刻得到执行。
五._ _irq • 为了方便使用高级语言编写异常处理函数,ARM编译器对异常处理函数作了特定扩展,只要使用关键词__irq,编译出来的函数就满足异常响应对现场保护和恢复的需要。
六.访问绝对地址的内存位置 对于定义 #define pISR_EINT4567 (*(unsigned*)(_ISR_STARTADDRESS+0x74)) 把无符号数“_ISR_STARTADDRESS+0x74”强制转换为指针,指向RAM中的地址空间,可以用下面的方法访问它。 pISR_EINT4567=(int)Eint4567ISR; 为了访问一个绝对地址,把一个整型数强制转换(typecast)为一个指针就可以了。
编写中断服务程序的基本原则 • 避免在中断服务中做浮点运算。编写中断服务程序应该遵循短而有效(SS,Simple and Short)的原则。若在中断服务程序中做浮点运算严重违背这一原则,有些处理器/编译器不允许在中断服务程序中做浮点运算。 • 中断服务程序不能有返回值,所以中断服务程序的返回值类型都是void。 • 中断服务程序不能传递参数,所以中断服务程序的参数列表void。 void _irqEint4567Isr(void)
七.定义变量时的技巧 char a; char c; short b; int d; char a; short b; char c; int d; 说明:pad为无效数据。
八.使用循环时的技巧 int fact2(int n) { float ti=1; for(i=n;i!=0;i--) ti=fact*i } int fact1(int n) { float ti=1; for(i=1;i<n;i++) ti=fact*i } Fact1: …… 0x000010: MUL R2,R1,R2 0x000014: ADD R1,R1,#1 0x000018: CMP R2,R0 0x00001C: BLE 0x10 …… Fact2: …… 0x000010: MUL R0,R1,R0 0x000014: SUBS R1,R1,#1 0x000018: BNE 0x10 ……
解释 • 累加法比递减法在循环中多用了1条指令,当循环次数比较大时,程序性能会产生明显的差异; • 当进行一个非0常数比较时,必须用专门的CMP指令来执行;ARM指令则可以直接利用条件执行的特性(NE)来进行判别; • 在ARM的体系结构中编程时,尽量使用递减至0的方法来设置循环条件。