微軟體系的產品給人的感覺一直是易學易用,但是其執行性能卻屢受詬病。所以一些對性能要求相對較高的硬件產品研發,一般都是采用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