zoukankan      html  css  js  c++  java
  • 【物联网智能网关11】流式驱动之用户驱动(MDK C++开发)

          微软体系的产品给人的感觉一直是易学易用,但是其执行性能却屡受诟病。所以一些对性能要求相对较高的硬件产品研发,一般都是采用linux体系的技术,或者是无操作系统开发,其开发语言也绝大数是C/C++(启动代码或中断部分的代码有时会用汇编代码实现)。但是对工控集成类的项目开发来说,由于项目开发周期比较短,对稳定性要求比较高,如果全部采用C/C++开发,不仅对开发人员的能力要求比较高,并且开发和调试的代价非常大。所以PC平台的,大都是用组态系统搭建,嵌入式系统则是采用嵌入式组态软件,其定制化的软件则采用WinCE等易用的嵌入式系统来开发了,但是对再小型的嵌入式系统,由于选择目前比较少,也只有选用传统的C/C++来开发了。

         2001年.NET Micro Framework的开始研发,其实就是基于比尔盖茨所谓的.NET战略,上至服务器大型系统,下至嵌入式领域的芯片都希望是.NET系统,都可以用C#等.NET开发语言进行开发。所以最开始.NET Micro Framework系统就是开发硬件产品的,MSN Direct产品、SideShow,还有一些高端遥控器,键盘,都是采用.NET Micro Framework系统开发(相关介绍,请参见《MSN Direct项目简介》),虽然用.NET Micro Framework系统开发比较容易,但是要达到同样的性能,必须要求系统的主频更快,RAM更大,这对批量生产的硬件产品来说,长远发展来看,不是一个好选择。

         我个人认为微软官方的.NET Micro Framework产品类开发的定位是错误的,这一点我可以看到微软在Windows领域的开发也是放弃了全用.NET托管代码实现的诉求,大部分底层或对性能要求很高的代码,依然采用原生C/C++实现(目前iOS和安卓系统的开发,基于性能的考虑,很多开发人员都开始用原生C/C++进行开发)。

         用.NET Micro Framework开发用户需求变化少,不需要二次开发接口的产品来说,是非常不适合的,特别是销售量数量非常大的产品,因为随着产品的销量不断增加,前期开发成本所占的成本比重将越来越小。但是对用户需求变化大,用户需要有二次开发,或者是销量比较少的产品来说,用.NET Micro Framework优势就比较明显了。特别是工控集成类的产品,.NET Micro Framework系统有天然的优势(这是我7年工控领域的工作经历深切感受到的)。

          PC领域的组态化技术已经非常成熟了,目前已经在向组态软件的第二代或第三代进行发展。但是在嵌入式领域,特别是低端MCU方面,这方面做得远远不够,我工作的定位就是致力于嵌入式领域组态化,并且我认为.NET Micro Framework系统是实现这个愿景的最好的一种技术支撑。

          .NET Micro Framework的平台的C#(或VB.NET)开发虽然开发比较简单,但是其执行性能却是一个必须面对的问题(2009年我在微软总部和MSN Direct开发人员交流的时候,他们对.NET Micro Framework的执行性能颇有微词)。

          我的解决方案就是:.NET Micro Framework必须尽可能的封装,C#语言执行的不是大段功能代码,而只是一些工艺流程代码即可,那些功能性的代码尽可能用C/C++实现。C#起到粘连串接的作用即可,这一点和网页开发中的脚本语言的角色非常类似。

          目前这类封装,必须是porting开发人员完成,是.NET Micro Framework TinyCLR的不可分的部分,普通用户是不能进行C/C++开发的。而我这篇文章所介绍的重点,就是为普通的开发用户,开启C/C++ 基于.NET Micro Framework编程之门。在《.NET Micro Framework动态调用C/C++底层代码》文章中我介绍了这种技术的实现原理,本篇文章就是基于应用的角度,介绍如果用MDK进行.NET Micro Framework用户驱动开发。

         在进行用户流式驱动开发介绍之前,我先比较一下C#和C++开发的性能,让大家有一个直观的感受。 

        上图C#代码如下:

        OutputPort io = new OutputPort((Cpu.Pin)GPIO_NAMES.PA6,false);
    
        while (true)
    
        {
    
            io.Write(true);
    
            io.Write(false);
    
        }

    C++的代码如下(NativeSample下运行)

    CPU_GPIO_EnableOutputPin(STM32F20x_GPIO_Driver::PA6,FALSE);
    
    while(TRUE) 
    
    {
    
         CPU_GPIO_SetPinState(STM32F20x_GPIO_Driver::PA6,FALSE); 
    
              CPU_GPIO_SetPinState(STM32F20x_GPIO_Driver::PA6,TRUE); 
    
    }

         硬件平台采用紫藤207(STM32F207 主频120M),通过示波器检查PA6管脚。

         从示波器的显示结果来看,二者相差近60倍,所以说在C#层很难实现微秒级别的控制。

         另外我也比较了一下C#和C++的for循环的执行效率。

         代码很简单,就是:for(x=0;x<1000;x++);

    执行次数

    C#

    C++

    倍数

    10

    316us

    -

    -

    100

    2.5ms

    3.5us

    714

    1000

    24ms

    33us

    727

    10000

    -

    333us

    -

         C#层提供的Sleep延时也是毫秒级别的,我做了一个简单的测试,结果如下:

    Sleep参数

    0

    1

    10

    执行时间

    176-184 us

    1.3ms

    10.4ms

         注:由于底层时钟中断不断触发,Sleep的时间是不确定的。

         相信以上的测试结果,对大家的印象是深刻的。所以说,不考虑C#的封装优点,而是非要用C#和C++实现同样的功能,只能是让大家越来越远离.NET Micro Framework。

    -------- 分割线 ---------

         在《.NET Micro Framework动态调用C/C++底层代码》这篇文章中,我介绍g_GeneralStream_Function的时候,其支持的函数才15个,并且主要是GPIO和时钟类的函数,这次调整以后,已经扩展支持61个了,并且也可以传递初始化函数的字符串或整型变量参数了,新的g_GeneralStream_Function定义如下:

    IGeneralStream_Function g_GeneralStream_Function  =
    
    {       
    
          -1,
    
               NULL,
    
               //--
    
          &Notice_GenerateEvent,
    
               &lcd_printf,
    
               &debug_printf,  
    
               &HAL_Time_Sleep_MicroSeconds_InterruptEnabled,
    
               &Events_WaitForEvents,
    
               &disable_interrupts,
    
               &enable_interrupts,
    
               &private_malloc,
    
               &private_free,
    
               //mem
    
               &hal_snprintf,
    
               &hal_stricmp,
    
               &hal_strncmp_s,
    
               &hal_strlen_s,
    
               &memcpy,
    
               &memset,    
    
               //Flash
    
               &YFSoft_Flash_Erase,
    
               &YFSoft_Flash_Read,
    
               &YFSoft_Flash_Write,
    
               //GPIO
    
               &CPU_GPIO_DisablePin, 
    
               &CPU_GPIO_EnableInputPin, 
    
               &CPU_GPIO_EnableOutputPin,
    
               &CPU_GPIO_GetPinState, 
    
               &CPU_GPIO_SetPinState,
    
               //TIMER
    
               &CPU_TIMER_Initialize, 
    
               &CPU_TIMER_Uninitialize,
    
               &CPU_TIMER_Start,
    
               &CPU_TIMER_Stop,
    
               &CPU_TIMER_GetState,
    
               &CPU_TIMER_SetState,
    
               //USART
    
               &USART_Initialize,
    
               &USART_Uninitialize,
    
               &USART_Write,
    
               &USART_Read,
    
               &USART_Flush,
    
               &USART_BytesInBuffer,
    
               &USART_DiscardBuffer,
    
               //DA/AD
    
               &DA_Initialize,
    
               &DA_Write,
    
               &AD_Initialize,
    
               &AD_Read,
    
               //PWM
    
               &PWM_Initialize,
    
               &PWM_Uninitialize,
    
               &PWM_ApplyConfiguration,
    
               &PWM_Start,
    
               &PWM_Stop,
    
               &PWM_GetPinForChannel,
    
               //TinyGUI
    
               &LCD_ClearEx,
    
               &LCD_SetPixel,
    
               &LCD_GetPixel,
    
               &LCD_DrawLine,
    
               &LCD_DrawRectangle,
    
               &LCD_DrawEllipse,
    
               &LCD_DrawImage,
    
               &LCD_DrawImageEx,
    
               &LCD_DrawString,
    
               &LCD_DrawStringEx,
    
               &LCD_FillRectangle,
    
               &LCD_FillEllipse,
    
               &LCD_GetFrameBufferEx,
    
               &LCD_SuspendLayout,
    
               &LCD_ResumeLayout,
    
    };

          有了这些函数支持,就可以在MDK中独立编写 MF的用户流驱动了。当然,你也可以不用这些函数,也可以调用MDK相关的库或STM32提供的库,直接通过寄存器对硬件进行操作(前提是和已有的功能不要冲突就行)。

         为了便于在MDK 4.x中开发用户流式驱动,我提供了yfmflib.h和grenralstream.h头文件,也提供了一个UserDriver.cpp模板,用户只要简单修改一下即可。 

         UserDriver.cpp模板中的代码如下:

    //说明:代码空间 0x08010000 -  0x08020000  64K
    
    //      内存空间 0x20002000 -  0x20004000   8K
    
    #include "YFMFLib.h"
    
    #include "GeneralStream.h"
    
    #define UserDriver_Flag            "UserDriver"
    
    #define UserDriver_Hander          1
    
    const IGeneralStream_Function *MF=NULL;
    
    int GeneralStream_Open1_UserDriver(LPCSTR config)  { return 0;}      //Open1永远也不会被调用
    
    //int GeneralStream_Open2_UserDriver(int config)   { return 0;}
    
    int GeneralStream_Close_UserDriver()  { return 0;}
    
    int GeneralStream_IOControl1_UserDriver(int code, BYTE *inBuffer, int inCount, BYTE *outBuffer, int outCount){return -1;}
    
    int GeneralStream_IOControl2_UserDriver(int code, int parameter){return -1;} 
    
    int GeneralStream_Read_UserDriver(BYTE *buffer, int offset, int count){return -1;}
    
    int GeneralStream_Write_UserDriver(BYTE *buffer, int offset, int count){return -1;}
    
     
    
    int GeneralStream_Open2_UserDriver(int config)
    
    { 
    
      //获取系统函数的指针
    
      MF = (IGeneralStream_Function*)config;
    
     
    
      //C#下传的参数
    
      MF->lcd_printf("%d,%s\r\n",MF->iParam1,MF->sParam1);
    
      MF->debug_printf("%d,%s\r\n",MF->iParam1,MF->sParam1);
    
    }
    
     
    
    extern const IGeneralStream g_GeneralStream_UserDriver;
    
    const IGeneralStream g_GeneralStream_UserDriver  =
    
    {
    
             UserDriver_Flag,
    
             &GeneralStream_Open1_UserDriver,
    
             &GeneralStream_Open2_UserDriver,   
    
             &GeneralStream_Close_UserDriver,     
    
             &GeneralStream_IOControl1_UserDriver, 
    
             &GeneralStream_IOControl2_UserDriver, 
    
             &GeneralStream_Read_UserDriver,
    
             &GeneralStream_Write_UserDriver,     
    
    };

         为了让大家印象深刻,我们以LCD1602的驱动为示例,进行用户驱动编写(我已经为其专门开发了一个流式驱动,以其为例只是便于说明,后续还将详细介绍LCD1602)。

         其实LCD1602的显示就是IO操作,其实理论上在C#层也可以实现,但是通过我以上的性能测试,估计大家会鲜有尝试了。LCD1602的驱动,通过上网搜索,无论是C51、STM32还是Arduino都提供了相关的源码,我们只要把相关的IO操作的函数,转换为我们MF的IO操作函数即可。

        主要代码如下:

    void LCD1602_Write_byte(BYTE data)        
    
    {
    
        MF->CPU_GPIO_SetPinState(LCD1602_E_Pin,TRUE);   
    
             MF->CPU_GPIO_SetPinState(LCD1602_D7_Pin,(data & 0x80)>0);
    
             MF->CPU_GPIO_SetPinState(LCD1602_D6_Pin,(data & 0x40)>0);
    
             MF->CPU_GPIO_SetPinState(LCD1602_D5_Pin,(data & 0x20)>0);
    
             MF->CPU_GPIO_SetPinState(LCD1602_D4_Pin,(data & 0x10)>0);
    
             MF->HAL_Time_Sleep_MicroSeconds_InterruptEnabled(1);
    
             MF->CPU_GPIO_SetPinState(LCD1602_E_Pin,FALSE);
    
            
    
             MF->HAL_Time_Sleep_MicroSeconds_InterruptEnabled(1);
    
             MF->CPU_GPIO_SetPinState(LCD1602_E_Pin,TRUE);
    
             MF->CPU_GPIO_SetPinState(LCD1602_D7_Pin,(data & 0x08)>0);
    
             MF->CPU_GPIO_SetPinState(LCD1602_D6_Pin,(data & 0x04)>0);
    
             MF->CPU_GPIO_SetPinState(LCD1602_D5_Pin,(data & 0x02)>0);
    
             MF->CPU_GPIO_SetPinState(LCD1602_D4_Pin,(data & 0x01)>0);
    
             MF->HAL_Time_Sleep_MicroSeconds_InterruptEnabled(1);
    
             MF->CPU_GPIO_SetPinState(LCD1602_E_Pin,FALSE);
    
    }
    
     
    
    void LCD1602_Write_Command(BYTE cmd)        
    
    {
    
        MF->HAL_Time_Sleep_MicroSeconds_InterruptEnabled(100);
    
             MF->CPU_GPIO_SetPinState(LCD1602_RS_Pin,FALSE);
    
        LCD1602_Write_byte(cmd);
    
    }
    
     
    
    void LCD1602_Write_Data(BYTE data)        
    
    {
    
        MF->HAL_Time_Sleep_MicroSeconds_InterruptEnabled(100);
    
             MF->CPU_GPIO_SetPinState(LCD1602_RS_Pin,TRUE);
    
        LCD1602_Write_byte(data);
    
    }
    
     
    
    void LCD1602_SetXY(BYTE x,BYTE y)//x:0~15,y:0~1
    
    {
    
        if(y) LCD1602_Write_Command(0xc0+x);//第二行显示
    
        else  LCD1602_Write_Command(0x80+x);//第一行显示
    
    }
    
     
    
    void LCD1602_Write_Char(BYTE x,BYTE y,char data)
    
    {
    
        LCD1602_SetXY( x, y); //写地址
    
        LCD1602_Write_Data(data);
    
    }
    
     
    
    void LCD1602_Print(BYTE x,BYTE y,char *s)
    
    {
    
             if(x>15)x=15;
    
        LCD1602_SetXY( x, y ); //写地址  
    
        int i=0;
    
        while (*s && (x+i++)<16)             //写显示字符
    
        {
    
            LCD1602_Write_Data( *s++ );
    
        }       
    
    }
    
     
    
    void LCD1602_Init()
    
    {
    
        MF->CPU_GPIO_SetPinState(LCD1602_RW_Pin,FALSE);  //只写
    
       
    
        MF->HAL_Time_Sleep_MicroSeconds_InterruptEnabled(100000);
    
             LCD1602_Write_Command(0x33);
    
             MF->HAL_Time_Sleep_MicroSeconds_InterruptEnabled(20000);
    
             LCD1602_Write_Command(0x32);
    
             MF->HAL_Time_Sleep_MicroSeconds_InterruptEnabled(20000);
    
     
    
        LCD1602_Write_Command(0x28);
    
             LCD1602_Write_Command(0x0C); //显示开
    
             LCD1602_Write_Command(0x01); //清屏
    
             MF->HAL_Time_Sleep_MicroSeconds_InterruptEnabled(20000);
    
    }

         以上就是LCD驱动相关的代码,下面我们填写接口代码

    int GeneralStream_Open2_UserDriver(int obj)
    
    { 
    
        //获取系统函数的指针
    
        MF = (IGeneralStream_Function*)obj;
    
       //--
    
      LPCSTR config = MF->sParam1;
    
        //不能有空格
    
        //    012345678901234567890123456789012345678901234567890123
    
        //格式RS=PC08,RW=PC09,E=PB06,D4=PB07,D5=PC00,D6=PC02,D7=PC03
    
        if(config[3]!='P' || config[11]!='P' || config[18]!='P' || config[26]!='P' || config[34]!='P' || config[42]!='P' || config[50]!='P')
    
        {
    
           return -1;
    
        }
    
     
    
        LCD1602_RS_Pin  =(GPIO_PIN)((config[4]-'A') * 16 + (config[5]-'0') * 10+ (config[6] - '0'));
    
      LCD1602_RW_Pin  =(GPIO_PIN)((config[12]-'A') * 16 + (config[13]-'0') * 10+ (config[14] - '0'));
    
        LCD1602_E_Pin  =(GPIO_PIN)((config[19]-'A') * 16 + (config[20]-'0') * 10+ (config[21] - '0'));
    
      LCD1602_D4_Pin  =(GPIO_PIN)((config[27]-'A') * 16 + (config[28]-'0') * 10+ (config[29] - '0'));
    
        LCD1602_D5_Pin  =(GPIO_PIN)((config[35]-'A') * 16 + (config[36]-'0') * 10+ (config[37] - '0'));
    
      LCD1602_D6_Pin   =(GPIO_PIN)((config[43]-'A') * 16 + (config[44]-'0') * 10+ (config[45] - '0'));
    
      LCD1602_D7_Pin   =(GPIO_PIN)((config[51]-'A') * 16 + (config[52]-'0') * 10+ (config[53] - '0'));
    
     
    
     MF->CPU_GPIO_EnableOutputPin(LCD1602_RS_Pin,FALSE);
    
      MF->CPU_GPIO_EnableOutputPin(LCD1602_RW_Pin,FALSE);
    
      MF->CPU_GPIO_EnableOutputPin(LCD1602_E_Pin,FALSE);
    
     MF->CPU_GPIO_EnableOutputPin(LCD1602_D4_Pin,FALSE);
    
      MF->CPU_GPIO_EnableOutputPin(LCD1602_D5_Pin,FALSE);
    
      MF->CPU_GPIO_EnableOutputPin(LCD1602_D6_Pin,FALSE);
    
     MF->CPU_GPIO_EnableOutputPin(LCD1602_D7_Pin,FALSE);
    
     
    
      LCD1602_Init(); //初始化液晶 
    
      return 0;
    
    }
    
     int GeneralStream_Write_UserDriver(BYTE *buffer, int offset, int count)
    
    {
    
        UINT8 x = (BYTE)((offset>>8) & 0xFF); 
    
      UINT8 y = (BYTE)(offset & 0xFF);
    
        buffer[count]=0;
    
       if(x>15 || y>1 ) return -1;
    
        LCD1602_Print(x,y,(char *)buffer);
    
       return 0;
    
    }

        以上代码在MDK中直接编译,编译后的bin文件,经过转换适当转换,变为MF部署工具所支持的Hex文件,用MFDeploy或YFAccessFlash工具直接部署即可,如下图所示:  

        基于C++的代码我们已经完成,下一步我们开始写C#代码,以便调用我们写好的C++代码。

        代码如下:

    using System;
    
    using Microsoft.SPOT;
    
    using Microsoft.SPOT.Hardware;
    
    using YFSoft.IO;
    
     
    
    namespace UserDriverTest
    
    {
    
        public class Program
    
        {   
    
            public static void Main()
    
            {
    
                Debug.Print("UserDriver Test ...");
    
                LCD1602 lcd = new LCD1602();
    
     
    
                lcd.Print(0, 0, "Hello .NET MF!!!");
    
                lcd.Print(0, 1, "YFSoft 20120920");
    
     
    
                while (true)
    
                {
    
                    System.Threading.Thread.Sleep(500);
    
                }
    
            }
    
        }
    
     
    
        //Width = 16 Height = 2
    
        public class LCD1602
    
        {
    
            GeneralStream gs = null;
    
            public LCD1602()
    
            {
    
                gs = new GeneralStream();
    
                int ret = 0;
    
                if ((ret = gs.Open("UserDriver", "RS=PC08,RW=PC09,E=PB06,D4=PB07,D5=PC00,D6=PC02,D7=PC03")) <= 0)
    
                {
    
                    Debug.Print("ERR=" + ret.ToString());
    
                    gs = null;
    
                }
    
            }
    
     
    
            public void Print(byte x, byte y, string s)
    
            {
    
                if (gs == null) return;
    
                byte[] temp = System.Text.UTF8Encoding.UTF8.GetBytes(s);
    
                byte[] buff = new byte[temp.Length + 1];
    
                Array.Copy(temp, buff, temp.Length);
    
                buff[buff.Length - 1] = 0;
    
                gs.Write(buff, x << 8 | y, temp.Length);
    
            }
    
        }
    
    }

         代码执行后,其运行效果如下图所示:   

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

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

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

  • 相关阅读:
    Linux基本网络设置(IP配置等,网卡驱动缓存,网卡中断)
    posix多线程有感线程高级编程(进程的优先级)
    posix多线程有感线程高级编程(均衡负载CPU绑定)
    posix多线程有感线程高级编程(线程调度以及优先级设置)
    linux多线程和锁
    posix多线程有感—sysconf系统变量
    Linux环境下获取网卡连接状态
    posix多线程有感线程高级编程(线程和fork,exec)
    posix多线程有感线程高级编程(线程堆栈)
    Day64:codeblocks安装 for Mac
  • 原文地址:https://www.cnblogs.com/yefanqiu/p/2730246.html
Copyright © 2011-2022 走看看