美容儀器IPL鐳射脫毛儀程式設計
今日再次把幾年前做過的脫毛儀拿出來,重新換了個8051的更便宜的微控制器做了一遍,重複是最沒有意義的,做的更快更好,才有意義,所以我綜合了近年來程式設計方面的心得,特意記下來,形成總結文件,既是拋磚引玉,也是自我提高。 我常常思考,怎麼樣才能提高程式碼複用,像高階語言那樣,基本上呼叫一些介面就可以了,驅動和應用分離,這種思路就可以實現。 開局一張圖,內容全靠吹
在這個功能多樣,雜散的局面下,我綜合採取了面向物件+模組化+狀態機+分層封裝的方法,下面分別總結下。 先說狀態機,最核心的部分就是複雜的升壓和出光了。要求是額定功率下最快的速度升壓,出光之前要穩住電壓400V左右(根據檔位)。此時還有自動模式和手動模式,根據設定和面板觸控情況來出光。 狀態機切換程式原則上只做狀態的切換,不要做功能上的實現。功能方面另外單獨做個函式去實現。 主題的狀態機就是:
1 //系統狀態 2 enum systermstate 3 { 4 SYSERROR, //系統錯誤 5 SYSIDLE, //待機 6 AGINGMODE, //老化模式 7 NOGETVOL, //未滿電,升壓 8 GETVOL, //滿電待出光 9 SHINING, //出光 10 AGINGDONE, //老化模式完成 11 LIFEAPANREDUCE, //出光後壽命減 12 }; 13 //---------------------------------------------------------------------- 14 // Function: static void sysStateDispatch(void)15 // Description: 16 // 狀態機切換 17 //------------------------------------------------------------------- 18 static void sysStateDispatch(void) // 19 { 20 switch (sysState) 21 { 22 case SYSERROR: //系統錯誤SYSERROR 23 { 24 PWM1SetdutyCycle((UINT8)0); 25 } 26 break; 27 case SYSIDLE: //待機 28 { 29 PWM1SetdutyCycle(0); 30 if (keyPowerLongFlag) // 開機鍵 31 { 32 keyPowerLongFlag = 0; 33 sysState = NOGETVOL; 34 PWM1ChargeInit(); 35 timeOuts = 0; 36 timeOutsConts = 0; 37 pwm1DutyCycle = 13; 38 } 39 } 40 break; 41 case NOGETVOL: //升壓 42 { 43 timeOuts++; 44 if (timeOuts > 30) // 45 { 46 timeOuts = 0; 47 timeOutsConts++; 48 adcSelectADCCh(POWER_I_CHA); 49 if(gadcValue < 80) 50 { 51 pwm1DutyCycle++; 52 } 53 } 54 PWM1SetdutyCycle(pwm1DutyCycle); adcSelectADCCh(HIGHVOL); 55 if ((timeOutsConts > 80) ||(gadcValue > ADCMAX[gearAdjustment])) 時間和電壓綜合判斷 56 { 57 timeOuts = 0; 58 timeOutsConts = 0; 59 pwm1DutyCycle = 0; 60 //PWM1dutyCycle((UINT8)0); //測試 61 sysState = GETVOL; //電壓升滿,去發射 62 } 63 } 64 break;
模組化:
雖然狀態機囊裹了幾乎程式所有的狀態,理論上所有的功能都可以在狀態機哈函式實現,但是我還是強烈建議分開實現,目的是為了程式可讀性,方便維護。例如一個簡單的燈光只是,我本來是在狀態機函式去做的,後來我還是獨立出來了,其中sysState 是狀態機變數:
1 //----------------------------------------------------------------------------- 2 // Function: static void ledtask(void) 3 // Description: 4 // 指示燈任務,在每個任務下的表現。 在100Ms輪詢事件裡呼叫 5 //----------------------------------------------------------------------------- 6 static void ledtask(void) 7 { 8 if (sysState == SYSERROR || sysState == AGINGDONE || (sysState == GETVOL && !touchIn)) //異常或者測試完成後 9 { 10 timeOuts++; 11 if (timeOuts > ((sysState == SYSERROR) ? 1 : 6)) 12 { 13 timeOuts = 0; 14 greenled = !greenled; //指示燈閃爍 15 } 16 } 17 if (sysState == SYSIDLE || sysState == SYSIDLEPRE || sysState == NOGETVOL) //異常或者測試完成後 18 { 19 greenled = 0; 20 } 21 if ((sysState == GETVOL && touchIn) || sysState == SHINING) // 滿電和出光 22 { 23 greenled = 1; 24 } 25 }
如此一來,就把指示燈獨立出來了,客戶更改了需求,可以快速更改!否則會有啥情況,是不是你得全域性搜尋這個led,然後滿大街的去修改? 其他的比如風扇控制,放電控制,等等都獨立出來,依據狀態去改變。
分層封裝:
以斷碼屏為例,斷碼屏驅動晶片是HT1621B,最終是需要把相關資料傳送在HT1621。那麼此時,驅動層和應用層應該獨立,只保留一個介面來呼叫即可。 介面型別宣告:
1 typedef struct { 2 void (*Init)(void); //初始化 3 void (*sendCmd)(unsigned char); //發命令 4 void (*writeData)(unsigned char addr, unsigned char xdata *buf, unsigned char cnt); //更新顯示資料 5 }HT1621funs_t; 6 HT1621funs_t HT1621funs;介面型別定義:
在底層驅動檔案HT1621Dri.c裡面定義好每一個驅動函式:
1 static void HT1621B_SendCmd(unsigned char command) 2 static void HT1621B_SendBit(unsigned char ddata, unsigned char cnt) 3 。。。
在應用層檔案diaplcd.c中,只需要呼叫: HT1621funs.writeData(0, bufferDisp.Icon_buf, 8); //重新整理資料 即可。
面向物件:
還是以HT1621為例,如果下次換成了HT1902(假設),可能驅動有所不同,此時再例項化一個: HT1621funs_t HT1902funs; 例項化的時候,使用1902的驅動去例項化即可,介面HT1621funs_t型別還是不變。
1 //----------------------------------------------------------------------------------------- 2 //函式名稱:void HT1902funsInit(void) 3 //功能描述: HT1902例項化介面成員函式 4 //說 明:初始化後,其他檔案只需要呼叫HT1902funs結構體成員函式即可 5 //----------------------------------------------------------------------------------------- 6 void HT1902funsInit(void) 7 { 8 HT1902funs.Init = HT1902_Init; //初始化 9 HT1902funs.sendCmd = HT1902_SendCmd;//發命令 10 HT1902funs.writeData = HT1902_WriteData;//更新顯示資料 11 }
以上,基本上說完了使用到的方法,思想,細節方面歡迎討論。接下來我嘗試移植RTThread到此專案上,目的是在狀態複雜的專案裡面,多使用分時系統,以應對更復雜的需求。 歡迎交流,交流技術和專案合作均可。等你來撩我哦!加我請說明來意,謝謝。