TMS320F2812 学习总结 一、复位 2812 复位后,芯片会采样 XMPNMC 引脚的状态,这个脚的状态决定了 2812 复位后是 从内部的 Boot Rom 引导还是从外部接口区域 7 引导,如果 XMPNMC=1(微处理模式),那 么复位的中断向量将会指向外部的存储区域 7,当然这种模式下必须保证外部的存储区域可 用,同时引导程序必须由程序员事先写好,才能保证芯片的正常启动。如果 XMPNMC=0(微 计算机模式) ,那么外部存储区域 7 将被禁止而内部的 Boot Rom 使能,在这种情况下,芯 片复位后将会从内部的 Boot Rom 获得复位向量。一般情况下我们都是采用微计算机模式, 因此对这一引导方式做详细地说明。在微计算机模式下,芯片复位后从内部的 Boot Rom 0x3fffc0 处读取 0z3ffc00 这个地址,因此程序就从这个地址开始执行。图 1.1 是 Boot Rom
图 1.1
片上 Boot Rom
的存储器映射表,可以看出 0x3fffc0 处正好是 2812 的复位向量,跳转后的地址 0x3ffc00 正 好是芯片的引导加载函数的入口,因此芯片复位后将会执行引导加载函数,然后该函数根据 芯片的特殊 GPIO 口的状态确定芯片的引导模式。表 1.1 给出了 4 个 GPIO 引脚的状态来确 定所要使用的引导模式。 表 1.1 由 4 个 GPIO 引脚选择的引导模式 GPIOF4
GPIOF12
GPIOF3
GPIOF2
(SCITXDA)
(MDXA)
(SPISTEA)
(SPICLK)
PU
NO PU
NO PU
NO PU
1
x
x
x
0
1
x
x
0
0
1
1
调用 SCI_Boot 函数从 SCI_A 口进行引导
0
0
1
0
跳转到 H0 SARAM 中 0x3F8000 地址处
0
0
0
1
跳转到 OTP 中 0x3D7800 地址处
0
0
0
0
调用 Parallel_Boot 函数从 GPIO 口 B 进行引导
模式选择
跳转到 Flash 0x3F7FF6 地址处 调用 SPI_Boot 函数,利用 SPI 口从外部串行 EEPROM 中引导
注:1、PU 代表该引脚内部被拉高,NO PU 则表示该引脚没有被内部拉高。 2、必须要得别注意 SPICLK 可能具有外部逻辑。 3、如果引导模式选择 Flash、H0 或者 OTP,那么引导加载器不加载外部代码。
由于通常情况下我们都使用的是 Flash 引导。下面着重介绍 Flash 引导模式:在这种模 式下,Boot Rom 会将芯片配置成 28x 操作模式,并直接跳转到 Flash 存储器的 0x3f7ff6 处, 这个地址恰好位于 128 位密码所在的地址前面,只要在这个地址处事先写好一条跳转指令就 可以把程序的执行重新定位到包含自定义引导加载器函数或者用户的应用代码的存储区域 中(我的理解:通过 ccs3.1 编译的时候,编译器会自动生成这样一句指令 LB _c_int00,放 在 0x3f7ff6 处,执行完这句指令之后,程序会跳到_c_int00 这个函数,这个函数也是 ccs 自 动生成的,目的是建立一个 c 程序运行的环境,等建立完这个环境之后,又会执行这样一句 指令 LB _main,mian 函数就是用户的应用程序,芯片的复位引导完成)
二、初始化 2812 完成复位引导之后,执行用户的应用程序之前必须对器件进行配置,也就是一个 初始化的过程,该过程包括看门狗、时钟模块、外设时钟、Flash、以及中断向量初始化。 (1)看门狗 2812 的看门狗是一个 8 位的计数器,当 8 位的计数器计数到最大值的时候,看门狗模 块会产生一个输出脉冲(512 个振荡器时钟宽度)。如果不希望产生脉冲信号,必须在计数 最大值之前给看门狗复位控制寄存器依次写“0x55、0xAA” 。用户可以通过看门狗控制寄 存器使能或者屏蔽看门狗,使能后必须周期性写看门狗复位控制寄存器,屏蔽后看门狗不起 作用。 (2)时钟模块 2812 的处理器上有基于 PLL 的时钟模块,为器件以及各种外设提供时钟信号,这个时 钟模块提供两种操作模式。 内部振荡器:在 X1/XCLKIN 和 X2 两个引脚之间连接一个石英晶体。 外部时钟源:将输入时钟信号直接接到 X1/XCLKIN 引脚上,X2 悬空,这种情况下不 使用内部振荡器。 外部的 XPLLDIS 引脚的状态可以选择是否需要进行倍频。当 XPLLDIS 为低电平,系 统直接采用外部时钟或晶振直接作为系统时钟;当 XPLLDIS 为高电平时外部的时钟经过 PLL 倍频后为系统提供时钟。
图 2.1
晶体振荡器及锁相环模块
因此当使能 PLL 单元时,就必须对锁相环控制寄存器进行配置,选择相应的时钟频率。 同时通过高低速外设时钟寄存器对外设时钟进行定标(也就是根据所选外设以及系统 的要求,对 CPU 时钟进行分频,满足外设的要求)。 (3)Flash 一般情况下可以不对 Flash 进行初始化,但是对于时序要求比较高的系统通过对 Flash 的初始化可以提高程序的运行速度,所以对 Flash 的初始化显得比较重要,下面着重介绍 Flash 的初始化过程。 在 Flash 引导模式下,由于程序代码是烧写在 Flash 中,当然这些代码中间也包括 Flash
的初始化程序,因此这样的初始化是无效的,因为不可能在 Flash 中对 Flash 进行初始化, 所以在执行这段代码的时候必须将其 COPY 到 RAM 当中执行(调试中发现如果不进行 COPY 程序将会运行在一段不可知的空间)。 先给出 Flash 的初始化程序。 void InitFlash(void) { EALLOW; //Enable Flash Pipeline mode to improve performance //of code executed from Flash. FlashRegs.FOPT.bit.ENPIPE = 1; // CAUTION //Minimum waitstates required for the flash operating //at a given CPU rate must be characterized by TI. //Refer to the datasheet for the latest information. //Set the Random Waitstate for the Flash FlashRegs.FBANKWAIT.bit.RANDWAIT = 5; //Set the Paged Waitstate for the Flash FlashRegs.FBANKWAIT.bit.PAGEWAIT = 5; // CAUTION //Minimum cycles required to move between power states //at a given CPU rate must be characterized by TI. //Refer to the datasheet for the latest information. //For now use the default count //Set number of cycles to transition from sleep to standby FlashRegs.FSTDBYWAIT.bit.STDBYWAIT = 0x01FF; //Set number of cycles to transition from standby to active FlashRegs.FACTIVEWAIT.bit.ACTIVEWAIT = 0x01FF; EDIS; //Force a pipeline flush to ensure that the write to //the last register configured occurs before returning. asm(" RPT #7 || NOP"); } 在上述初始化的函数中主要完成的是对 Flash 的读时序的配置,完成上面的函数之后, 使用如下指令定义这段函数的代码段: #pragma CODE_SECTION(InitFlash, "ramfuncs"); 关于这句指令的用法可以参考 TI 的 C 语言手册(TMS320F28x Optimizing C/C++
Compiler User’s Guide)SPRU514 通过这句指令的定义之后,就把 InitFlash 函数定义在一个程序段名已知的空间中,该 段段名为“ramfuncs”。 在 CMD 文件中实现以下描述: ramfuncs : LOAD = FLASHH, PAGE =0 RUN = RAML0L1, PAGE =1 LOAD_START(_RamfuncsLoadStart), LOAD_END(_RamfuncsLoadEnd), RUN_START(_RamfuncsRunStart) 通过这样的描述之后,编译器会把 InitFlash 函数所在的段“ramfuncs”编译到 FLASHH, 然后指定它的运行空间为 RAML0L1,这样编译器会对 ramfuncs 定位,指定它所在 FLASHH 中的开始地址_RamfuncsLoadStart,结束地址_RamfuncsLoadEnd,以及在 RAML0L1 中 运行的开始地址_RamfuncsRunStart,至于该地址位于所在 Flash 或者 RAM 的什么地方, 编译器会根据 CMD 文件中其他段的定义来分配,如果内存溢出或者 Flash 空间不够,编译 器会自动报错,然后可以根据错误内容修改相应的空间到一个合适的位置。完成上述工作之 后,还不能实现程序的 COPY,因为这时并没有真正的把程序 COPY 到 RAM 中,只是指定 了它们各自的位置,所以最后一步要做的就是按照这三个已知的地址,通过一个函数把它们 真正的实现转移,这个函数的原型如下: void MemCopy(Uint16 *SourceAddr, Uint16* SourceEndAddr, Uint16* DestAddr) { while(SourceAddr < SourceEndAddr) { *DestAddr++ = *SourceAddr++; } return; } 有了这个函数的支持之后,就可以实现 Flash 初始化了,步骤如下。 先在外部声明三个参数: extern Uint16 RamfuncsLoadStart; extern Uint16 RamfuncsLoadEnd; extern Uint16 RamfuncsRunStart; 再调用函数: MemCopy(&RamfuncsLoadStart, &RamfuncsLoadEnd, &RamfuncsRunStart); 因为在 CMD 文件中已经使得这三个参数获得实际的地址,此时它们所携带的参数就是 要 COPY 程序的开始和结束地址,以及要运行程序的开始地址,执行完这个函数之后,初 始化 Flash 函数的代码已经被转移到 RAM 当中了,因此这时候再执行函数: InitFlash(); 就已经在 RAM 当中执行了,这样就可以实现对 Flash 的初始化了。 (4)中断向量 2812 的 CPU 支持 17 个 CPU 级的中断,这其中包括一个不可屏蔽中断和 16 个可屏蔽中 断。2812 的器件有很多外设,每个外设都会产生一个或者多个外设级中断,由于 CPU 没有 能力去处理这么多的中断请求,因此需要一个中断扩展寄存器来仲裁这些中断。外设中断扩 展模块(PIE)就是用来管理这些扩展中断的 ,在这个模块中多个中断源复用几个中断输入
信号,PIE 最多可以支持 96 个中断,其中 8 个中断分为一组,复用一个 CPU 中断,总共 12 组中断,每个中断都有自己的中断向量表存放在 RAM 中,因此在系统初始化时,必须定位 中断向量表。 在 28x 器件中,中断向量可以映射到 5 个不同的存储空间。在实际的应用中只使用 PIE 中断向量的映射。中断向量映射主要由以下位/信号来控制。 WMAP: 该位在状态寄存器 1(ST1)的位 3,复位后的值为 1。可以通过改变 ST1 的 值或使用 STEC/CLRC 指令来改变 WMAP 的值,正常操作时该位置 1。 M0M1MAP: 该位在状态寄存器 1(ST1)的位 11,复位后的值为 1。可以通过改变 ST1 的值或使用 STEC/CLRC 指令来改变 M0M1MAP 的值,正常操作时该位置 1。 MP/MC:该位在 XINTCNF2 寄存器的位 8。对于有外部接口(XINTF)的器件(如 F2812) , 复位时 XMP/MC 引脚上的值为该寄存器的值。对于没有外部接口的器件(如 F2810), XMP/MC 内部拉低。器件复位后,可以通过调整 XINTCNF2 寄存器(地址::0x00000B34) 来改变该位的值。 ENPIE:该位在 PIECTRL 寄存器的位 0,复位的默认值为 0(PIE 被屏蔽)。器件复位 后可以通过调整 PIECTRL 寄存器(地址: 0x00000CE0)来改变该位的值。 根据上述控制位的不同设置,中断向量表有不同的映射方式,如下表所示: 表 2.1 中断向量表映射配置表 向量映射
向量获取位置
地址范围
WMAP
M0M1MAP
MP/MC
ENPIE
M1 向量
M1 SARAM
0x000000~0x00003F
0
0
X
X
M0 向量
M0 SARAM
0x000000~0x00003F
0
1
X
X
BROM 向量
ROM
0x3FFFC0~0x3FFFFF
1
X
0
0
XINTF 向量
XINTF Zone7
0x3FFFC0~0x3FFFFF
1
X
1
0
PIE 向量
PIE
0x000D00~0x000DFF
1
X
X
1
保留 M1 和 M0 向量表映射,只供 TI 测试用。当使用其他向量表映射时,M0 和 M1 存 储器作为 RAM 使用,可以随便使用,没有任何限制。复位后器件默认的向量映射如下所示: 表 2.2 复位后中断向量表映射配置表 向量映射
向量获取位置
地址范围
WMAP
M0M1MAP
MP/MC
ENPIE
BROM 向量
ROM
0x3FFFC0~0x3FFFFF
1
X
0
0
XINTF 向量
XINTF Zone7
0x3FFFC0~0x3FFFFF
1
X
1
0
复位后的中断向量表和前面所描述的 2812 的复位是一致的,需要注意的是当器件复位 时,总是从上表中获取复位向量,复位引导程序完成之后,用户需要重新初始化 PIE 中断向 量表,应用程序必须使能 PIE 中断向量表,中断将从 PIE 向量表中获取向量。 到底 2812 是如何从 PIE 向量表中获取中断向量的,首先必须清楚 PIE 中断向量表的存 储地址,从表 2.1 可以看出 PIE 中断向量表位于 0x000D00~0x000DFF,这段地址位于 2812 的数据空间,大小为 256×16 的 RAM 区域,通过初始化 Flash 的过程我们知道,如果 PIE 向量表位于 RAM 区,那么用户应用程序就必须对其初始化,使得中断向量必须指向用户定 义的空间,这样中断发生时中断响应才能有效。如何实现这一过程了,下面我们给出一段例 程说明实现的方法。 先定义一个中断函数,我们以 CPU 定时器 0 的中断为例说明,说明之前假设中断是开 放的。 interrupt void ISRTimer0(void) { volatile Uint32 i; i ++ ;
} (关于 interrupt 的理解可以参考 TI 的 C 语言手册(TMS320F28x Optimizing C/C++ Compiler User’s Guide)SPRU514,它是 ccs 的 c 语言编译器中的一个扩展关键字,用来定 义中断函数,如果使用了该关键字,那么函数执行之前会对相应的寄存器进行压栈保护,函 数返回之后弹出压栈的寄存器值,用该关键字定义的函数没有返回值。 ) 定义完函数之后,我们将该函数的地址赋给一个指针: PieVectTable.TINT0 = &ISRTimer0; 解释上面语句之前,我们先来看如下的描述: #pragma DATA_SECTION(PieVectTable,"PieVectTable"); 关于这句指令的用法可以参考 TI 的 C 语言手册(TMS320F28x Optimizing C/C++ Compiler User’s Guide)SPRU514 这句话定义一个数据段 PieVectTable, 段名为 PieVectTable,在 CMD 文件中 PieVectTable 的描述是这样的: PieVectTable : > PIE_VECT, PAGE = 1 PIE_VECT : origin = 0x000D00, length = 0x000100 从以上表述可以看出 PieVectTable 已经被定义在了开始于 0x000D00 的地址处,这个地 址恰好就是 PIE 中断向量表在 RAM 中所在的位置, 再来看有关 PieVectTable 结构体的定义: typedef interrupt void(*PINT)(void); // Define Vector Table: struct PIE_VECT_TABLE { // Reset is never fetched from this table. // It will always be fetched from 0x3FFFC0 in either // boot ROM or XINTF Zone 7 depending on the state of // the XMP/MC input signal. On the F2810 it is always // fetched from boot ROM. PINT PIE1_RESERVED; PINT PIE2_RESERVED; PINT PIE3_RESERVED; PINT PIE4_RESERVED; PINT PIE5_RESERVED; PINT PIE6_RESERVED; PINT PIE7_RESERVED; PINT PIE8_RESERVED; PINT PIE9_RESERVED; PINT PIE10_RESERVED; PINT PIE11_RESERVED; PINT PIE12_RESERVED; PINT PIE13_RESERVED; // Non-Peripheral Interrupts: PINT XINT13; // XINT13 PINT TINT2; // CPU-Timer2 PINT DATALOG; // Datalogging interrupt PINT RTOSINT; // RTOS interrupt PINT EMUINT; // Emulation interrupt
PINT XNMI; // Non-maskable interrupt PINT ILLEGAL; // Illegal operation TRAP PINT USER0; // User Defined trap 0 PINT USER1; // User Defined trap 1 PINT USER2; // User Defined trap 2 PINT USER3; // User Defined trap 3 PINT USER4; // User Defined trap 4 PINT USER5; // User Defined trap 5 PINT USER6; // User Defined trap 6 PINT USER7; // User Defined trap 7 PINT USER8; // User Defined trap 8 PINT USER9; // User Defined trap 9 PINT USER10; // User Defined trap 10 PINT USER11; // User Defined trap 11 // Group 1 PIE Peripheral Vectors: PINT PDPINTA; // EV-A PINT PDPINTB; // EV-B PINT rsvd1_3; PINT XINT1; PINT XINT2; PINT ADCINT; // ADC PINT TINT0; // Timer 0 PINT WAKEINT; // WD // Group 2 PIE Peripheral Vectors: PINT CMP1INT; // EV-A PINT CMP2INT; // EV-A PINT CMP3INT; // EV-A PINT T1PINT; // EV-A PINT T1CINT; // EV-A PINT T1UFINT; // EV-A PINT T1OFINT; // EV-A PINT rsvd2_8; // Group 3 PIE Peripheral Vectors: PINT T2PINT; // EV-A PINT T2CINT; // EV-A PINT T2UFINT; // EV-A PINT T2OFINT; // EV-A PINT CAPINT1; // EV-A PINT CAPINT2; // EV-A PINT CAPINT3; // EV-A PINT rsvd3_8; // Group 4 PIE Peripheral Vectors: PINT CMP4INT; // EV-B PINT CMP5INT; // EV-B
PINT CMP6INT; // EV-B PINT T3PINT; // EV-B PINT T3CINT; // EV-B PINT T3UFINT; // EV-B PINT T3OFINT; // EV-B PINT rsvd4_8; // Group 5 PIE Peripheral Vectors: PINT T4PINT; // EV-B PINT T4CINT; // EV-B PINT T4UFINT; // EV-B PINT T4OFINT; // EV-B PINT CAPINT4; // EV-B PINT CAPINT5; // EV-B PINT CAPINT6; // EV-B PINT rsvd5_8; // Group 6 PIE Peripheral Vectors: PINT SPIRXINTA; // SPI-A PINT SPITXINTA; // SPI-A PINT rsvd6_3; PINT rsvd6_4; PINT MRINTA; // McBSP-A PINT MXINTA; // McBSP-A PINT rsvd6_7; PINT rsvd6_8; // Group 7 PIE Peripheral Vectors: PINT rsvd7_1; PINT rsvd7_2; PINT rsvd7_3; PINT rsvd7_4; PINT rsvd7_5; PINT rsvd7_6; PINT rsvd7_7; PINT rsvd7_8; // Group 8 PIE Peripheral Vectors: PINT rsvd8_1; PINT rsvd8_2; PINT rsvd8_3; PINT rsvd8_4; PINT rsvd8_5; PINT rsvd8_6; PINT rsvd8_7; PINT rsvd8_8; // Group 9 PIE Peripheral Vectors: PINT RXAINT; // SCI-A
PINT TXAINT; // SCI-A PINT RXBINT; // SCI-B PINT TXBINT; // SCI-B PINT ECAN0INTA; // eCAN PINT ECAN1INTA; // eCAN PINT rsvd9_7; PINT rsvd9_8; // Group 10 PIE Peripheral Vectors: PINT rsvd10_1; PINT rsvd10_2; PINT rsvd10_3; PINT rsvd10_4; PINT rsvd10_5; PINT rsvd10_6; PINT rsvd10_7; PINT rsvd10_8; // Group 11 PIE Peripheral Vectors: PINT rsvd11_1; PINT rsvd11_2; PINT rsvd11_3; PINT rsvd11_4; PINT rsvd11_5; PINT rsvd11_6; PINT rsvd11_7; PINT rsvd11_8; // Group 12 PIE Peripheral Vectors: PINT rsvd12_1; PINT rsvd12_2; PINT rsvd12_3; PINT rsvd12_4; PINT rsvd12_5; PINT rsvd12_6; PINT rsvd12_7; PINT rsvd12_8; }; // PIE Interrupt Vector Table External References & Function Declarations: extern struct PIE_VECT_TABLE PieVectTable; 先来看下面这条语句: typedef interrupt void(*PINT)(void); 这句话完成了一个类型的定义,以后使用 PINT 将定义一个函数的地址。 再来看 TINT0 的位置,它位于该结构体的第 39 个字节处,对照 PIE 中断向量表,第 39 个字节处正好是 CPU 定时器 0 的中断向量,因此完成了的定义之后,当 CPU 定时器 0 的中断发生后,CPU 从中断向量表 0x00000D4C 处取中断向量,0x00000D4C 处正好放的是 中断函数 interrupt void ISRTimer0(void)的地址,因为 interrupt 关键字的定义,执行该中
断函数之前先保护寄存器,然后执行函数功能,执行完后弹出保护寄存器,实现了 CPU 定 时器 0 的中断响应。 上面以 CPU 定时器 0 的中断为例进行了详细的介绍,其他的中断可以参考以上的描述。
三、CMD 文件的编写 扩展名为.CMD 的文件称为链接器命令文件,该文件主要对 2812 的内部资源进行分配, 2812 的程序能不能正确运行,很大程度上取决于该文件的编写,因此要弄懂 2812 的运行机 理,必须搞清楚 CMD 文件的编写,要搞清楚 CMD 文件的编写,又必须了解 2812 片上存 储器的地址分配,下面给出 2812 存储器映射图。
图 3.1 TMS320F2812 存储器映射图 从上图可以看出 2812 的所有存储空间采用统一寻址:低 64K 地址存储器相当于 F24x/F240 处理器的数据存储空间,高 64K 地址的存储器相当于 F24x/F240 处理器的程序存 储空间。 在 CMD 文件中主要完成两个单元的定义,一个是 MEMORY,另一个是 SECTION。 MEMORY 中主要完成对存储器资源的分配。 下面给出一个完整的 MEMORY 例子: MEMORY { PAGE 0: /* Program Memory */ ZONE0 : origin = 0x002000, length = 0x002000 ZONE1 : origin = 0x004000, length = 0x002000 RAML0 : origin = 0x008000, length = 0x001000 ZONE2 : origin = 0x080000, length = 0x080000 ZONE6 : origin = 0x100000, length = 0x080000
OTP FLASHJ FLASHI FLASHH FLASHG FLASHF FLASHE FLASHD FLASHC FLASHA
: origin = 0x3D7800, length = 0x000800 : origin = 0x3D8000, length = 0x002000 : origin = 0x3DA000, length = 0x002000 : origin = 0x3DC000, length = 0x004000 : origin = 0x3E0000, length = 0x004000 : origin = 0x3E4000, length = 0x004000 : origin = 0x3E8000, length = 0x004000 : origin = 0x3EC000, length = 0x004000 : origin = 0x3F0000, length = 0x004000 : origin = 0x3F6000, length = 0x002000
BEGIN : origin = 0x3F8000, length = 0x000002 PRAMH0 : origin = 0x3F8002, length = 0x001FFE ZONE7 : origin = 0x3FC000, length = 0x003FC0 ROM : origin = 0x3FF000, length = 0x000FC0 RESET : origin = 0x3FFFC0, length = 0x000002 VECTORS : origin = 0x3FFFC2, length = 0x00003E PAGE 1 : /* Data Memory */ RAMM0 : origin = 0x000000, length = 0x000400 RAMM1 : origin = 0x000400, length = 0x000400 DEV_EMU : origin = 0x000880, length = 0x000180 FLASH_REGS : origin = 0x000A80, length = 0x000060 CSM : origin = 0x000AE0, length = 0x000010 XINTF : origin = 0x000B20, length = 0x000020 CPU_TIMER0 : origin = 0x000C00, length = 0x000008 PIE_CTRL : origin = 0x000CE0, length = 0x000020 PIE_VECT : origin = 0x000D00, length = 0x000100 ECAN_A : origin = 0x006000, length = 0x000100 ECAN_AMBOX : origin = 0x006100, length = 0x000100 SYSTEM : origin = 0x007010, length = 0x000020 SPI_A : origin = 0x007040, length = 0x000010 SCI_A : origin = 0x007050, length = 0x000010 XINTRUPT : origin = 0x007070, length = 0x000010 GPIOMUX : origin = 0x0070C0, length = 0x000020 GPIODAT : origin = 0x0070E0, length = 0x000020 ADC : origin = 0x007100, length = 0x000020 EV_A : origin = 0x007400, length = 0x000040 EV_B : origin = 0x007500, length = 0x000040 SCI_B : origin = 0x007750, length = 0x000010 MCBSP_A : origin = 0x007800, length = 0x000040 RAML1 : origin = 0x009000, length = 0x001000 FLASHB : origin = 0x3F4000, length = 0x002000 CSM_PWL : origin = 0x3F7FF8, length = 0x000008 DRAMH0 : origin = 0x3f9000, length = 0x001000
} 在 PAGE0 中: ZONE0 : origin = 0x002000, length = 0x002000; 指定外部空间 0 的开始地址为 0x002000,长度为 0x002000,这和 2812 对 ZONE0 空间 的定义是一致的,因此 memory 就是按照 2812 的存储空间对其进行声明或者说定义,使其 所包括的 RAM 区、Flash 区、扩展空间、以及所有的控制寄存器与其实际的地址对应,以 免在内存分配的时候出错。PAGE0 中主要完成对程序空间的定义,PAGE1 中主要完成对数 据空间的定义。 SECTION 指定用户的程序段、数据段、或者变量在以上 memory 中如何分配。 下面给出一个例子: SECTIONS { /*** Compiler Required Sections ***/ .text : > PRAMH0, PAGE = 0 .cinit : > PRAMH0, PAGE = 0 .stack : > RAMM1, PAGE = 1 .bss : > RAMM0, PAGE = 1 .ebss : > RAMM0, PAGE = 1 .const : > RAMM0, PAGE = 1 .econst : > RAMM0, PAGE = 1 .sysmem : > RAMM1, PAGE = 1 .reset : > RESET, PAGE = 0, TYPE = DSECT /*** User Defined Sections ***/ codestart : > BEGIN, PAGE = 0 / /*** Peripheral Frame 0 Register Structures ***/ DevEmuRegsFile : > DEV_EMU, PAGE = 1 FlashRegsFile : > FLASH_REGS, PAGE = 1 CsmRegsFile : > CSM, PAGE = 1 XintfRegsFile : > XINTF, PAGE = 1 CpuTimer0RegsFile : > CPU_TIMER0, PAGE = 1 PieCtrlRegsFile : > PIE_CTRL, PAGE = 1 PieVectTable : > PIE_VECT, PAGE = 1 /*** Peripheral Frame 1 Register Structures ***/ SysCtrlRegsFile : > SYSTEM, PAGE = 1 SpiaRegsFile : > SPI_A, PAGE = 1 SciaRegsFile : > SCI_A, PAGE = 1 XIntruptRegsFile : > XINTRUPT, PAGE = 1 GpioMuxRegsFile : > GPIOMUX, PAGE = 1 GpioDataRegsFile : > GPIODAT PAGE = 1 AdcRegsFile : > ADC, PAGE = 1 EvaRegsFile : > EV_A, PAGE = 1 EvbRegsFile : > EV_B, PAGE = 1 ScibRegsFile : > SCI_B, PAGE = 1 McbspaRegsFile : > MCBSP_A, PAGE = 1
/*** Peripheral Frame 2 Register Structures ***/ ECanaRegsFile : > ECAN_A, PAGE = 1 ECanaMboxesFile : > ECAN_AMBOX PAGE = 1 /*** Code Security Password Locations ***/ CsmPwlFile : > CSM_PWL, PAGE = 1 } 在上面的 section 中: .text : > PRAMH0, PAGE = 0 .text 在汇编指令中表示程序代码段,经过上述语句的定义之后,就把.text 分配到了 memory 空间的 PRAMH0 中去,到时程序执行的话就会运行在 PRAMH0 中。 在上面的定义中还有一些特殊的汇编伪指令,下面给出它们的定义:.cinit 存放C程序 中的变量初值和常量 ; .const 存放C程序中的字符常量、浮点常量和用const声明的常 量;.switch 存放C程序中switch语句的跳针表;.text 存放C程序的代码;.bss 为C程序中 的全局和静态变量保留存储空间 ; .far 为C程序中用far声明的全局和静态变量保留空 间;.stack 为C程序系统堆栈保留存储空间,用于保存返回地址、函数间的参数传递、存储 局部变量和保存中间结果;.sysmem 用于C程序中malloc、calloc和realloc函数动态分配存储 空间。 至于DevEmuRegsFile、ECanaRegsFile等等这些块主要分配一些外设寄存器的空间,而 这些空间的分配主要基于c语言中的结构体和联合体对于连续地址空间的分配。下面通过 CpuTimer0Regs寄存器对寄存器地址的分配做详细的介绍。 为了便于理解,我们按照TI所给的定义采用倒序的方式进行解释。 首先给出下面一句伪指令: #pragma DATA_SECTION(CpuTimer0Regs,"CpuTimer0RegsFile"); 关于这句指令的用法可以参考 TI 的 C 语言手册(TMS320F28x Optimizing C/C++ Compiler User’s Guide)SPRU514 这 句 话 定 义 一 个 数 据 段 , 段 名 为 CpuTimer0RegsFile , 在 CMD 文 件 中 CpuTimer0RegsFile 的描述是这样的: CpuTimer0RegsFile : > CPU_TIMER0, PAGE = 1 CPU_TIMER0 : origin = 0x000C00, length = 0x000008 CMD 文件指出,CpuTimer0RegsFile 被分配在 PAGE1 中的 CPU_TIMER0 存储空间, 这段空间开始于 0x000C00,长度为 8 个字节,通过查阅相关的手册,2812CPU 定时器 0 的 相关寄存器确实位于 0x000C00 开始的地址处,长度为 8 个字节,并且这些寄存器位于数据 存储空间,因此经过#pragma DATA_SECTION 的定义之后,CpuTimer0Regs 结构体中的 内容将被定位在数据存储空间 0x000C00 开始的地址处,以后对这个结构体内部元素所做的 操作实际上就是对 CPU 定时器 0 的相关寄存器进行操作。有了上述的定义之后,要实现 CPU 定时器 0 的寄存器配置就显得非常简单,只需要通过 c 语言中的结构体和联合体对每个寄存 器的空间进行分配,使得所操作的寄存器以及相关的位和实际 2812 内部地址一致,下面一 步步分析实现过程。 首先定义一个结构体 CpuTimer0Regs : extern volatile struct CPUTIMER_REGS CpuTimer0Regs; 给出结构体原型: struct CPUTIMER_REGS { union TIM_GROUP TIM; // Timer counter register union PRD_GROUP PRD; // Period register
union TCR_REG TCR; // Timer control register Uint16 rsvd1; // reserved union TPR_REG TPR; // Timer pre-scale low union TPRH_REG TPRH; // Timer pre-scale high }; 给出联合体 1 原型: struct TIM_REG { Uint16 Uint16 };
LSW; MSW;
union TIM_GROUP { Uint32 all; struct TIM_REG half; }; 通过上面的描述之后,我们先来看结构体 CPUTIMER_REGS 的第一个元素 union TIM_GROUP TIM,按照 CPU 定时器 0 寄存器地址的分配,结构体 CPUTIMER_REGS 地 址 开 始 的 前 两 个 字 节 应 该 是 CPU 定 时 器 0 的 计 数 寄 存 器 , 我 们 再 来 看 联 合 体 TIM_GROUP,它所表示的意义就是 all 和 TIM_REG 定义的结构体 half 共用一段连续的地 址空间,所以针对联合体 TIM_GROUP 中 all 操作,实际上就是对结构体 half 的操作,而 结构体 half 具有的结构原型是两个连续的字节单元 ,因此针对 CPU 定时器 0 的计数寄存 器低字节的操作可以采用如下这种表述: TIM . half . LSW 针对 CPU 定时器 0 的计数寄存器高字节的操作可以采用如下这种表述: TIM . half . MSW 针对 CPU 定时器 0 的计数寄存器全部的操作可以采用如下这种表述: TIM . all 可以看出利用结构体和联合体的特性,使得对于固定地址的操作变得非常简单,为了进 一步说明这种用法,下面再举一例说明如何对特殊寄存器的位进行操作,先给出代码原型: struct TCR_BITS { Uint16 OUTSTS:1; Uint16 FORCE:1; Uint16 POL:1; Uint16 TOG:1; Uint16 TSS:1; Uint16 TRB:1; Uint16 FRCEN:1; Uint16 PWIDTH:3; Uint16 SOFT:1; Uint16 FREE:1; Uint16 rsvd:2; Uint16 TIE:1; Uint16 TIF:1; };
union TCR_REG { Uint16 all; struct TCR_BITS bit; }; 在结构体 CPUTIMER_REGS 中 union TIM_GROUP TIM 占用两个字节,union PRD_GROUP PRD 也占用两个字节,因此 union TCR_REG TCR 所在的地址空间通过相 关手册可以知道是 CPU 定时器 0 的控制寄存器,因此根据联合体 union TCR_REG 中的定 义可以知道结构体 truct TCR_BITS bit 所占有的 16 个位,就是 CPU 定时器 0 的控制寄存 器所在的位。 因此在结构体 bit 的原型 struct TCR_BITS 中分别对这些特殊的位进行了定义。 以后所有针对 CPU 定时器 0 控制寄存器的操作可以通过如下的表述: TCR . all 针对 CPU 定时器 0 控制寄存器的位的操作可以通过如下的表述: TCR . bit . OUTSTS 或者 TCR . bit . TSS 有了以上两种用法,针对 CPU 定时器 0 控制寄存器的操作显得非常简单和直观。 有关 2812 的其他外设寄存器的结构体的编写,以及在 CMD 文件中的定义和上面所举 的 CPU 定时器 0 控制寄存器的例子是一样,具体可以参考上面的描述,在此不再逐一解释。
四、应用程序的执行空间 F2812 内部有 128K 的 Flash 存储器,可以用来存放用户的应用程序代码,这一块被划 分为 4 个 8K×16 位的扇区和 6 个 16K×16 位的扇区。所有的 28x 器件包含两个单访问存储 器 M0 和 M1,每一个大小为 1K×16 位,它们同时映射到程序和数据存储空间,因此既可 以用来执行代码,也可以用来存放数据变量。F2812 还包含一块 16K×16 位的单周期访问的 RAM 存储器(SARAM),这部分存储器被分成三块,分别是 L0(4K),L1(4K),H0(8K)。 每个模块都能够独立访问,而且每个模块都能够映射到程序和数据空间。 通常情况下,用户的代码都是通过烧写 Flash 的方法,存储在 Flash 中,芯片复位后, 程序从 Flash 中自举,系统运行,对于要求较高的系统还可以通过初始化 Flash 来满足指令 的执行速度,但是在不同的情况下,可能需要把一些应用代码 COPY 到内部 RAM 或者外部 RAM 中来执行,关于程序的 COPY 在前面的 Flash 初始化中已经有所介绍,下面就应用程 序的三种不同执行空间做简要的介绍。 1、程序在 RAM 中执行 一般情况下, 系统在 Debug 阶段都是采用这种方式, 把程序通过仿真器下载到内部 RAM 中进行调试,这种调试方法是基于 JTAG 协议的,可以把 F2812 内部的实时信息通过仿真器 传输到用户的 PC 终端,在 CSS 仿真环境下通过设置断点,查看变量的值来确定系统的运行 状态,达到调试的目的,因此必须先掌握这种运行环境。 要实现程序在 RAM 中执行,只要编写正确的 CMD 文件就可以实现。下面给出完整的 代码。 MEMORY { PAGE 0 : PRAMH0 : origin = 0x3f8000, length = 0x001000 FLASHJ : origin = 0x3D8000, length = 0x002000 PAGE 1 : /* SARAM */ RAMM0 : origin = 0x000000, length = 0x000400 RAMM1 : origin = 0x000400, length = 0x000400 /* Peripheral Frame 0: */ DEV_EMU : origin = 0x000880, length = 0x000180 FLASH_REGS : origin = 0x000A80, length = 0x000060 CSM : origin = 0x000AE0, length = 0x000010 XINTF : origin = 0x000B20, length = 0x000020 CPU_TIMER0 : origin = 0x000C00, length = 0x000008 CPU_TIMER1 : origin = 0x000C08, length = 0x000008 CPU_TIMER2 : origin = 0x000C10, length = 0x000008 PIE_CTRL : origin = 0x000CE0, length = 0x000020 PIE_VECT : origin = 0x000D00, length = 0x000100 /* Peripheral Frame 1: */ ECAN_A : origin = 0x006000, length = 0x000100 ECAN_AMBOX : origin = 0x006100, length = 0x000100
/* Peripheral Frame 2: */ SYSTEM : origin = 0x007010, length = 0x000020 SPI_A : origin = 0x007040, length = 0x000010 SCI_A : origin = 0x007050, length = 0x000010 XINTRUPT : origin = 0x007070, length = 0x000010 GPIOMUX : origin = 0x0070C0, length = 0x000020 GPIODAT : origin = 0x0070E0, length = 0x000020 ADC : origin = 0x007100, length = 0x000020 EV_A : origin = 0x007400, length = 0x000040 EV_B : origin = 0x007500, length = 0x000040 SPI_B : origin = 0x007740, length = 0x000010 SCI_B : origin = 0x007750, length = 0x000010 MCBSP_A : origin = 0x007800, length = 0x000040 /* CSM Password Locations */ CSM_PWL : origin = 0x3F7FF8, length = 0x000008 /* SARAM */ DRAMH0 : origin = 0x3f9000, length = 0x001000 } SECTIONS { /* Allocate program areas: */ .reset : > PRAMH0, PAGE = 0 .text : > PRAMH0, PAGE = 0 .cinit : > PRAMH0, PAGE = 0 /* Allocate data areas: */ .stack : > RAMM1, PAGE = 1 .bss : > DRAMH0, PAGE = 1 .ebss : > DRAMH0, PAGE = 1 .const : > DRAMH0, PAGE = 1 .econst : > DRAMH0, PAGE = 1 .sysmem : > DRAMH0, PAGE = 1 test_table : > FLASHJ, PAGE = 0 /* Allocate Peripheral Frame 0 Register Structures: DevEmuRegsFile : > DEV_EMU, PAGE = 1 FlashRegsFile : > FLASH_REGS, PAGE = 1 CsmRegsFile : > CSM, PAGE = 1 XintfRegsFile : > XINTF, PAGE = 1 CpuTimer0RegsFile : > CPU_TIMER0, PAGE = 1 CpuTimer1RegsFile : > CPU_TIMER1, PAGE = 1 CpuTimer2RegsFile : > CPU_TIMER2, PAGE = 1 PieCtrlRegsFile : > PIE_CTRL, PAGE = 1 PieVectTable : > PIE_VECT, PAGE = 1 /* Allocate Peripheral Frame 2 Register Structures:
*/
*/
ECanaRegsFile : > ECAN_A, PAGE = 1 ECanaMboxesFile : > ECAN_AMBOX PAGE = 1 /* Allocate Peripheral Frame 1 Register Structures: */ SysCtrlRegsFile : > SYSTEM, PAGE = 1 SpiaRegsFile : > SPI_A, PAGE = 1 SciaRegsFile : > SCI_A, PAGE = 1 XIntruptRegsFile : > XINTRUPT, PAGE = 1 GpioMuxRegsFile : > GPIOMUX, PAGE = 1 GpioDataRegsFile : > GPIODAT PAGE = 1 AdcRegsFile : > ADC, PAGE = 1 EvaRegsFile : > EV_A, PAGE = 1 EvbRegsFile : > EV_B, PAGE = 1 ScibRegsFile : > SCI_B, PAGE = 1 McbspaRegsFile : > MCBSP_A, PAGE = 1 /* CSM Password Locations */ CsmPwlFile : > CSM_PWL, PAGE = 1 } 在上述 CMD 文件中,我们把 .text 定位在 PRAMH0 空间中,即就是 2812 的单周期访 问 RAM 寄存器的 H0 块中,该块的大小为 8K,因此小于 8K 的程序代码都可以在此空间中 进行仿真调试,如果程序大于 8K 该怎么办?这有两种情况:第一种情况是程序代码及程序 变量总共超过了 18K,内部的 RAM 已经不够用,只能外扩 RAM 进行调试。另外一种情况 是程序代码及程序变量总共并没有超过 18K。 先来解决第一种情况。在这种情况下由于内部 RAM 的空间太小,因此必须根据系统的 大小来选择合适的外部 RAM 来扩展,一般选择 64 K、128 K、256 K 的 RAM,然后通过外 部接口扩展在 ZONE6 空间,片选信号 XZCS6ANDCS7。外扩了 RAM 之后接下来需要修改 CMD 文件。 在 MEMORY 的 PAGE1 中添加如下定义: EXRAM : origin = 0x100000, length = 0x010000 在 SECTIONS 中把 .text 定位修改为: .text : > EXRAM, PAGE = 1 这样程序仿真时将会运行在外部扩展空间 ZONE6 的 RAM 上。 第二种情况我们可以采取代码分离的方法解决。先回顾前面提到的这句指令: #pragma CODE_SECTION(, ""); 有了这句指令的保证,我们可以把应用程序中不同的模块定义到不同的程序段,然后 把这些程序段分配到 2812 内部 18K 的 RAM 中去。举例说明这种应用。 假如用户应用程序大小为 10K,H0 块中无法实现调试,在用户的应用程序中有这么一 个函数 ADC_Cali( ),该函数的大小为 3K 左右,现在我们做如下定义: #pragma CODE_SECTION(ADC_Cali, "adcfuncs"); 把这段函数定位到以 adcfuncs 为段名的程序段中,在 CMD 文件中: adcfuncs : > DRAMH0, PAGE = 1 经过这样的描述之后,3K 左右大小的函数 ADC_Cali( )将被编译到 4K 的 L0 块中,剩 下 7K 就可以直接定位在 8K 的 H0 中,掌握这种方法之后,就可以很灵活的定位用户的应 用代码。
2、程序在 Flash 中执行 当一个 2812 的系统最终 Debug 通过之后,就到了 Release 阶段,此时必须将用户的代 码烧写到 Flash 中,烧写 Flash 的方法将在下面一节做详细的介绍。本节主要讨论 Flash 烧写 时 CMD 文件如何编写,以及如何把在 Flash 中固化的程序运行在内部 RAM 或者外部的 RAM 中。 烧写 Flash 的 CMD 文件和仿真调试的 CMD 文件主要区别在于.text 的定位,在仿真调 试时.text 可以被定位在内部 RAM 的 L0、L0、H0 这三个块中,Flash 烧写的时候就必须把.text 定位在 Flash 的块中,2812 的 Flash 被划分为 4 个 8K×16 位的扇区和 6 个 16K×16 位的扇 区,它们的地址是连续的,因此可是非常灵活的使用。下面举例说明该 CMD 文件的编写。 MEMORY { PAGE 0 : FLASHH : origin = 0x3DC000, length = 0x006000 PRAMH0 : origin = 0x3f8000, length = 0x002000 BEGIN : origin = 0x3F7FF6, length = 0x000002 VECTORS : origin = 0x3FFFC2, length = 0x00003E RAML0L1 : origin = 0x008000, length = 0x002000} SECTIONS { .reset : > BEGIN PAGE = 0 vectors : > VECTORS PAGE = 0 .cinit : > FLASHH PAGE = 0 .text : > FLASHH PAGE = 0 .stack : > RAMM0M1 PAGE = 1 .ebss : > PRAMH0 PAGE = 0 .econst : > FLASHH PAGE = 0 } 在上面的例子中,我们把.text 定位在 FLASHH,系统变量定位在 PRAMH0,堆栈定 位在 RAMM0、M1,常量也定位在 FLASHH,通过这样的定义之后,编译生成的.out 烧写 后就把应用代码固化在了 FLASHH 所在的地址空间里,系统复位完成后就会从 FLASHH 处取指,执行用户代码。 在一些对时间要求较高的系统中,考虑到程序在 Flash 中执行的速度不够快,即使初始 化 Flash 也不能解决问题,这时就必须把用户的应用程序从 Flash 中 COPY 到内部 RAM 中 去,它的实现方法和初始化 Flash 时的方法是一样,这里不再赘述。如果用户想把 Flash 中 的应用程序 COPY 到外部 RAM 中去执行,实现的方法基本和在 RAM 中的实现过程是一样, 只是不同的是,在 CMD 文件中把 RUN 的地址改为外部扩展 RAM 的地址就可以实现。
五、Flash 的烧写 烧写 Flash 是在整个系统最终成型后把用户的应用程序代码通过仿真器或者串口固化 在 Flash 中的过程,目前较常用的方法是在 ccs 调试环境下,通过安装烧写插件,利用仿真 器进行烧写,这种方法操作起来比较简单,由于使用串口烧写的方法比较繁琐,因此在这里 不再说明,只对 Flash 烧写插件的使用做详细的介绍。 第一步、安装 Flash 烧写插件(FLASH2X for CCS3.1),该插件是基于 ccs3.1 版本 第二步、安装完成后点击 ccs3.1 tool 菜单下 F28xx on-Chip Flash Programmer
然后会弹出如下界面:
第三步、点击 Flash Programmer Settings
第四步、选择器件
选择器件为 F2812 第五步、选择 Flash 烧写所需支持的 API 函数
点击 Browse,选择路径为: C:\CCStudio_v3.1\plugins\Flash28xx\Algorithms\2812\FlashAPIInterface2812V2_10.out 完成后选择 OK。 第六步、设置时钟,使其和系统时钟一致。不要轻易改变 Password,万一改变一定要 记下修改后的值。选择编程模式为 Erase,Program,Verity。
第七步、选择烧写文件,完成烧写。
点击 Browse,选择要烧写的.out,然后点击 Execute Operation,完成烧写。