查看: 400|回复: 0

[项目] 【树莓派+.NET MF打造视频监控智能车】遥控篇

[复制链接]

主题

好友

468

积分

秀才

  • TA的每日心情

    2018-11-20 13:51
  • 签到天数: 5 天

    连续签到: 1 天

    [LV.2]偶尔看看I

    发表于 2018-11-22 17:11:15 |显示全部楼层
    树莓派是最近比较火热的开源硬件,其设备只有信用卡大小,运行着Linux系统,专为学生编程教育而设计。我十多年的技术路线基本以学习微软的技术为主,中间也曾试图学习过linux,但是相对陡峭的学习曲线,只好让我放弃了。最近几年深入研究嵌入式系统,自然绕不过去linux学习这个坎。幸好有了树莓派,一是让人容易滋生学习的兴趣;二是全球范围内网友技术交流,便于问题的定位和解决;所以在学习的过程中,慢慢地解开了linux的神秘面纱,使得有机会一探linux设计架构之美。

    以前用.NET Micro Framework系统做过一些智能车控制,但是功能相对简单。这次有了树莓派的加入,增加了Sony PS2遥控器、视频监控和机械手,便变得很有意思了。下图是设备连接的示意图:
    1.jpg

    从上图来看,功能还是相对比较复杂的,需要9路PWM,其中7路来自凌霄评估板(.NET Micro Framework开发板)3路控制机械手,余下的四路PWM和8个GPIO分别驱动四个马达;另外2路PWM来自树莓派,用来驱动摄像头云台(两自由度,可以水平和垂直旋转);树莓派引出一个GPIO,用来控制LED闪烁,摄像头选取的是配套摄像头;由于Sony PS2接收器和凌霄评估板连接,所以还需要把一些按键信息通过串口发给树莓派,由树莓派驱动摄像头云台。

    下图是组装好的设备图片:
    2.jpg


    为了让大家有一个直观的印象,先看一段演示视频:


    由于需要介绍的内容相对较多,所以我们分四篇来讲解视频监控智能车的制作,分别为《遥控篇》:主要讲解Sony PS2遥控器信号接收处理;《控制篇(.NET MF)》:主要讲解用.NET MF如何驱动小车和控制机械手;《控制篇(树莓派)》:主要讲解如何用树莓派驱动GPIO、PWM和串口通信;《视频篇》:主要讲解视频服务的搭建,远程视频观看及自启动程序的配置。

    本篇先介绍Sony PS2遥控器信号获取。

    A 遥控器说明
    3.png



    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初始化相关代码
    1. MF->CPU_GPIO_EnableOutputPin(att,FALSE);

    2. MF->CPU_GPIO_EnableOutputPin(cmd,FALSE);

    3. MF->CPU_GPIO_EnableInputPin(dat,FALSE,NULL,GPIO_INT_NONE,RESISTOR_PULLUP);

    4. MF->CPU_GPIO_EnableOutputPin(clk,FALSE);
    复制代码


    原GPIO操作代码:
    1. inline void  PS2X::CMD_SET(void) {

    2.          *_cmd_lport_set |= _cmd_mask;

    3. }

    4. inline void  PS2X::CMD_CLR(void) {

    5.          *_cmd_lport_clr |= _cmd_mask;

    6. }

    7. inline void  PS2X::ATT_SET(void) {

    8.          *_att_lport_set |= _att_mask;

    9. }

    10. inline void PS2X::ATT_CLR(void) {

    11.          *_att_lport_clr |= _att_mask;

    12. }

    13. inline bool PS2X::DAT_CHK(void) {

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

    15. }
    复制代码
    改为:
    1. // On pic32, use the set/clr registers to make them atomic...

    2. inline void  PS2X::CLK_SET(void) {

    3.      MF->CPU_GPIO_SetPinState(SPI_CLK_Pin,TRUE);

    4. }

    5. inline void  PS2X::CLK_CLR(void) {

    6.     MF->CPU_GPIO_SetPinState(SPI_CLK_Pin,FALSE);

    7. }

    8. inline void  PS2X::CMD_SET(void) {

    9.          MF->CPU_GPIO_SetPinState(SPI_MO_Pin,TRUE);

    10. }

    11. inline void  PS2X::CMD_CLR(void) {

    12.          MF->CPU_GPIO_SetPinState(SPI_MO_Pin,FALSE);

    13. }

    14. inline void  PS2X::ATT_SET(void) {

    15.     MF->CPU_GPIO_SetPinState(SPI_CS_Pin,TRUE);

    16. }

    17. inline void PS2X::ATT_CLR(void) {

    18.          MF->CPU_GPIO_SetPinState(SPI_CS_Pin,FALSE);

    19. }

    20. inline bool PS2X::DAT_CHK(void) {

    21.          return MF->CPU_GPIO_GetPinState(SPI_MI_Pin);

    22. }
    复制代码

    定义几个宏:
    1. #define delayMicroseconds    MF->HAL_Time_Sleep_MicroSeconds_InterruptEnabled

    2. #define millis()                   (MF->HAL_Time_CurrentTime()/1000)

    3. #define delay(x)                      MF->HAL_Time_Sleep_MicroSeconds_InterruptEnabled(1000*x)
    复制代码

    由于没有map函数,需要我们自己实现:
    1. int map(INT32 x, int in_min, int in_max, int out_min, int out_max)

    2. {

    3.   return(x -in_min)*(out_max -out_min)/(in_max -in_min)+out_min;

    4. }
    复制代码

    另外就是重定义一些变量类型了,这里就不详述了。

    下面说一下用户驱动接口的编程:

    我们通过GeneralStream_Open2_UserDriver接口传递一个32位整型数,传入四个GPIO的值。
    1. int GeneralStream_Open2_UserDriver(int config)

    2. {

    3.    //必须第一个执行

    4.    InitUserDriver();

    5.    //获取系统函数的指针

    6.    MF = (IGeneralStream_Function*)config;      



    7.    //配置IO

    8.    att = (UINT8)(MF->iParam1>>24 & 0xFF);

    9.    cmd=  (UINT8)(MF->iParam1>>16 & 0xFF);

    10.    dat=  (UINT8)(MF->iParam1>>8 & 0xFF);

    11.    clk = (UINT8)(MF->iParam1>>0 & 0xFF);

    12.    ……

    13. }
    复制代码

    这里我们用到一个.NET Micro Framework PAL底层特有的一个功能函数:HAL_COMPLETION。可以定时去执行一个函数,类似一种多线程机制(可以定义多个)。

    我们定义一个20ms执行的扫描函数,用来扫描键值:

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

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

    完整的扫描函数代码如下:
    1. void ScanKey(void *arg)

    2. {   

    3.     if(error == 1 || type == 2)        

    4.     {

    5.            InitPS2();

    6.            MF->HAL_COMPLETION_EnqueueDelta(hcHander,1000000);  //1s执行一次

    7.       if(error == 1 || type == 2)return;

    8.     }



    9.          //读状态

    10.     ps2x.read_gamepad(false, vibrate);

    11.          UINT8 button = 0;

    12.          for(int i=0;i<16;i++)

    13.          {

    14.             if(ps2x.NewButtonState(Buttons[i]))

    15.             {

    16.                 button = ps2x.Button(Buttons[i]);

    17.                       //MF->debug_printf("%s:%d\r\n",ButtonNames[i],button);

    18.                       //MF->lcd_printf("%s:%d             \r\n",ButtonNames[i],button);

    19.                       if(button) ButtonState |= 1<<i;

    20.                       else  ButtonState &= ~(1<<i);

    21.                       //触发事件

    22.                 MF->Notice_GenerateEvent(UserDriver_Hander,(byte)i<<16 | button );

    23.             }

    24.     }

    25.          UINT8 lx=ps2x.Analog(PSS_LX);

    26.     UINT8 ly=ps2x.Analog(PSS_LY);

    27.     UINT8 rx=ps2x.Analog(PSS_RX);

    28.     UINT8 ry=ps2x.Analog(PSS_RY);

    29.          ButtonAnalog = lx<<24 | ly<<16 | rx<<8 | ry;

    30.          if(frist!=1)

    31.          {

    32.              if(lx!=olx || ly!=oly)

    33.                    {

    34.                        //MF->lcd_printf("lx:%d ly:%d     \r\n",lx,ly);

    35.                        //触发事件

    36.                  MF->Notice_GenerateEvent(UserDriver_Hander,(byte)16<<16 | lx<<8 | ly );

    37.                    }

    38.                    if(rx!=orx || ry!=ory)

    39.                    {

    40.                        //MF->lcd_printf("rx:%d ry:%d     \r\n",rx,ry);

    41.                             //触发事件

    42.                  MF->Notice_GenerateEvent(UserDriver_Hander,(byte)17<<16 | rx<<8 | ry );

    43.                    }

    44.          }

    45.          olx=lx;oly=ly;orx=rx;ory=ry;frist=0;

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

    47. }
    复制代码


    为了便于同时获取键值和摇杆值,我们还封装了一个接口,代码如下:
    1. int GeneralStream_IOControl2_UserDriver(int code,int parameter)

    2. {

    3.    //获取当前按键状态

    4.    if(code == 0) return ButtonState;

    5.    else if(code == 1) return ButtonAnalog;

    6.    return -1;

    7. }
    复制代码

    以上代码编译成bin文件,通过YFAccessFlash直接部署到设备中即可。



    下面我们介绍一下,用户C#代码

    我们先做一个简单的封装:
    1. public PS2(Cpu.Pin clk,Cpu.Pin cmd,Cpu.Pin att,Cpu.Pin dat )

    2.      {

    3.             gs = new GeneralStream();

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

    5.             {

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

    7.             }

    8.             gs.Notice += new GeneralStreamEventHandler(gs_Notice);

    9.      }



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

    11.      {

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

    13.             if (hander == 1)

    14.             {

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

    16.                 int state = 0,x=0,y=0;

    17.                 if (key == Key.LRocker || key == Key.RRocker)

    18.                 {

    19.                     x = (int)(data >> 8 & 0xFF);

    20.                     y = (int)(data & 0xFF);

    21.                 }

    22.                 else

    23.                 {

    24.                     state = (int)(data & 0xFF);

    25.                 }

    26.                 if (Click != null) Click(this, new ButtonArgs(key, state, x, y));

    27.             }

    28. }



    29.     public class Program

    30.     {

    31.         public static void Main()

    32.         {

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

    34. );

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

    36.             Thread.Sleep(Timeout.Infinite);

    37.         }



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

    39.         {

    40.             Debug.Print(e.ToString());

    41.         }      

    42.     }
    复制代码


    D用户应用程序功能测试

    接上设备,把以上的程序运行,操作游戏机手柄,我们就可以看到按键信息了。
    251809564231300.jpg



    小结:

    1、 有了用户驱动C/C++二次开发接口,很容易移植相关C/C++代码。

    2、 .NET Micro Framework的封装性能,让用户程序仅关注业务逻辑即可,显得非常的简单易用。

    3、 VS2010/VS2012可以在线调试.NET Micro Framework(加断点、单步执行等等),便于问题诊断和调试。


    本文转载叶帆,转载自cnblogs













    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    关闭

    站长推荐上一条 /2 下一条

    手机版|爱板网

    GMT+8, 2018-12-10 22:25 , Processed in 0.343673 second(s), 14 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-5   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001056号

    Powered by Discuz!

    返回顶部