DSP中某个寄存器怎么分配地址?
在数据手册中,我们常常看到说某个寄存器地址是多少,以TMS320F28335的时钟系统寄存器为例,在ti公司给出的手册我们看到如下信息
我们看到HISPCP中的地址为0x701A;翻看ti公司给的一系列库我们发现其寄存器定义在结构体SYS_CTRL_REGS中,经过一系列查找,我们发现SYS_CTRL_REGS映射的是 DSP281x_Headers_nonBIOS.cmd文件中,其对应的地址映射是System:0
我们发现其首地址是0x7010;而resvd1不代表任何含义,仅仅方便位置偏移设数,而结构体中HISPCP前面有10个16进制变量,HISPCP是第11个,在C语言中,下标是从0开始,所有HISPCP是第10个,也就是A,那么HISPCP地址是0x701A,查看芯片数据发现吻合,相关内容,可参考下面一片博客
用过F2812的朋友一定会对cmd文件很熟悉,因为这个文件中为每个程序和数据分配了相应的地址。我们常用的cmd文件包括连个:
(1) DSP281x_Headers_nonBIOS.cmd
(2) F2812_EzDSP_RAM_lnk.cmd
DSP281x_Headers_nonBIOS.cmd
上面第一个文件用于对DSP外设分配地址,而第二个文件是为系统的程序和数据分配地址。当然,如果DSP的外设地址我们用C语言已经自己定义,那第一个文件我们就可以不用了,笔者就是自己定义的,所以没有用到第一个文件。对于为什么要自己定义外设寄存器以及中断地址,有这几个原因:
(1) 自己定义外设寄存器地址可以很清楚的了解DSP的工作原理,虽然这样很耗费时间,但是会了解到DSP的中断等等是怎么工作的。
(2) 因为DSP外设寄存器地址的分配时采用寄存器形式分配到的。举个例子,以sci串口通信为例,其他的外设以及中断都一样。
比如我们设置波特率, 肯定是设置某个寄存器的相应位来实现。而DSP是这样实现的:
ScibRegs.SCIHBAUD = (BRRVal >> 8);
ScibRegs.SCILBAUD = (BRRVal);
上面的程序, ScibRegs是串口的寄存器结构体,也就说说DSP的每个寄存器都是以结构体为单位来分配地址的,而每个寄存器的具体地址通过偏移来分配,因为在DSP中,某个外设的寄存器地址是基本连续的。而这个结构体的定义是这样的:
extern volatile struct SCI_REGS SciaRegs;
extern volatile struct SCI_REGS ScibRegs;
大家可以看到,这里定义了两个结构体,一个是SciaRegs ,一个是ScibRegs,因为DSP内部有两个串行通信,A和B,这两个都是结构体,而类型就是前面的SCI_REGS的,这个结构体类型是这样定义的:
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; // Recieve status register
Uint16 SCIRXEMU; // Recieve emulation buffer register
union SCIRXBUF_REG SCIRXBUF; // Recieve data buffer
Uint16 rsvd1; // reserved
Uint16 SCITXBUF; // Transmit data buffer
union SCIFFTX_REG SCIFFTX; // FIFO transmit register
union SCIFFRX_REG SCIFFRX; // FIFO recieve register
union SCIFFCT_REG SCIFFCT; // FIFO control register
Uint16 rsvd2; // reserved
Uint16 rsvd3; // reserved
union SCIPRI_REG SCIPRI; // FIFO Priority control
};
也是就就是说,在分配地址的时候,只要给定义好的结构体一个起始地址,后面的寄存器地址就可以根据后面的字长进行分配。rsvd1,rsvd2,rsvd3表示不分配给任何寄存器,只是为了获得后面地址的偏移而已。这个结构体中还有union union SCIFFTX_REG等,而这个共同体是这样定义:
union SCIFFTX_REG {
Uint16 all;
struct SCIFFTX_BITS bit;
}
对这个共同体进行操作,要么是all,要么是struct SCIFFTX_BITS bit,而后面的这个结构体是这样定义的:
struct SCIFFTX_BITS { // bit description
Uint16 TXFFILIL:5; // 4:0 Interrupt level
Uint16 TXFFIENA:1; // 5 Interrupt enable
Uint16 TXINTCLR:1; // 6 Clear INT flag
Uint16 TXFFINT:1; // 7 INT flag
Uint16 TXFFST:5; // 12:8 FIFO status
Uint16 TXFIFOXRESET:1; // 13 FIFO reset
Uint16 SCIFFENA:1; // 14 Enhancement enable
Uint16 SCIRST:1; // 15 SCI reset rx/tx channels
};
大家一看就知道这是每个寄存器的位,而后面的数字,例如TXFFILIL:5后面的5,表示这个寄存器位是连续使用5个位。其实说到这里,大家应该明白DSP是怎么使用外设寄存器的了吧,例如下面:
ScibRegs.SCIRXST.bit.RXRDY = 1;
就是把ScibRegs 结构体中,SCIRXST 寄存器的RXRDY位置1 ,而
ScibRegs.SCIRXST.all = 1;就是把SCIRXST所有位置1.
DSP的外设和系统寄存器都是这样分配的。这样有一个好处,就是这样看到每个寄存器每个位,省去了查阅datasheet的麻烦,也会时操作更明显,当然,任何事物都有两面性,这样调用至少一个结构体,会浪费很多堆栈,使系统运行迟钝。说到这里还没说地址到底置怎么分配,好我们看文件DSP281x_GlobalVariableDefs.c。还是串口通信的例子。
//----------------------------------------
#ifdef __cplusplus
#pragma DATA_SECTION("SciaRegsFile")
#else
#pragma DATA_SECTION(SciaRegs,"SciaRegsFile");
#endif
volatile struc t SCI_REGS SciaRegs;
//----------------------------------------
#ifdef __cplusplus
#pragma DATA_SECTION("ScibRegsFile")
#else
#pragma DATA_SECTION(ScibRegs,"ScibRegsFile");
#endif
volatile struct SCI_REGS ScibRegs;
看到上面的程序,我又必要说一个#pragma .DSP中,pragma对象告诉编译器的预处理器如何对待函数,而且必须在声明、定义、引用程序或数据之前指定 pragma 。#pragma后面一般接两种形式:
(1) #pragma DATA_SECTION
(2) #pragma CODE_SECTION
顾名思义,这两种一个是针对数据,一个是针对程序。举例说明:
#pragma CODE_SECTION(funcA,“codeA ”);
char funcA(int i); //对函数funcA 的声明
上面中,funcA函数一定是外部声明或者定义的函数,CODE_SECTION为funcA 在一个名为codeA 的块中指定空间。如果你有一个代码对象并想将其链接到不同于.txt 的空间时,该语法就非常有用。而codeA在.cmd文件中指定了物理地址。
#pragma DATA_SECTION(bufferB,“my_sect”);
char bufferB [512]; // 对bufferB的定义放在pragma 的后面
DATA_SECTION为bufferB 在一个名为 my_sect 的块中指定空间。如果你有一个数据对象并想将其链接到不同于.bss 的空间时,该语法就非常有用. 数据块bufferB被定位于my_sect段中,my_sect段在 .cmd 文件中规定了物理地址。
看完上面的一段解释,估计您会对#pragma有了很好的了解了吧 我们使用的结构体是数据而不是函数,所以我们使用DATA_SECTION。其实就是把SciaRegs和ScibRegs分别在块SciaRegsFile和ScibRegsFile中指定空间,而块SciaRegsFile和ScibRegsFile在.cmd文件中指定了物理地址。这样一来,是不是结构体SciaRegs和ScibRegs的起始地址就知道了?答案是肯定的。我们在看分析.cmd文件。.cmd文件的源代码如下:
MEMORY
{
PAGE 0:
PAGE 1:
DEV_EMU : origin = 0x000880, length = 0x000180
PIE_VECT : origin = 0x000D00, length = 0x000100
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
ECANA : origin = 0x006000, length = 0x000040
ECANA_LAM : origin = 0x006040, length = 0x000040
ECANA_MOTS : origin = 0x006080, length = 0x000040
ECANA_MOTO : origin = 0x0060C0, length = 0x000040
ECANA_MBOX : origin = 0x006100, length = 0x000100
SYSTEM : origin = 0x007010, length = 0x000020
SPIA : origin = 0x007040, length = 0x000010
SCIA : origin = 0x007050, length = 0x000010
XINTRUPT : origin = 0x007070, length = 0x000010
GPIOMUX : origin = 0x0070C0, length = 0x000020
GPIODAT : origin = 0x0070E0, length = 0x000020
ADC : origin = 0x007100, length = 0x000020
EVA : origin = 0x007400, length = 0x000040
EVB : origin = 0x007500, length = 0x000040
SCIB : origin = 0x007750, length = 0x000010
MCBSPA : origin = 0x007800, length = 0x000040
CSM_PWL : origin = 0x3F7FF8, length = 0x000008
}
SECTIONS
{
PieVectTableFile : > PIE_VECT, PAGE = 1
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
SysCtrlRegsFile : > SYSTEM, PAGE = 1
SpiaRegsFile : > SPIA, PAGE = 1
SciaRegsFile : > SCIA, PAGE = 1
XIntruptRegsFile : > XINTRUPT, PAGE = 1
GpioMuxRegsFile : > GPIOMUX, PAGE = 1
GpioDataRegsFile : > GPIODAT PAGE = 1
AdcRegsFile : > ADC, PAGE = 1
EvaRegsFile : > EVA, PAGE = 1
EvbRegsFile : > EVB, PAGE = 1
ScibRegsFile : > SCIB, PAGE = 1
McbspaRegsFile : > MCBSPA, PAGE = 1
ECanaRegsFile : > ECANA, PAGE = 1
ECanaLAMRegsFile : > ECANA_LAM PAGE = 1
ECanaMboxesFile : > ECANA_MBOX PAGE = 1
ECanaMOTSRegsFile : > ECANA_MOTS PAGE = 1
ECanaMOTORegsFile : > ECANA_MOTO PAGE = 1
CsmPwlFile : > CSM_PWL, PAGE = 1
}
上面的程序可以看到,
SciaRegsFile : > SCIA, PAGE = 1 和
ScibRegsFile : > SCIB, PAGE = 1
Section的作用就是把输入段定义到那个地址空间,以及哪一页。伪指令 MEMORY用来标示每个存储器的名字、起始范围和长度。Page0 是第0 页,通常规定为程序存储器;Page1 是第1 页,规定为数据存储器。伪指令SECTIONS 描述输入段怎样被组合到输出段并规定在存储器内如何放置输出段。这样,SciaRegsFile的地址就是第一页的SCIA的地址,ScibRegsFile的地址就是第二页的SCIB的地址,在伪指令MEMORY中,SCIA和SCIB是这样定义的:
SCIA : origin = 0x007050, length = 0x000010
SCIB : origin = 0x007750, length = 0x000010