树莓派是最近比较火热的开源硬件,其设备只有信用卡大小,运行着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. Java多线程之Callable接口与Runnable的实现以及选择

    通过实现Runnable接口的实现 package Thread; import java.util.concurrent.ExecutorService;import java.util.concu ...

  2. LaTeX技巧:LaTeX括号总结

    功能 语法 显示 不好看     ( \frac{1}{2} ) 好一点 \left( \frac{1}{2} \right) 您可以使用\left和\right来显示不同的括号: 功能 语法 显示 ...

  3. 细说SQL Server中的加密【转】

    简介 加密是指通过使用密钥或密码对数据进行模糊处理的过程.在SQL Server中,加密并不能替代其他的安全设置,比如防止未被授权的人访问数据库或是数据库实例所在的Windows系统,甚至是数据库所在 ...

  4. PredicateBuilder类(linq多条件组合查询)

    PredicateBuilder类如下: public static class PredicateBuilder { /// <summary> /// 机关函数应用True时:单个AN ...

  5. Edit Distance leetcode java

    题目: Given two words word1 and word2, find the minimum number of steps required to convert word1 to w ...

  6. 构建-4 dependencies 依赖管理

    官方文档 Add build dependencies The Gradle build system in Android Studio makes it easy to include exter ...

  7. 热修复 AndFix 阿里 apkpatch MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  8. JSTL fn:contains()函数

    fn:contains() 函数判断一个输入字符串是否包含一个指定的子串. 语法 使用 fn:contains() 函数具有以下语法: boolean contains(java.lang.Strin ...

  9. printf()详解之终极无惑

    1.printf()简介 printf()是C语言标准库函数,用于将格式化后的字符串输出到标准输出.标准输出,即标准输出文件,对应终端的屏幕.printf()申明于头文件stdio.h. 函数原型: ...

  10. wifidog 源码初分析(1)-转

    wifidog 的核心还是依赖于 iptables 防火墙过滤规则来实现的,所以建议对 iptables 有了了解后再去阅读 wifidog 的源码. 在路由器上启动 wifidog 之后,wifid ...