- 基于ARM Cortex-M3的STM32系列嵌入式微控制器應用實踐
- 彭剛 秦志強編著
- 363字
- 2018-12-27 16:01:53
4.2 STM32單片機輸入端口的應用
在第2章,你就已經知道了STM32系列單片機有5個16位的并行I/O口:PA、PB、PC、PD和PE。這5個端口,既可以作為輸入,也可以作為輸出,既可按16位處理,也可按位方式使用。實際上,在單片機復位期間和剛復位后,復用功能未開啟,I/O端口被配置成浮空輸入模式。所有端口都有外部中斷能力。為了使用外部中斷線,端口必須配置成輸入模式。
作為輸入,如果I/O腳上的電壓為高電平(5V或3.3V),則其相對應的I/O口寄存器中的相應位存儲1;如果電壓為低電平(0V),則存儲0。
布置恰當的電路,你可以讓胡須達到上述效果:當胡須沒有被碰到時,使I/O腳上的電壓為高電平(5V或3.3V);當胡須被碰到時,則使I/O腳上電壓為低電平(0V)。然后,單片機就可以讀入相應數據,進行分析、處理,控制機器人的運動。安裝好觸須的機器人小車全貌如圖4.2所示。

圖4.2 安裝好觸須的機器人小車全貌
任務二 安裝并測試機器人的觸覺——胡須
讓機器人通過觸覺胡須進行導航,首先必須安裝并測試胡須。圖4.3所示是所需的硬件元件清單,包括:

圖4.3 胡須硬件
(1)金屬絲2根;
(2)平頭M3×22盤頭螺釘2個;
(3)13mm圓形立柱2個;
(4)M3尼龍墊圈2個;
(5)3-pin公-公接頭2個;
(6)2個220?電阻(色環:紅—紅—黑—黑);
(7)2個10k?電阻(色環:棕—黑—黑—紅)。
參考圖4.4,安裝胡須

圖4.4 安裝機器人胡須
螺釘依次穿過M3尼龍墊圈、13mm圓形立柱;
螺釘穿過主板上的圓孔之后,擰進主板下面的支架中,但不要擰緊;
把須狀金屬絲的其中一個鉤在尼龍墊圈之上,另一個鉤在尼龍墊圈之下,調整它們的位置使它們橫向交叉但又不接觸;擰緊螺釘到支架上;
參考接線圖4.5,搭建胡須電路。注意:右邊胡須狀態信息輸入是通過PE口的第1腳完成的,而左邊胡須狀態信息輸入是通過PE口的第0腳完成的;

圖4.5 胡須電路示意圖
確定兩條胡須比較靠近,但又不接觸面包板上的3-pin頭,推薦保持3mm的距離;
圖4.6所示是實際的參考接線圖。安裝好觸覺胡須的教學開發板如圖4.7所示。

圖4.6 胡須接線圖

圖4.7 安裝好觸須的教學開發板
測試胡須
觀察一下圖4.5所示的胡須電路示意圖,顯然每條胡須都是一個機械式的、接地常開的開關(類似按鍵)。胡須接地(GND)是因為教學板外圍的鍍金孔都連接到GND。金屬支架和螺絲釘提供電氣連接給胡須。
通過編程讓單片機探測什么時候胡須被觸動。由圖4.5可知,連接到每個胡須電路的I/O引腳監視著10kΩ上拉電阻上的電壓變化。當胡須沒有被觸動,連接胡須的I/O引腳的電壓是高電平;當胡須被觸動時,I/O短接到地,所以I/O引腳的電壓是低電平。
上拉電阻
上拉電阻就是與電源相連,并起到拉高電平作用的電阻。此電阻還起到限流的作用,如圖4.5中的10kΩ電阻即為上拉電阻。與之對應的還有“下拉電阻”,它與“地(GND)”相連,可把電平拉至低位。
例程:TestWhiskers.c
#include "stm32f10x_heads.h" #include "HelloRobot.h" int PE2state(void)//獲取PE2的狀態 { return GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2); } int PE3state(void)//獲取PE3的狀態 { return GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3); } int main(void) { BSP_Init(); USART_Configuration(); printf("Program Running!\r\n"); while(1) { printf("右邊胡須的狀態:%d ", PE2state()); printf("左邊胡須的狀態:%d\r\n",PE3state()); delay_nms(150); } }
上面的例程是用來測試胡須的功能是否正常。首先,定義了兩個無參數有返回值子函數int PE2state(void)和int PE3state(void)來獲取左右兩個胡須的狀態。STM32單片機的5個端口PA、PB、PC、PD和PE是可以按位來操作的,從低到高依次為第0口、第1口、……、第15口。
在搞清楚整個程序的執行原理后,按照下面的步驟實際執行程序,對觸覺胡須進行測試。
● 連接好串口電纜,接通教學板和伺服電機的電源。
● 輸入、保存并運行程序TestWhiskers.c。
● 檢查圖4.5,弄清楚哪條胡須是左胡須,哪條是右胡須。
● 此時調試終端顯示:“右邊胡須的狀態:1左邊胡須的狀態:1”,如圖4.8所示。

