天天看點

【物聯網智能網關-11】流式驅動之使用者驅動(MDK C++開發)

    微軟體系的産品給人的感覺一直是易學易用,但是其執行性能卻屢受诟病。是以一些對性能要求相對較高的硬體産品研發,一般都是采用linux體系的技術,或者是無作業系統開發,其開發語言也絕大數是C/C++(啟動代碼或中斷部分的代碼有時會用彙編代碼實作)。但是對工控內建類的項目開發來說,由于項目開發周期比較短,對穩定性要求比較高,如果全部采用C/C++開發,不僅對開發人員的能力要求比較高,并且開發和調試的代價非常大。是以PC平台的,大都是用組态系統搭建,嵌入式系統則是采用嵌入式組态軟體,其定制化的軟體則采用WinCE等易用的嵌入式系統來開發了,但是對再小型的嵌入式系統,由于選擇目前比較少,也隻有選用傳統的C/C++來開發了。

    我個人認為微軟官方的.NET Micro Framework産品類開發的定位是錯誤的,這一點我可以看到微軟在Windows領域的開發也是放棄了全用.NET托管代碼實作的訴求,大部分底層或對性能要求很高的代碼,依然采用原生C/C++實作(目前iOS和安卓系統的開發,基于性能的考慮,很多開發人員都開始用原生C/C++進行開發)。

    用.NET Micro Framework開發使用者需求變化少,不需要二次開發接口的産品來說,是非常不适合的,特别是銷售量數量非常大的産品,因為随着産品的銷量不斷增加,前期開發成本所占的成本比重将越來越小。但是對使用者需求變化大,使用者需要有二次開發,或者是銷量比較少的産品來說,用.NET MicroFramework優勢就比較明顯了。特别是工控內建類的産品,.NET Micro Framework系統有天然的優勢(這是我7年工控領域的工作經曆深切感受到的)。

    PC領域的組态化技術已經非常成熟了,目前已經在向組态軟體的第二代或第三代進行發展。但是在嵌入式領域,特别是低端MCU方面,這方面做得遠遠不夠,我工作的定位就是緻力于嵌入式領域組态化,并且我認為.NET Micro Framework系統是實作這個願景的最好的一種技術支撐。

    .NET Micro Framework的平台的C#(或VB.NET)開發雖然開發比較簡單,但是其執行性能卻是一個必須面對的問題(2009年我在微軟總部和MSNDirect開發人員交流的時候,他們對.NETMicro Framework的執行性能頗有微詞)。

    我的解決方案就是:.NET Micro Framework必須盡可能的封裝,C#語言執行的不是大段功能代碼,而隻是一些工藝流程代碼即可,那些功能性的代碼盡可能用C/C++實作。C#起到粘連串接的作用即可,這一點和網頁開發中的腳本語言的角色非常類似。

    在進行使用者流式驅動開發介紹之前,我先比較一下C#和C++開發的性能,讓大家有一個直覺的感受。

    上圖C#代碼如下:

    OutputPort io = newOutputPort((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); 

}

    從示波器的顯示結果來看,二者相差近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參數

1

執行時間

176-184 us

1.3ms

10.4ms

    注:由于底層時鐘中斷不斷觸發,Sleep的時間是不确定的。

    相信以上的測試結果,對大家的印象是深刻的。是以說,不考慮C#的封裝優點,而是非要用C#和C++實作同樣的功能,隻能是讓大家越來越遠離.NET Micro Framework。

-------- 分割線 ---------

IGeneralStream_Functiong_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;

intGeneralStream_Open1_UserDriver(LPCSTR config) { return 0;}      //Open1永遠也不會被調用

//intGeneralStream_Open2_UserDriver(int config)  { return 0;}

intGeneralStream_Close_UserDriver()  {return 0;}

intGeneralStream_IOControl1_UserDriver(int code, BYTE *inBuffer, int inCount, BYTE*outBuffer, int outCount){return -1;}

intGeneralStream_IOControl2_UserDriver(int code, int parameter){return -1;} 

intGeneralStream_Read_UserDriver(BYTE *buffer, int offset, int count){return -1;}

intGeneralStream_Write_UserDriver(BYTE *buffer, int offset, int count){return -1;}

intGeneralStream_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 IGeneralStreamg_GeneralStream_UserDriver;

const IGeneralStreamg_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)。

主要代碼如下:

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

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

   LCD1602_Write_Command(0x28);

         LCD1602_Write_Command(0x0C);//顯示開

         LCD1602_Write_Command(0x01);//清屏

以上就是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, intcount)

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

    以上代碼在MDK中直接編譯,編譯後的bin檔案,經過轉換适當轉換,變為MF部署工具所支援的Hex檔案,用MFDeploy或YFAccessFlash工具直接部署即可,如下圖所示:

    基于C++的代碼我們已經完成,下一步我們開始寫C#代碼,以便調用我們寫好的C++代碼。

    代碼如下:

using System;

usingMicrosoft.SPOT;

usingMicrosoft.SPOT.Hardware;

usingYFSoft.IO;

namespaceUserDriverTest

    public class Program

    {   

        public static void Main()

        {

           Debug.Print("UserDriverTest ...");

           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 = newbyte[temp.Length + 1];

           Array.Copy(temp, buff, temp.Length);

           buff[buff.Length - 1] = 0;

           gs.Write(buff, x << 8 | y, temp.Length);

    代碼執行後,其運作效果如下圖所示:

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

<a href="http://weibo.com/1804832611?s=6uyXnP"></a>

繼續閱讀