嵌入式硬體及介面開發實踐
瞭解嵌入式系統電路設計
時鐘模組
1、如下是時鐘模組結構圖
在圖中我們看到XTIPLL是外部晶振,EXTCLK是外部時鐘,他們為時鐘源,2個PLL,他們可以產生需要的高頻時鐘
2、時鐘源的選擇,軟體沒有對MPLLCON暫存器設定,使用外部晶振或外部時鐘為系統時鐘
3、鎖相環PLL模組結構圖
從結構圖中我們獲得輸出時鐘頻率的表示為:
其中,m=M(分配器M的值)+8,p=P(分配器P的值)+2
4、時鐘控制邏輯決定哪個時鐘源被使用,下圖是電源在上電重啟時的時鐘行為
從這個時序圖,我們可以知道在沒有配置PLLCON暫存器時,直接使用FIN為MPLL的FCLK,下圖是正常模式下改變PLL設定時的時序
從這個圖可以知道,我們可以通過寫P M L三個分配器的值(在PLL鎖存時間會被自動的插入)改變FCLK,下圖是PLL引數推薦值
5、使用分頻係數控制暫存器設定FCLK(用於CPU核),HCLK(用於AHB裝置,如儲存器控制,中斷控制,DMA,LCD控制,USB主模組,),PCLK(用於APB,如看門狗,UART,RTC,GPIO等)三者的比例。
6、系統時鐘設定的步驟
6.1、確定外部輸入晶振頻率,比如,Fin = 12MHz
6.2、確定系統輸出時鐘頻率,比如,FCLk=400MHZ
6.3、對照PLL引數推薦值表,找到合適的MDIV,PDIV.SDIV,設定MPLLCON。
6.4、設定UPLLCON
6.5、確定FCLK,HCLK,PCLK比例係數,設定時鐘分頻係數暫存器CLKDIVN,從而確定當前系統下的FCLK,HCLK,PCLK的具體頻率值
GPIO(通用的輸入輸出埠)
1、2440包含GPA,GPB,GPC,GPD,GPE,GPF,GPG,GPH,GPJ 9組埠
2、GPxCON:選擇引腳工作模式,每兩位控制一個引腳,00(輸入),01(輸出),10(特殊用途),11(保留)
3、GPxDAT:讀寫引腳資料,每一位控制一個引腳,0表示引腳為低電平,1表示引腳為高電平
4、GPxUP:確定是否使用內部上拉電阻,每一位控制一個引腳,0表示使用內部上拉電阻,1表示無內部上拉電阻
UART(通用非同步收發器)
1、下面是模組圖:
UART屬於APH裝置,包含控制單元,波特率發生器,傳送器和接收器。
2、傳送或接收的資料構成為1個開始位,5-8個數據位,1個可選的奇偶校驗位和1-2個停止位,由線性控制暫存器ULCONn來設定,如下圖
3、波特率描述序列通訊資料傳輸速率,每秒傳輸的二進位制位數,單位是bps,從TxD管腳傳送,從RxD管腳接收。
4、TTL/CMOS邏輯電平轉換為RS-232邏輯電平
5、非同步資料傳輸方式,依靠起始位來實現傳送和接收發的時鐘自同步
6、傳送資料流程,SOC將資料寫入傳送FIFO->傳送移位器->TxD資料線
7、接收資料流程,RxD資料線->接收移位器->SOC從接收FIFO讀取資料
8、串列埠硬體流控制
9、UART初始化
9.1、波特率除數因子暫存器UBRDIVn,設定波特率,計算方法如下圖:
9.2、行暫存器ULCONn,設定傳輸格式
9.3、模式控制暫存器UCONn,選擇UART時鐘源,設定UART中斷方式等
9.4、FIFO控制暫存器UFCONn,決定是否使用FIFO
9.5、狀態暫存器UTRSTATn,表明資料是否已經被髮送完畢、是否已經接收到資料
9.6、SOC將資料寫入這個暫存器(UTXHn),SOC讀取這個暫存器(URXHn)
中斷控制
1、CPU和外設之間的資料傳輸控制方式通常包含三種,查詢方式,中斷方式,DMA(Direct Memory access),中斷就是CPU在執行程式時,出現了緊急事件,必須轉去處理它(執行中斷服務程式),並在處理完後再返回的過程
2、中斷過程:請求->仲裁->響應->處理->返回,下圖是arm9中斷處理流程
3、中斷控制器支援60箇中斷源
4、如下是s3c2440A中斷過程
4.1、中斷源未決暫存器SRCPND,每一個位與一箇中斷源有關,自動置位,指示那個中斷源正在等待服務
4.2、中斷模式暫存器INTMOD,每一個位與一箇中斷源有關,為1時,相應的中斷將在FIQ模式下處理
4.3、中斷遮蔽暫存器INTMSK,每一個位與一箇中斷源有關,為1時,CPU不會服務相應中斷源的中斷請求
4.4、優先順序暫存器PRIORITY
4.5、SUBSRCPND暫存器,S3C2440有15個子中斷,當這些子中斷髮生,且未被遮蔽,他們對應的父中斷將會在SRCPND中被置位
4.6、中斷偏移暫存器
C和組合語言的混合程式設計
1、內嵌的彙編指令用法
1.1、運算元可以是表示式,表示式表示的資訊會被作為無符號數進行操作,表示式不要太複雜。
1.2、內嵌的彙編指令如果包含常量運算元,該指令肯能會被彙編器展開成幾條指令,並且該常量前的符號#可以省略。
1.3、只有指令B能使用C程式中的標號
2、在C程式中使用內嵌的彙編指令的語法格式和注意事項
2.1、語法格式
2.2、注意事項
2.2.1、彙編指令段中可以使用C語言的註釋
2.2.2、在彙編指令中“,”用作為分隔符
2.2.3、最好是不要在內嵌的彙編指令中使用物理暫存器,可以使用變數來實現
2.2.4、不要用物理暫存器去引用一個C變數
2.2.5、對於內嵌彙編指令可能會用到的暫存器,沒有必要保護和恢復他們
實驗一
學習使用ARM彙編指令,實現蜂鳴器BEEP鳴叫,四個LED點亮、熄滅實驗。
電路控制,如圖:
程式碼(arm彙編版本):
AREA beep, CODE, READONLY
ENTRY
CODE32
pGPBCON EQU 0x56000010
pGPBDAT EQU 0x56000014
pGPBUP EQU 0x56000018
START ;配置GPBCON[1:0]=01使引腳GPB0 為輸出工作模式
LDR R0, =pGPBCON
LDR R1, [R0]
LDR R2, =0x15401
ORR R1, R1, R2
STR R1, [R0]
;配置GPBUP[0]=1使引腳GPB0無內部上拉電阻
LDR R0, =pGPBUP
LDR R1, [R0]
LDR R2, =0x07ff
ORR R1, R1, R2
STR R1, [R0]
;light on
beep_on
LDR R0, =pGPBDAT
MOV R2, #0x01
STR R2, [R0]
MOV R2, #0x10000
BL delay
; b .
;light off
beep_off
LDR R0, =pGPBDAT
MOV R2, #0x1e0
STR R2, [R0]
MOV R2, #0x100000
BL delay
b beep_on
delay
SUB R2, R2, #0x1
CMP R2, #0x0
BNE delay
MOV PC, LR
NOP
END
實驗二
;C - > 彙編程式
通過C語言程式調用匯編子程式字串拷貝函式
程式碼:
心得:
1、設定系統時鐘,
實驗三
彙編程式->C
彙編程式呼叫C程式g()計算5個整數i, 2*i, 3*i, 4*i, 5*i的和
本程式使用5個引數,分別使用暫存器R0儲存第一個引數,R1、R2、R3分別儲存第二、三、四個引數,第五個引數利用堆疊傳送
函式返回值保持在R0中
程式碼(C語言程式部分):
//C程式g()返回5個整數的和
int g(int a, int b, int c, int d ,int e)
{
return (a + b + c + d + e);
}
程式碼(組合語言部分):
Stack_Size EQU 0x00000400 ;指定棧的大小
AREA STACK, DATA, NOINIT, READWRITE, ALIGN=3 ;初始化一個數據段,初始化為0
Stack_Mem SPACE Stack_Size ;分配一片記憶體單元用作棧空間
PRESERVE8 ;宣告8位元組對齊
AREA asmCT1, CODE, READONLY ;申明一個程式碼段
ENTRY ;定義程式入口
CODE32 ;宣告32位arm指令
START
;初始化棧
LDR R0, =Stack_Mem
; set its Stack Pointer
MOV SP, R0
; MUL SL, SP, #Stack_Size
BL call_g
B stop
call_g
IMPORT g
STR LR, [sp, #-4]!;儲存返回地址
;set parameter passed to function
MOV R0, #01 ;if i = 1
ADD R1, R0, R0 ;R1 = 2i
ADD R2, R0, R1 ;R2 = 3i
ADD R3, R1, R2 ;R3 = 5i
;先移動棧指標,然後將R3壓入堆疊
STR R3, [SP, #-4]!
ADD R3, R1, R1 ;R3 = 4i
BL g ;呼叫C程式
NOP
ADD sp, sp, #4;調整資料棧指標,準備返回
LDR PC, [SP], #4;返回
stop
b stop
END
心得:
1、偽操作SPACE的用法,它的功能是分配一塊記憶體單元,並用0初始化
2、偽操作EQU的用法,語法格式為name EQU expr type,它的功能是為數字常量(32位地址或32位常數),基於暫存器的地址值,程式中的標號(基於PC的值)定義一個字元名稱(命名規範?)。其中type指示expr的資料型別時CODE16,CODE32,DATA
3、偽操作AREA的用法,語法格式為 AREA sectionname {,attr,attr}…。它的功能是定義個程式碼段或資料段,輕重attr是段的屬性,各個屬性用,隔開,比如屬性CODE(程式碼段),DATA(表示資料段),COMMON(一個通用的段,不包含程式碼和資料),COMDEF(一個通用的段,可以包含程式碼和資料),READONLY,READWRITE,ALIGN=expression,NOINIT,ASSOC等。
4、CODE32,CODE12告訴彙編編譯器後面指令的型別,ENTRY指定程式的入口點,END告訴編譯器已經到了源程式結尾,
5、偽操作PRESERVE8指示當前程式碼資料棧是8位元組對齊
6、偽操作IMPORT的用法,它的語法格式是IMPORT symbol[WEAK],它的功能是告訴編譯器符號symbol不是在本原始檔定義的,而是在其他原始檔中定義的。
7,子程式引數傳遞和返回值的規則:引數數量不超過4個時,使用R0-R3傳遞,否則,可以使用棧(sp)傳遞。
8、棧的生長方向是向低地址增長,棧記憶體放的值一般是指標型別,這樣一來就通過棧間接操作記憶體單元。比如
如果棧指標拿到的地址是0x30800000,sp-4拿到的地址就是0x307ffffc,這時如果記憶體單元0x307ffffc儲存的資料為,0x3000000C,那麼執行指令STR LR, [sp, #-4]!後,連結暫存器LR=,0x3000000C,並且更新了sp,此時sp=0x307ffffc
實驗四
在終端執行uart_test程式,PC端通過超級終端向串列埠傳送一行字元(直到敲入回車鍵結束),通過串列埠0傳送到開發板,終端接收串列埠資料後,儲存在陣列中,再傳回到PC端,通過超級終端回顯。
程式碼:
//串列埠傳送一個字元=======
void Uart_SendByte(int data)
{
if(whichUart==0)
{
if(data=='\n')
{
while(!(rUTRSTAT0 & 0x2));
Delay(10); //because the slow response of hyper_terminal
WrUTXH0('\r');
}
while(!(rUTRSTAT0 & 0x2)); //Wait until THR is empty.
Delay(10);
WrUTXH0(data);//把字元送到傳送緩衝暫存器
}
else if(whichUart==1)
{
if(data=='\n')
{
while(!(rUTRSTAT1 & 0x2));
Delay(10); //because the slow response of hyper_terminal
rUTXH1 = '\r';
}
while(!(rUTRSTAT1 & 0x2)); //Wait until THR is empty.
Delay(10);
rUTXH1 = data;
}
else if(whichUart==2)
{
if(data=='\n')
{
while(!(rUTRSTAT2 & 0x2));
Delay(10); //because the slow response of hyper_terminal
rUTXH2 = '\r';
}
while(!(rUTRSTAT2 & 0x2)); //Wait until THR is empty.
Delay(10);
rUTXH2 = data;
}
}
//串列埠接收一個字元=====================================================
char Uart_Getch(void)
{
if(whichUart==0)
{
while(!(rUTRSTAT0 & 0x1)); //Receive data ready
return RdURXH0();
}
else if(whichUart==1)
{
while(!(rUTRSTAT1 & 0x1)); //Receive data ready
return RdURXH1();
}
else if(whichUart==2)
{
while(!(rUTRSTAT2 & 0x1)); //Receive data ready
return RdURXH2();
}
return 0;
}
/*********************************************************************************************
* name: uart0_test
* func: uart test function
* para: none
* ret: none
* modify:
* comment:
*********************************************************************************************/
void uart0_test()
{
char cInput[256];
UINT8T ucInNo=0;
char c;
//
Uart_Init( 0,115200 );
Uart_Select( 0 ); // 使用串列埠0
Uart_Printf("\n UART0 Communication Test Example\n");
Uart_Printf(" Please input words, then press Enter:\n");
while(1)
{
c=Uart_Getch();
Uart_Printf("%c",c);
if(c!='\r') //enter key
cInput[ucInNo++]=c;
else
{
cInput[ucInNo]='\0';
break;
}
}
Delay(1000);
Uart_Printf("\n The words that you input are: \n %s\n",cInput);
Uart_Printf(" end.\n");
}
心得:
1、串列埠0一次接收一個字元,直到接收到回車鍵‘\r‘結束。串列埠0一次傳送一個字元,直到遇到回車鍵’\r’結束。
2、串列埠初始化實質是依次配置行控制暫存器,控制暫存器,波特率分頻係數暫存器,FIFO控制暫存器,MODEL控制暫存器。
3、傳送緩衝暫存器為空時自動設定TX/RX狀態暫存器的相應位為1,所以檢測TX/RX狀態暫存器的相應位,如果為1,則SOC能把資料位寫到傳送緩衝暫存器。接收緩衝暫存器包含有效資料時自動設定TX/RX狀態暫存器的相應位為1,所以檢測TX/RX狀態暫存器的相應位,如果為1,則SOC能從接收緩衝器讀到資料
實驗五
(1)實現單按鍵中斷處理程式。
(2)實現六按鍵中斷處理程式。
(3)實現按鍵控制LED點亮/熄滅實驗。
eg:
K1 - 點亮LED1,其他3個LED熄滅
K2 - 點亮LED2,其他3個LED熄滅
K3 - 點亮LED3,其他3個LED熄滅
K4 - 點亮LED4,其他3個LED熄滅
K5 - 點亮4個LED
K6 - 全熄滅4個LED
程式碼:
//掃描6按鍵所接GPIO口,觀察GPIOx電平值,鍵按下:低電平; 鍵擡起:高電平
//返回當前按下鍵所對應的鍵值
U8 Key_Scan( void )
{
Delay( 80 ) ;
if( (rGPGDAT&(1<< 0)) == 0 ) // K1按下
return 1 ;
else if( (rGPGDAT&(1<< 3)) == 0 ) // K2按下
return 2;
else if( (rGPGDAT&(1<< 5)) == 0 ) // K3按下
return 3 ;
else if( (rGPGDAT&(1<< 6)) == 0 ) // K4按下
return 4 ;
else if( (rGPGDAT&(1<< 7)) == 0 ) // K5按下
return 5 ;
else if( (rGPGDAT&(1<< 11)) == 0 ) // K6按下
return 6 ;
else
return 0xff;
}
// 按鍵中斷處理程式
void __irq Key_ISR(void)
{
U8 key;
U32 r;
//Uart_Printf("\nKey_ISR+!\n");
EnterCritical(&r); // 進入臨界區
if(rINTPND==BIT_EINT8_23) { //判斷INTPEND暫存器中是否為EINT8_23觸發中斷,如果EINT8_23觸發中斷,則INTPEND暫存器中對應bit位被置一
ClearPending(BIT_EINT8_23); //清空BIT_EINT8_23位
// 繼續比較EINTPEND暫存器,確定外面中斷源
if(rEINTPEND&(1<<8)) { //EINT8觸發中斷
Uart_Printf("eint8\n");
rEINTPEND |= 1<< 8; // 清空EINTPEND暫存器中EINT8對應的位
}
if(rEINTPEND&(1<<11)) { //EINT11觸發中斷
Uart_Printf("eint11\n");
rEINTPEND |= 1<< 11;
}
if(rEINTPEND&(1<<13)) { //EINT13觸發中斷
Uart_Printf("eint13\n");
rEINTPEND |= 1<< 13;
}
if(rEINTPEND&(1<<14)) { //EINT14觸發中斷
Uart_Printf("eint14\n");
rEINTPEND |= 1<< 14;
}
if(rEINTPEND&(1<<15)) { //EINT15觸發中斷
Uart_Printf("eint15\n");
rEINTPEND |= 1<< 15;
}
if(rEINTPEND&(1<<19)) { //EINT19觸發中斷
Uart_Printf("eint19\n");
rEINTPEND |= 1<< 19;
}
}
key=Key_Scan(); //掃描GPGx埠,返回按鍵鍵值
if( key != 0xff )
Uart_Printf( "Interrupt occur... K%d is pressed!\n", key) ;
ExitCritical(&r); // 出臨界區
//Uart_Printf("\nKey_ISR-!\n");
}
void KeyScan_Test(void)
{
Uart_Printf("\nKey Scan Test, press ESC key to exit !\n");
// 配置GPGCON,設定6按鍵對應的GPGx管腳功能為外部中斷引腳EINT
rGPGCON = rGPGCON & (~((3<<0)|(3<<6))) | ((2<<0)|(2<<6)) ; //GPG0,11 set EINT
rGPGCON = rGPGCON & (~((3<<10)|(3<<12))) | ((2<<10)|(2<<12)) ; //GPG5,6 set EINT
rGPGCON = rGPGCON & (~((3<<14)|(3<<22))) | ((2<<14)|(2<<22)) ; //GPG7,11 set EINT
// 設定中斷觸發方式
rEXTINT1 &= ~(7<<0);
rEXTINT1 |= (2<<0); //set eint8 falling edge int
rEXTINT1 &= ~(7<<12);
rEXTINT1 |= (2<<12); //set eint11 falling edge int
rEXTINT1 &= ~(7<<20);
rEXTINT1 |= (2<<20); //set eint13 falling edge int
rEXTINT1 &= ~(7<<24);
rEXTINT1 |= (2<<24); //set eint14 falling edge int
rEXTINT1 &= ~(7<<28);
rEXTINT1 |= (2<<28); //set eint15 falling edge int
rEXTINT2 &= ~(7<<12);
rEXTINT2 |= (2<<12); //set eint19 falling edge int
// 將按鍵中斷處理程式註冊,入口地址對應EINT8_23中斷IRQ
pISR_EINT8_23 = (U32)Key_ISR;
rEINTPEND = 0xFFFFFF; //清空 EINTPEND中斷請求
rSRCPND = BIT_EINT8_23; //to clear the previous pending states in SRCPND
rINTPND = BIT_EINT8_23; // to clear the previous pending states in INTPND
rEINTMASK=~( (1<<8)|(1<<11)|(1<<13)|(1<<14)|(1<<15)|(1<<19) ); //清空六個外部中斷對應的中斷遮蔽位
rINTMSK=~(BIT_EINT8_23); // 清空BIT_EINT8_23對應的中斷遮蔽位
Uart_Printf("\nPlease press the Key to test !\n");
Uart_Printf("\nrINTMSK=0x%x\n",rINTMSK);
while( Uart_GetKey() != ESC_KEY ) ; // 無限迴圈,直到使用者鍵入ESC鍵,退出。但此時可被中斷打斷
Uart_Printf("\nExit Int test !\n");
rEINTMASK=0xFFFFFF; // 重新設定EINTMASK遮蔽位
rINTMSK=BIT_ALLMSK; // 重新設定INTMSK遮蔽位
}
心得:
1、設定6按鍵對應的GPGx管腳功能為外部中斷引腳EINT
2、設定中斷觸發方式,
3、將按鍵中斷處理程式註冊,入口地址對應EINT8_23中斷IRQ
4、#define rSRCPND ((volatile unsigned )0x4a000000) //Interrupt request status
4.1、上述表示式拆開來分析,首先(volatile unsigned *) 0x4a000000的意思是把0x4a000000強制轉換成volatile unsigned 型別的指標,暫記為p,那麼就是#define A *p,即A為P指標指向位置的內容了。這裡就是通過記憶體定址訪問到暫存器A,可以讀/寫操作。
4.2、unsigned 型別指標,意思是說讀寫這個地址時,要寫進unsigned 的資料,讀出也是unsigned。
4.3、volatile變數可變 允許除了程式之外的比如硬體來修改他的內容
4.4、訪問該資料任何時候都會直接訪問該地址處內容,即通過cache提高訪問速度的優化被取消
4.5、簡而言之,我們把rSRCPND 看成是一個暫存器變數