圖4.8 左右胡須均未碰到
● 把右胡須按到3-pin轉接頭上,注意顯示為:“右邊胡須的狀態:0左邊胡須的狀態:1”,如圖4.9所示。

圖4.9 右胡須碰到
● 把左胡須按到3-pin轉接頭上,注意顯示為:“右邊胡須的狀態:1左邊胡須的狀態:0”,如圖4.10所示。

圖4.10 左胡須碰到
● 同時把兩個胡須按到各自的3-pin轉接頭上,顯示為:“右邊胡須的狀態:0左邊胡須的狀態:0”,如圖4.11所示。

圖4.11 左右胡須均碰到
● 如果兩個胡須都通過測試,你可以繼續下面的內容;否則檢查程序或電路中存在的錯誤。
任務三 基于胡須的機器人觸覺導航
任務二中,你已經通過編程檢測胡須是否被觸動。在本任務中將利用這些信息對機器人進行運動導航。在機器人行走過程中,如果有胡須被觸動,那就意味著碰到了什么。導航程序需要接受這些輸入信息,判斷它的意義,調用一系列使機器人倒退、旋轉朝不同方向行走的動作子函數以避開障礙物。
下面的程序讓機器人向前走直到碰到障礙物。在這種情況下,機器人用它的一根或者兩根胡須探測障礙物。一旦胡須探測到障礙物,調用第3 章中的導航程序和子程序使小車倒退或者旋轉,然后再重新向前行走,直到遇到另一個障礙物。
賦值運算符“=”與關系運算符“==”
注意賦值運算符“=”與關系運算符“==”的區別:賦值運算符“=”用來給變量賦值;關系運算符“==”判斷兩個值是否是相等的關系。
邏輯與“&&”運算符的運算規則:
A&&B 若A、B都為真,則A&&B為真。 注意區分位操作符“&”和邏輯運算符“&&”。
例程:RoamingWithWhiskers.c
● 打開主板和伺服電機的電源;
● 輸入、保存并運行程序RoamingWithWhiskers.c;
● 嘗試讓機器人運動,當在其路線上遇到障礙物時,它將后退、旋轉并向另一個方向。
#include "stm32f10x_heads.h"
#include "HelloRobot.h"
int PE2state(void)//獲取PE2的狀態
{
return GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2);
}
int PE3state(void)//獲取PE3的狀態
{
return GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3);
}
void Forward(void)
{
GPIO_SetBits(GPIOD, GPIO_Pin_10);
delay_nus(1700);
GPIO_ResetBits(GPIOD,GPIO_Pin_10);
GPIO_SetBits(GPIOD, GPIO_Pin_9);
delay_nus(1300);
GPIO_ResetBits(GPIOD,GPIO_Pin_9);
delay_nms(20);
}
void Left_Turn(void)
{
int i;
for(i=1;i<=26;i++)
{
GPIO_SetBits(GPIOD, GPIO_Pin_10);
delay_nus(1300);
GPIO_ResetBits(GPIOD,GPIO_Pin_10);
GPIO_SetBits(GPIOD, GPIO_Pin_9);
delay_nus(1300);
GPIO_ResetBits(GPIOD,GPIO_Pin_9);
delay_nms(20);
}
}
void Right_Turn(void)
{
int i;
for(i=1;i<=26;i++)
{
GPIO_SetBits(GPIOD, GPIO_Pin_10);
delay_nus(1700);
GPIO_ResetBits(GPIOD,GPIO_Pin_10);
GPIO_SetBits(GPIOD, GPIO_Pin_9);
delay_nus(1700);
GPIO_ResetBits(GPIOD,GPIO_Pin_9);
delay_nms(20);
}
}
void Backward(void)
{
int i;
for(i=1;i<=65;i++)
{
GPIO_SetBits(GPIOD, GPIO_Pin_10);
delay_nus(1300);
GPIO_ResetBits(GPIOD,GPIO_Pin_10);
GPIO_SetBits(GPIOD, GPIO_Pin_9);
delay_nus(1700);
GPIO_ResetBits(GPIOD,GPIO_Pin_9);
delay_nms(20);
}
}
int main(void)
{
BSP_Init();
USART_Configuration();
printf("Program Running!\n");
while(1)
{
if((PE2state()==0)&&(PE3state()==0)) //兩胡須同時碰到
{
Backward(); //向后
Left_Turn(); //向左
Left_Turn(); //向左
}
else if(PE2state()==0) //右胡須碰到
{
Backward(); //向后
Left_Turn(); //向左
}
else if(PE3state()==0) //左胡須碰到
{
Backward(); //向后
Right_Turn(); //向右
}
else //胡須沒有碰到
Forward(); //向前
}
}
注意:函數Forward()有一個變動。它只發送一個脈沖,然后返回。這點相當重要,因為機器人可以在向前運動中的每兩個脈沖之間檢查胡須的狀態。意味著,機器人在向前行走的過程中,每秒檢查觸須狀態大概43次(1000ms/23ms≈=43)。
因為每個全速前進的脈沖都使得機器人前進大約半厘米。只發送一個脈沖,然后回去檢查胡須的狀態是一個好主意。每次程序從Forward()返回后,程序再次從while循環的開始處執行,此時if…else語句會再次檢查胡須的狀態。
任務四 機器人進入死區后的人工智能決策
你或許已注意到機器人卡在墻角里的情況。當機器人進入墻角時,左胡須觸墻,于是它右轉,向前行走,右胡須觸墻,于是左轉前進,又碰到左墻,再次碰到右墻……如果不是你把它從墻角拿出來,它就會一直困在墻角里而出不來。
編程逃離墻角死區
你可以修改RoamingWithWhiskers.c來讓機器人碰到上述問題時逃離死區。技巧是記下胡須交替觸動的總次數。技巧的關鍵是程序必須記住每個胡須的前一次觸動狀態,并和當前觸動狀態對比。如果狀態相反,就在交替總數上加1。如果這個交替總數超過了程序中預先給定的閥值,表示這是個墻角(死區),那么就該做一個“U”型轉彎,并且把胡須交替計數器復位。
這個技巧的編程實現依賴于if…else嵌套語句。換句話說,程序檢查一種條件,如果該條件成立(條件為真),則再檢查包含于這個條件之內的另一個條件。下面是用偽代碼說明嵌套語句的用法。
IF (condition1) { commands for condition1 IF(condition2) { commands for both condition2 and condition1 } ELSE { commands for condition1 but not condition2 } } ELSE { commands for not condition1 }
偽代碼通常用來描述不依賴于計算機語言的算法。實際上在前面幾章的任務和小結中,已經多次提醒和暗示你,無論是哪種計算機語言,都必須能夠描述人類知識的邏輯結構。而人類知識的邏輯結構是統一的,如條件判斷就是人類知識最核心的邏輯之一。因此,各種計算機語言都有語法和關鍵詞來實現條件判別。因此,在寫條件判斷算法時,經常用一種用于描述人類知識結構邏輯的偽代碼來描述在計算機中如何實現這些邏輯算法,以使算法具有通用性。有了偽代碼,用具體的語言來實現算法就很簡單了。
下面的例程,用于探測連續的、交替出現的胡須觸動過程。這個程序使機器人在第四次或第五次交替探測到墻角后,完成一個“U”型的拐彎,次數依賴于哪一個胡須先被觸動。
● 輸入、保存并運行程序EscapingCorners.c;
● 在機器人行走時,輪流觸動它的胡須,測試該程序。
例程:EscapingCorners.c
#include "stm32f10x_heads.h" #include "HelloRobot.h" int PE2state(void)//獲取PE2的狀態 { return GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2); } int PE3state(void)//獲取PE3的狀態 { return GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3); } void Forward(void) { … //略,同前 } void Left_Turn(void) { … //略,同前 } void Right_Turn(void) { … //略,同前 } void Backward(void) { … //略,同前 } int main(void) { int counter=1; //胡須碰撞總次數 int old2=1; //右胡須舊狀態 int old3=0; //左胡須舊狀態 BSP_Init(); USART_Configuration(); printf("Program Running!\n"); while(1) { if(PE3state()!=PE2state()) { if((old2!=PE2state())&&(old3!=PE3state())) { counter=counter+1; old2=PE2state(); old3=PE3state(); if(counter>4) { counter=1; Backward();//向后 Left_Turn();//向左 Left_Turn();//向左 } } else counter=1; } if((PE3state()==0)&&(PE2state()==0)) { Backward(); //向后 Left_Turn(); //向左 Left_Turn(); //向左 } else if(PE2state()==0) { Backward(); //向后 Left_Turn(); //向左 } else if(PE3state()==0) { Backward(); //向后 Right_Turn();//向右 } else Forward(); //向前 } }
EscapingCorners.c是如何工作的?
由于該程序是經RoamingWithWhiskers.c修改而來,下面只討論與探測和逃離墻角相關的新特征。
int counter=1; int old2=1; int old3=0;
這三個變量用于探測墻角。int型變量counter用來存儲交替探測的次數。例程中,設定的交替探測的最大值為4。int型變量old2、old3存儲胡須舊的狀態值。
程序賦counter初值為1,當機器人卡在墻角此值累計到4時,counter復位為1。old2和old3必須賦值以至于看起來好像兩根胡須的其中一根在程序開始之前被觸動了。這些工作之所以必須做,是因為探測墻角的程序總是對比交替觸動的部分,或者PE2state()==0,或者PE3state()==0。與之對應,old2和old3的值也相互不同。
現在看探測連續而交替觸動墻角的部分。
首先要檢查的是,是否有且只有一個胡須被觸動。簡單的方法就是詢問“是否PE2state()不等于PE3state()”。其具體判斷語句如下:
if(PE3state()!=PE3state())
假如真有胡須被觸動,接下來要做的事情就是檢查當前狀態是否確實與上次不同。換句話說,是old2不等于PE2state()和old3不等于PE3state()嗎?如果是,就在胡須觸動計數器上加1,同時記下當前的狀態,設置old2等于當前的PE2state(),old3等于當前的PE3state()。
if((PE2state()==0)&&(PE3state()==0)) { Backward();//向后 Left_Turn();//向左 Left_Turn();//向左 }
如果發現胡須連續四次被觸動,那么計數值置1,并且進行“U”型拐彎。
if(counter>4) { counter=1; Backward(); Left_Turn(); Left_Turn(); }
緊接的else語句是機器人沒有陷入墻角情況,故需要將計數器值置1。之后的程序和RoamingWithWhiskers.c中的一樣。
該你了
● 嘗試增加變量counter的數值為5和6,注意結果;
● 嘗試減小變量counter的數值,觀察小車在正常行走過程中是否有任何不同。