基於路虎開發板的嵌入式實驗指導
作者:傾心琴心
聯繫方式:[email protected]
指導主要有兩個部分,一個是官方實驗例程指導,一個是嵌入式實驗課程實驗指導,有錯誤之處歡迎批評指正。
2.2.1 使用uC/OS-II實現AD採集的功能。... 31
1、 官方例程指導
1.1、LPC1768簡要概述
路虎開發板使用的NXP公司的lpc1768芯片,LPC1768 是NXP 公司推出的基於ARM Cortex-M3 內核的微控制器LPC17XX 系列中的一員。LPC17XX 系列Cortex-M3 微處理器用於處理要求高度集成和低功耗的嵌入式應用。LPC17XX 系列微控制器的外設組件包含高達512KB 的flash 存儲器、64KB 的數據存儲器、以太網MAC、USB 主機/從機/OTG 接口、8 通道DMA 控制器、4 個UART、2 條CAN 通道、2 個SSP 控制器、SPI 接口、3 個IIC 接口、2 輸入和2 輸出的IIS 接口、8 通道的12 位ADC、10位DAC、電機控制PWM、正交編碼器接口、4 個通用定時器、6 輸出的通用PWM、帶有獨立電池供電的超低功耗RTC 和多達70 個的通用IO 管腳。
紅色部分就是這款芯片主要提供的功能,在嵌入式項目開發的前期選型時我們就需要在成本和功能之間做一個平衡,選擇一款能夠滿足需要的成本更低的芯片。
1.2、路虎開發板概述
下面這張圖片是路虎開發板的一些接口介紹,關於路虎開發板的詳細信息大家可以查看路虎開發板用戶手冊(路虎光盤/2.用戶手冊-User Manual/路虎開發板用戶手冊.pdf)
這個學期我們將利用這款開發板學習嵌入式開發中一些主要接口的使用方法,例如串口、AD轉換、GPIO、LCD、以太網接口、flash編程技術。還有更多的功能大家可以參考例程自己學習理解。
1.3、實驗例程的講解與學習
該部分要講解的實驗例程包括路虎開發板所提供的例程(路虎\3.例程-Example\2、基礎例程)和NXP官方例程(/lpc17xx.cmsis.driver.library.rar)。後面一個需要從FTP上下載下來,解壓後如下所示:
其中core爲內核,Drivers爲驅動庫、Examples爲我們需要學習的的例程,makesection是make工具,LPC1700 Peripheral Driver LibraryManual.chm爲使用手冊(這個可以好好看看,有講解例程).
下面開始講解實驗例程。
1.3.1 ADC例程
ADC例程包括(3.例程-Example\2、基礎例程\【7】路虎_ADC(2012.4.13)\【7】路虎_ADC(2012.4.13)\【實驗7】ADC)和(lpc17xx.cmsis.driver.library\Examples\ADC\Polling\Keil)
基礎例程是路虎開發板團隊自己封裝寄存器操作成庫,然後使用庫完成功能。
1.3.1.1 路虎ADC例程
我們先看基礎例程打開 LandTiger_ADC.uvproj,
項目視圖爲Group分組,分組管理右鍵,選擇manage components,
在這裏可以管理整個項目,ProjectTargets是生成目標,同一個項目可以有不同的項目生成方式,不同的target生成方式不一樣,最後獲得的bin文件也不一樣,Groups是爲了更加清晰地管理應用程序文件。Files是所選中的Group所對應的的文件。
我們看到這個項目有三個Group:CMSIS、APP、Read,分別管理內核文件、應用文件、還有就是文檔。
首先之首先,我們打開readme.txt
它告訴我們要怎麼使用這個例程,
它說使用串口0時,我們需要把JP6 JP7拔掉(JP6 7 是使用串口燒寫hex時使用的,ISP下載)
這個程序的主要功能可以打開main.c
我們看到如下一段代碼
int main(void)
{
uint32_tADC_Data;
volatile uint32_t ADC_Buf = 0;
uint8_ti;
SystemInit();
UART0_Init();
UART2_Init();
ADC_Init();
while (1)
{
ADC_Data= 0;
for(i = 0;i < 8; i++)
{
ADC_Buf = ADC_Get();
ADC_Data += ADC_Buf;
}
ADC_Data = (ADC_Data / 8); /* 採樣8次進行慮波處理 */
ADC_Data = (ADC_Data * 3300)/4096;
UART0_SendString("AD通道5輸入電壓是:");
UART0_SendChar(ADC_Data); /* 將數據發送到串口顯示 */
UART0_SendByte('m');
UART0_SendByte('V');
UART0_SendByte('\n');
UART0_SendByte('\r');
UART2_SendString("路虎(LandTiger)ADC測試程序\n\r");
Delay(1000);
}
}
程序首先調用SystemInit();UART0_Init();UART2_Init();ADC_Init();對相關設備接口進行初始化。SystemInit()完成系統的初始化,它是內核system_17xx.c裏面的一個函數,主要是配置時鐘還有初始設備接口爲初始狀態等等,如果不顯式調用這個函數,我們也需要SysTick_Config((SystemCoreClock/CLOCK_CONF_SECOND) 這個函數來對系統時鐘進行初始化。
在程序主循環中
for(i = 0;i < 8; i++)
{
ADC_Buf = ADC_Get();
ADC_Data +=ADC_Buf;
}
該段代碼進行循環採樣,採樣八次,
ADC_Data =(ADC_Data / 8);/* 採樣8次進行慮波處理 */
ADC_Data =(ADC_Data * 3300)/4096;
首先濾波,然後根據公式計算實際電壓值
(附錄:如何計算實際電壓值
1.首先確定ADC用幾位表示,最大數值是多少。比如一個8位的ADC,最大值是0xFF,就是255。2.然後確定最大值時對應的參考電壓值。一般而言最大值對應3.3V。這個你需要看這個芯片ADC模塊的說明。寄存器中有對於輸入信號參考電壓的設置。3.要計算電壓,就把你的ADC數值除以剛纔確定的最大數值再乘以參考電壓值。比如你ADC值爲0x80,那麼實際值就是0x80/(0xFF+1)*3.3V = 1.65V4.計算出來的電壓值只是ADC管腳處的電壓值。你可以用電壓表量一下,計算值和實際值是否一樣。至於放大器等等,都是芯片外部的事情。外部電路怎麼接,和芯片ADC的採樣值無關。5.如果你想知道芯片外部某處的電壓,你需要從得出的ADC管腳處的電壓(比如剛纔的1.65V),再根據電路圖進行計算
由手冊 第29章我們知道 ,LPC1768使用的是12位漸進式AD轉換器。所以0xfff是最大值。
所以公式是 ADC_VALUE*3300 / (OXFFF+1)
然後接下來的代碼就是使用串口的庫從串口輸出字符串
UART0_SendString("AD通道5輸入電壓是:");
UART0_SendChar(ADC_Data); /* 將數據發送到串口顯示 */
UART0_SendByte('m');
UART0_SendByte('V');
UART0_SendByte('\n');
UART0_SendByte('\r');
UART2_SendString("路虎(LandTiger)ADC測試程序\n\r");
然後是一個延時,這種利用循環來延時是非常常見的,大家要學會使用,延時多少是根據一個指令佔用的時鐘週期來計算的。
Delay(1000);
好,基礎例程,就到這裏,至於uart.c adc.c是怎麼實現的,我們不必深究,他們是通過對寄存器,按照一定的時序操作來實現的,涉及到非常多寄存器的操作,感興趣的同學可以研究一下。
1.3.1.2 NXP官方ADC例程
第二個部分是NXP官方例程
這個是我們重點需要學習的例程。
共有五個分組,分別爲啓動文件、內核文件、驅動、應用、文檔。
首先還是打開adc_polling_test.c
int main (void)
{
returnc_entry();
}
c_entry函數的內容如下
PINSEL_CFG_Type PinCfg;
uint32_tadc_value, tmp;
/*Initialize debug via UART0
* ?115200bps
* ?8 data bit
* ?No parity
* ?1 stop bit
* ?No flow control
*/
debug_frmwrk_init();
// printwelcome screen
print_menu();
/*Initialize ADC ----------------------------------------------------*/
/* Becausethe potentiometer on different boards (MCB & IAR) connect
* with different ADC channel, so we have toconfigure correct ADC channel
* on each board respectively.
* If you want to check other ADC channels, youhave to wire this ADC pin directly
* to potentiometer pin (please see schematicdoc for more reference)
*/
#ifdef MCB_LPC_1768
/*
* Init ADC pin connect
* AD0.2 on P0.25
*/
PinCfg.Funcnum= 1;
PinCfg.OpenDrain= 0;
PinCfg.Pinmode= 0;
PinCfg.Pinnum= 25;
PinCfg.Portnum= 0;
PINSEL_ConfigPin(&PinCfg);
#elif defined (IAR_LPC_1768)
/*
* Init ADC pin connect
* AD0.5 on P1.31
*/
PinCfg.Funcnum= 3;
PinCfg.OpenDrain= 0;
PinCfg.Pinmode= 0;
PinCfg.Pinnum= 31;
PinCfg.Portnum= 1;
PINSEL_ConfigPin(&PinCfg);
#endif
/*Configuration for ADC :
* Select: ADC channel 2 (if using MCB1700 board)
* ADC channel 5 (if usingIAR-LPC1768 board)
* ADCconversion rate = 200Khz
*/
ADC_Init(LPC_ADC,200000);
ADC_IntConfig(LPC_ADC,_ADC_INT,DISABLE);
ADC_ChannelCmd(LPC_ADC,_ADC_CHANNEL,ENABLE);
while(1)
{
//Start conversion
ADC_StartCmd(LPC_ADC,ADC_START_NOW);
//Waitconversion complete
while(!(ADC_ChannelGetStatus(LPC_ADC,_ADC_CHANNEL,ADC_DATA_DONE)));
adc_value= ADC_ChannelGetData(LPC_ADC,_ADC_CHANNEL);
//Displaythe result of conversion on the UART0
#ifdef MCB_LPC_1768
_DBG("ADCvalue on channel 2: ");
#elif defined (IAR_LPC_1768)
_DBG("ADCvalue on channel 5: ");
#endif
_DBD32(adc_value);
_DBG_("");
//delay
for(tmp= 0; tmp < 8000000; tmp++);
}
ADC_DeInit(LPC_ADC);
return 1;
這段代碼首先是初始化串口調試工具debug_frmwrk_init,跳轉到定義發現這個函數就是初始化管腳,裏面有一個USED_UART_DEBUG_PORT,需要設定爲0,這樣就可以使用板子上的UART0與電腦進行通信。
#ifdef MCB_LPC_1768 這個宏是在文件開始的地方定義的。
#defineMCB_LPC_1768
//#defineIAR_LPC_1768
#ifdefMCB_LPC_1768
#define_ADC_INT ADC_ADINTEN2
#define_ADC_CHANNEL ADC_CHANNEL_2
#elifdefined (IAR_LPC_1768)
#define_ADC_INT ADC_ADINTEN5
#define_ADC_CHANNEL ADC_CHANNEL_5
#endif
我們的路虎開發板使用的是AD0.5,所以我們需要定義IAR_LPC_1768宏。
#ifdef MCB_LPC_1768
/*
* Init ADC pin connect
* AD0.2 on P0.25
*/
PinCfg.Funcnum= 1;
PinCfg.OpenDrain= 0;
PinCfg.Pinmode= 0;
PinCfg.Pinnum= 25;
PinCfg.Portnum= 0;
PINSEL_ConfigPin(&PinCfg);
#elif defined (IAR_LPC_1768)
/*
* Init ADC pin connect
* AD0.5 on P1.31
*/
PinCfg.Funcnum= 3;
PinCfg.OpenDrain= 0;
PinCfg.Pinmode= 0;
PinCfg.Pinnum= 31;
PinCfg.Portnum= 1;
PINSEL_ConfigPin(&PinCfg);
#endif
這段代碼的邏輯是,如果定義了MCB_LPC_1768宏則初始化管腳0.25爲ADC(AD0.2)口,否則如果定義了IAR_LPC_1768則定義管腳1.31爲ADC(AD0.5)口.所以我們使用的是0.31 AD0 第五個通道。這個也可以從數據手冊中瞭解到。 我們打開LPC17XX-USER-Manual_0[1].05.pdf
打開chapter8LPC17XX pin connect block
Table 60
在表格Pin name一欄我們找到了p1.31 然後往右看,找到了AD0.5,向上看指導function when 11 ,也就是當功能選擇爲3(11B)時,這個管腳就是複用爲AD轉換口0的第5個通道。
所以PinCfg.Funcnum = 3;
OpenDrain代表的是開漏模式 0爲非開漏模式
Pinmode是代表管腳模式是上拉、下拉、雙邊模式
Portnum和Pinnum分別是Port和Pin 早這裏就是1和31
PINSEL_ConfigPinc初始化,這個管腳功能就定義好了
/*Configuration for ADC :
* Select: ADC channel 2 (if using MCB1700board)
* ADCchannel 5 (if using IAR-LPC1768 board)
* ADC conversion rate = 200Khz
*/
ADC_Init(LPC_ADC, 200000);
ADC_IntConfig(LPC_ADC,_ADC_INT,DISABLE);
ADC_ChannelCmd(LPC_ADC,_ADC_CHANNEL,ENABLE);
ADC_Init初始化ADC模塊,採樣頻率爲20khz
ADC_IntConfig初始化ADC中斷,DISABLE爲禁止中斷
ADC_ChannelCmd使能相應的通道,我們使用的是通道5,所以對通道5進行使能,ENABLE.
while(1)
{
// Start conversion
ADC_StartCmd(LPC_ADC,ADC_START_NOW);
//Wait conversion complete
while(!(ADC_ChannelGetStatus(LPC_ADC,_ADC_CHANNEL,ADC_DATA_DONE)));
adc_value = ADC_ChannelGetData(LPC_ADC,_ADC_CHANNEL);
//Display the result of conversion on theUART0
#ifdefMCB_LPC_1768
_DBG("ADC value on channel 2:");
#elifdefined (IAR_LPC_1768)
_DBG("ADC value on channel 5:");
#endif
_DBD32(adc_value);
_DBG_("");
//delay
for(tmp = 0; tmp < 8000000; tmp++);
}
ADC_DeInit(LPC_ADC);
在這個循環裏,ADC_StartCmd啓動ADC模塊,開始轉換,也就是開始採集值,ADC_ChannelGetStatus查看相應通道轉換有沒有完成,如果完成則跳出while循環,使用ADC_ChannelGetData獲得採樣轉換的值,接下來呢可以使用和路虎例程一樣的方法對值進行處理,具體的我就不多說了,給大家一些思考的餘地。
1.3.2 串口例程
串口例程主要包括(3.例程-Example\2、基礎例程\【3】路虎_UART(2013.6.3)\【3】路虎_UART(2013.6.3)\UART0_中斷接收)和NXP官方例(lpc17xx.cmsis.driver.library\Examples\UART\Interrupt\Keil)
我們主要需要了解的是如何利用串口中斷接收數據,而串口輪詢(polling)比較簡單,大家可以看例程自己理解學習。
1.3.2.1 路虎串口中斷例程
首先還是依然看readme.txt
使用串口0需要將JP6 JP7跳線給拔掉。
接下來我們看主程序
第一個部分是串口初始化UART0_Init();
這部分代碼是根據直接使用寄存器來完成相關配置,包括配置管腳爲串口功能、配置串口參數、使能串口功能、設置串口中斷優先級。我們不需要學會對寄存器操作。
接收字符是通過中斷來完成的。
在UART.C最後的一個函數
//中斷程序
void UART0_IRQHandler()
{
volatileuint32_t iir,count,i;
count = 16;
iir =LPC_UART0->IIR;
switch(iir&0x0F)
{
case0x02: //發送中斷
break;
case0x04: //接收中斷
//break;
case0x0C: //接收超時中斷
if(LPC_UART0->LSR& 0x01) // Rx FIFO is not empty
Rec_date[Rec_len++] = LPC_UART0->RBR;
break;
default:
break;
}
}
UART0_IRQHandler 這個函數是特定的,是在中斷向量表中綁定了的。在startup_LPC17xx.s文件中
; External Interrupts
DCD WDT_IRQHandler ; 16: Watchdog Timer
DCD TIMER0_IRQHandler ; 17: Timer0
DCD TIMER1_IRQHandler ; 18: Timer1
DCD TIMER2_IRQHandler ; 19: Timer2
DCD TIMER3_IRQHandler ; 20: Timer3
DCD UART0_IRQHandler ; 21: UART0
DCD UART1_IRQHandler ; 22: UART1
DCD UART2_IRQHandler ; 23: UART2
DCD UART3_IRQHandler ; 24: UART3
DCD PWM1_IRQHandler ; 25: PWM1
DCD I2C0_IRQHandler ; 26: I2C0
DCD I2C1_IRQHandler ; 27: I2C1
//************
我們主要來分析這個中斷函數的內容:
獲得IIR,這個是獲得中斷標誌,通過中斷標誌知道發生中斷的是發送、接收還是接收超時中斷。
如果是接收中斷,我們可以將接收緩衝區的內容LPC_UART0->RBR保存到程序的數據區,以便後續處理。
1.3.2.2 NXP官方串口中斷例程
我們依然還是從函數入口開始分析。
c_entry()前面部分內容(349-422)
// UART Configuration structure variable
UART_CFG_TypeUARTConfigStruct;
// UART FIFOconfiguration Struct variable
UART_FIFO_CFG_TypeUARTFIFOConfigStruct;
// Pinconfiguration for UART0
PINSEL_CFG_TypePinCfg;
uint32_t idx,len;
__IO FlagStatusexitflag;
uint8_tbuffer[10];
/*
* Initialize UART0 pin connect
*/
PinCfg.Funcnum= 1;
PinCfg.OpenDrain= 0;
PinCfg.Pinmode= 0;
PinCfg.Pinnum =2;
PinCfg.Portnum= 0;
PINSEL_ConfigPin(&PinCfg);
PinCfg.Pinnum =3;
PINSEL_ConfigPin(&PinCfg);
/* InitializeUART Configuration parameter structure to default state:
* Baudrate = 9600bps
* 8 data bit
* 1 Stop bit
* None parity
*/
UART_ConfigStructInit(&UARTConfigStruct);
UARTConfigStruct.Baud_rate= 57600;
// InitializeUART0 peripheral with given to corresponding parameter
UART_Init((LPC_UART_TypeDef*)LPC_UART0, &UARTConfigStruct);
/* InitializeFIFOConfigStruct to default state:
* -FIFO_DMAMode = DISABLE
* -FIFO_Level = UART_FIFO_TRGLEV0
* -FIFO_ResetRxBuf = ENABLE
* -FIFO_ResetTxBuf = ENABLE
* -FIFO_State = ENABLE
*/
UART_FIFOConfigStructInit(&UARTFIFOConfigStruct);
// InitializeFIFO for UART0 peripheral
UART_FIFOConfig((LPC_UART_TypeDef*)LPC_UART0, &UARTFIFOConfigStruct);
// Enable UARTTransmit
UART_TxCmd((LPC_UART_TypeDef*)LPC_UART0, ENABLE);
/* EnableUART Rx interrupt */
UART_IntConfig((LPC_UART_TypeDef*)LPC_UART0, UART_INTCFG_RBR, ENABLE);
/* Enable UARTline status interrupt */
UART_IntConfig((LPC_UART_TypeDef*)LPC_UART0, UART_INTCFG_RLS, ENABLE);
/*
* Do not enable transmit interrupt here, sinceit is handled by
* UART_Send() function, just to reset TxInterrupt state for the
* first time
*/
TxIntStat =RESET;
// Reset ringbuf head and tail idx
__BUF_RESET(rb.rx_head);
__BUF_RESET(rb.rx_tail);
__BUF_RESET(rb.tx_head);
__BUF_RESET(rb.tx_tail);
/* preemption= 1, sub-priority = 1 */
NVIC_SetPriority(UART0_IRQn, ((0x01<<3)|0x01));
/* EnableInterrupt for UART0 channel */
NVIC_EnableIRQ(UART0_IRQn);
這部分代碼的操作順序和功能路虎的例程中UART0_Init()是一樣的。
首先是PINSEL_ConfigPin(&PinCfg);初始化芯片管腳功能。
然後使用UART_ConfigStructInit(&UARTConfigStruct);對結構體UARTConfigStructInit進行初始化,初始化後該結構體就設置了一些默認的參數,使用UARTConfigStructInit可以設置一些串口的參數,包括波特率,停止位,奇偶校驗等等。然後用該結構體使用UART_Init((LPC_UART_TypeDef*)LPC_UART0, &UARTConfigStruct);對串口0的參數進行設置。
接下來是FIFO的操作。FIFO是一個先入先出的雙口緩衝器,它主要是對連續的數據流進行緩存,防止在數據操作時丟失數據。
UART_FIFOConfigStructInit(&UARTFIFOConfigStruct);//初始化結構體UARTFIFOConfigStruct
// InitializeFIFO for UART0 peripheral
UART_FIFOConfig((LPC_UART_TypeDef*)LPC_UART0, &UARTFIFOConfigStruct);//使用該結構體對LPC_UART0參數進行設置。。
最重要的參數是FIFO_Level 它可以設置多少個字符發生一次中斷。
// 使能串口發送
UART_TxCmd((LPC_UART_TypeDef *)LPC_UART0,ENABLE);
/* 使能串口接收中斷 */
UART_IntConfig((LPC_UART_TypeDef *)LPC_UART0,UART_INTCFG_RBR, ENABLE);
/*使能串口數據中斷狀態 */
UART_IntConfig((LPC_UART_TypeDef *)LPC_UART0,UART_INTCFG_RLS, ENABLE);
/* 第一優先級 = 1,次優先級 = 1 */
NVIC_SetPriority(UART0_IRQn,((0x01<<3)|0x01));
/* 使能串口0中斷*/
NVIC_EnableIRQ(UART0_IRQn);
以上這段代碼流程在使用串口對串口進行初始化的時候基本是一致的。
在這個例程中我們主要需要了解的就是串口中斷。
void UART0_IRQHandler(void)
{
uint32_tintsrc, tmp, tmp1;
/* 決定中斷源*/
intsrc =UART_GetIntId(LPC_UART0);
tmp = intsrc& UART_IIR_INTID_MASK;
// 接收狀態
if (tmp ==UART_IIR_INTID_RLS){
//檢查狀態
tmp1 =UART_GetLineStatus(LPC_UART0);
//除去接收就緒和發送等待狀態
tmp1 &=(UART_LSR_OE | UART_LSR_PE | UART_LSR_FE \
|UART_LSR_BI | UART_LSR_RXFE);
// 如果有錯誤
if (tmp1) {
UART_IntErr(tmp1);
}
}
// 如果接收到數據或者是字符接收超時
if ((tmp ==UART_IIR_INTID_RDA) || (tmp == UART_IIR_INTID_CTI)){
UART_IntReceive();
}
// 數據發送中斷。
if (tmp ==UART_IIR_INTID_THRE){
UART_IntTransmit();
}
}
這段代碼的作用就是根據中斷類型來進行處理,我們主要需要做的就是處理接收中斷,一般情況下我們不會用到發送中斷。
接下來看我在一個項目中用到的中斷處理
void _UART_IRQHander(void)
{
uint32_tintsrc, tmp, tmp1;
//OSIntEnter();
// Determinethe interrupt source
intsrc =UART_GetIntId((LPC_UART_TypeDef *)_LPC_UART);
tmp = intsrc& UART_IIR_INTID_MASK;
// Receive LineStatus
if (tmp ==UART_IIR_INTID_RLS){
// Checkline status
tmp1 =UART_GetLineStatus((LPC_UART_TypeDef *)_LPC_UART);
// Mask outthe Receive Ready and Transmit Holding empty status
tmp1 &=(UART_LSR_OE | UART_LSR_PE | UART_LSR_FE \
|UART_LSR_BI | UART_LSR_RXFE);
// If anyerror exist
if (tmp1) {
UART_IntErr(tmp1);
}
}
// Receive DataAvailable or Character time-out
if ((tmp ==UART_IIR_INTID_RDA) || (tmp == UART_IIR_INTID_CTI)){
UART_IntReceive();
//UART_Receive((LPC_UART_TypeDef*)_LPC_UART,&tmpchar,1,BLOCKING);
}
// TransmitHolding Empty
if (tmp ==UART_IIR_INTID_THRE){
//UART_IntTransmit();
}
//OSIntExit();
}
//發生接收中斷時調用,基本上是發生8字節中斷後調用,或者超時後調用
void UART_IntReceive(void)
{
uint8_t rlen;//接收數據的長度
uint8_tchars[16];
uint8_t index =0;
//接收一個字符
rlen =UART_Receive((LPC_UART_TypeDef *)_LPC_UART,chars,16,NONE_BLOCKING);
for(index=0;index<rlen;index++)
{
my_printf("-%d-",chars[index]);
UART_Send((LPC_UART_TypeDef*)_LPC_UART,chars,index,BLOCKING);
}
}
主要關心的是UART_IntReceive();
這段代碼主要是中斷接收數據,然後通過調試串口打印出來,同時將字符回發給接收的串口。(這裏需要注意的是,我們的調試串口和通信串口不能設置爲同一個串口,這樣會干擾我們對數據的分析)
在實驗三種我們就可以利用這段代碼,比如在會發的時候同時將採樣的值回發回去。
2、 嵌入式實驗指導
2.1 嵌入式實驗總的說明:
嵌入式原理課程主要是讓大家瞭解嵌入式的整個情況,而實驗課呢主要是側重讓大家學會使用uc/os-II來完成我們嵌入式的開發任務。uC/OS-II是一個非常優秀的嵌入式操作系統,它移植非常方便,可方便的剪裁,甚至在非常小的51單片機上也能夠運行。使用uC/OS-II主要是使用它的多任務的功能,簡化編程任務。uC/OS-II代碼非常簡潔清晰,大家有空可以去看看它的源碼。後面有時間呢我也會給大家講講。
2.2 實驗工程分析
爲了更好的講解代碼規範等等一些東西,我就直接使用實驗三的參考代碼來講解。
這個工程有11個分組,分別爲內核、啓動文件、用戶任務程序、用戶程序庫、文檔、uC/OS-II移植文件、uC/OS-II源碼、uC/OS-II配置文件、UIP移植文件、UIP源碼、NXP LPC1768驅動庫。這麼多的分組和文件大家可能剛看到會有點頭暈。但其實非常簡單,當初這樣建立分組呢,就是爲了非常清晰地管理代碼文件。我們主要使用到的分組就是User和UserLib這兩個。
其中User下面的AppTaskDisplay.c就是一個任務。
void AppTaskDisplay(void * pdata)
{
INT8U err;
pdata = pdata;
my_printf("display");
while(1)
{
OSSemPend(semSample,0,&err);//等待數據
OSSemPend(semDataVisit,0,&err);//互斥訪問
dislpay(sampleData);
OSSemPost(semDataVisit);
OSTimeDly(200);
}
}
一個任務主要就是一個任務函數,在創建任務的使用我們需要將這個指針傳遞給OSTaskCreate函數。
一個任務函數一般裏面有一個主循環,這個循環一般是死循環,就是說這個任務需要一直進行下去,任務裏最後一行代碼一般會是一個延時函數,一個任務需要主動讓出時間給其他任務執行,否則優先級低的任務將永遠沒有調度運行的機會。
這個是剛纔那個.C文件所對應的的頭文件 這裏使用了一個頭文件保護,避免函數聲明重複包含。使用頭文件的目的,就是讓這個函數能夠在需要使用的地方能夠#include “xxx.h”就能夠把頭文件裏面的內容包含進去,這樣頭文件裏面的內容對於那個文件就是可見的,從而可以調用這裏面的函數,以及訪問數據等等。這個是個一個好習慣,有助於大家講各個模塊封裝起來,重複使用。
下面講解如何創建任務:
OSTaskCreate(AppTaskDisplay,
(void*)0,
&App_TaskDisplayStk[APP_TASK_DISPLAY_STK_SIZE-1],
OS_TASK_DISPLAY_PRIO);
這個函數就是用於創建任務的,函數的不同顏色部分代表不一樣的參數類型,AppTaskDisplay就是一個函數名稱,(void*)0就是空指針,相當於NULL,App_TaskDisplayStk是堆棧,其實就是一個數組,APP_TASK_DISPLAY_STK_SIZE是一個宏,定義堆棧的大小,OS_TASK_DISPLAY_PRIO是一個宏,定義的是該任務的優先級。
設計多任務程序時,首先要確定需要多少個任務,在實驗三中,我們只需要兩個任務就可以了, 一個採集數據,一個顯示,當然我們還有很多不同的方法。確定好多少個任務之後,需要確定任務的優先級,優先級高的任務有更高的調度權,比如當兩個任務都是就緒的,那麼優先級高的任務將獲得CPU並運行,而當優先級低的任務在運行時如果優先級高的任務就緒,則優先級低的任務將或被剝奪執行的權利,而需要將CPU讓渡給優先級高的任務。再然後就需要確定堆棧大小,堆棧大小跟數據操作有關係,如果在某個任務中使用了較大的數組,則需要大的堆棧,以便容納數據。
我們點開APP.C文件。發現它包含有很多.h文件
打開app_cfg.h文件
這裏就是定義任務優先級和堆棧大小
一般大的3個優先級要給系統使用,所以我們使用4以後的優先級(數值越大優先級越低)。
打開main.c文件
顯示如下
首先是包含各個頭文件。其中includes.h文件是基礎庫的包含,也就是標準工程會包含的庫頭文件。
接下來就可以包含自己的庫,比如LEDControl.h 、KeyControl.h等等
在頭文件包含的後面是堆棧聲明。可以看到堆棧是一個類型爲OS_STK的數組,大小就是我們在APP_CFG.H文件裏宏所定義的大小,記得加上註釋,這樣代碼會更加清晰
在啓動任務中,此處創建任務區域就可以嗲用OSTaskCreate創建任務
因爲我們這次實驗要求按鍵以後開始採樣,所以我在這邊使用了一個死循環,如果P2.11沒有按鍵事件則一直循環,如果有按鍵則進入if模塊,創建採樣任務和顯示任務。
任務創建好以後,uC/OS會繼續執行,因爲AppTaskStart任務優先級很高,在循環外面OSTaskDel(OS_PRIO_SELF);//當所有任務創建完成刪除本任務
這條語句將自身這個任務刪除,我們創建的任務就可以調度運行了,可以確定的是首先運行的APP_TASK_SAMPLE,因爲它的優先級比較高。
接下來我來演示一下創建一個任務。
設計一個任務主要包括定義任務函數、任務堆棧數組、任務優先級、任務堆棧大小。
2.3 實驗分析與指導
2.2.1 使用uC/OS-II實現AD採集的功能。
這裏主要的功能還是非常簡單的。我主要講兩個地方
1、按鍵處理
按鍵後對應的管腳會發生電平變化。
比如這段代碼,我們讀取port這個端口,它返回的是這個端口對應的一個32位的值,比如Port=2時,返回的就是p2.0-p2.31的值組成的一個32位指,然後我們需要判斷對應的位上是不是1或者是0,藉此來判斷是否有按鍵事件。在路虎開發板中,如果有按鍵對應的管腳p2.11應該是會是0.
爲什麼我加上了一個DelayMs(10)呢?
這個函數內容如下
其實就是延時。
這個原因是按鍵抖動,因爲我們的按鈕是機械按鈕,所以會發生抖動,具體情況大家可以去百度一下咯。
2、任務之間數據的傳遞
採樣任務採集到的數據我們使用全局變量保存起來,訪問使用互斥信號量控制,保證數據的完整性和可靠性。而顯示任務也通過信號量來了解是否已經採樣到了數據,這個大家可以去看看操作系統的讀者寫者問題,如果有不懂之處再問我,給大家一些思考的地方。