DSP程序中寄存器如何分配地址


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

参考博客


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM