天天看點

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

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

2001年.NET MicroFramework的開始研發,其實就是基于比爾蓋茨所謂的.NET戰略,上至伺服器大型系統,下至嵌入式領域的晶片都希望是.NET系統,都可以用C#等.NET開發語言進行開發。是以最開始.NET MicroFramework系統就是開發硬體産品的,MSNDirect産品、SideShow,還有一些高端遙控器,鍵盤,都是采用.NET Micro Framework系統開發(相關介紹,請參見《MSN Direct項目簡介》),雖然用.NET MicroFramework系統開發比較容易,但是要達到同樣的性能,必須要求系統的主頻更快,RAM更大,這對批量生産的硬體産品來說,長遠發展來看,不是一個好選擇。

我個人認為微軟官方的.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#起到粘連串接的作用即可,這一點和網頁開發中的腳本語言的角色非常類似。

目前這類封裝,必須是porting開發人員完成,是.NETMicro Framework TinyCLR的不可分的部分,普通使用者是不能進行C/C++開發的。而我這篇文章所介紹的重點,就是為普通的開發使用者,開啟C/C++ 基于.NET MicroFramework程式設計之門。  在《.NETMicro Framework動态調用C/C++底層代碼》文章中我介紹了這種技術的實作原理,本篇文章就是基于應用的角度,介紹如果用MDK進行.NET MicroFramework使用者驅動開發。

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

【物聯網智能網關-11】流式驅動之使用者驅動(MDK 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); 

}           

硬體平台采用紫藤207(STM32F207 主頻120M),通過示波器檢查PA6管腳。

從示波器的顯示結果來看,二者相差近60倍,是以說在C#層很難實作微秒級别的控制。

另外我也比較了一下C#和C++的for循環的執行效率。

代碼很簡單,就是:

for(x=0;x<1000;x++);           
【物聯網智能網關-11】流式驅動之使用者驅動(MDK C++開發)

C#層提供的Sleep延時也是毫秒級别的,我做了一個簡單的測試,結果如下:

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

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

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

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

在《.NET Micro Framework動态調用C/C++底層代碼》這篇文章中,我介紹g_GeneralStream_Function的時候,其支援的函數才15個,并且主要是GPIO和時鐘類的函數,這次調整以後,已經擴充支援61個了,并且也可以傳遞初始化函數的字元串或整型變量參數了,新的g_GeneralStream_Function定義如下:

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模闆,使用者隻要簡單修改一下即可。

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

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

其實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, 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);

 

  return 0;

}           

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

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

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

        }

    }

}           

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

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

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

MF簡介:

http://blog.csdn.net/yefanqiu/article/details/5711770

MF資料:

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