1.4 嵌入式C语言
1.4.1 寄存器定义解释
在具体讲解寄存器的定义及使用方式之前,首先看一个例子。
#ifdef __BIG_ENDIAN #define rBCDSEC (*(volatile unsigned char*)0x57000070) #else
根据上述定义,思考如下问题:
➢ 为何要分字节序符定义?
➢ volatile表示什么含义?
➢ 为何要用无符号的数字?
➢ 宏定义最前面的*表示什么含义?
1.为何要分字节序符定义
寄存器的地址位于一个线性空间上,但是不同字节序下,同一寄存器的地址会发生变化。这可参见CPU的手册,为了隐藏这个细节,所以定义两套。
另外,已经知道32位寄存器的各个位是固定的,不同的CPU字节序有不同的结果,这样进行对位操作时,同一个寄存值在不同字节序下,会产生不同的位排列。因此为防止字节序对寄存器进行干扰,后面的ADS的C代码一般会直接采用大端字节序,在Linux下很多时候会采用移位来排除字节序的干扰。
2.volatile表示的含义
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,如操作系统、硬件或者其他线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
volatile能区分C程序员和嵌入式系统程序员最基本的问题。嵌入式程序员经常同硬件、中断、RTOS等打交道,所有这些都要用到volatile变量,因此必须熟悉volatile的相关知识。
当要求使用volatile声明的变量值时,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据,并且读取的数据立刻被保存。
如一个变量没有volatile,则编译器会自动进行优化,如自动清零。如果这个整数是一个多线程,或硬件地址,那么可能会带来意想不到的结果。
volatile的应用场合:
➢ 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同的意义,如并行设备的硬件寄存器(如状态寄存器)。
➢ 中断服务程序中修改的供其他程序检测的全局变量需要加volatile,如一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)。
➢ 多任务环境下各任务间共享的数据,如多线程应用中被几个任务共享的变量。
3.为何要用无符号的数字
数字有两种移位:一种是算术移位,一种是逻辑移位。前者在有符号位的情况下,会对符号位做特殊处理。而且寄存器对应的就是原始位,最高位也有特定的含义。这种情况下,指定为无符号位数是为了防止编译器对最高位采用特殊算术移位,从而违反设计者的本意。
4.宏定义最前面的*表示的含义
表示取指针的内容,类似于char *p=&a; *p='A'但这里不是一个变量,而是一个常量地址。但效果是一样的,相当于取对应地址的内容,或者对这个地址赋值。
1.4.2 寄存器操作
因为每个寄存器都为32位宽,因此在程序中可将寄存器的值看成一个无符号整数,对寄存器的取值看成是取一个整数值,而对寄存器设置看成是对一个整数变量赋值。
在很多情况下,人们需要取寄存器某一位或某几位的值。这时需要用到两个固定的位操作,即置位操作和取位操作。通常的操作方法为:置1位操作采用 |=,置0位操作采用 &=~。
1.置某一位为0
➢ 置第一位为0。
//1取反,表示0x11111110 ,与上rRTCCON表示将最低0位置0,其余位不变 rRTCCON&=~1; //RTC read and write disable
➢ 置第三位为0。
//0x0100 取反。再与上相应的寄存器地址 rRTCCON &= ~0x4;
➢ 假设置12~13 bit为0,则二进制展开为11000000000000,换算为0x3000。
rADCDAT0 &=~0x3000;
➢ 置采用左移或右移来置位,以置14bit为0为例。
rADCDAT0 &=~(0x1<<14);
2.置某一位为1
➢ 置第一位为1。
//1取反,表示0x00000001 ,或上rRTCCON表示将最低0位置1,其余位不变 rRTCCON|=1; //RTC read and write disable
➢ 置第三位为1。
//0x0100,再或上相应的寄存器地址 rRTCCON |= 0x4;
➢ 假设置12~13bit为1,则二进制展开为11000000000000。
rADCDAT0 |=0x3000;
➢ 置采用左移,或右移来置位,以置14bit为例。
rADCDAT0 |=(0x1<<14);
3.取位操作
➢ 假设是否判断rADCDAT0的第14位的值。
If(rADCDAT0&(0x01<<14)) //值为1的操作 else //值为0的操作,假设取rADCDAT0的12~13bit的值 (rADCDAT0 & 0x3000)>>12;
现在来看一个例子:
rGPBCON&=~0x3; //set GPB0 as tout0,pwm output rGPBCON|=0x2; //
在上面的程序中,0x3的二进制表示为00000011,取反就是11111100。与上(&)它,表示将低1、低2位置0,现在看一下GPBCON的DataSheet,GPB端口描述如表1.2所示。
表1.2 GPB端口描述
rGPBCON &=~3表示将GPB0端口设为00,即设为input端口。
再次执行rGPBCON |=2即将GPB0设为0x2=00000010,即TOUT0的端口。