【樹莓派+.NET MF打造視頻監控智能車】遙控篇


樹莓派是最近比較火熱的開源硬件,其設備只有信用卡大小,運行着Linux系統,專為學生編程教育而設計。我十多年的技術路線基本以學習微軟的技術為主,中間也曾試圖學習過linux,但是相對陡峭的學習曲線,只好讓我放棄了。最近幾年深入研究嵌入式系統,自然繞不過去linux學習這個坎。幸好有了樹莓派,一是讓人容易滋生學習的興趣;二是全球范圍內網友技術交流,便於問題的定位和解決;所以在學習的過程中,慢慢地解開了linux的神秘面紗,使得有機會一探linux設計架構之美。

以前用.NET Micro Framework系統做過一些智能車控制,但是功能相對簡單。這次有了樹莓派的加入,增加了Sony PS2遙控器、視頻監控和機械手,便變得很有意思了。下圖是設備連接的示意圖:

 

從上圖來看,功能還是相對比較復雜的,需要9路PWM,其中7路來自凌霄評估板(.NET Micro 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(兩個標准.NET Gadgeteer接口和一個子板接口)。提供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++進行開發。

用戶驅動我已經寫過幾篇文章了,請網友自行參考《.NET Micro 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 void PS2X::ATT_CLR(void) {

         *_att_lport_clr |= _att_mask;

}

inline bool PS2X::DAT_CHK(void) {

         return (*_dat_lport & _dat_mask)? true : false;

}

改為:

// On pic32, use the 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 void PS2X::ATT_CLR(void) {

         MF->CPU_GPIO_SetPinState(SPI_CS_Pin,FALSE);

}

inline bool PS2X::DAT_CHK(void) {

         return MF->CPU_GPIO_GetPinState(SPI_MI_Pin);

}

 

定義幾個宏:

#define delayMicroseconds    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 Micro Framework PAL底層特有的一個功能函數:HAL_COMPLETION。可以定時去執行一個函數,類似一種多線程機制(可以定義多個)。

我們定義一個20ms執行的掃描函數,用來掃描鍵值:

    hcHander = MF->HAL_COMPLETION_Initialize(ScanKey,NULL);

    MF->HAL_COMPLETION_EnqueueDelta(hcHander,20000);  //20ms執行一次 

完整的掃描函數代碼如下:

void ScanKey(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 = new GeneralStream();

            if (gs.Open("UserDriver", (int)((int)clk << 24 | (int)cmd << 16 | (int)att << 8 | (int)dat)) <= 0)

            {

                throw  new Exception("Open UserDriver failed!");

            }

            gs.Notice += new GeneralStreamEventHandler(gs_Notice); 

     }

 

     void gs_Notice(uint hander, uint data, DateTime timestamp)

     {

            //Debug.Print(hander.ToString() + " - " + data.ToString());

            if (hander == 1)

            {

                Key key = (Key)(data >> 16 & 0xFF);

                int state = 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()

        {

            PS2 ps2 = new PS2(Mainboard.SubPort.Pin12, Mainboard.SubPort.Pin10, Mainboard.SubPort.Pin8, Mainboard.SubPort.Pin14

);

            ps2.Click += new PS2.ClickHandle(ps2_Click);

            Thread.Sleep(Timeout.Infinite);

        }

 

        static void ps2_Click(object sender, PS2.ButtonArgs e)

        {

            Debug.Print(e.ToString());

        }      

    }

 

D用戶應用程序功能測試

接上設備,把以上的程序運行,操作游戲機手柄,我們就可以看到按鍵信息了。

 

文章導航:

1、【樹莓派+.NET MF打造視頻監控智能車】遙控篇

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(加斷點、單步執行等等),便於問題診斷和調試。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM