《電子技術應用》
您所在的位置:首頁 > 其他 > 其他 > SPMC75系列單片機地C和ASM(匯編)混合編程的應用

SPMC75系列單片機地C和ASM(匯編)混合編程的應用

2009-09-19
關鍵詞: SPMC75

SPMC75系列單片機地C和ASM(匯編)混合編程的應用

本文主要介紹凌陽16位變頻控制單片機SPMC75系列單片機地C和ASM(匯編)混合編程的應用,。
關鍵詞:SPMC75 嵌入式匯編
 
1 引言
  支持C語言幾乎是所有微控制器程序設計的一項基本要求,當然SPMC75系列單片機也不例外,。μ'nSPTM 指令結構的設計就著重考慮對C語言的支持,,GCC就是一種針對μ'nSPTM 操作平臺的ANSI-C編譯器。但是在應用中對于程序的設計,,特別是C和ASM混合使用的時候有些地方是需要注意的,,在C中如何嵌入ASM也是一個不可回避的問題。

2 芯片特性簡介
  SPMC75系列單片機是由凌陽科技設計開發(fā)的16位微控制器芯片,,其內核采用凌陽科技自主知識產(chǎn)權的μ'nSP微處理器,。SPMC75系列單片機集成了能產(chǎn)生變頻電機驅動的PWM發(fā)生器、多功能捕獲比較模塊,、BLDC電機驅動專用位置偵測接口,、兩相增量編碼器接口等硬件模塊;以及多功能I/O口,、同步和異步串行口,、ADC、定時計數(shù)器等功能模塊,,利用這些硬件模塊支持,,SPMC75可以完成諸如家電用變頻驅動器,、標準工業(yè)變頻驅動器、多環(huán)伺服驅動系統(tǒng)等復雜應用,。下面介紹SPMC75系列單片機資源特性:

■ 高性能的 16 位 CPU 內核
- 凌陽 16 位 u'nSP 處理器
- 2 種低功耗模式: Wait/Standby
- 片內低電壓檢測電路
- 片內基于鎖相環(huán)的時鐘發(fā)生模塊
- 最高系統(tǒng)頻率 Fck : 24MHz
■ 片內存儲器
- 32KW (32K × 16bit) Flash
- 2KW (2K × 16bit) SRAM
■ 工作溫度: -40 ℃~ 85 ℃
■ 10 位 ADC 模塊
- 可編程的轉換速率,,最大轉換速率 100Ksps
- 6 ~~ 8 個通道
- 可與 PDC 或 MCP 等定時器聯(lián)動,實現(xiàn)電機控制中的電參量測量
■ 串行通 訊 接口
- 通用異步串行通訊接口 (UART)
- 標準外圍接口 (SPI)
■ 可編程看門狗定時器
■ 內嵌在線仿真電路 ICE 接口:可實現(xiàn)在線仿真,、調試和下載
■ 兩個 CMT 定時器
- 通用 16 位定時 / 計數(shù)器

■ MCP 定時器
- 能產(chǎn)生三相六路可編程的 PWM 波形,,如三相 SPWM 、 SVPWM 等
- 提供 PWM 占空比值同步載入邏輯
- 可選擇與 PDC 的位置偵測變化同步
- 可編程硬件死區(qū)插入功能,,死區(qū)時間可設定
- 可編程的錯誤和過載保護邏輯
- 16 位定時 / 計數(shù)器功能
■ PDC 定時器
- 可同時處理三路捕獲輸入
- 可產(chǎn)生三路 PWM 輸出(中心對稱或邊沿方式)
- BLDC 驅動的專用位置偵測接口
- 兩相增量碼盤接口,,支持四種工作模式,擁有四倍頻電路
- 16 位定時 / 計數(shù)器功能
■ TPM 定時器
- 可同時處理二路捕獲輸入
- 可產(chǎn)生二路 PWM 輸出(中心對稱或邊沿方式)
- 16 位定時 / 計數(shù)器功能
■ 封裝
- QFP 和 SDIP 兩種封裝,,
- 42 ~~ 80 腳系列

3 函數(shù)調用
3.1 調用協(xié)議
  模塊代碼間的調用,,是遵循μ'nSPTM體系的調用協(xié)議(Calling Convention)。所謂調用協(xié)議,,是指用于標準子程序之間一個模塊與令一個模塊的通信約定,。即使兩個模塊是以不同的語言編寫而成。
  調用協(xié)議是指這樣一套法則:它使不同的子程序之間形成一種握手通信接口,,并完成一個子程序到另一個子程序之間的參數(shù)傳遞和控制,,以及定義出子程序調用與子程序返回值的常規(guī)規(guī)則。
調用協(xié)議包括以下相關要素:
(1)調用子程序間的參數(shù)傳遞,;
(2)子程序返回值,;
(3)調用子程序過程中所用堆棧;
(4)用于暫存數(shù)據(jù)的中間寄存器,。
μ'nSPTM調用協(xié)議的內容如下:
1,、由于C編譯器產(chǎn)生的所有標號都以下劃線(_)為前綴,而C程序在調用匯編程序時要求匯編程序名也以下劃線(_)為前綴,。
2,、參數(shù)以相反的順序(從右到左)被壓入堆棧中。必要時所有的參數(shù)都被轉換成其在函數(shù)原型中被聲明過的數(shù)據(jù)類型,。但如果函數(shù)的調用發(fā)生在其聲明之前,,則傳遞在調用函數(shù)里的參數(shù)不會進行任何數(shù)據(jù)類型轉換的。
3,、各參數(shù)和局部變量在堆棧中的排列如圖3-1所示,。
4、16-Bit的返回值放在寄存器R1中,,32-Bit的返回值存入寄存器R1和R2中,,其中低字節(jié)在R1中,高字節(jié)在R2中,。若要返回結構或指針需要在R1中存放一個指向結構的指針,。
5,、編譯器會產(chǎn)生prolog/epilog過程動作來暫存或恢復PC、SR及BP寄存器,。匯編器則通過CALL指令可將PC和SR自動壓入堆棧中,,而通過RETF或RETI指令將其自動彈出堆棧。
6,、編譯器所認可的指針是16-Bit的,。函數(shù)指針實際上并非指向函數(shù)的入口地址,而是一個段地址的向量_function_entry,,在該向量的兩個連續(xù)Word的數(shù)據(jù)單元存放的值才是函數(shù)的入口地址,。

圖3-1 程序調用的堆棧使用

3.2 舉例說明
◆ C程序中調用ASM函數(shù)
【例3-1】 無參數(shù)傳遞的C語言調用ASM函數(shù)。

/*-------------------------------------------------------*/

/* C 程序

/*-------------------------------------------------------*/

extern void F_Function(void);

main()

{

/*-------------------------------------------------------*/

/* C 程序調用 ASM 函數(shù)

/*-------------------------------------------------------*/

F_Function();

while(1){;}

}

/*-------------------------------------------------------*/

/* ASM 程序

/*-------------------------------------------------------*/

//=============================================================

// ----Function: void F_Function(void);

// -Description: ASM 函數(shù)

// --Parameters: none

// -----Returns: none

// -------Notes: none

// -----Destroy: none

//=============================================================

.CODE

.PUBLIC _F_Function

_F_Function: .proc

nop;

RETF;

.endp

【例1-2】C 程序調用ASM函數(shù),,輸入兩個UInt16參數(shù),,返回一個UInt16參數(shù)。

/*-------------------------------------------------------*/

/* C 程序

/*-------------------------------------------------------*/

extern UInt16 F_F_Addition(UInt16 arg1,UInt16 arg2);

main()

{

UInt16 uiErr=0;

/*-------------------------------------------------------*/

/* C 程序調用 ASM 函數(shù),,輸入兩個 UInt16 參數(shù),,

/* 返回一個 UInt16 參數(shù)

/*-------------------------------------------------------*/

uiErr = F_Addition(0x00F3,0x9F00);

while(1){;}

}

/*-------------------------------------------------------*/

/* ASM 程序

/*-------------------------------------------------------*/

.CODE

//=============================================================

// ----Function: UInt16 F_Addition(UInt16 arg1,UInt16 arg2);

// -Description: 兩數(shù)相加

// --Parameters: arg1 ,被加數(shù),; arg2 ,,加數(shù)

// -----Returns: UInt16 兩數(shù)相加的和

// -------Notes: ASM 程序,示范參數(shù)傳遞及 UInt16 參數(shù)返回

// -----Destroy: R1 ,、 R2

//=============================================================

.PUBLIC _F_Addition

_F_Addition: .proc

PUSH BP to [SP]; // 保護 BP 數(shù)據(jù) ( 1 )

BP = SP+1; // 調整指針 ( 2 )

R1 = [BP+3]; // 第一個參數(shù) ( 3 )

R2 = [BP+4]; // 第二個參數(shù) ( 4 )

R1 += R2; // 通過 R1 返回結果 ( 5 )

POP BP from [SP]; ( 6 )

RETF;

.endp

  如圖3-2所示程序調用時堆棧使用情況。通過圖可以清楚的看出在C調用ASM函數(shù)的時候,,第一個參數(shù)將跟著第二個參數(shù)陸續(xù)自動的壓入堆棧,;接下來是PC指針和SR寄存器在CALL指令執(zhí)行后壓入堆棧,這些都是自動完成的,,使用者只需要了解,,是無法也沒有必要干預的。
  在下來將跳入執(zhí)行ASM函數(shù),,在執(zhí)行語句(1)的時BP被壓入堆棧保護起來,。那么可以發(fā)現(xiàn)ASM所需要接收的參數(shù)在堆棧中的實際位置,再執(zhí)行語句(2)將當前堆棧指針加一賦給變址寄存器BP,,則第一個參數(shù)的位置就應該是BP+3,,第二個參數(shù)的位置為BP+4,可以通過語句(3),、(4)來取出參數(shù),。
  結果的返回可以按照調用協(xié)議所講的保存在R1中來返回參數(shù)。

圖3-2 程序調用時堆棧使用情況

【例1-3】C 程序調用ASM函數(shù),,輸入兩個UInt16參數(shù),,返回一個Uint32參數(shù),。

/*-------------------------------------------------------*/

/* C 程序

/*-------------------------------------------------------*/

extern UInt32 F_Multiplication(UInt16 arg1,UInt16 arg2);

main()

{

UInt32 ulErr=0;

/*-------------------------------------------------------*/

/* C 程序調用 ASM 函數(shù),輸入兩個 UInt16 參數(shù),,

/* 返回一個 Uint32 參數(shù)

/*-------------------------------------------------------*/

uiErr = F_ Multiplication(0xF0F3,0x0F00);

while(1){;}

}

/*-------------------------------------------------------*/

/* ASM 程序

/*-------------------------------------------------------*/

.CODE

//=============================================================

// ----Function: UInt32 F_Multiplication(UInt16 arg1,UInt16 arg2);

// -Description: 兩數(shù)相乘

// --Parameters: arg1 ,,被乘數(shù); arg2 ,,乘數(shù)

// -----Returns: UInt32 兩數(shù)相乘的積

// -------Notes: ASM 程序,,示范參數(shù)傳遞及 UInt32 參數(shù)返回

// -----Destroy: R1 、 R2 ,、 R3 ,、 R4

//=============================================================

.PUBLIC _F_Multiplication

_F_Multiplication: .proc

PUSH BP to [SP];

BP = SP+1;

R1 = [BP+3];

R2 = [BP+4];

MR = R1*R2,uu;

R1 = R3; // 通過 R1 、 R2 返回一個 UInt32/Int32 數(shù)據(jù)

R2 = R4;

POP BP from [SP];

RETF;

.endp

◆ ASM函數(shù)中調用C程序
  在ASM函數(shù)中要調用C子函數(shù),,那么應該根據(jù)C的函數(shù)原型所要求的參數(shù)類型,,分別把參數(shù)壓入堆棧后再調用C函數(shù),以保證參數(shù)的正確傳遞,。在調用調用結束后還需要進行彈棧,,以恢復調用C函數(shù)前的堆棧指針。在這個過程中很容易產(chǎn)生bug,,所以在使用的時候希望細心的處理,。
【例3-4】ASM程序調用C 函數(shù),輸入兩個UInt16參數(shù),,返回一個UInt16參數(shù),。

/*-------------------------------------------------------*/

/* ASM 程序

/*-------------------------------------------------------*/

.CODE

.EXTERNAL _SP_Addition //C 函數(shù)

.PUBLIC _F_Dummy_Main

_F_Dummy_Main: .proc

PUSH R1,R2 to [SP]; // 寄存器保護

R2 = 0xA800; // 第二個參數(shù)

R1 = 0x00E9; // 第一個參數(shù)

// PUSH R1,R2 to [SP]; // 傳遞參數(shù)入棧

PUSH R2 to [SP]; // 第二個參數(shù)入棧 ( 1 )

PUSH R1 to [SP]; // 第一個參數(shù)入棧 ( 2 )

call _SP_Addition; // 調用 C 函數(shù) ( 3 )

R1 = R1; // 函數(shù)返回值 ( 4 )

// SP + = 2; // 調整堆棧指針 ( 5 )

POP R1 from [SP]; ( 6 )

POP R2 from [SP]; ( 7 )

POP R1,R2 from [SP];

RETF;

.endp

/*-------------------------------------------------------*/

/* C 程序

/*-------------------------------------------------------*/

//=============================================================

// ----Function: UInt16 SP_Addition(UInt16 i,UInt16 j)

// -Description: C 函數(shù),示范匯編調用 C 函數(shù)

// --Parameters: i ,, j :被加數(shù)和加數(shù)

// -----Returns: 兩數(shù)的和

// -------Notes: none

//=============================================================

UInt16 SP_Addition(UInt16 i,UInt16 j)

{

UInt16 sum = 0;

sum = i+j;

return(sum);

}

  如圖3-3所示程序調用時堆棧使用情況,。在ASM調用C的時候需要把堆棧調整成和C調用C函數(shù)的樣子,所以需要對參數(shù)的傳遞方式有個了解,,按照圖3-3的形式來調整堆棧,。

圖3-3 程序調用時堆棧使用情況

4 嵌入?yún)R編
  為了使C語言程序具有更高的效率和更多的功能,需在C語言里嵌入用匯編語言寫的子程序,。一方面,,是為了提高子程序的執(zhí)行速度和效率;另一方面,,可以解決某些用C語言程序無法實現(xiàn)的機器語言操作,。勿庸置疑,C語言代碼與匯編代碼的接口問題是任何C編譯器都要解決的問題,。
  通常有兩種方法可以將匯編語言代碼與C語言代碼聯(lián)合起來,,一種是把獨立的匯編語言程序用C函數(shù)連接起來,通過API(Application Program Interface)的方式調用;另一種就是下面將要提到的在線匯編方法,,即將直接插入?yún)R編指令嵌入到C函數(shù)中,。
  采用GCC規(guī)定的在線匯編指令格式進行指令的輸入,是GCC實現(xiàn)將μ'nSPTM匯編指令嵌入C函數(shù)中的方法,。GCC在線匯編指令格式規(guī)定如下:
  asm(匯編指令模板:輸出參數(shù):輸入?yún)?shù):clobbers參數(shù)),;
  若無clobber參數(shù),則在線匯編指令格式可以簡化為:
  asm(匯編指令模板:輸出參數(shù):輸入?yún)?shù)),;

4.1 嵌入式匯編介紹
1,、匯編指令模板
  模板是在線匯編指令中的主要成分,GCC據(jù)此可以在當前產(chǎn)生匯編指令輸出,。例如下面的一條在線匯編指令:
asm("%0 += %1":"+r(foo):"r"(bar)),;
  其中:"%0 += %1"就是模板。操作數(shù)"%0",、 "%1"作為一種形式參數(shù),,分別會由第一個冒號后面實際的輸入、輸出參數(shù)取代,。帶百分號后的數(shù)字表示的是冒號后參數(shù)的序號,。例如:
asm("%0 = %1 + %2":"=r(foo):"r"(bar), "i"(10));
  "%0"會由參數(shù)foo取代,,"%1"會由參數(shù)bar取代,,而"%2"會由數(shù)值10取代。
  在匯編輸出中,,一個匯編指令模板里可以掛接多條匯編指令,。其方法是用換行符"\n"來結束每一條指令,并可以用Tab鍵符"\t"將同一模板產(chǎn)生的匯編輸出中的各條指令的換行顯示時縮進到同一列,,以使匯編指令顯示清晰,。例如:
asm("%0 += %1" \n\t "%0 += %1":"+r(foo):"i"(10));

2,、操作數(shù)
  在線匯編指令格式中,第一個冒號后的參數(shù)為輸出操作數(shù),,第二個冒號后的參數(shù)為輸入操作數(shù),,第三個冒號后跟著的則是clobber操作數(shù)。在各類操作數(shù)中,,引號里的字符代表的是其存儲類型約束符,,括號里面的字符串表示是實際的操作數(shù)。
  如果輸出參數(shù)有若干個,,可以用逗號將每一個參數(shù)隔開,。同樣,該法則適用于輸入?yún)?shù)或clobber參數(shù)。注意clobber參數(shù)只能是1,、2,、3和4中的一個或多個,但不能是全部,。

3,、操作符約束符
  約束符的作用在于指示GCC,使用在匯編指令模板中的操作數(shù)的存儲類型,。表1-1列出了一些約束符和它們分別代表的操作數(shù)不同的存儲類型,,也列出了用在操作數(shù)約束符之間的兩個約束符前綴。

表 1-1 操作數(shù)存儲類型約束符及約束符前綴

約束符

操作數(shù)存儲類型

約束符前綴及含義解釋

r

寄存器中的數(shù)值

m

存儲器中的數(shù)值

為操作數(shù)賦值

操作數(shù)在賦值前先參加運算

i

立即數(shù)

p

全局變量操作數(shù)

4.2  應用舉例
【例4-1】利用嵌入式匯編實現(xiàn)對端口寄存器的操作,。

//===================================================================

asm(".include Spmc75_regs.inc"); ( 1 )

//===================================================================

//-------------------------------------

asm("[P_IOD_Attrib_ADDR] = %0 \n\t" \ ( 2 )

"[P_IOD_Dir_ADDR] = %0 \n\t" \ ( 3 )

"[P_IOD_Buffer_ADDR] = %0 \n\t" \ ( 4 )

"[P_IOD_Data_ADDR] = %1 \n\t" \ ( 5 )

: \ ( 6 )

:"r"(0xFFFF),"r"(0x0000) \ ( 7 )

:"1"); ( 8 )

//-------------------------------------

  在C的嵌入式匯編中,,當使用端口寄存器時,需要在C文件中加入?yún)R編的包含頭文件,,(1)所示,。那么可以使用端口寄存器的名稱,而不必去使用端口的實際地址,;(2),、(3)、(4)和(5)分別對端口寄存器的各個屬性賦值初始化,;(6)沒有輸出參數(shù),;(7)操作數(shù)%0=0xFFFF,%1=0x0000,,操作數(shù)的存儲類型都是寄存器中的數(shù)值,;(8)clobber參數(shù),在寄存器傳遞實參的時候不能使用寄存器R1,。

