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