(TI技术手册翻译)用 C/C++ 编程 TMS320x28xx 和 TMS320x28xxx

文章目录


工作需要,随便译的,我看得懂就行^ _ ^

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

原文

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值