《TMS 320 F28x源碼解讀》第1章DSP F28x 使用入門,通過位域結構體的方法爲F28x 提供了一個完整的頭文件體系,並且針對F28x 的外圍設備給出了20 個外設示例,這是DSP 控制類芯片在軟件領域的一大進步。本節爲大家介紹傳統#define 方法。
1.2.1 傳統#define 方法
1.2 外設位域結構體方法綜述
DSP281x 頭文件和外設示例使用位域結構體方法,映射和訪問基於F28x 外設寄存器。本節將介紹這種方法,並把它和傳統的#define 方法加以比較。
1.2.1 傳統#define 方法
C代碼訪問寄存器的傳統方法是使用#define宏爲每一個寄存器分配一個地址。例如:
- //*****************************************************************************
- //傳統的頭文件
- //*****************************************************************************
- //存儲器映像地址寄存器
- #define CPUTIMER0_TIM (volatile unsigned long *)0x0C00
- //0xC00定時器0 計數器低位
- //0xC01定時器0 計數器高位
- #define CPUTIMER0_PRD (volatile unsigned long *)0x0C02
- //0xC02定時器0 週期寄存器低位
- //0xC03定時器0 週期寄存器高位
- #define CPUTIMER0_TCR (volatile unsigned int *)0x0C04
- //0xC04定時器0 控制寄存器
- //0xC05保留
- #define CPUTIMER0_TPR (volatile unsigned int *)0x0C06
- //0xC06定時器0 預定標寄存器低位
- #define CPUTIMER0_TPRH (volatile unsigned int *)0x0C07
- //0xC07定時器0 預定標寄存器高位
同樣的#define 方法將在每個外設寄存器上不斷重複。甚至對於諸如SCI-A 和SCI-B這樣完全相同的外設,每個寄存器都必須被一一分配地址。傳統#define 方法有以下顯著弊端:
(1)不容易訪問寄存器中的位域部分;
(2)不容易在CCS 觀察窗內顯示位域的值;
(3)不能利用CCS 的自動完成功能;
(4)對於重複的外設,頭文件開發者不能獲得重複使用的便利。
1.2.2 位域及結構體方法
1.2.2 位域及結構體方法
位域及結構體方法採用C 代碼結構體方式,將屬於某個指定外設的所有寄存器組成一個集合。通過鏈接器,每個C 代碼結構體就是外設寄存器的內存映射。這一映射允許編譯器通過使用CPU 數據頁指針(DP)直接訪問外設寄存器。另外,多數寄存器都定義了位域,從而使編譯器能讀取或者操作某個寄存器中的單個位域。
1)外設寄存器結構體
在1.2.1節中,我們使用傳統#define方法定義了CPU定時器0寄存器(CPU -Timer0)。本節改爲採用C 代碼結構體方法將CPU 定時器的寄存器集合在一起,定義同一個CPU定時器0 寄存器。通過使用鏈接器,將結構體映射到內存CPU-Timer0 寄存器上。
以下代碼示例是一個與DSP281x CPU 定時器外設對應的C 代碼結構體類型:
- //*****************************************************************************
- //採用結構體形式的CPU 定時器頭文件
- //*****************************************************************************
- struct CPUTIMER_REGS //僅僅定義了一個結構體類型CPUTIMER_REGS,還沒有
- //定義變量
- { Uint32 TIM; //定時器計數寄存器
- Uint32 PRD; //定時器週期寄存器
- Uint16 TCR; //定時器控制寄存器
- Uint16 rsvd1; //保留
- Uint16 TPR; //定時器預定標寄存器低位
- Uint16 TPRH; //定時器預定標寄存器高位
- };
該結構體類型由6 個成員組成,前後順序與它們在內存中的順序相同。“CPUTIMER_
REGS”代表了一個結構體類型。即它和系統已經定義的標準類型(如int,char 等)一
樣可以用來作爲定義變量的類型。
注意以下幾點:
(1)寄存器名出現的順序必須與它們在內存中被安排的順序相同;
(2)在結構體中,通過使用保留變量(rsvd1,rsvd2 等)來預留內存中的保留位置。這種保留結構僅僅用以預留內存中的空間;
(3)Uint16 和Uint32 分別是無符號16 位或者32 位數的類型定義,在DSP281x 中,則用來定義無符號整型和無符號長整型。這樣使用起來就方便一些。相應的類型定義聲明由DSP281x_Device.h 文件建立。
2)聲明可訪問寄存器的變量
寄存器結構體類型可被用於聲明一個可訪問寄存器的變量,對器件的每個外設都採用這一相同的做法,同一種外設的複用外設可以採用同樣的結構體類型定義。例如,如果一個器件上有3 個CPU-Timers,可以創建如下所示的3 個具有“struct CPUTIMER_REGS”結構體類型的變量。
- //*****************************************************************************
- //採用結構體形式的CPU 定時器頭文件
- //*****************************************************************************
- volatile struct CPUTIMER_REGS CpuTimer0Regs; //定義CpuTimer0Regs 是
- //一個具有CPUTIMER_REGS
- //類型的變量,下同
- volatile struct CPUTIMER_REGS CpuTimer1Regs;
- volatile struct CPUTIMER_REGS CpuTimer2Regs;
這裏,關鍵字volatile 在變量聲明中十分重要。它告訴編譯器,這些變量的內容可由硬件改變,並且編譯器無須優化使用volatile 變量的代碼。
注意:定義結構體類型變量都需要關鍵字struct。變量CpuTimer0Regs,CpuTimer1-Regs,CpuTimer2Regs 都是具有CPUTIMER_REGS 結構體類型的結構體變量。這裏把CPUTIMER_REGS 看做是CPU 定時器的同一種外設,而把CpuTimer0Regs,CpuTimer1-Regs,CpuTimer2Regs 看做是同一外設的3 次複用。每一次複用均包含前一個代碼框定義的那些專用寄存器變量。這種定義方式與F28x 外設寄存器的分配相對應。
SPRC097 1.00 版本用頭文件方式爲F28x 的所有外設提供了一個完整的位域結構體體系。對接觸C 語言時間不長的讀者,花點時間把它弄懂是非常必要的。
單有上面兩個定義是不夠的,還必須爲3 個CPU 定時器分配數據區。分配給某個定時器在數據區的起始地址必須與系統定義的該定時器的內存地址一致。這可通過#pragmaDATA_SECTION 等指令完成。否則,編譯器將其視爲普通的結構體類型變量,統一分配數據區,導致對CPU 定時器不能有效地訪問。
3)分配專用的數據區
在DSP281x_GlobalVariableDefs.c 文件中(該文件位於DSP281x_Headers\common\source 目錄內),通過使用編譯器的#pragma DATA_SECTION 指令,與外設寄存器結構體類型相對應的每一個變量都將被分配一個專用的數據區。在下面所示的代碼中,變量CpuTimer0Regs 被分配到CpuTimer0RegsFile 的數據區。
- //*****************************************************************************
- //DSP281x_headers\source\DSP281x_GlobalVariableDefs.c
- //*****************************************************************************
- //採用#pragma 編譯器聲明,將CpuTimer0Regs 變量分配到CpuTimer0RegsFile 數據
- //區。C 或C++採用不同的#pragma 聲明方式。當對一個C++程序進行編譯時,編譯器自
- //動定義__cplusplus。
- #ifdef __cplusplus //用於 C++代碼
- #pragma DATA_SECTION("CpuTimer0RegsFile");
- #else //用於C 代碼
- #pragma DATA_SECTION(CpuTimer0Regs,"CpuTimer0RegsFile");
- #endif
- Volatile structCPUTIMER_REGS CpuTimer0Regs; //定義CpuTimer0Regs 是一
- //個具有CPUTIMER_REGS 類
- //型的變量
- //#ifdef、#else 及#endif 爲預處理器條件編譯指令。其中#ifdef 和#else 格式類似
- //C 中的if 和else。主要差異爲預處理器不能識別標記代碼塊的大括號"{}",因此使用
- //#else(如果需要)和#end if(必須存在)來標記指令塊。上面指令的含義爲:如果採
- //用的是C++,則執行語句#pragma DATA_SECTION("CpuTimer0RegsFile");
- //倘若採用的是C,則執行語句#pragma DATA_SECTION(CpuTimer0Regs,
- //"CpuTimer0RegsFile");
對器件的每個外設寄存器結構體變量,都會重複這一數據區分配操作。
4)映射到外設寄存器
當每個結構體都分配到自身的數據區之後,通過使用鏈接命令文件DSP281x_Headers_nonBIOS.cmd,每個數據區都將被直接映射到外設內存映射寄存器上,如以下代碼所示:
- //*****************************************************************************
- //DSP281x_headers\include\DSP281x_Headers_nonBIOS.cmd
- //*****************************************************************************
- MEMORY /* 定義存儲區域。注意:cmd 文件不可以用“//”註釋符 */
- {
- PAGE 1: /* PAGE 1 在cmd 文件中表示數據區 */
- CPU_TIMER0 : origin = 0x000C00, length = 0x000008
- /* 將起始地址爲0x0C00,長度爲8 個單元的內存區域 */
- /* 定義爲CPU 定時器0 寄存器存儲區 */
- }
- SECTIONS /* 分配存儲區域 */
- {
- CpuTimer0RegsFile : > CPU_TIMER0, PAGE = 1
- /* 將 CpuTimer0RegsFile 段分配到CPU_TIMER0 區域 */
- }
通過把變量直接映射到外設寄存器的同一內存地址,用戶採用C 代碼對寄存器進行訪問,只需要通過訪問變量中所需的成員即可進行。例如,要對CPU-Timer0TCR 寄存器進行寫操作,只需訪問CpuTimer0Regs 變量中的TCR 成員,如以下代碼所示:
- //***********************************************
****************************** - //用戶源文件
- //**************************************************
*************************** - CpuTimer0Regs.TCR.all = TSS_MASK; //訪問TCR 寄存器示例
1.2.3 添加位域結構體
1.2.3 添加位域結構體
1)增加位域定義
我們經常需要直接訪問寄存器中的某個位域。C281x C/C++頭文件及外設示例所涉及的位域結構體方法,爲多數片上外設寄存器提供了位域定義。例如,可以爲CPU 定時器(CPU-Timer)中的每個寄存器定義一個位域結構體類型。CPU 定時器(CPU-Timer)控制寄存器的位域定義如下所示:
- //*****************************************************************************
- //DSP281x_headers\include\DSP281x_CpuTimers.h CPU 定時器頭文件
- //*****************************************************************************
- struct TCR_BITS //定義一個TCR_BITS 結構體類型(不是變量)
- { Uint16 rsvd1:4; //3:0 保留,從最低位開始,順序取位到最高位。取低4 位
- Uint16 TSS:1; //4 定時器開始/停止,取第5 位
- Uint16 TRB:1; //5 定時器重裝,取第6 位
- Uint16 rsvd2:4; //9:6 保留,取第7 位到第10 位
- Uint16 SOFT:1; //10 仿真模式,取第11 位
- Uint16 FREE:1; //11 仿真模式,取第12 位
- Uint16 rsvd3:2; //12:13 保留,取第13 位到第14 位
- Uint16 TIE:1; //14 輸出使能,取第15 位
- Uint16 TIF:1; //15 中斷標誌,取第16 位
- };
然後,通過共用體進行聲明,以便訪問位域結構體定義的各個成員或者16 位或32位寄存器的值。例如,定時器的控制寄存器共用體如下所示:
- //*****************************************************************************
- //DSP281x_headers\include\DSP281x_CpuTimers.h CPU 定時器頭文件
- //*****************************************************************************
- union TCR_REG //定義共用體類型TCR_REG(不是變量)
- { Uint16 all;
- struct TCR_BITS bit; //bit 是一個具有TCR_BITS 結構體類型的變量
- };
- //all 和bit 是共用體的兩個成員,它們都是16 位結構,佔用內存的同一單元
一旦每個寄存器的位域結構體類型和共用體的定義都建立起來了,則在CPU 定時器(CPU-Timer)的寄存器結構體類型中,各個成員可通過採用共用體定義的形式重寫:
- //*****************************************************************************
- //DSP281x_headers\include\DSP281x_CpuTimers.h CPU 定時器頭文件
- //*****************************************************************************
- struct CPUTIMER_REGS
- { union TIM_GROUP TIM; //定時器計數寄存器,TIM 是一個具有 TIM_GROUP 共
- //用體類型的變量
- union PRD_GROUP PRD; //定時器週期寄存器
- union TCR_REG TCR; //定時器控制寄存器
- Uint16 rsvd1; //保留
- union TPR_REG TPR; //定時器預定標寄存器低位
- union TPRH_REG TPRH; //定時器預定標寄存器高位
- };
現在,既可以通過C 代碼以位域的方法訪問CpuTimer 寄存器中的某位,也可以對整個寄存器進行訪問:
- //*****************************************************************************
- //用戶源文件
- //*****************************************************************************
- CpuTimer0Regs.TCR.bit.TSS = 1; //訪問一個單獨的位域的示例
- CpuTimer0Regs.TCR.all = TSS_MASK; //訪問整個寄存器的示例
採用位域結構體的方法具有以下優點:
(1)無須用戶確定掩模值,就可對位域進行操作;
(2)可在CCS 觀察窗中看到寄存器和位域的值;
(3)當使用CCS 時,編輯器會提供一張現有結構體/位域成員的列表以供選擇。這一功能是CCS 自動完成的,它使編寫代碼變得更容易,而不必查閱寄存器和位域名文件。
掩模值是指位掩碼(位屏蔽碼),在下面的代碼段中,常數TCR_MASK 是位掩碼是用於置位或清除較大字段中的一個特殊位的常數值。
- #define TCR_MASK 0x0010
- …
- CpuTimer0Regs.TCR.all = TCR_MASK;
2)使用位域時,“讀—修改—寫”的注意事項當對寄存器中的單個位域進行寫操作時,硬件將執行一個讀—修改—寫的操作,即讀出寄存器中的內容,修改單個位域的值及回寫整個寄存器。上述操作在F28x 上的單個週期內完成。當發生回寫操作時,寄存器內的其他位將被寫入讀出時所讀到的同一個數值。有些寄存器沒有采用共用體定義,是因爲不推薦採用這種方式訪問,也存在一些例外情況,包括:
(1)具有寫1 清除位的寄存器,如事件管理標誌寄存器;
(2)無論在什麼時候訪問寄存器,都必須用特殊方式對位進行寫入操作的寄存器,如看門狗控制寄存器。
沒有位域結構體和共用體定義的寄存器,不使用*.bit 或*.all 名稱進行訪問,例如:
- //*****************************************************************************
- //用戶源文件
- //*****************************************************************************
- SysCtrlRegs.WDCR = 0x0068;
3)代碼長度考量
採用位域定義訪問寄存器,可使代碼變得易讀、易修改和易維護。當需要對寄存器中單獨某位域進行訪問或者查詢時,使用這種方法也非常有效。然而,值得注意的是:當對一個寄存器進行一定數量的訪問時,使用*.bit 位域定義形式進行訪問將導致比使用*.all 形式對寄存器進行寫操作需要更多的代碼,例如:
- //*****************************************************************************
- //用戶源文件
- //*****************************************************************************
- CpuTimer0Regs.TCR.bit.TSS = 1; //1 = 停止定時器
- CpuTimer0Regs.TCR.bit.TRB = 1; //1 = 重裝定時器
- CpuTimer0Regs.TCR.bit.SOFT = 1; //當SOFT=1 且FREE=1 時,定時器自由運行
- CpuTimer2Regs.TCR.bit.FREE = 1;
- CpuTimer2Regs.TCR.bit.TIE = 1; //1 = 使能定時器中斷
採用上述的方法,可以得到可讀性非常強並且易於修改的代碼。不足是代碼有些長。如果用戶更加關心代碼的長度,可使用*.all 結構對寄存器進行一次性的寫操作。
- //*****************************************************************************
- //用戶源文件
- //*****************************************************************************
- CpuTimer0Regs.TCR.all = TCR_MASK; //TCR_MASK 可在文件頭部用#define 定義
1.2.4 共用體結構體位域的應用實例
1.2.4 共用體結構體位域的應用實例
【例】設count 是一個16 位的無符號整型計數器,最大計數爲十六進制0xffff,要求將這個計數值以十六進制半字節的形式分解出來。
對於上述實例通常採用移位的方法求解,而採用共用體結構體位域的方法不需要通過移位運算。以下,對CCS 在頭文件中大量使用的共用體結構體位域進行註解。
先定義一個共用體結構體位域:
- …
- Uint16 cont,g,s,b,q; //16 位無符號整型變量定義
- cont=0xfedc; //對cont 賦值
- …
- union //共用體類型定義
- { Uint16 i; //定義i 爲16 位無符號整型變量
- struct //結構體類型定義
- {
- Uint16 low:4; //最低4 位在前。從最低4 位開始,取每4 位構成半字節
- Uint16 mid0:4;
- Uint16 mid1:4;
- Uint16 high:4; //最高4 位在後
- }HalfByte; //HalfByte 爲具有所定義的結構體類型的變量
- }Count; //Count爲具有所定義的共用體類型的變量
union 定義一個共用體類型,它包含兩個成員:一個是16 位無符號整型變量i,另一個是包含4 個半字節變量(low,mid0,mid1,high)的結構體類型。它們佔用同一個內存單元,通過對i(Count.i)進行賦值,可以完成對結構體4 個變量的賦值。
上面的程序,在定義共用體類型和結構體類型的同時,直接完成了這兩個類型變量的定義,而未定義共用體和結構體類型名。即HalfByte 是一個具有所定義的結構體類型的變量,Count 是一個具有所定義的共用體類型的變量。理解了共用體與結構體之間的關係,下面的賦值指令就清楚了。
Count.i = cont; //對共用體類型成員i 進行賦值
- g = Count.HalfByte.low; //將cont 的0~3 位賦值給g,g=0x000c
- s = Count.HalfByte.mid0; //將cont 的4~7 位賦值給s,s=0x000d
- b = Count.HalfByte.mid1; //將cont 的8~11 位賦值給b,b=0x000e
- q = Count.HalfByte.high; //將cont 的12~15 位賦值給q,q=0x000f
通過共用體結構體定義,當對共用體類型成員i 進行賦值時,由於結構體類型變量HalfByte 與i 佔用同一個內存單元,因此,也就完成了對HalfByte 的各成員的賦值。
C 語言的共用體結構體位域定義,可以完成對寄存器位域的訪問。至於被訪問的位域在內存中的具體位置則由編譯器安排,編程者可以不必關注。
下面是一個訪問寄存器位域的例子,供讀者參考。
先建立一個共用體結構體位域定義,將某個寄存器的16 位,從最低位到最高位分別
定義爲Bit1,Bit2,…,Bit16。
- union //共用體類型定義
- { Uint16 all; //定義all 爲16 位無符號整型變量
- struct //結構體類型定義
- {
- Uint16 Bit1:1; //0 位Bit1 取寄存器最低位0 位,以下順序取1 位直到最高位
- Uint16 Bit2:1; //1
- Uint16 Bit3:1; //2
- Uint16 Bit4:1; //3
- Uint16 Bit5:1; //4
- Uint16 Bit6:1; //5
- Uint16 Bit7:1; //6
- Uint16 Bit8:1; //7
- Uint16 Bit9:1; //8
- Uint16 Bit10:1; //9
- Uint16 Bit11:1; //10
- Uint16 Bit12:1; //11
- Uint16 Bit13:1; //12
- Uint16 Bit14:1; //13
- Uint16 Bit15:1; //14
- Uint16 Bit16:1; //15
- }bit; //bit爲具有所定義的結構體類型的變量
- }CtrlBit; //CtrlBit 爲具有所定義的共用體類型的變量
有了上面的定義之後,要訪問某一個位或某些位就很容易了。比如要置Bit4,Bit8,Bit12 及Bit16 爲1,可用兩種方法進行:
方法一:
- CtrlBit.bit.Bit4 = 1;
- CtrlBit.bit.Bit8 = 1;
- CtrlBit.bit.Bit12 = 1;
- CtrlBit.bit.Bit16 = 1;
方法二:
- CtrlBit.all = 0x8888;
來源:http://book.51cto.com/art/201012/237937.htm