【例4-2】利用嵌入式匯編實現(xiàn)對端口寄存器的位值讀取,。

A .

//-------------------------------------

asm("r1 = %1; \n\t" \ ( 1.a )

"tstb [r1],%2; \n\t" \ ( 2.a )

"jz 2; \n\t" \ ( 3.a )

"%0 = 0x01; \n\t" \ ( 4.a )

"jmp 1; \n\t" \ ( 5.a )

"%0 = 0x00; \n\t" \ ( 6.a )

:"=r"(result) \ ( 7 )

:"i"(P_IOD_Buffer),"i"(14) \ ( 8 )

:"1","2"); ( 9 )

//-------------------------------------

B .

//-------------------------------------

// GCC inline ASM start

r1 = 28793; ( 1.b )

tstb [r1],14; ( 2.b )

jz 2; ( 3.b )

R3 = 0x01; ( 4.b )

jmp 1; ( 5.b )

R3 = 0x00; ( 6.b )

// GCC inline ASM end

  上面A、B分別是嵌入式匯編和實際編譯出來的代碼,。首先需要清楚一點%0=i,、%1=P_IOD_Buffer、%2=14,,通過(7)和(8)行可以了解,。(1.a)將端口IOD的地址存放到R1中;(2.a)測試IOD的14位,;(3.a)如果等于零跳過兩行,,即跳過(4.a)和(5.a)在(6.a)中為輸出參數(shù)賦值0x00;如果不等于零則順序執(zhí)行(4.a)為輸出參數(shù)賦值0x01;(5.a)跳過一行,,即跳過(6.a),。通過上面的過程可以應用嵌入式匯編實現(xiàn)對端口位的測試,將測試的結果保存在變量result中,行(7)所示,。行(9)clobber參數(shù),,約束行(7)的"r"在編譯時不能使用R1和R2,所以可以在(4.b)和(6.b)中看到使用了R3,。但如果行(9)是":"1","2","3");",,那么編譯出來的(4.b)和(6.b)中只能使用R4,由此可知":"1","2","3","4");"是絕對不允許的,。

【例4-3】典型的應用方式,。
  通常的應用是用宏匯編的形式定義出來,使用的時候就象函數(shù)一樣來使用,。

//================================================================

//Function: SETB Function

//Example: SETB(_P_IOA_Data,0x8);

//================================================================

#define SETB(Addr,Num) \

asm( \

"r1=%0;\n\t" \

"r2=%1;\n\t" \

"setb [r1],r2\n\t" \

: \

:"i"(Addr),"i"(Num) \

:"1","2" \

);

SETB ( P_IOD_Data ,, 14 ); // 置位 IOD14

SETB ( P_IOB_Data ,, 10 ),; // 置位 IOB10

本站內容除特別聲明的原創(chuàng)文章之外,轉載內容只為傳遞更多信息,,并不代表本網(wǎng)站贊同其觀點,。轉載的所有的文章、圖片,、音/視頻文件等資料的版權歸版權所有權人所有,。本站采用的非本站原創(chuàng)文章及圖片等內容無法一一聯(lián)系確認版權者。如涉及作品內容,、版權和其它問題,,請及時通過電子郵件或電話通知我們,以便迅速采取適當措施,,避免給雙方造成不必要的經(jīng)濟損失,。聯(lián)系電話:010-82306118;郵箱:[email protected],。