文章目录
- 1. introduction
- 2. Traditional #define Approach
- 3. Bit Field and Register-File Structure Approach
- 4. Bit Field and Register-File Structure Advantages
- 5. Code Size and Performance Using Bit Fields
- 6.Read-Modify-Write Considerations When Using Bit Fields
- 7 Special Case Peripherals
- 8. C2000 Peripheral Driver Library Approach
- 9. Code Size and Performance Using Driverlib
- 10. Comparing and Combining Approaches
- 11. References
- 原文
工作需要,随便译的,我看得懂就行^ _ ^
1. introduction
TMS320x28xx 和 TMS320x28xxx 属于 C2000 系列微控制器 (MCU),主要面向嵌入式控制应用。为便于在这些器件上编写高效且易于维护的嵌入式 C/C++ 代码,TI提供了 hardware abstraction layer方法来访问内存映射的外设寄存器。这些方法包括bit field和寄存器文件结构方法以及 C2000 外设驱动程序库方法。本应用报告解释了这些 hardware abstraction layer的实现,并将它们与传统的 #define 宏进行了比较。 此外,还讨论了代码效率和特殊情况寄存器等主题。 本应用报告中讨论的bit field和寄存器文件结构硬件抽象层已作为 C/C++ 头文件集实现,可在TI的 C2000Ware™ 中下载:
C2000Ware 的设备支持部分提供对所有新型微控制器的支持。目前,它支持以下设备,并且是这些设备的首选方法:
- Piccolo™ 系列微控制器
- Delfino™ 系列微控制器
- F28M3x 系列微控制器(C28x 子系统)
C2000 外围设备驱动程序库(通常称为 “Driverlib”)也可在 C2000Ware 中下载。目前,它支持以下设备: - F2807x
- F28004x
- F2837xS
- F2837xD
根据您当前的需求,这些下载中包含的软件既可以是学习工具,也可以是开发平台的基础。
- learning tool:
C/C++ 头文件和外设示例包括多个 Code Composer Studio™ 示例项目。这些示例解释了初始化器件和使用片上外设所需的步骤。可以复制和修改这些示例,以快速尝试外设配置。 - development plotform
头文件可作为硬件抽象层纳入项目,以便使用 C 或 C++ 代码访问片上外设。您还可以根据需要挑选功能,舍弃其他功能。本应用报告不提供有关 C、C++、C28x 汇编或仿真工具的教程。您应具备对 C 代码的基本理解,并能使用 Code Composer Studio 加载和运行代码。虽然了解硬件抽象层不需要 C28x 汇编知识,但了解代码优化和读-改-写部分很有用。如果您有汇编指令相关问题,请参阅《TMS320C28x CPU and
Instruction Set Reference Guide》。
以下文件被使用为:
- C/C++ Header Files and Peripheral Examples指的是可参考任何头文件和设备支持包。
- Driverlib指的是C2000的外围设备驱动库。
- TMS320x280x and 280x指的是所有的TMS320x280x和280x系列的器件。例如TMS320F2801,TMS320F2806,TMS320F2808,TMS320F28015和TMS320F28016。
- TMS320x2804x and 2804x指的是所有的TMS320x2804x和2804x系列的器件。例如:TMS320F28044。
- TMS320x281x and 281x指的是TMS320x281x和281x系列的器件。例如:TMS320F2810,TMS320F2811,TMS320F2812,TMS320C2810。
- C28x指的是TMS320C28x系列CPU。
2. Traditional #define Approach
开发人员传统上使用#define宏定义来访问C和C++上的寄存器资源,为说明这一方法,可参照表一中SCI-A和SCI-B的寄存器文件。
开发人员可以通过在应用程序头文件中添加例 1 中的定义,为 SCI 外围设备实现 #define 宏。这些宏为每个寄存器位置提供了地址标签或指针。即使外设是完全相同的拷贝,也要为每个寄存器定义宏。比如说,SCI-A 和 SCI-B 中的每个寄存器都是单独定义的。
eg1:Traditional #define Macros
/********************************************************************
* Traditional header file
********************************************************************/
#define Uint16 unsigned int
#define Uint32 unsigned long
// Memory Map
// Addr Register
#define SCICCRA (volatile Uint16 *)0x7050 // 0x7050 SCI-A Communications Control
#define SCICTL1A (volatile Uint16 *)0x7051 // 0x7051 SCI-A Control Register 1
#define SCIHBAUDA (volatile Uint16 *)0x7052 // 0x7052 SCI-A Baud Register, High Bits
#define SCILBAUDA (volatile Uint16 *)0x7053 // 0x7053 SCI-A Baud Register, Low Bits
#define SCICTL2A (volatile Uint16 *)0x7054 // 0x7054 SCI-A Control Register 2
#define SCIRXSTA (volatile Uint16 *)0x7055 // 0x7055 SCI-A Receive Status
#define SCIRXEMUA (volatile Uint16 *)0x7056 // 0x7056 SCI-A Receive Emulation Data Buffer
#define SCIRXBUFA (volatile Uint16 *)0x7057 // 0x7057 SCI-A Receive Data Buffer
#define SCITXBUFA (volatile Uint16 *)0x7059 // 0x7059 SCI-A Transmit Data Buffer
#define SCIFFTXA (volatile Uint16 *)0x705A // 0x705A SCI-A FIFO Transmit
#define SCIFFRXA (volatile Uint16 *)0x705B // 0x705B SCI-A FIFO Receive
#define SCIFFCTA (volatile Uint16 *)0x705C // 0x705C SCI-A FIFO Control
#define SCIPRIA (volatile Uint16 *)0x705F // 0x705F SCI-A Priority Control
#define SCICCRB (volatile Uint16 *)0x7750 // 0x7750 SCI-B Communications Control
#define SCICTL1B (volatile Uint16 *)0x7751 // 0x7751 SCI-B Control Register 1
#define SCIHBAUDB (volatile Uint16 *)0x7752 // 0x7752 SCI-B Baud Register, High Bits
#define SCILBAUDB (volatile Uint16 *)0x7753 // 0x7753 SCI-B Baud Register, Low Bits
#define SCICTL2B (volatile Uint16 *)0x7754 // 0x7754 SCI-B Control Register 2
#define SCIRXSTB (volatile Uint16 *)0x7755 // 0x7755 SCI-B Receive Status
#define SCIRXEMUB (volatile Uint16 *)0x7756 // 0x7756 SCI-B Receive Emulation Data Buffer
#define SCIRXBUFB (volatile Uint16 *)0x7757 // 0x7757 SCI-B Receive Data Buffer
#define SCITXBUFB (volatile Uint16 *)0x7759 // 0x7759 SCI-B Transmit Data Buffer
#define SCIFFTXB (volatile Uint16 *)0x775A // 0x775A SCI-B FIFO Transmit
#define SCIFFRXB (volatile Uint16 *)0x775B // 0x775B SCI-B FIFO Receive
#define SCIFFCTB (volatile Uint16 *)0x775C // 0x775C SCI-B FIFO Control
#define SCIPRIB (volatile Uint16 *)0x775F // 0x775F SCI-B Priority Control
eg2:Accessing Registers Using #define Macros
/********************************************************************
* Source file using #define macros
********************************************************************/
...
*SCICTL1A = 0x0003;
//write entire register
*SCICTL1B |= 0x0001;
...
传统#define宏定义的优点:
- 宏程序简单、快速、易写
- 变量名与寄存器名完全匹配;变量名易于记忆
传统#define的缺点如下:
- 位域不易访问;必须生成掩码才能对位进行操作
- 无法在 Code Composer Studio 观察窗口中轻松显示位字段
- 宏无法利用 Code Composer Studio 的自动完成功能
- 宏无法从重复使用外设中受益
3. Bit Field and Register-File Structure Approach
与使用 #define 宏访问寄存器相比,使用位域和寄存器文件结构体的方法更为灵活高效。
- Register-File Structure
寄存器文件是属于外设的寄存器集合。在 C/C++ 中,这些寄存器作为一个结构的成员被组合在一起,称为寄存器文件结构体。编译时,每个寄存器文件结构体在内存中直接映射到外设寄存器上。这种映射允许编译器使用 CPU 的数据页指针(DP)高效地访问寄存器。 - bit field
位域可用于为寄存器中的每个功能字段指定名称和宽度。以位域定义的寄存器允许编译器操作寄存器内的单个元素。例如,可以通过引用该标志对应的位字段名来读取该标志
本节的其余部分将介绍 SCI 外围设备的带位域的寄存器文件结构体的执行过程。该过程包括以下步骤:
- 创建一个简单的 SCI 寄存器文件结构变量类型;该实现不包括位域。
- 为每个 SCI 实例创建一个新类型的变量。
- 使用链接器将寄存器文件结构变量映射到寄存器的首地址。
- 为选定的 SCI 寄存器添加位域定义。
- 添加联合定义,以便访问位域或整个寄存器。重写寄存器文件结构类型,以包含位域和联合定义
在 C/C++ 头文件和外设示例中,已为 TMS320x28xx 和 TMS320x28xxx 器件 C28x 内核上的所有外设实现了寄存器文件结构和位域。
3.1 Defining A Register-File Structure
eg1展示了使用 #define 宏的硬件抽象实现。本节将改用简单的寄存器文件结构体来实现。表 2 列出了 SCI 外围设备的寄存器。SCI 的每个实例(即 SCI-A 和 SCI-B)的寄存器文件都是相同的。
eg3 中的代码将 SCI 寄存器作为 C/C++ 结构的成员组合在一起。最低内存位置的寄存器在结构中列在最前面,最高内存位置的寄存器列在最后。保留的内存位置存放着一些变量,这些变量除了作为空间保持器使用外并不使用,例如 rsvd1、rsvd2、rsvd3 等。寄存器的大小由其类型表示:Uint16 表示 16 位(无符号 int),Uint32 表示 32 位(无符号 long)。SCI 外围寄存器均为 16 位,因此只使用了 Uint16。
eg3:SCI Register-File Structure Definition
/********************************************************************
* SCI header file
* Defines a register file structure for the SCI peripheral
********************************************************************/
#define Uint16 unsigned int
#define Uint32 unsigned long
struct SCI_REGS {
union SCICCR_REG
SCICCR;
union SCICTL1_REG SCICTL1;
Uint16
// Communications control register
// Control register 1
SCIHBAUD; // Baud rate (high) register
Uint16
SCILBAUD; // Baud rate (low) register
union SCICTL2_REG SCICTL2;
// Control register 2
union SCIRXST_REG SCIRXST;
Uint16
// Receive status register
SCIRXEMU; // Receive emulation buffer register
union SCIRXBUF_REG SCIRXBUF; // Receive data buffer
Uint16
rsvd1;
Uint16
// reserved
SCITXBUF; // Transmit data buffer
union SCIFFTX_REG SCIFFTX;
// FIFO transmit register
union SCIFFRX_REG SCIFFRX;
union SCIFFCT_REG SCIFFCT;
Uint16
Uint16
union SCIPRI_REG
};
eg3 中的结构体定义创建了一个名为 struct SCI_REGS 的新类型。仅凭定义并不能创建任何变量。eg4 展示了 struct SCI_REGS 类型变量的创建方式,与 int 或无符号 int 等内置类型类似。同一外设的多个实例使用相同的类型定义。如果一个设备上有两个 SCI 外围设备,那么就会创建两个变量:SciaRegs 和 ScibRegs。
eg4:SCI Register-File Structure Variables
/********************************************************************
* Source file using register-file structures
* Create a variable for each of the SCI register files
********************************************************************/
volatile struct SCI_REGS SciaRegs;
volatile struct SCI_REGS ScibRegs;
在eg4中,volatile 关键字非常重要。只要变量的值可能被其所在代码控制之外的东西改变,该变量就被声明为易失性变量。例如,外设寄存器可以由硬件本身或在中断中改变。如果未指定 volatile,则假定变量只能由其所在代码修改,编译器可能会优化不必要的访问。但是,编译器不会优化任何易失性变量的访问;即使编译器的优化器已启用,情况也是如此。
3.2 Using the DATA_SECTION Pragma to Map a Register-File Structure to Memory
编译器会生成可重置的代码和数据块。这些块被称为 “段”,以多种方式分配到内存中,以适应不同的系统配置。默认情况下,编译器会将 SciaRegs 和 ScibRegs 等全局变量和静态变量分配到 .ebss 或 .bss 部分。但在抽象层中,寄存器文件变量被分配到与外设寄存器文件相同的内存中。通过使用编译器的 pragma DATA_SECTION,每个变量都被分配到 .bss/ebss 以外的特定数据部分。
C 语言中 pragma DATA_SECTION的语法是:
#pragma DATA_SECTION (symbol,"section name")
C++ 语言中 pragma DATA_SECTION的语法是:
#pragma DATA_SECTION ("section name")
pragma DATA_SECTION 会为名为 section name 的部分中的符号分配空间。在eg5中,使用pragma DATA_SECTION 将变量 SciaRegs 和 ScibRegs 分配给名为 SciaRegsFile 和 ScibRegsFile 的数据段。然后,数据区段直接映射到相应 SCI 寄存器占用的同一内存块。
eg5:Assigning Variables to Data Sections
/********************************************************************
* Assign variables to data sections using the #pragma compiler statement
* C and C++ use different forms of the #pragma statement
* When compiling a C++ program, the compiler will define __cplusplus automatically
********************************************************************/
//---------------------------------------
#ifdef __cplusplus
#pragma DATA_SECTION("SciaRegsFile")
#else
#pragma DATA_SECTION(SciaRegs,"SciaRegsFile");
#endif
volatile struct SCI_REGS SciaRegs;
//---------------------------------------
#ifdef __cplusplus
#pragma DATA_SECTION("ScibRegsFile")
#else
#pragma DATA_SECTION(ScibRegs,"ScibRegsFile");
#endif
volatile struct SCI_REGS ScibRegs;
每个外设都要重复这一数据段分配。然后修改链接器命令文件,将每个数据段直接映射到寄存器映射的内存空间。例如,表 1 显示 SCI-A 寄存器从地址 0x7050 开始映射到内存空间。如eg6 所示,在链接器命令文件(.cmd)中定义了内存分配。有关使用 C28x 连接器和连接器命令文件的更多信息,请参阅《TMS320C28x Assembly Language
Tools User’s Guide 》。
eg6:Mapping Data Sections to Register Memory Locations
/********************************************************************
* Memory linker .cmd file
* Assign the SCI register-file structures to the corresponding memory
********************************************************************/
MEMORY
{
...
PAGE 1:
SCIA : origin = 0x007050, length = 0x000010 /* SCI-A registers */
SCIB : origin = 0x007750, length = 0x000010 /* SCI-B registers */
...
}
SECTIONS
{
...
SciaRegsFile : > SCIA, PAGE = 1
ScibRegsFile : > SCIB, PAGE = 1
...
}
通过直接将寄存器过滤结构变量映射到外设寄存器的内存地址,只需修改结构中的相应成员,就可以在 C/C++ 代码中直接访问这些寄存器。例如,要写入 SCI-AC 控制寄存器(SCICCR),可访问 SciaRegs 中的 SCICCR ,如eg7所示。
eg7:Accessinga Member of the SCI Register-File Structure
/********************************************************************
*User's source file
********************************************************************/
...
SciaRegs.SCICCR =SCICCRA_MASK;
ScibRegs.SCICCR =SCICCRB_MASK;
...
3.3 Adding Bit-Field Definitions
在寄存器中访问特定的比特通常是有用的;比域定义提供了这种灵活性。比域是用 C/C++ 结构定义的,它提供了一系列比特字段名称,每个字段名称后面都有冒号和字段占用的比特数。
比域是在 C或C++ 中表达困难操作的一种有效方式。然而,比域在不同硬件平台之间缺乏可移植性。在 C28x 设备上,以下规则适用于比域:
- 比特字节在内存中从右到左顺序排列,即寄存器中最不重要的比特(轨道零)对应于第一个字节。
- 如果由具有结构的字节定义的比特总数超过 16 比特,则下一个字节将连续存储在下一个内存中。
图 1 和图 2 中的SCICCR 和SCICTL1 寄存器转换为eg8 中的 C/C++ 位的定义。这些寄存器中的预留位置带有未用作占位符的位,即 rsvd、rsvd1、rsvd2 等。
eg8:SCI Control Registers Defined Using Bit Fields
/********************************************************************
* SCI header file
********************************************************************/
//---------------------------------------------------------
// SCICCR communication control register bit definitions:
//
struct SCICCR_BITS { // bit description
Uint16 SCICHAR:3; // 2:0 Character length control
Uint16 ADDRIDLE_MODE:1; // 3 ADDR/IDLE Mode control
Uint16 LOOPBKENA:1; // 4 Loop Back enable
Uint16 PARITYENA:1; // 5 Parity enable
Uint16 PARITY:1; // 6 Even or Odd Parity
Uint16 STOPBITS:1; // 7 Number of Stop Bits
Uint16 rsvd1:8; // 15:8 reserved
};
//------------------------------------------
// SCICTL1 control register 1 bit definitions:
//
struct SCICTL1_BITS { // bit description
Uint16 RXENA:1; // 0 SCI receiver enable
Uint16 TXENA:1; // 1 SCI transmitter enable
Uint16 SLEEP:1; // 2 SCI sleep
Uint16 TXWAKE:1; // 3 Transmitter wakeup method
Uint16 rsvd:1; // 4 reserved
Uint16 SWRESET:1; // 5 Software reset
Uint16 RXERRINTENA:1; // 6 Receive interrupt enable
Uint16 rsvd1:9; // 15:7 reserved
};
3.4 Using Unions
虽然位字段提供了对单个位的访问,但你可能仍然希望以单个值的形式访问寄存器。为了提供这种选择,需要创建一个联合声明,以便根据定义的位字段或作为一个整体来访问寄存器。SCI 通信控制寄存器和控制寄存器 1 的联合定义如eg9所示。
eg9:Union Definition to Provide Access to Bit Fields and the Whole Register
/********************************************************************
* SCI header file
********************************************************************/
union SCICCR_REG {
Uint16 all;
struct SCICCR_BITS bit;
};
union SCICTL1_REG {
Uint16 all;
struct SCICTL1_BITS bit;
};
一旦为特定寄存器确定了位域和联合定义,SCI 寄存器文件结构就可以根据联合定义进行重写,如eg10 所示。请注意,并非所有寄存器都有位字段定义;有些寄存器,如 SCITXBUF,总是作为一个整体被访问,因此不需要位字段定义。
/********************************************************************
* SCI header file
********************************************************************/
//--------------------------------------------------------------------------
// SCI Register File:
//
struct SCI_REGS {
union SCICCR_REG SCICCR; // Communications control register
union SCICTL1_REG SCICTL1; // Control register 1
Uint16 SCIHBAUD; // Baud rate (high) register
Uint16 SCILBAUD; // Baud rate (low) register
union SCICTL2_REG SCICTL2; // Control register 2
union SCIRXST_REG SCIRXST; // Receive status register
Uint16 SCIRXEMU; // Receive emulation buffer register
union SCIRXBUF_REG SCIRXBUF; // Receive data buffer
Uint16 rsvd1; // reserved
Uint16 SCITXBUF; // Transmit data buffer
union SCIFFTX_REG SCIFFTX; // FIFO transmit register
union SCIFFRX_REG SCIFFRX; // FIFO receive register
union SCIFFCT_REG SCIFFCT; // FIFO control register
Uint16 rsvd2; // reserved
Uint16 rsvd3; // reserved
union SCIPRI_REG SCIPRI; // FIFO Priority control
};
一旦为特定寄存器确定了位域和联合定义,SCI 寄存器文件结构就可以根据联合定义进行重写,如eg10 所示。请注意,并非所有寄存器都有位字段定义;有些寄存器,如 SCITXBUF,总是作为一个整体被访问,因此不需要位字段定义。
eg10:SCI Register-File Structure Using Unions
/********************************************************************
* SCI header file
********************************************************************/
//--------------------------------------------------------------------------
// SCI Register File:
//
struct SCI_REGS {
union SCICCR_RE SCICCR; // Communications control register
union SCICTL1_REG SCICTL1; // Control register 1
Uint16 SCIHBAUD; // Baud rate (high) register
Uint16 SCILBAUD; // Baud rate (low) register
union SCICTL2_REG SCICTL2; // Control register 2
union SCIRXST_REG SCIRXST; // Receive status register
Uint16 SCIRXEMU; // Receive emulation buffer register
union SCIRXBUF_REG SCIRXBUF; // Receive data buffer
Uint16 rsvd1; // reserved
Uint16 SCITXBUF; // Transmit data buffer
union SCIFFTX_REG SCIFFTX; // FIFO transmit register
union SCIFFRX_REG SCIFFRX; // FIFO receive register
union SCIFFCT_REG SCIFFCT; // FIFO control register
Uint16 rsvd2; // reserved
Uint16 rsvd3; // reserved
union SCIPRI_REG SCIPRI; // FIFO Priority control
};
与其他结构一样,每个成员(.all 或 .bit)都可以使用 C/C++ 中的点操作符进行访问,如eg11 所示。指定 .all 成员时,将访问整个寄存器。指定 .bit 成员时,可以直接访问定义的位字段。
eg11:Accessing Bit Fields in C/C++
/********************************************************************
* User's source file
********************************************************************/
// Access registers without a bit field definition (.all, .bit not used)
SciaRegs.SCIHBAUD = 0;
SciaRegs.SCILBAUD = 1;
// Write to bit fields in SCI-A SCICTL1
SciaRegs.SCICTL1.bit.SWRESET = 0;
SciaRegs.SCICTL1.bit.SWRESET = 1;
SciaRegs.SCIFFCT.bit.ABDCLR = 1;
SciaRegs.SCIFFCT.bit.CDC = 1;
// Poll (i.e., read) a bit
while(SciaRegs.SCIFFCT.bit.CDC == 1) { }
// Write to the whole SCI-B SCICTL1/2 registers (use .all)
ScibRegs.SCICTL1.all = 0x0003;
ScibRegs.SCICTL2.all = 0x0000;
4. Bit Field and Register-File Structure Advantages
位域和寄存器文件结构体方法有许多优点,其中包括:
- TI已经提供寄存器文件结构体和位域
在 C/C++ 头文件和外设示例中,已为 TMS320x28xx 和 TMS320x28xxx 器件 C28x 内核上的所有外设实现了寄存器文件结构体和位域。如第 1 节所示,完整的实现可从 TI 网站的软件下载中获取。 - 使用位域生成的代码易写、易读、易更新且高效
无需确定寄存器掩码值,即可快速操作位字段。此外,还可以灵活地按位字段或按单一数量访问寄存器,如eg11 所示。使用寄存器文件结构编写的代码也非常高效。代码效率将在第 5 节中讨论。 - 位域可利用 Code Composer Studio 编辑器的自动完成功能
初看起来,使用寄存器文件结构体和位域时,变量名似乎更难记忆,键入时间也更长。Code Composer Studio 编辑器会在您键入时提供可能的结构体/位域元素列表;这使得编写代码更加容易,而无需参考寄存器和位域名称的文档。图 3 显示了 CPU 定时器 TCR 寄存器自动完成功能的示例。
- 提高 Code Composer Studio 观察窗口的效率
您可以在 Code Composer Studio 的观察窗口中添加和扩展寄存器文件结构,如图 4 所示。直接读取位字段值,无需手动提取其值
5. Code Size and Performance Using Bit Fields
在访问寄存器内的单个位或轮询位时,位域和寄存器文件结构体方法非常高效。举例来说,在 TMS320x280x 器件上初始化 PCLKCR0 寄存器的代码。PCLKCR0 在《TMS320x280x, 2801x, 2804x System
Control and Interrupts Reference Guide 》(SPRU712)中有详细描述。该寄存器的位域定义如eg12 所示。
eg12:TMS320x280x PCLKCR0 Bit-Field Definition
// Peripheral clock control register 0 bit definitions:
struct PCLKCR0_BITS { // bits description
Uint16 rsvd1:2; // 1:0 reserved
Uint16 TBCLKSYNC:1; // 2 eWPM Module TBCLK enable/sync
Uint16 ADCENCLK:1; // 3 Enable high speed clk to ADC
Uint16 I2CAENCLK:1; // 4 Enable SYSCLKOUT to I2C-A
Uint16 rsvd2:1; // 5 reserved
Uint16 SPICENCLK:1; // 6 Enable low speed clk to SPI-C
Uint16 SPIDENCLK:1; // 7 Enable low speed clk to SPI-D
Uint16 SPIAENCLK:1; // 8 Enable low speed clk to SPI-A
Uint16 SPIBENCLK:1; // 9 Enable low speed clk to SPI-B
Uint16 SCIAENCLK:1; // 10 Enable low speed clk to SCI-A
Uint16 SCIBENCLK:1; // 11 Enable low speed clk to SCI-B
Uint16 rsvd3:2; // 13:12 reserved
Uint16 ECANAENCLK:1; // 14 Enable SYSCLKOUT to eCAN-A
Uint16 ECANBENCLK:1; // 15 Enable SYSCLKOUT to eCAN-B
};
eg13 中的代码启用了 TMS320x2801 器件的外设时钟。C28x 编译器为每个 C 代码寄存器访问生成一条汇编代码指令。这样做非常高效;C 代码指令和汇编指令之间是一一对应的。唯一的开销是设置数据页指针 (DP) 的初始指令
eg13:Assembly Code Generated by Bit Field Accesses
C-Source Code Generated Assembly
Memory Instruction
// Enable only 2801 Peripheral Clocks
EALLOW; 3F82A7 EALLOW
3F82A8 MOVW DP,#0x01C0
SysCtrlRegs.PCLKCR0.bit.rsvd1 = 0; 3F82AA AND @28,#0xFFFC
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0; 3F82AC AND @28,#0xFFFB
SysCtrlRegs.PCLKCR0.bit.ADCENCLK = 1; 3F82AE OR @28,#0x0008
SysCtrlRegs.PCLKCR0.bit.I2CAENCLK = 1; 3F82B0 OR @28,#0x0010
SysCtrlRegs.PCLKCR0.bit.rsvd2 = 0; 3F82B2 AND @28,#0xFFDF
SysCtrlRegs.PCLKCR0.bit.SPICENCLK = 1; 3F82B4 OR @28,#0x0040
SysCtrlRegs.PCLKCR0.bit.SPIDENCLK = 1; 3F82B6 OR @28,#0x0080
SysCtrlRegs.PCLKCR0.bit.SPIAENCLK = 1; 3F82B8 OR @28,#0x0100
SysCtrlRegs.PCLKCR0.bit.SPIBENCLK = 1; 3F82BA OR @28,#0x0200
SysCtrlRegs.PCLKCR0.bit.SCIAENCLK = 1; 3F82BC OR @28,#0x0400
SysCtrlRegs.PCLKCR0.bit.SCIBENCLK = 0; 3F82BE AND @28,#0xF7FF
SysCtrlRegs.PCLKCR0.bit.rsvd3 = 0; 3F82C0 AND @28,#0xCFFF
SysCtrlRegs.PCLKCR0.bit.ECANAENCLK= 1; 3F82C2 OR @28,#0x4000
SysCtrlRegs.PCLKCR0.bit.ECANBENCLK= 0; 3F82C4 AND @28,#0x7FFF
EDIS; 3F82C6 EDIS
NOTE:EALLOW 和 EDIS 是 C/C++ 头文件和外设示例中定义的宏。这些宏可扩展为 EALLOW 和 EDIS 汇编指令。 EALLOW 保护机制可防止 CPU 对多个寄存器进行错误写入。 执行 EALLOW 可允许 CPU 自由写入受保护的寄存器,执行 EDIS 可再次保护这些寄存器。有关 EALLOW 保护和受保护寄存器列表的信息,请参阅特定设备的《device-specific System Control and Interrupts Reference Guide》或《Technical Reference Manual 》(TRM)。
要计算EG13 中的代码需要多少个周期,需要知道访问 PCLKCR0 寄存器需要多少个等待状态。所有内存块和外设帧的等待状态信息都列在器件专用数据手册中。PCLKCR0 寄存器位于外设帧 2 中;该帧的读取访问需要两个等待状态,而写入访问不需要等待状态。这意味着从 PCLKCR0 读取总共需要三个周期,而写入只需要一个周期。此外,在上一次写入完成之前,不能开始对 PCLKCR0 的新访问。这种内置保护机制消除了流水线效应,确保操作以正确的顺序进行;所有外设寄存器都具有这种保护功能。在eg13 中,每次访问 PCLKCR0 寄存器需要 6 个周期;流水线阶段如表 3 所示:
当代码量和周期数必须保持在最低水平时,尽可能减少初始化寄存器所需的指令数是有好处的。下面是一些减少代码量的方法:
- 启用编译器的优化器:
如第 3.1 节所述,寄存器文件变量被声明为易失性变量。因此,仅启用优化器并不能减少指令数。关键字 volatile 提醒编译器,变量的值可能会在当前执行代码之外发生变化。虽然删除 volatile 关键字可以减少代码量,但并不建议这样做。删除 volatile 关键字必须非常谨慎,只有在开发人员确信这样做不会产生错误结果的情况下才可以删除。 - 写入完整寄存器(.all union member):
第 3.4 节讨论的联合定义允许访问特定位字段或整个寄存器。当使用联合定义的 .all member对整个寄存器进行写入操作时,代码量就会减少。如eg14 所示,这种方法可以创建非常高效的代码。不过,使用 .all 会增加代码的编写和读取难度。寄存器中的不同位域是如何配置的,并不是一眼就能看出来的。
eg14:Optimization Using the .all Union Member
C-Source Code Generated Assembly
Memory Instruction
EALLOW; 3F82A7 EALLOW
SysCtrlRegs.PCLKCR0.all = 0x47D8; 3F82A8 MOVW DP,#0x01C0
EDIS; 3F82AA MOV @28,#0x47D8
3F82AC EDIS
- 使用shadow 寄存器,并启用编译器的优化器:
这种方法是最好的折衷方案。如eg15 所示,寄存器的内容被加载到相同类型的影子寄存器中。然后使用位字段修改影子寄存器的内容。由于 shadow的PCLKCR0 不是易失性的,编译器会在启用优化器时合并位字段写入。请注意,所有保留位置也都已初始化。代码结束时,影子寄存器中的值将被写入 PCLKCR0。这种方法保留了位域定义的优点,而且代码易于阅读。所示汇编程序是在启用编译器优化级别-o1 的情况下生成的。
eg15:Optimization Using a Shadow Register
C-Source Code Generated Assembly
Memory Instruction
// Enable only 2801 Peripheral Clocks
union PCLKCR0_REG shadowPCLKCR0;
EALLOW; 3F82A7 EALLOW
shadowPCLKCR0.bit.rsvd1 = 0; 3F82A8 MOV @AL,#0x47D8
shadowPCLKCR0.bit.TBCLKSYNC = 0; 3F82AA MOVW DP,#0x01C0
shadowPCLKCR0.bit.ADCENCLK = 1; /*ADC*/ 3F82AC MOV @28,AL
shadowPCLKCR0.bit.I2CAENCLK = 1; /*I2C*/ 3F82AD EDIS
shadowPCLKCR0.bit.rsvd2 = 0;
shadowPCLKCR0.bit.SPICENCLK = 1; // SPI-C
shadowPCLKCR0.bit.SPIDENCLK = 1; // SPI-D
shadowPCLKCR0.bit.SPIAENCLK = 1; // SPI-A
shadowPCLKCR0.bit.SPIBENCLK = 1; // SPI-B
shadowPCLKCR0.bit.SCIAENCLK = 1; // SCI-A
shadowPCLKCR0.bit.SCIBENCLK = 0; // SCI-B
shadowPCLKCR0.bit.rsvd3 = 0;
shadowPCLKCR0.bit.ECANAENCLK= 1; // eCAN-A
shadowPCLKCR0.bit.ECANBENCLK= 0; // eCAN-B
SysCtrlRegs.PCLKCR0.all = shadowPCLKCR0.all;
EDIS;
6.Read-Modify-Write Considerations When Using Bit Fields
在写入位字段时,编译器会生成所谓的 "读取-修改-写入 "汇编指令。 读取-修改-写入 "是指用于实现位或字节操作(如 AND、OR 和 XOR)的技术。也就是说,读取位置,修改单个比特字段,然后将结果写回。eg16 显示了一些 C28x 读取-修改-写入汇编指令。
eg16:A Few Read-Modify-Write Operations
AND @Var, #0xFFFC ; Read 16-bit value "Var"
; AND the value with 0xFFFC
; Write the 16-bit result to "Var"
;
;
OR @Var, #0x0010 ; Read 16-bit value "Var"
; AND the value with 0xFFFC
; Write the 16-bit result to "Var"
;
;
XOR @VarB, AL ; Read 16-bit value "Var"
; XOR with AL
; Write the 16-bit result to "Var"
;
;
MOVB *+XAR2[0], AH.LSB ; Read 16-bit value pointed to by XAR2
; Modify the least significant byte
; Write the 16-bit value back
通过完整的 CPU 流水线,基于 C28x 的设备可以在每个周期内完成一次对零等待状态 SARAM 的读-修改-写操作。但是,在访问外设寄存器或外部存储器时,必须考虑所需的等待状态。此外,流水线保护机制还能进一步阻滞 CPU 流水线中的指令。第 5 节和 《TMS320C28x
CPU and Instruction Set Reference Guide》 (SPRU430) 中对此有更详细的说明。
读取-修改-写入指令通常不会产生不良的副作用。但必须认识到,读取-修改-写入指令并不只限制对寄存器中特定位的访问;这些指令会写入寄存器的所有位。在某些情况下,读取-修改-写入序列可能会在写入位时产生意想不到的结果。对读-修改-写指令敏感的寄存器可分为三类:
- 具有硬件可在读取后、写入前更改的位的寄存器
- 具有写 1 到清零位的寄存器
- 具有必须写入不同于读回位值的位的寄存器
属于这三类的寄存器通常出现在较旧的外设中。为了保持寄存器的兼容性,寄存器文件没有重新设计以避免这一问题。而较新的外设,如 ePWM、eCAP 和 eQEP,其寄存器布局是专门为避免这些问题而设计的。
本节将详细介绍应谨慎使用读-修改-写操作的三类寄存器。此外,还给出了每类寄存器的示例以及安全修改该寄存器的建议方法。本节末尾提供了一份读修改写敏感寄存器列表,以供参考。
6.1 Registers That Hardware Can Modify During Read-Modify-Write Operations
在 CPU 流水线的读取和写入阶段之间,设备本身可能会改变某些位的状态。例如,PIE 中断标志寄存器(PIEIFRx,其中 x = 1、2、…12)可能会因外部硬件或外设事件而发生变化。写回的值可能会覆盖一个标志,从而破坏该值,导致错过中断。
6.1.1 PIEIFRx Registers
如果需要清除 PIEIFRx 位,那么规则是始终让 CPU 利用中断来清除标志。具体做法是将中断向量重新映射到伪中断服务例程(ISR)。在伪 ISR 中,中断向量被重新映射到真正 ISR 例程的中断向量,如eg17所示。
eg17:Clearing PIEIFRx (x = 1, 2…12) Registers
/********************************************************************
* User's source file
********************************************************************/
// Pseudo ISR prototype. PTIN = pointer to an interrupt
interrupt void PseudoISR(void);
PINT TempISR;
....
if( PieCtrlRegs.PIEIFR1.bit.INTx4 == 1)
{
// Temp save current vector and remap to pseudo ISR
// Take the interrupt to clear the PIEIFR flag
EALLOW;
TempISR = PieVectTable.XINT1;
PieVectTable.XINT1 = PseudoISR;
PieCtrlRegs.PIEIER1.bit.INTx4 = 1;
EDIS;
}
....
// Pseudo ISR
// Services the interrupt & the hardware clears the PIEIFR flag
// Re-maps the interrupt to the proper ISR
interrupt void PseudoISR(void)
{
EALLOW;
PieVectTable.XINT1 = TempISR;
EDIS;
}
NOTE:此规则不适用于 CPU 的 IFR 寄存器。为清除 CPU IFR 位提供了特殊指令,不会导致中断丢失。使用 OR IFR 指令设置 IFR 位,使用 AND IFR 指令清除挂起的中断。
6.1.2 GPxDAT Registers
GPIO 数据寄存器是另一种在读取和写入之间可能发生变化的位。请看eg18 中的代码。除 281x 器件外,GPxDAT 寄存器反映的是引脚的状态,而不是输出锁存器的状态。这意味着寄存器反映的是引脚的实际值。不过,从写入寄存器到新引脚值反映回寄存器之间会有一个滞后期。当在后续程序语句中使用该寄存器改变 GPIO 引脚的状态时,这可能会造成问题。在eg18 中,两条程序语句试图驱动两个不同的 GPIO 引脚。由于该外设框架采用了 "先写后读 "保护,第二条指令将等待第一条指令完成写入。 不过,在写入 GPIO16 和 GPxDAT 位反映引脚上的新值(1)之间会有一些滞后。在此滞后期间,第二条指令将读取 GPIO16 的旧值(0),并将其与 GPIO17 的新值(0)一起写回。解决方法之一是在读取-修改-写入指令之间加入一些 NOP。更好的解决方案是使用 GPxSET/GPxCLEAR/GPxTOGGLE 寄存器,而不是 GPxDAT 寄存器。这些寄存器总是读回 0,写入 0 不会有任何影响。只有需要更改的位才能被指定,而不会影响当前正在更改的任何其他位。eg19 显示了使用 GPxSET 和 GPxCLEAR 寄存器的相同代码。
eg18:Read-Modify-Write Effects on GPxDAT Registers
/********************************************************************
* User's source file
********************************************************************/
for(;;)
{
// Make LED Green
GpioDataRegs.GPADAT.bit.GPIO16 = 1; // (1) RED_LED_OFF;
// Read-modify-write occurs
GpioDataRegs.GPADAT.bit.GPIO17 = 0; // (2) GREEN_LED_ON;
// Read: Because of the delay between output to input
//
the old value of GPIO16 (zero) is read
// Modify: Changes GPIO17 to a 0
// Write: Writes back GPADAT with GPIO16 = 0 and GPIO17 = 0
delay_loop();
}
// Make LED Red
GpioDataRegs.GPADAT.bit.GPIO16 = 0; // (3) RED_LED_ON;
GpioDataRegs.GPADAT.bit.GPIO17 = 1; // (4) GREEN_LED_OFF;
delay_loop();
eg19:Using GPxSET and GPxCLEAR Registers
/********************************************************************
* User's source file
********************************************************************/
for(;;)
{
// Make LED Green
GpioDataRegs.GPASET.bit.GPIO16 = 1; // RED_LED_OFF;
GpioDataRegs.GPACLEAR.bit.GPIO17 = 1; // GREEN_LED_ON;
delay_loop();
// Make LED Red
GpioDataRegs.GPACLEAR.bit.GPIO16 = 1; // RED_LED_ON;
GpioDataRegs.GPASET.bit.GPIO17 = 1; // GREEN_LED_OFF;
delay_loop();
}
6.2 Registers With Write 1-to-Clear Bits.
有些寄存器有所谓的 "写 1 清零 "位。这意味着当位被设置时,只有向该位写入 1 才能将其清除。在读取-修改-写入操作中,如果某位在读取时为 1,那么它也将被写入为 1,除非在访问的修改部分对其进行修改。TCR 寄存器中的 CPU 定时器中断标志 (TIF) 就是一个写 1 清零位的例子。读取 TIF 可确定 CPU 定时器是否溢出并标记中断。eg20 显示了停止 CPU-Timer 然后检查中断标志是否被设置的代码。
eg20:Read-Modify-Write Operation Inadvertently Modifies Write 1-to-Clear Bits (TCR[TIF])
C-Source Code Generated Assembly
Memory Instruction
// Stop the CPU-Timer
CpuTimer0Regs.TCR.bit.TSS = 1; 3F80C7 MOVW DP,#0x0030
3F80C9 OR @4,#0x0010
// Check to see if TIF is set 3F80CB TBIT @4,#15
if (CpuTimer0Regs.TCR.bit.TIF == 1) 3F80CC SBF L1,NTC
{ 3F80CD NOP
/* TIF set, insert action here*/ 3F80CE L1:
/* NOP is only a place holder*/ ....
asm(" NOP");
}
即使标记了中断,eg20 中的 TIF 测试也不会为真。设置 TSS 位的 OR 汇编指令对 TCR 寄存器执行读-修改-写操作。如果 TIF 位在读取-修改-写入操作发生时被设置,那么 TIF 将被读取为 1,同时写回 1。为避免这种情况,对 TIF 位的写入应始终为 0。 TIF 位会忽略 0 的写入,因此其值将被保留。eg21 显示了一种保留 TIF 的可能实现方式。
eg21: Using a Shadow Register to Preserve Write 1-to-Clear Bits
C-Source Code Generated Assembly
Memory Instruction
union TCR_REG shadowTCR;
// Use a shadow register to stop the timer
// and preserve TIF (write 1-to-clear bit)
shadowTCR.all = CpuTimer0Regs.TCR.all; 3F80C7 MOVW DP,#0x0030
shadowTCR.bit.TSS = 1; 3F80C9 MOV AL,@4
shadowTCR.bit.TIF = 0; 3F80CA ORB AL,#0x10
CpuTimer0Regs.TCR.all = shadowTCR.all; 3F80CB MOVL XAR5,#0x000C00
3F80CD AND AL,@AL,#0x7FFF
/* Check the TIF flag*/ 3F80CF MOV *+XAR5[4],AL
if(CpuTimer0Regs.TCR.bit.TIF == 1) 3F80D0 TBIT *+XAR5[4],#15
{ 3F80D1 SBF L1,NTC
/*TIF set, insert action here*/ 3F80D2 NOP
/* NOP is only a place holder*/ 3F80D3 L1:
asm(" NOP");
}
TCR 寄存器的内容被复制到影子寄存器中。在影子寄存器中,TSS 位被置位,TIF 位被清零。然后将影子寄存器写回 TCR;定时器停止,TIF 的状态保持不变。汇编指令是在启用优化级别-o2 的情况下生成的。
6.3 Register Bits Requiring a Specific Value
有些寄存器的位必须写入特定的值。如果该值与位读取的值不同,那么读取-修改-写入操作很可能会写入错误的值。
例如,看门狗控制寄存器中的看门狗校验位字段 (WDCHK)。看门狗检查位必须写为 1,0,1;任何其他值都被视为非法值,并将重置设备。由于这些位的读数总是 0,0,0,因此除非在修改操作过程中改变 WDCHK,否则读取-修改-写入操作将写入 0,0,0。
另一种解决方案是避免读取-修改-写入操作,而只向 WDCR 寄存器写入 16 位值。为了提醒您注意这一要求,C/C++ 头文件和外设示例中没有为 WDCR 寄存器提供位域定义。访问没有位字段定义或联合定义的寄存器时,请不要使用 .bit 或 .all 名称,如eg22 所示。
eg22: Watchdog Check Bits (WDCR[WDCHK])
/********************************************************************
* User's source file
********************************************************************/
SysCtrlRegs.WDCR = 0x0068;
关于看门狗的更多信息详情见《TMS320x280x, 2801x, 2804x DSP System Control and Interrupts Reference Guide 》(SPRU712)和《
TMS320x281x System Control and Interrupts Reference Guide》 (SPRU078)。
6.4 Read-Modify-Write Sensitive Registers
表 4 列出了对读修改写指令敏感的寄存器。根据寄存器和外设在应用中的使用方式,读修改写操作的影响可能会也可能不会引起关注。此列表可能并不完整。
7 Special Case Peripherals
外设访问通过三个外设框架(或总线)中的一个进行。外设寄存器位于能够访问最适合寄存器集的框架内。
- 外设框架 0:
该帧内的外设位于设备的内存总线上。该总线可进行 16 位或 32 位访问。例如,CPU-定时器位于内存总线上。 - 外设框架 1:
外设框架 1 使用的总线可进行 16 位和 32 位访问。例如 ePWM 和 eCAN 外设。 - 外设帧框架2:
外设框架 2 使用的总线只能进行 16 位访问。第 2 帧上的所有外设寄存器长度都只有 16 位。例如 SCI、SPI、ADC 和 I2C。
7.1 eCANControl Registers
eCAN 控制寄存器和状态寄存器的访问范围仅限于 32 位。仅 16 位的访问可能会产生不可预知的结果。eCAN 控制和状态寄存器必须作为特例处理;它们是唯一限制 32 位宽访问的外设框架 1 寄存器。
通常情况下,编译器会将访问减少到 16 位,以节省代码大小或提高性能。必须注意确保编译器不会将对 eCAN 控制和状态寄存器的 32 位访问简化为 16 位访问。例如,编译器将eg23 中所示的访问简化为 16 位访问 CANMC 寄存器的一半。
eg23:Invalid eCAN Control Register 16-Bit Write
C-Source Code Generated Assembly
Memory Instruction
/* The compiler will simplify this to*/ 3F81FA EALLOW
/* a 16-bit read-modify-write*/ 3F81FB MOVW DP,#0x0180
EALLOW; 3F81FD OR @20,#0x2000
ECanaRegs.CANMC.bit.SCB = 1; 3F81FF EDIS
EDIS;
为强制 32 位访问,不得使用位字段定义和读-修改-写操作。必须使用联合定义的 .all member读写寄存器,并且必须读写所有 32 位数据。
遗憾的是,不能使用位域或读写修改操作会降低代码的可读性。一种解决方法是将整个寄存器读入影子寄存器,对值进行操作,然后使用 .all 将新的 32 位值写入寄存器。eg24 中的代码使用影子寄存器强制进行 32 位访问。如果要访问的寄存器不止一个,那么可以对整个 eCAN 寄存器文件进行阴影处理(即 struct ECAN_REGS shadowECanaRegs;)。
eg24: Using a Shadow Register to Force a 32-Bit Access
C-Source Code Generated Assembly
Memory Instruction
// Use a shadow register to force a
// 32-bit access
union CANMC_REG shadowCANMC;
EALLOW; 3F81FA EALLOW
3F81FB MOVW DP,#0x0180
/* 32-bit read of CANMC*/ 3F81FD MOVL ACC,@20
shadowCANMC.all = ECanaRegs.CANMC.all; 3F81FE OR @AL,#0x2000
shadowCANMC.bit.SCB = 1; 3F8200 MOVL @20,ACC
3F8201 EDIS;
/* 32-bit write of CANMC*/
ECanaRegs.CANMC.all = shadowCANMC.all;
EDIS
7.2 Byte Peripheral Registers
有些外设需要 8 位字节访问,为了实现这一点,这些外设被放置在桥接器上,允许外设像字节寻址一样被访问。表 5 列出了该桥接器上的外设。
由于外设寄存器采用字节可寻址方式,32 位内存映射寄存器的地址以增量 4(如 48 位字节)的地址偏移量替换,而不是通常在字可寻址外设上以增量 2(如 48 位字节)的地址偏移量替换,16 位字以增量 2(而不是 1)的地址偏移量替换。
例如,eg 25 显示了在 F2837xDCAN-A 模块上使用按常规方法定义的位字段头文件写入测试 CAN_IF1CMD 寄存器的代码。CAN_IF1CMD 位于地址 0x048100,但下面的代码访问的是 0x0480D4,因为代码生成工具不理解外设桥将地址视为字节地址。
eg25: Invalid Byte Peripheral Register Access
C-Source Code Generated Assembly
Instruction
/* Set Directiontowriteandset*/ MOVB AL,#0x0
/* DATA-A/DATA-B tobetransferredto*/ MOVB AH,#0x83
/* message object*/ MOVW DP,#0x1203
CanaRegs.CAN_IF1CMD.all=0x830000; MOVL @0x14,ACC
OR @0x15,#0x0004
// Set Tx RequestBit
CanaRegs.CAN_IF1CMD.bit.TXRQST=1;
幸运的是,STS 16.6.0.增加了一些功能可以正确处理这些对齐差异。有关该属性的详细信息,请参阅《TMS320C28x
OptimizingC/C++CompilerUser’sGuide》。eg26 显示了使用 "byte_peripheral "类型属性生成的正确代码。
eg26: Byte Peripheral Register Access Using “byte_peripheral” Attribute
C-Source Code Generated Assembly
Instruction
/* Set Directiontowriteandset*/ MOVB AL,#0x0
/* DATA-A/DATA-B tobetransferredto*/ MOVB AH,#0x83
/* message object*/ MOVL XAR4,#0x048100
CanaRegs.CAN_IF1CMD.all=0x830000; MOVL *+XAR4[0],ACC
MOVL ACC,*+XAR4[0]
/* Set Tx RequestBit*/ ORB AH,#0x4
CanaRegs.CAN_IF1CMD.bit.TXRQST=1; MOVL *+XAR4[0],ACC
8. C2000 Peripheral Driver Library Approach
C2000 Peripheral Driver Library(或称 Driverlib)是一套用于配置内存映射外设寄存器的低级驱动程序,与通过比特字段或 #define 方法直接访问寄存器相比,Driverlib 更易于阅读和便携。
驱动程序库由 C2000Ware 和源代码编写。它为所有外设提供驱动程序,并提供几乎所有功能的访问权限。
以下各节将介绍如何使用驱动程序以及驱动程序的架构。
8.1 Using the Peripheral Driver Library
Driverlib提供了一个配置外设的接口,该接口由函数、数据类型和用作这些函数参数的#define组成。
每个函数都有详细的文档说明,解释了函数的目的、使用方法、最终值的含义(如果没有无效)以及每个参数的有效值。例如,ADC_enableConverter()函数建议在调用该函数和开始采样之间需要间隔一段时间,以便 ADC 定时器启动。
大多数外设函数将数据库地址作为其第一个参数,以指示要配置的外设实例(例如,SCI-A 或 SCI-B);例外情况是每核只有一个实例的外设,如系统控制和 PIE。在名为 hw_memmap.h 的开头文件中,为每个外设实例的基地址提供了 #define,同样以 SCI 为例,在eg27 中进行了说明。
eg27: SCI-A Driverlib Function Proto type
/
//
// Snippet from hw_memmap.hshowingbaseaddress#defines
//
/
...
#define SCIA_BASE 0x00007050U//SCIARegisters
#define SCIB_BASE 0x00007750U//SCIBRegisters
...
/
//
// Snippet from sci.hshowingAPIdescriptionandbaseparameter
//
/
//
//! Sets the FIFOinterruptlevelatwhichinterruptsaregenerated.
//!
//! \param base isthebaseaddressoftheSCIport.
//!
//! \param txLevelisthetransmitFIFOinterruptlevel,specifiedas
//! one of thefollowing:
//! SCI_FIFO_TX0,SCI_FIFO_TX1,SCI_FIFO_TX2,...orSCI_FIFO_TX16.
//!
//! \param rxLevelisthereceiveFIFOinterruptlevel,specifiedasone
//! of the following:
//! SCI_FIFO_RX0,SCI_FIFO_RX1,SCI_FIFO_RX2,...orSCI_FIFO_RX16.
//!
//! This functionsetstheFIFOlevelatwhichtransmitandreceive
//! interrupts aregenerated.
//!
//! \return None.
//
static inline void
SCI_setFIFOInterruptLevel(uint32_tbase,SCI_TxFIFOLeveltxLevel,
SCI_RxFIFOLevelrxLevel)
对于其他参数,#define或枚举类型通常用来提供可读取的方式来指定期望值。当一个类型是uint32_t或uint16_t且能取多个#defined值的比特OR时使用经典的#define。枚举类型只在相关值不适用时使用。
这些值决定了要向外设寄存器写入哪些内容以配置外设,函数将决定要向哪个寄存器或哪些寄存器写入哪些值,函数还将执行任何必要的 "EALLOW "或 "EDIS "指令。eg 28 显示了可在用户应用程序中找到的演示此功能的代码;在给定源时钟速率和所需波形速率的情况下,该功能计算出所需的预设值,并将其写入相应的寄存器。
eg28: SCI-A Configuration Using the Driverlib
/
//
// User’ssourcefile
//
/
//
// Configure SCI-Awithabaudrateof9600,8-bitdata,onestopbit,
// and no parity
//
SCI_setConfig(SCIA_BASE,25000000,9600,(SCI_CONFIG_WLEN_8|
SCI_CONFIG_STOP_ONE|
SCI_CONFIG_PAR_NONE));
//
// Set the FIFO interruptlevelto8charactersforbothFIFOs
//
SCI_setFIFOInterruptLevel(SCIA_BASE,SCI_FIFO_TX8,SCI_FIFO_RX8)
//
// While the transmitFIFOisnotfull,write0x00
//
while(SCI_getTxFIFOStatus(SCIA_BASE)!=SCI_FIFO_TX16)
{
SCI_writeCharNonBlocking(SCIA_BASE,0x00);
}
由于API 的names 主要由完整的英文单词和有限的首字母缩略词组成,因此即使只有有限的注释,代码的大部分作用也很容易理解。
8.2 Constructionofa Driver Library Function
在调试或希望访问现有驱动程序函数无法配置的寄存器或字段时,了解驱动程序函数是如何构造的是非常有用的。Driverlib 使用一种与第 2 节中讨论的传统 #define方法 类似的方法来执行其寄存器访问。为每个外设生成前导文件,其中包含寄存器地址设置和位掩码,以及这些寄存器中每个字段的移位量:
- 用于访问寄存器值的包含 O_寄存器地址的值设置。例如,SCI_O_CCR 用于访问 SCI 模块中的 SCICCR 寄存器时,可将这些地址添加到基本地址值中,以获得寄存器地址。
- 以_M结尾的值代表寄存器中多位字段的寄存位。例如,SCI_CCR_SCICHAR_Mis 代表 SCICCR 寄存器中的 SCICHAR 字段。请注意,寄存器整个宽度的字段不会被赋予掩码。
- 以_S 结尾的值表示为使其与多位字段对齐而对齐的位数。这些值与以_ M结尾 的相同基准值相匹配。
- 其他均为单比特位域。例如,SCI_CCR_LOOPBKENA 与 SCICCR 寄存器中的 LOOPBKENA 位相对应。
一个简单的外设寄存器头文件示例见eg 29。
eg29: SCI Register Description Header File(hw_sci.h)
/
//
// Example register#definesfromhw_sci.h
//
/
//*******************************************************************
//
// The followingaredefinesfortheSCIregisteroffsets
//
//*******************************************************************
#define SCI_O_CCR 0x0U //Communicationscontrol
#define SCI_O_CTL1 0x1U //Controlregister1
#define SCI_O_HBAUD 0x2U //Baudrate(high)
#define SCI_O_LBAUD 0x3U //Baudrate(low)
#define SCI_O_CTL2 0x4U //Controlregister2
#define SCI_O_RXST 0x5U //Receivestatus
#define SCI_O_RXEMU 0x6U //Receiveemulationbuffer
#define SCI_O_RXBUF 0x7U //Receivedatabuffer
#define SCI_O_TXBUF 0x9U //Transmitdatabuffer
#define SCI_O_FFTX 0xAU //FIFOtransmitregister
#define SCI_O_FFRX 0xBU //FIFOreceiveregister
#define SCI_O_FFCT 0xCU //FIFOcontrolregister
#define SCI_O_PRI 0xFU //SCIPrioritycontrol
//*******************************************************************
//
// The followingaredefinesforthebitfieldsintheSCICCRregister
//
//*******************************************************************
#define SCI_CCR_SCICHAR_S 0U
#define SCI_CCR_SCICHAR_M 0x7U //Characterlengthcontrol
#define SCI_CCR_ADDRIDLE_MODE 0x8U //ADDR/IDLEModecontrol
#define SCI_CCR_LOOPBKENA 0x10U //LoopBackenable
#define SCI_CCR_PARITYENA 0x20U //Parityenable
#define SCI_CCR_PARITY 0x40U //EvenorOddParity
#define SCI_CCR_STOPBITS 0x80U //NumberofStopBits
...
这些 #define 与 hw_types.h 中定义的一组 "HWREG(x) "宏结合使用,其中 x 是要访问的内存位置的地址
- HWREG(x) 用于 32 位访问,例如从 32 位计数寄存器读取数值
- HWREGH(x) 用于 16 位访问,它可用于访问 16 位寄存器或 32 位寄存器的上字或下字。这通常是最有效的宏
- HWREGB(x) 用于使用 __byte() 本征的 8 位访问。更多信息,请参阅《TMS320C28x Optimizing C/C++ Compiler User’s Guide》。通常只有在硬件需要 8 位访问时才使用它。否则,请使用 HWREGH() 并屏蔽和移除不需要的位
- HWREG_BP(x) 是另一个用于 32 位访问的宏,但它使用 __byte_peripheral_32() 编译器本征。它用于第 7 节中描述的字节外设。它告诉编译器,32 位访问不能分成两个 16 位读取-修改-写入操作,因为上位字不在字节外设的预期地址偏移量上
这些宏与寄存器描述和基地址 #defines 结合使用,构成了 Driverlib 的大部分代码。eg 30 展示了如何使用它们来实现 SCI_setConfig()函数。
eg30: SCI Function Implementation
/
//
// Example function implementation from Driverlib sci.c
//
/
//*******************************************************************
//
// SCI_setConfig
//
//*******************************************************************
void SCI_setConfig(uint32_t base, uint32_t lspclkHz, uint32_t baud,
uint32_t config)
{
...
...
}
//
// Compute the baud rate divider.
//
divider = ((lspclkHz / (baud * 8U))- 1U);
//
// Set the baud rate.
//
HWREGH(base + SCI_O_HBAUD) = (divider & 0xFF00U) >> 8U;
HWREGH(base + SCI_O_LBAUD) = divider & 0x00FFU;
//
// Set parity, data length, and number of stop bits.
//
HWREGH(base + SCI_O_CCR) = ((HWREGH(base + SCI_O_CCR) &
~(SCI_CCR_SCICHAR_M |
SCI_CCR_PARITYENA |
SCI_CCR_PARITY |
SCI_CCR_STOPBITS)) | config);
...
}
8.3 Peripheral Driver Library Advantages
外设驱动程序库有很多优点,包括:
- TI已经提供了驱动程序和头文件
C2000Ware 中提供了 Driverlib 驱动程序、头文件和示例项目。所有源代码均已提供,因此驱动程序可按原样使用,也可根据您的特定需求进行扩展。 有关下载 C2000Ware 的位置和 Driverlib 适用设备的信息,请参见第 1 节。 - Driverlib 生成的代码易于编写和阅读
由于 Driverlib 对实际发生的寄存器访问进行了抽象,因此编写应用程序所需的硬件知识较少。例如,在使用 Driverlib 时,第 6 节中讨论的读取-修改-写入(read-modify-write)考虑因素往往不是问题,因为驱动程序实现会处理这些问题这也意味着不同 C2000 设备之间的微小硬件差异被抽象化,从而使代码更容易移植。此外,Driverlib 在编写时还考虑到了可读性,因此函数名称和参数值都是对其功能的描述。 - Driverlib 内置调试功能
许多驱动程序函数都包含某种方式的参数检查。使用枚举类型可对某些参数进行编译时参数检查。对于其他参数,运行时断言可以检查传递给函数的值的有效性。不调试时,可以关闭断言,从而消除性能损耗。 - Driverlib 的优化好
第 10 节将详细讨论驱动程序的性能和用于生成高效代码的功能。 - Driverlib已通过 MISRA-C:2012 静态分析
驱动程序符合 C2000 MISRA-C:2012 政策。有关该政策的详细信息,请参阅 C2000™ MISRA-C Policy。
9. Code Size and Performance Using Driverlib
一般来说,软件抽象化会以牺牲性能为代价。然而,Driverlib 的低抽象级别和注重优化的设计使其非常高效。
Driverlib 易于优化的一个主要特点是将大多数函数声明为内联函数。当优化器开启(编译器选项–opt_level 设置为 0 或更高)时,内联函数允许编译器像宏一样处理函数。这样就消除了函数调用的损耗,加快了代码执行速度。
eg31 显示了使用优化级别为-o2 的内联 ADC_readResult() 函数读取 ADC 转换结果的代码。每次函数调用只产生一条 MOV 指令。同样的代码在编译时优化级别为-o2,但关闭了内联(–disable_inlining),会产生 22 个字的代码(ADC_readResult() 为 4 个字,调用函数为 18 个字),执行周期为 53 个周期。
eg31: Inlined ADC_readResult() Function Calls
C-Source Code Generated Assembly
Instruction
tmp[0]=ADC_readResult(ADCARESULT_BASE, MOV *-SP[3], *(0:0x0b00)
ADC_SOC_NUMBER0);
tmp[1]=ADC_readResult(ADCARESULT_BASE, MOV *-SP[2], *(0:0x0b01)
ADC_SOC_NUMBER1);
tmp[2]=ADC_readResult(ADCARESULT_BASE, MOV *-SP[1], *(0:0x0b02)
ADC_SOC_NUMBER2);
除了消除函数调用的开销外,内联 Driverlib 函数还能让编译器在编译时评估部分代码,从而使代码更小、更快。当常量作为参数传递给函数时,这种效果尤为明显。
eg32 显示了 ADC_setupSOC() Driverlib 函数的实现。该函数根据基数和 socNumber 参数计算需要写入的地址。所有其他参数在写入寄存器之前都需要移位、调整和组合。
eg32: ADC Function Implementation to be Optimized
/
//
// Example function implementation from Driverlib adc.h
//
/
static inline void
ADC_setupSOC(uint32_t base, ADC_SOCNumber socNumber,
ADC_Trigger trigger, ADC_Channel channel,
uint32_t sampleWindow)
{
uint32_t ctlRegAddr;
...
}
// Calculate address for the SOC control register.
ctlRegAddr = base + ADC_SOCxCTL_OFFSET_BASE +
((uint32_t)socNumber * 2U);
// Set the configuration of the specified SOC.
EALLOW;
HWREG(ctlRegAddr) = ((uint32_t)channel << ADC_SOC0CTL_CHSEL_S) |
((uint32_t)trigger << ADC_SOC0CTL_TRIGSEL_S) |
(sampleWindow- 1U);
EDIS;
}
eg33 显示了函数内联并传递常量后生成的程序集。 请注意,所有计算都已在编译时完成,剩下的工作就是访问保护和写入寄存器。
eg33: Inlined ADC_setupSOC() Function Call
C-Source Code Generated Assembly
Instruction
ADC_setupSOC(ADCA_BASE, EALLOW
ADC_SOC_NUMBER0, MOVB AL, #0xf
ADC_TRIGGER_EPWM1_SOCA, MOVB AH, #0x50
ADC_CH_ADCIN0, 16); MOVL XAR4, #0x007410
MOVL *+XAR4[0], ACC
EDIS
10. Comparing and Combining Approaches
位域和寄存器文件结构体头与外设驱动程序库方法是兼容的,既可在同一应用程序中使用,也可单独使用。本节将对这两种方法进行比较,并提供指导,说明如果采用组合方法,其中一种方法可能优于另一种方法。
其中一个重要原因是编译器能够在位字段代码中使用数据页指针。 eg34 显示了两种方法配置 CPU 定时器的示例,以及下面相应的程序集。所示程序集是在启用优化级别-o2 和关闭 Driverlib ASSERTs 的情况下生成的。数据页指针的使用意味着在这种情况下,位字段代码生成的代码更小、更快。不过,Driverlib 代码更易于阅读,并能无缝处理单独的预刻度寄存器。
eg34: CPU Timer Bit-Field(Left) and Driverlib(Right) Disassembly Comparison
在eg34 中,每一行比特字段代码都对应一个驱动库函数,但情况并非如此;例 32 显示了在 ADCSOC0CTL 寄存器中配置多个字段的 ADC_setupSOC()函数。eg35 中另一个值得注意的地方是,在驱动程序库函数中使用了 EALLOW 和EDIS 指令来禁用和重新启用对必要寄存器的写保护。
eg35: ADC Bit-Field(Left) and Driverlib(Right) Disassembly Comparison
如果在一个应用中同时使用两种方法,您可以从以下几个方面进行考虑:
- 在使用驱动程序库时,需要对硬件有详细的了解,这使其成为快速开发应用程序的良好选择。
- 当从旧的 C2000 设备向新的 C2000 设备移植遗留代码时,您可以继续使用位域和寄存器文件结构。在 C2000 设备的历代版本中,都有位字段头文件,而且它们与其他外设基本兼容。
- 当 Driverlib 不能满足要求时,使用比特字段和寄存器-文件结构应用程序特别适用于性能关键的代码。一般来说,当重复访问同一页面时,比特字段应用方法将生成更小、更快的代码。
11. References
• C281x C/C++ Header Files and Peripheral Examples
• C280x, C2801x C/C++ Header Files and Peripheral Examples
• C2804x C/C++ Header Files and Peripheral Examples
• TMS320C28x CPU and Instruction Set Reference Guide
• TMS320C28x Optimizing C/C++ Compiler User’s Guide
• TMS320C28x Assembly Language Tools User’s Guide
• TMS320x281x System Control and Interrupts Reference Guide
• TMS320x280x, 2801x, 2804x DSP System Control and Interrupts Reference Guide
• TMS320x281x Serial Communications Interface (SCI) Reference Guide
• C2000™MISRA-C Policy