树莓派是最近比较火热的开源硬件,其设备只有信用卡大小,运行着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>> & 0xFF);

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

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

   clk = (UINT8)(MF->iParam1>> & 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 ==  || type == )        

    {

           InitPS2();

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

      if(error ==  || type == )return;

    }

         //读状态

    ps2x.read_gamepad(false, vibrate);

         UINT8 button = ;

         for(int i=;i<;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 |= <<i;

                      else  ButtonState &= ~(<<i);

                      //触发事件

                MF->Notice_GenerateEvent(UserDriver_Hander,(byte)i<< | 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<< | ly<< | rx<< | ry;

         if(frist!=)

         {

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

                   {

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

                       //触发事件

                 MF->Notice_GenerateEvent(UserDriver_Hander,(byte)<< | lx<< | ly );

                   }

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

                   {

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

                            //触发事件

                 MF->Notice_GenerateEvent(UserDriver_Hander,(byte)<< | rx<< | ry );

                   }

         }

         olx=lx;oly=ly;orx=rx;ory=ry;frist=;

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

}

为了便于同时获取键值和摇杆值,我们还封装了一个接口,代码如下:

int GeneralStream_IOControl2_UserDriver(int code,int parameter)

{ 

   //获取当前按键状态 

   if(code == ) return ButtonState;

   else if(code == ) return ButtonAnalog;

   return -;

}

以上代码编译成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 <<  | (int)cmd <<  | (int)att <<  | (int)dat)) <= )

            {

                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 == )

            {

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

                int state = ,x=,y=;

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

                {

                    x = (int)(data >>  & 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(加断点、单步执行等等),便于问题诊断和调试。

【树莓派+.NET MF打造视频监控智能车】遥控篇的更多相关文章

  1. 【树莓派+.NET MF打造视频监控智能车】控制篇(树莓派)

    对已经具备一定Linux基础的人来说,树莓派学习起来应该非常简单自然.在他们眼中,树莓派就是一个简易版的,卡通版的Linux而已.但是对我这样一个早已习惯微软技术生态系统的人或者初学者来说,要实现一个 ...

  2. 【树莓派+.NET MF打造视频监控智能车】控制篇(.NET MF)

    在上一篇<遥控篇>文章中,我们介绍了Sony PS2手柄信号的采集和编程,通过简单的封装,以事件的方式向我们提供按键信息.本篇文章主要介绍.NET Micro Framework系统接受到 ...

  3. 深度 | AI芯片之智能边缘计算的崛起——实时语言翻译、图像识别、AI视频监控、无人车这些都需要终端具有较强的计算能力,从而AI芯片发展起来是必然,同时5G网络也是必然

    from:https://36kr.com/p/5103044.html 到2020年,大多数先进的ML袖珍电脑(你仍称之为手机)将有能力执行一整套任务.个人助理将变的更加智能,它是打造这种功能的切入 ...

  4. 【miscellaneous】监狱智能视频监控系统设计解决方案

    监狱智能视频监控系统设计解决方案 一.系统概况 随着司法监狱管理系统内视频监控系统的日益发展,现有的被动式人工监控这一传统模式已无法满足新形势下的监管工作需求,尤其是现在靠轮询的视频监控方式,无法对突 ...

  5. 开源倾情奉献:基于.NET打造IP智能网络视频监控系统(一)开放源代码

    本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载. 开源倾情奉献系列链接 开源倾情奉献:基于.NET打造IP智能网络视频监控系统(一)开放源代码 开源倾 ...

  6. 基于.NET打造IP智能网络视频监控系统

    开源倾情奉献:基于.NET打造IP智能网络视频监控系统(一)开放源代码   开源倾情奉献系列链接 开源倾情奉献:基于.NET打造IP智能网络视频监控系统(一)开放源代码 开源倾情奉献:基于.NET打造 ...

  7. 【miscellaneous】监狱安防系统智能视频监控系统设计方案

    1监狱安防新需求 随着司法监狱管理系统内视频监控系统的日益发展,现有的被动式人工监控这一传统模式已无法满足新形势下的监管工作需求,尤其是现在靠轮询的视频监控方式,无法对突发恶性事件做到第一时间的防御和 ...

  8. 【miscellaneous】华为智能视频监控系统设计解决方案

    [导读] 近年来,随着经济的快速增长.社会的迅速进步,校园.工厂园区.中小企业.楼宇等领域对安全防范和现场记录报警系统的需求与日俱增,视频监控在工作.生活各方面得到了非常广泛的应用. 1.中小型视频监 ...

  9. 基于Spring4+SpringMVC4+Mybatis3+Hibernate4+Junit4框架构建高性能企业级的部标1077视频监控平台

    开发企业级的部标GPS监控平台,投入的开发力量很大,开发周期也很长,选择主流的开发语言以及成熟的开源技术框架来构建基础平台,是最恰当不过的事情,在设计之初就避免掉了技术选型的风险,避免以后在开发过程中 ...

随机推荐

  1. LaTeX技巧22:LaTeX文档中的参考文献初级

    用 LaTeX 处理文档, 经常就要书写参考文献, 本篇就是介绍如何在 LaTeX 中使用参考文献, 注意这里讲的是LaTeX默认的 thebibliography 环境, 如果要了解 LaTeX 中 ...

  2. django的mysql设置和mysql服务器闲置时间设置

    服务器启动后,每个进程都会主动连接到mysql,要是长时间没有数据交互,mysql会自动断开连接. show variables like  '%timeout%'; 闲置连接的超时时间由wait_t ...

  3. org.codehaus.jackson.map.JsonMappingException: Can not construct instance of java.util.Date from String value '2012-12-12 12:01:01': not a valid representation (error: Can not parse date "2012-12-

    Jackson对于date的反序列化只支持几种,如果不符合默认格式则会报一下错误 org.codehaus.jackson.map.JsonMappingException: Can not cons ...

  4. C语言中将0到1000的浮点数用强制指针类型转换的方式生成一幅图像

    搞过计算机图像的人都知道,图像中的每一个像素通常为一个整型数,它可以分成4个无符号的char类型,以表示其RGBA四个分量.一幅图像可以看做是一个二维整型数组.这里我会生成一个float数组,其数组大 ...

  5. go语言之进阶篇定时器停止

    1.定时器停止 示例: package main import ( "fmt" "time" ) func main() { timer := time.New ...

  6. SharePoint2013 以其他用户登录和修改AD域用户密码 功能

    sharepoint默认是没有修改AD密码 和切换 用户的功能,这里我用future的方式来实现. 部署wsp前: 部署后: 点击以其他用户身份登录 点击修改用户密码: 这里的扩展才菜单我们用Cust ...

  7. MFC/Windows API 使用过的函数(持续更新)

    /*******************使用默认画笔对象**************************** // //绘制矩形 pDC->MoveTo(50, 50); //返回值是一个指 ...

  8. Laravel validate 500异常 添加手机验证,中文验证与Validator验证的“半个”生命周期

    今天来讲一下,Lumen的Validator函数 1 2 3 4 5 6 7 8 9 10 11 use Validator;   ...   Class .. {   public function ...

  9. Android -- DisplayMetrics

    干货 DisplayMetrics dm = new DisplayMetrics(); this.getWindowManager().getDefaultDisplay().getMetrics( ...

  10. TensorFlow (RNN)深度学习 双向LSTM(BiLSTM)+CRF 实现 sequence labeling 序列标注问题 源码下载

    http://blog.csdn.net/scotfield_msn/article/details/60339415 在TensorFlow (RNN)深度学习下 双向LSTM(BiLSTM)+CR ...