【樹莓派+ NET MF打造視訊監控智慧車】遙控篇
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
樹莓派是最近比較火熱的開源硬體,其裝置只有信用卡大小,執行著Linux系統,專為學生程式設計教育而設計。我十多年的技術路線基本以學習微軟的技術為主,中間也曾試圖學習過linux,但是相對陡峭的學習曲線,只好讓我放棄了。最近幾年深入研究嵌入式系統,自然繞不過去linux學習這個坎。幸好有了樹莓派,一是讓人容易滋生學習的興趣;二是全球範圍內網友技術交流,便於問題的定位和解決;所以在學習的過程中,慢慢地解開了linux的神祕面紗,使得有機會一探linux設計架構之美。
以前用.NET MicroFramework系統做過一些智慧車控制,但是功能相對簡單。這次有了樹莓派的加入,增加了Sony PS2遙控器、視訊監控和機械手,便變得很有意思了。下圖是裝置連線的示意圖:
從上圖來看,功能還是相對比較複雜的,需要9路PWM,其中7路來自凌霄評估板(.NETMicro Framework開發板)3路控制機械手,餘下的四路PWM和8個GPIO分別驅動四個馬達;另外2路PWM來自樹莓派,用來驅動攝像頭雲臺(兩自由度,可以水平和垂直旋轉);樹莓派引出一個GPIO,用來控制LED閃爍,攝像頭選取的是配套攝像頭;由於Sony PS2接收器和凌霄評估板連線,所以還需要把一些按鍵資訊通過串列埠發給樹莓派,由樹莓派驅動攝像頭雲臺。
下圖是組裝好的裝置圖片:
為了讓大家有一個直觀的印象,先看一段演示視訊:
視訊連結:http://v.youku.com/v_show/id_XNjY2MTE1NjQ0.html
由於需要介紹的內容相對較多,所以我們分四篇來講解視訊監控智慧車的製作,分別為《遙控篇》:主要講解Sony PS2遙控器訊號接收處理;《控制篇(.NET MF)》:主要講解用.NET MF如何驅動小車和控制機械手;《控制篇(樹莓派)》:主要講解如何用樹莓派驅動GPIO、PWM和串列埠通訊;《視訊篇》:主要講解視訊服務的搭建,遠端視訊觀看及自啟動程式的配置。
本篇先介紹Sony PS2遙控器訊號獲取。
A 遙控器說明
Sony PS2遊戲機手柄有兩個搖桿,14個功能鍵(不包含模式鍵),非常適合我們控制複雜的系統,比如控制機械手、攝像頭雲臺、小車行進及速度快慢。
目前網上購買一個這樣的遊戲手柄大概40元左右的樣子,價效比還是非常高的。
B 裝置接線
購買遊戲手柄的時候已經包含了一個接收頭了。有些店家還額外提供兩種轉接頭,一種是SPI介面的,一種是串列埠的。SPI介面的其實就是進行了一個電平轉換(3V3=>5V),沒有進行什麼特別的處理。串列埠的轉接頭是中間加了一個AVR單片,可以主動把採集的按鍵資訊,通過串列埠(TTL電平)傳送出去。使用相對簡單,但是功能上有問題,一是程式似乎有bug,在操作PSB_PINK和PSB_BLUE按鍵的時候,其返值和其它按鍵不同(PSB_PINK僅擡起發鍵值,PSB_BLUE按下和擡起都發鍵值,其它鍵都是按下發鍵值)。另外搖桿的鍵值是必須按下L2或R2時,才傳送對應搖桿的X/Y值,此外多個按鍵如果同時按下,是無法區分的。
所以我們選用SPI介面的(其實我們也可以直接把手柄接收頭和我們的凌霄系統進行連線,只是增加轉接板便於接線)。
凌霄評估板包含一個USB、一個TF卡槽和一路RS485介面,另外直接引出31個PIN(兩個標準.NETGadgeteer介面和一個子板介面)。提供2路SPI、1路I2C、5個串列埠、16路PWM、12路AD、2路DA、若干GPIO(Pin腳會有複用)。
四個馬達,兩個驅動器供分別需要4個GPIO和2路PWM,為了便於連線,我們分別通過.NET Gadgeteer介面提供,所以遙控手柄接收器我們連線在子板介面上。
接線如下:
Mainboard.SubPort.Pin2 (5V) -- 電源(4.vcc,如果是直接連,則連線Pin1 3V3)
Mainboard.SubPort.Pin12(PA5) -- att(6.ATT選取)
Mainboard.SubPort.Pin10(PA7) -- cmd(2.命令)
Mainboard.SubPort.Pin8(PC7) -- dat(1.資料)
Mainboard.SubPort.Pin14(PB3) -- clk(7.時鐘)
Mainboard.SubPort.Pin3 (GND) -- 地(GND)
【注】中間的四個GPIO可以任意,只要在程式中指定就可以。
C 使用者驅動開發
雖然介面類似SPI,但是實際用SPI介面去通訊,設定各種模式(A/B/C/D四種模式),通訊都不正常(返回0xFF等系列值)。所以我們採用使用者驅動,用C++進行開發。
使用者驅動我已經寫過幾篇文章了,請網友自行參考《.NETMicro Framework之MDK C++二次開發》。
我們直接從Arduino相關驅動進行修改移植,包含兩個檔案:ps2x_lib.h和ps2x_lib.cpp。
我們需要修改和GPIO操作、時鐘操作相關的部分。
在config_gamepad函式中我們新增GPIO初始化相關程式碼
MF->CPU_GPIO_EnableOutputPin(att,FALSE);
MF->CPU_GPIO_EnableOutputPin(cmd,FALSE);
MF->CPU_GPIO_EnableInputPin(dat,FALSE,NULL,GPIO_INT_NONE,RESISTOR_PULLUP);
MF->CPU_GPIO_EnableOutputPin(clk,FALSE);
原GPIO操作程式碼:
inline void PS2X::CMD_SET(void) {
*_cmd_lport_set |= _cmd_mask;
}
inline void PS2X::CMD_CLR(void) {
*_cmd_lport_clr |= _cmd_mask;
}
inline void PS2X::ATT_SET(void) {
*_att_lport_set |= _att_mask;
}
inline voidPS2X::ATT_CLR(void) {
*_att_lport_clr |= _att_mask;
}
inline boolPS2X::DAT_CHK(void) {
return (*_dat_lport & _dat_mask)?true : false;
}
修改為:
// On pic32, usethe set/clr registers to make them atomic...
inline void PS2X::CLK_SET(void) {
MF->CPU_GPIO_SetPinState(SPI_CLK_Pin,TRUE);
}
inline void PS2X::CLK_CLR(void) {
MF->CPU_GPIO_SetPinState(SPI_CLK_Pin,FALSE);
}
inline void PS2X::CMD_SET(void) {
MF->CPU_GPIO_SetPinState(SPI_MO_Pin,TRUE);
}
inline void PS2X::CMD_CLR(void) {
MF->CPU_GPIO_SetPinState(SPI_MO_Pin,FALSE);
}
inline void PS2X::ATT_SET(void) {
MF->CPU_GPIO_SetPinState(SPI_CS_Pin,TRUE);
}
inline voidPS2X::ATT_CLR(void) {
MF->CPU_GPIO_SetPinState(SPI_CS_Pin,FALSE);
}
inline boolPS2X::DAT_CHK(void) {
returnMF->CPU_GPIO_GetPinState(SPI_MI_Pin);
}
定義幾個巨集:
#definedelayMicroseconds MF->HAL_Time_Sleep_MicroSeconds_InterruptEnabled
#define millis() (MF->HAL_Time_CurrentTime()/1000)
#define delay(x) MF->HAL_Time_Sleep_MicroSeconds_InterruptEnabled(1000*x)
由於沒有map函式,需要我們自己實現:
int map(INT32 x,int in_min, int in_max, int out_min, int out_max)
{
return(x -in_min)*(out_max -out_min)/(in_max-in_min)+out_min;
}
另外就是重定義一些變數型別了,這裡就不詳述了。
下面說一下使用者驅動介面的程式設計:
我們通過GeneralStream_Open2_UserDriver介面傳遞一個32位整型數,傳入四個GPIO的值。
int GeneralStream_Open2_UserDriver(int config)
{
//必須第一個執行
InitUserDriver();
//獲取系統函式的指標
MF =(IGeneralStream_Function*)config;
//配置IO
att = (UINT8)(MF->iParam1>>24 &0xFF);
cmd= (UINT8)(MF->iParam1>>16 & 0xFF);
dat= (UINT8)(MF->iParam1>>8 & 0xFF);
clk =(UINT8)(MF->iParam1>>0 & 0xFF);
……
}
這裡我們用到一個.NET MicroFramework PAL底層特有的一個功能函式:HAL_COMPLETION。可以定時去執行一個函式,類似一種多執行緒機制(可以定義多個)。
我們定義一個20ms執行的掃描函式,用來掃描鍵值:
hcHander =MF->HAL_COMPLETION_Initialize(ScanKey,NULL);
MF->HAL_COMPLETION_EnqueueDelta(hcHander,20000); //20ms執行一次
完整的掃描函式程式碼如下:
voidScanKey(void *arg)
{
if(error == 1 || type == 2)
{
InitPS2();
MF->HAL_COMPLETION_EnqueueDelta(hcHander,1000000); //1s執行一次
if(error == 1 || type == 2)return;
}
//讀狀態
ps2x.read_gamepad(false, vibrate);
UINT8 button = 0;
for(int i=0;i<16;i++)
{
if(ps2x.NewButtonState(Buttons[i]))
{
button = ps2x.Button(Buttons[i]);
//MF->debug_printf("%s:%d\r\n",ButtonNames[i],button);
//MF->lcd_printf("%s:%d \r\n",ButtonNames[i],button);
if(button) ButtonState |= 1<<i;
else ButtonState &= ~(1<<i);
//觸發事件
MF->Notice_GenerateEvent(UserDriver_Hander,(byte)i<<16 | button);
}
}
UINT8 lx=ps2x.Analog(PSS_LX);
UINT8 ly=ps2x.Analog(PSS_LY);
UINT8 rx=ps2x.Analog(PSS_RX);
UINT8 ry=ps2x.Analog(PSS_RY);
ButtonAnalog = lx<<24 |ly<<16 | rx<<8 | ry;
if(frist!=1)
{
if(lx!=olx || ly!=oly)
{
//MF->lcd_printf("lx:%d ly:%d \r\n",lx,ly);
//觸發事件
MF->Notice_GenerateEvent(UserDriver_Hander,(byte)16<<16 |lx<<8 | ly );
}
if(rx!=orx || ry!=ory)
{
//MF->lcd_printf("rx:%d ry:%d \r\n",rx,ry);
//觸發事件
MF->Notice_GenerateEvent(UserDriver_Hander,(byte)17<<16 |rx<<8 | ry );
}
}
olx=lx;oly=ly;orx=rx;ory=ry;frist=0;
MF->HAL_COMPLETION_EnqueueDelta(hcHander,20000); //20ms執行一次
}
為了便於同時獲取鍵值和搖桿值,我們還封裝了一個介面,程式碼如下:
int GeneralStream_IOControl2_UserDriver(int code,int parameter)
{
//獲取當前按鍵狀態
if(code == 0)return ButtonState;
else if(code ==1) return ButtonAnalog;
return -1;
}
以上程式碼編譯成bin檔案,通過YFAccessFlash直接部署到裝置中即可。
下面我們介紹一下,使用者C#程式碼
我們先做一個簡單的封裝:
public PS2(Cpu.Pin clk,Cpu.Pin cmd,Cpu.Pin att,Cpu.Pin dat )
{
gs = newGeneralStream();
if(gs.Open("UserDriver", (int)((int)clk<< 24 | (int)cmd << 16 | (int)att << 8 | (int)dat))<= 0)
{
throw new Exception("OpenUserDriver failed!");
}
gs.Notice += new GeneralStreamEventHandler(gs_Notice);
}
void gs_Notice(uinthander, uint data, DateTimetimestamp)
{
//Debug.Print(hander.ToString()+ " - " + data.ToString());
if(hander == 1)
{
Keykey = (Key)(data >> 16 & 0xFF);
intstate = 0,x=0,y=0;
if(key == Key.LRocker || key == Key.RRocker)
{
x = (int)(data >> 8 & 0xFF);
y = (int)(data& 0xFF);
}
else
{
state = (int)(data & 0xFF);
}
if(Click != null) Click(this,new ButtonArgs(key,state, x, y));
}
}
public class Program
{
public static void Main()
{
PS2ps2 = new PS2(Mainboard.SubPort.Pin12,Mainboard.SubPort.Pin10,Mainboard.SubPort.Pin8,Mainboard.SubPort.Pin14
);
ps2.Click += newPS2.ClickHandle(ps2_Click);
Thread.Sleep(Timeout.Infinite);
}
static void ps2_Click(objectsender, PS2.ButtonArgse)
{
Debug.Print(e.ToString());
}
}
D使用者應用程式功能測試
接上裝置,把以上的程式執行,操作遊戲機手柄,我們就可以看到按鍵資訊了。
文章導航:
2、【樹莓派+.NET MF打造視訊監控智慧車】控制篇(.NET MF)
3、【樹莓派+.NET MF打造視訊監控智慧車】控制篇(樹莓派)
4、【樹莓派+.NET MF打造視訊監控智慧車】視訊篇
小結:
1、 有了使用者驅動C/C++二次開發介面,很容易移植相關C/C++程式碼。
2、 .NET Micro Framework的封裝效能,讓使用者程式僅關注業務邏輯即可,顯得非常的簡單易用。
3、 VS2010/VS2012可以線上除錯.NET Micro Framework(加斷點、單步執行等等),便於問題診斷和除錯。