• 【物联网智能网关05】扫描键盘编程设计


    .NET Micro Framework模拟器提供了5个模拟按键(上、下、左、右和确认按键),所以一般.NET MF开发板也只需要提供5个按键就可以了,而这5个键,也是直接和CPU的pin脚相连,用GPIO的输入相关的函数就可以操作了,使用非常简单。

    但是对一些特殊的应用,如一些.NET Micro Framework教育箱或一些工业实际用的系统,5个按键显然太少了点。但是如果需要十几个按键,如果直连芯片pin脚,显然占用的资源比较多了,也会导致其它的功能无法使用了,这时候最常用的就是扫描键盘了。

     

    上述扫描键盘的原理图应该是最简单的一种了,复杂一点的,在行或列上,通过一个上拉电阻接VCC。这样,我们只需要8个pin脚,就可以获取16个按键的信息了。

    一般实现的思路也比较简单:就是把行(或列)接芯片输出pin脚,把列(或行)接芯片输入pin脚,输出pin脚依次输出低(或高,需要看电路中接的上拉还是下拉电阻)电平,然后检查输入pin脚的电平变化。如果有变化,那么就说明,该列和该行的按键被按下了。

    往往这个判断就放在while循环或线程里,不断的去运行。对一些单片而言,如果实现的功能单一,这样做也无可厚非,但是对一个系统平台来说,如果也这样做,显然对系统的资源占用还是比较厉害的。

    所以最好的办法还是要采用中断的方式,平时的时候不去判断,靠中断触发,一旦中断触发了,然后再启动一轮判断,确定是哪一个按键被按下了。

    1、扫描方式实现按键获取

    public class ScanKeypad
    
        {
    
            public event NativeEventHandler OnInterrupt;
    
     
    
            OutputPort[] rows = null;
    
            InputPort[] cols = null;
    
            public ScanKeypad(Cpu.Pin[] Output_Pins, Cpu.Pin[] Input_Pins)
    
            {
    
                rows = new OutputPort[] { new OutputPort(Output_Pins[0], false), new OutputPort(Output_Pins[1], false), new OutputPort(Output_Pins[2], false), new OutputPort(Output_Pins[3], false) };
    
                cols = new InputPort[] { new InputPort(Input_Pins[0], true, Port.ResistorMode.PullUp), new InputPort(Input_Pins[1], true, Port.ResistorMode.PullUp), new InputPort(Input_Pins[2], true, Port.ResistorMode.PullUp), new InputPort(Input_Pins[3], true, Port.ResistorMode.PullUp) };
    
     
    
                Thread threadKeypad = new Thread(new ThreadStart(KeypadScan));
    
                threadKeypad.Start();
    
            }
    
     
    
            void KeypadScan()
    
            {
    
                int key = -1, oldKey = -1;
    
                while (true)
    
                {
    
                    key = -1;
    
                    for (int i = 0; i < rows.Length; i++)
    
                    {
    
                        rows[i].Write(false);
    
                        for (int j = 0; j < cols.Length; j++)
    
                        {
    
                            if (!cols[j].Read())
    
                            {
    
                                key = i * rows.Length + j;
    
                                break;
    
                            }
    
                        }
    
                        rows[i].Write(true);
    
                        if (key > -1) break;
    
                    }
    
                    if (key > -1 && key != oldKey)
    
                    {
    
                        if (OnInterrupt != null) OnInterrupt((uint)key, 1, DateTime.Now);
    
                        oldKey = key;
    
                    }
    
                    else if (oldKey > -1 && key == -1)
    
                    {
    
                        if (OnInterrupt != null) OnInterrupt((uint)oldKey, 0, DateTime.Now);
    
                        oldKey = -1;
    
                    }
    
                    Thread.Sleep(100);
    
                }
    
            }
    
        }

    2、中断方式实现按键获取

    public class InterruptKeypad
    
        {
    
            public event NativeEventHandler OnInterrupt;
    
     
    
            OutputPort[] rows = null;
    
            InterruptPort[] cols = null;
    
            Cpu.Pin[] Pins = null;
    
            uint key = 0;
    
            public InterruptKeypad(Cpu.Pin[] Output_Pins, Cpu.Pin[] Input_Pins)
    
            {
    
                rows = new OutputPort[] { new OutputPort(Output_Pins[0], false), new OutputPort(Output_Pins[1], false), new OutputPort(Output_Pins[2], false), new OutputPort(Output_Pins[3], false) };
    
                cols = new InterruptPort[Input_Pins.Length];
    
                Pins = Input_Pins;
    
                for (int i = 0; i < Input_Pins.Length; i++)
    
                {
    
                    cols[i] = new InterruptPort(Input_Pins[i], true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeBoth);
    
                    cols[i].OnInterrupt += new NativeEventHandler(InterruptKeypad_OnInterrupt);
    
                }
    
            }
    
     
    
            private uint GetPinIndex(uint pin)
    
            {
    
                for (uint i = 0; i < Pins.Length; i++)
    
                {
    
                    if (pin == (uint)Pins[i]) return i;
    
                }
    
                return 0;
    
            }
    
     
    
            void InterruptKeypad_OnInterrupt(uint data1, uint data2, DateTime time)
    
            {
    
                if (data2 == 1)
    
                {
    
                    for (int i = 0; i < cols.Length; i++)
    
                    {
    
                        cols[i].OnInterrupt -= new NativeEventHandler(InterruptKeypad_OnInterrupt);
    
                    }
    
                    //--
    
                    uint col = GetPinIndex(data1);
    
                    for (int i = 0; i < rows.Length; i++)
    
                    {
    
                        rows[i].Write(true);
    
                        if (cols[col].Read())
    
                        {
    
                            key = (uint)(i * rows.Length + col);
    
                            Thread threadKeypad = new Thread(new ThreadStart(KeypadRun));
    
                            threadKeypad.Start();
    
                            break;
    
                        }
    
                    }
    
                    //--
    
                    for (int i = 0; i < rows.Length; i++) rows[i].Write(false);
    
                    for (int i = 0; i < cols.Length; i++)
    
                    {
    
                        cols[i].OnInterrupt += new NativeEventHandler(InterruptKeypad_OnInterrupt);
    
                    }
    
                }
    
            }
    
     
    
            void KeypadRun()
    
            {
    
                OnInterrupt(key, 1, DateTime.Now);
    
                OnInterrupt(key, 0, DateTime.Now);
    
            }
    
        }

    注意,中断方式中,触发事件必须放在线程里执行,否则会有问题(如果在Winform中使用,最好不用线程,而用winfrom提供的timer,否则就无法直接操作UI了,那就必须用委托方式了,和windows上的编程类似)。

    问题点1由于我们采用的键盘并没有加上拉(或下拉)电阻电路,在最初做这个程序的时候,InputPort(Input_Pins[1], true, Port.ResistorMode.PullUp),最后一个参数,底层并没有实现内部上拉,下拉和悬空功能,所以设置是无效的。这就造成了,在按钮没有按下时,输入pin脚的状态是未知的,有时候是1,有时候是0,程序是无法正确运行的。

    此外STM32F103和STM32F207的GPIO寄存器差别很大,内部实现上拉、下拉的设置也是不同的。分别实现后,发现内部上拉正常,设置下拉效果不明显,pin脚的状态还是未知的。所以我们实现的程序都设置为上拉。

    问题点2在实现中断方式的扫描键盘的代码的时候,发现PB6、PC0和PB1三个pin脚触发中断异常,但是在NativeSample层面又正常。目前没有发现这三个pin脚有何特别之处,此问题以后待查。所以如果采用中断方式,这三个pin脚不能使用。

    以上两种方式都是在应用层面实现的,其实如果扫描键盘的pin脚固定,更好的方式可以在底层用C++实现,并且还可以把8个物理pin脚,虚拟出16个pin脚来,用法和物理的pin脚完全一样。

    官方SimpleWPFApplication示例,是一个比较典型的WPF应用,但是需要5个按键才能操作,我们的紫藤207系统仅提供了一个物理按钮,所以是无法操作的。接上扫描键盘后,我们就有可能完整的演示这个示例了,不过由于我们使用的是扫描键盘,所以原程序无法使用,必须做如下修改才可以。

       

     public sealed class GPIOButtonInputProvider
    
        {
    
            public readonly Dispatcher Dispatcher;
    
            private DispatcherOperationCallback callback;
    
            private InputProviderSite site;
    
            private PresentationSource source;
    
     
    
            public GPIOButtonInputProvider(PresentationSource source)
    
            {
    
                this.source = source;
    
                site = InputManager.CurrentInputManager.RegisterInputProvider(this);
    
                callback = new DispatcherOperationCallback(delegate(object report)
    
                {
    
                    InputReportArgs args = (InputReportArgs)report;
    
                    return site.ReportInput(args.Device, args.Report);
    
                });
    
                Dispatcher = Dispatcher.CurrentDispatcher;
    
     
    
                Cpu.Pin[] Output_Pins = { (Cpu.Pin)GPIO_NAMES.PC8, (Cpu.Pin)GPIO_NAMES.PC9, (Cpu.Pin)GPIO_NAMES.PB7, (Cpu.Pin)GPIO_NAMES.PC2 };
    
                Cpu.Pin[] Input_Pins = { (Cpu.Pin)GPIO_NAMES.PC3, (Cpu.Pin)GPIO_NAMES.PA0, (Cpu.Pin)GPIO_NAMES.PA5, (Cpu.Pin)GPIO_NAMES.PA6 };
    
               
    
                InterruptKeypad key = new InterruptKeypad(Output_Pins, Input_Pins);
    
                key.OnInterrupt += new NativeEventHandler(key_OnInterrupt);
    
            }
    
     
    
            void key_OnInterrupt(uint data1, uint data2, DateTime time)
    
            {
    
                RawButtonActions action = (data2 != 0) ? RawButtonActions.ButtonUp : RawButtonActions.ButtonDown;
    
                RawButtonInputReport report = new RawButtonInputReport(source, time, GetButton(data1), action);
    
                Dispatcher.BeginInvoke(callback, new InputReportArgs(InputManager.CurrentInputManager.ButtonDevice, report));
    
            }
    
     
    
            Button GetButton(uint data)
    
            {
    
                switch (data)
    
                {
    
                    case 2:
    
                        return Button.VK_UP;
    
                    case 5:
    
                        return Button.VK_LEFT;
    
                    case 6:
    
                        return Button.VK_SELECT;
    
                    case 10:
    
                        return Button.VK_DOWN;
    
                    case 7:
    
                        return Button.VK_RIGHT;
    
                }
    
                return Button.None;
    
            }
    
        }

    把GpioButtonInputProvider.cs里面的程序这样修改后,就可以使用了。

    效果图如下: 

    实际运行视频链接如下:

     http://v.youku.com/v_show/id_XNDI3ODU4OTg4.html

    从视频可以看出,STM32F207平台运行WPF程序还是蛮流畅的。

    ------------------------------------------------------------------------------- 

    下载地址:http://www.sky-walker.com.cn/MFRelease/Sample/ScanKey_WPFTest.rar     

    MF简介:http://blog.csdn.net/yefanqiu/article/details/5711770

    MF资料:http://www.sky-walker.com.cn/News.asp?Id=25

  • 相关阅读:
    过滤器
    自定义指令
    Window setTimeout() 方法
    Window setInterval()方法
    ThingJS官方案例(五):物联网室内3D定位导航,上下楼怎么办?
    基于WebGL的虚拟太阳系漫游技术实现 ThingJS 科幻片
    ThingJS官方案例(四):快速应用3D场景下的模拟导航能力
    ThingJS官方示例(三):3D标记“Marker”跳跃、闪烁和发光动画效果
    ThingJS官方示例(二):利用电子标注判断物联网设备的位置
    ThingJS 官方示例(一):禁区告警的3D电子围栏可视化
  • 原文地址:https://www.cnblogs.com/yefanqiu/p/2592855.html
Copyright © 2020-2023  润新知