ARM接口编程
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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的端口。