微軟體系的産品給人的感覺一直是易學易用,但是其執行性能卻屢受诟病。是以一些對性能要求相對較高的硬體産品研發,一般都是采用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++開發的性能,讓大家有一個直覺的感受。

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++);
C#層提供的Sleep延時也是毫秒級别的,我做了一個簡單的測試,結果如下:
注:由于底層時鐘中斷不斷觸發,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模闆,使用者隻要簡單修改一下即可。
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工具直接部署即可,如下圖所示:
基于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);
}
}
}
代碼執行後,其運作效果如下圖所示:
-------------------------------------------------------------------------------------------------
MF簡介:
http://blog.csdn.net/yefanqiu/article/details/5711770MF資料:
http://www.sky-walker.com.cn/News.asp?Id=25