【物聯網智能網關-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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM