C语言是一门通用计算机编程语言,应用广泛。C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。尽管C语言提供了许多低级处理的功能,但仍然保持着良好跨平台的特性。
以一个标准规格写出的C语言程序可在许多电脑平台上进行编译,甚至包含一些嵌入式处理器(单片机或称MCU)以及超级电脑等作业平台。二十世纪八十年代,为了避免各开发厂商用的C语言语法产生差异,由美国国家标准局为C语言订定了一套完整的国际标准语法,称为ANSI C,作为C语言最初的标准。
控制器(英文名称:controller)是指按照预定顺序改变主电路或控制电路的接线和改变电路中电阻值来控制电动机的启动、调速、制动和反向的主令装置。由程序计数器、指令寄存器、指令译码器、时序产生器和操作控制器组成,它是发布命令的“决策机构”,即完成协调和指挥整个计算机系统的操作。
那么C语言如何控制寄存器呢?接下来我们一起来了解一下。
C语言控制寄存器
在嵌入式软件的开发过程中,我们常用的语言主要是:汇编语言和C语言。相比较于汇编语言,C语言对我们来说,更贴近我们的一些语言习惯。在DSP的开发过程中,我们主要还是用C语言,其中最最常用的操作就是对于DSP各个寄存器的控制了。
那么如何用C语言对DSP的寄存器进行操作呢?
我们先来说书单片机里面是如何操作的:一般寄存器在单片机头文件中的宏定义都有如下的形式:
#define TIFR *((volatile unsigned char *)0x58) /*ATmega16的TIFR寄存器*/
在ATmega16中TIFR寄存器的地址是0x0058,我们要实现:
TIFR = 0x01
这条,就是要把0x58这个地址的内容修改成0x01。 而在C语言中,指针就是地址。现在要告诉编译器0x58是地址,就要把0x58强制转换成指针(unsigned char *)0x58。 这样(unsigned char *)0x58就表示TIFR在ATmega16 中的地址了,而*((unsigned char *)0x58)表示这个地址的内容。
然后如果想对寄存器TIFR单个的位进行下面的操作,
(1)将寄存器TIFR的第1位置“1”
TIFR |= (1 《《 1);
(2) 将寄存器REG的第3位清零
TIFR &= ~(1 《《 3);
(3) 将寄存器REG的第3、5位置“1”
TIFR |= (1 《《 5) | (1 《《 3);
(4) 将寄存器REG的第3、5位清零
TIFR &= ~( (1 《《 5) | (1 《《 3) );
在单片机里面是使用宏定义的方式来对寄存器进行操作。而在DSP里面,是使用位定义+共同体的方式来定义和操作寄存器。如下:
// ECCTL1控制寄存器的位定义如下:
struct ECCTL1_BITS { // bits description
Uint16 CAP1POL:1; // 0 Capture Event 1 Polarity select
Uint16 CTRRST1:1; // 1 Counter Reset on Capture Event 1
Uint16 CAP2POL:1; // 2 Capture Event 2 Polarity select
Uint16 CTRRST2:1; // 3 Counter Reset on Capture Event 2
Uint16 CAP3POL:1; // 4 Capture Event 3 Polarity select
Uint16 CTRRST3:1; // 5 Counter Reset on Capture Event 3
Uint16 CAP4POL:1; // 6 Capture Event 4 Polarity select
Uint16 CTRRST4:1; // 7 Counter Reset on Capture Event 4
Uint16 CAPLDEN:1; // 8 Enable Loading CAP1-4 regs on a Cap Event
Uint16 PRESCALE:5; // 13:9 Event Filter prescale select
Uint16 FREE_SOFT:2; // 15:14 Emulation mode
};
struct ECCTL1_BITS bit;
在上面的代码中我们用位域的方式表示了 TCR寄存器的数据结构。同时声明了一个
struct TCR_BITS类型的变量bit,那么我们就可以通过bit对寄存器每个位进行控制,比如
bit.CTRRST2=0;
此时有一个问题,就是如果我们想对整个寄存器进行整体的控制该如何呢?我们通过定义共同体来实现既可以对寄存器的每个位进行控制,又能对寄存器整体方便的控制。如下:
union ECCTL1_REG {
Uint16 all;
struct ECCTL1_BITS bit;
};
TCR_REG.all=0xxxx; //对寄存器整体操作
TCR_REG.CTRRST2=0; //对寄存器单个位操作
而在DSP里面,某一个功能是靠一个模块来实现的,而每一个功能模块包含了许多不同的寄存器,比如28335里面CAP脉冲捕获模块的寄存器就有以下这么多:
为了方便统一管理和编程开发方便,TI公司将ECap模块的所有寄存器定义成一个结构体ECAP_REGS,如下:
struct ECAP_REGS {
Uint32 TSCTR; // Time stamp counter
Uint32 CTRPHS; // Counter phase
Uint32 CAP1; // Capture 1
Uint32 CAP2; // Capture 2
Uint32 CAP3; // Capture 3
Uint32 CAP4; // Capture 4
Uint16 rsvd1[8]; // reserved
union ECCTL1_REG ECCTL1; // Capture Control Reg 1
union ECCTL2_REG ECCTL2; // Capture Control Reg 2
union ECEINT_REG ECEINT; // ECAP interrupt enable
union ECFLG_REG ECFLG; // ECAP interrupt flags
union ECFLG_REG ECCLR; // ECAP interrupt clear
union ECEINT_REG ECFRC; // ECAP interrupt force
Uint16 rsvd2[6]; // reserved
};
我们可以看到,在结构体ECAP_REGS中有的成员是 Uint32形式,有的是union形式,其中定义为union形式的成员既可以对寄存器整体操作,又可以对寄存器进行位操作,而定义为Uint16 的成员只能进行位操作。
在定义了ECAP_REGS结构体之后,需要声明ECAP_REGS类型的变量,而28335有6路的ECap,所以声明如下:
extern volatile struct ECAP_REGS ECap1Regs;
extern volatile struct ECAP_REGS ECap2Regs;
extern volatile struct ECAP_REGS ECap3Regs;
extern volatile struct ECAP_REGS ECap4Regs;
extern volatile struct ECAP_REGS ECap5Regs;
extern volatile struct ECAP_REGS ECap6Regs;
其中extern表示声明的是一个全局变量,关键字volatile代表寄存器的值可以被外部代码任意改变,比如被中断改变。
volatile 在 DSP 中的理解:该单词的意思是可变的,易变的。在 DSP 中一些寄存器的值的变化有两种情况:
(2)软件上的变化,例如对某个变量赋值等。
当加入了关键字 volatile,对软件来说,硬件上变化的值是不可预知的,提示编译器每次读取该变量时,都要直接读取该变量地址中的寄存器,保证了数据的正确性。
以上我们回顾一下:经过位定义——共同体——结构体的过程就是TI公司提供给我们的DSP2833x_ECap.h里面的内容。
而28335的寄存器结构是固定不变的,所以这个头文件我们在项目的开发中就可以直接拿来用了,添加到include中即可。
此外,大家也不必去死记各种各样的寄存器,因为CCS软件有“感应”功能。在我们加载了头文件之后,输入寄存器名字之后,输入 “。”就会弹出可选的下拉列表来选择你需要的寄存器。
ECap1Regs.ECCTL1_REG.bit.CAP2POL=0xxxxx;
上面一行代码从右至左依次代表:
功能模块结构体——某一个寄存器——寄存器的某一位;
这样我们就可以在自己的.c源文件中对各种各样的寄存器进行配置和操作,来实现自己的开发目的。
评论
查看更多