這個(gè)
步進(jìn)電機說(shuō)起來(lái)挺簡(jiǎn)單的,就是用戶(hù)輸入指令,讓兩個(gè)電機上下運動(dòng)或者停止。并且運動(dòng)范圍通過(guò)紅外對管限制,并且有
步進(jìn)電機的硬件驅動(dòng),我只要控制 IO 并且輸出 PWM 脈沖就行。驅動(dòng)每收到一個(gè)脈沖走動(dòng)一步。
這是公司的第一個(gè)項目,因為使用對象是人,所以必須對一些輸入進(jìn)行參數檢查。實(shí)際上該項目完成的時(shí)候,雖然有一些檢查,但是卻沒(méi)有全部檢查,所以還是會(huì )有可能出現 bug 現象。本來(lái)在第三次更新該驅動(dòng)程序的時(shí)候,也準備將其改寫(xiě)的,但是我發(fā)現目前的水平實(shí)在有限,即使再改寫(xiě),對于參數檢查還是沒(méi)有更好的方法去優(yōu)化算法。所以在所有函數都重寫(xiě)的情況下唯有這部分代碼保留下來(lái)了。
這個(gè)程序一共進(jìn)行了三次更新,第一次花了一個(gè)星期將基本功能實(shí)現了。
當時(shí)采用計時(shí)的方式進(jìn)行運行距離的控制。1 ms 中斷,每次變量自加 1,這樣通過(guò)時(shí)間乘以速度的方式就可以得到電機的運行距離了,而整個(gè)指令組的運行控制在主函數執行。
想法很正確,當時(shí)確實(shí)也運行起來(lái)了,但是后來(lái)測試人員告訴我,當設定運行時(shí)間很短暫的時(shí)候,電機不運行,想想也是,時(shí)間太短,并且指令運行的控制是在主函數的,必然有比較大的誤差。
雖然第一次寫(xiě)這個(gè)程序的時(shí)候也想過(guò)通過(guò)對脈沖計數的方式來(lái)實(shí)現對電機的控制,這樣只要知道我輸出了多少個(gè)脈沖,就能確定運行了多大的距離。但是當時(shí)卻不知道該如何獲取脈沖數。因為之前只知道輸出 PWM 波,根本沒(méi)想過(guò)如何獲取 PWM 的數量。
能想到的也就是改硬件了,就是再用一個(gè)定時(shí)器作為脈沖計數器,這樣兩個(gè)電機就需要兩個(gè)定時(shí)器,包括輸出 PWM 的一個(gè)定時(shí)器,共用三個(gè)……所以這個(gè)項目當時(shí)對于我來(lái)說(shuō)有心無(wú)力了,只能將就,只能等以后了。
而且這個(gè)程序還有兩個(gè)缺陷,就是兩個(gè)電機速度不能單獨控制,因為電機速度是通過(guò)單位時(shí)間內的脈沖數決定的,而輸出 PWM 的兩個(gè)引腳使用了同一個(gè)定時(shí)器。
而控制 PWM 周期的方式有兩種,一種是修改定時(shí)器的基本時(shí)鐘,另一個(gè)就是修改計數周期,但這兩種方法實(shí)際上在一個(gè)定時(shí)器上都只有一個(gè)寄存器。
另一個(gè)問(wèn)題就是當時(shí)需要暫停功能,我沒(méi)辦法實(shí)現,因為我根本不知道程序運行到哪里了,又如何從原來(lái)的地方開(kāi)始運行呢?
后來(lái)在看一個(gè)有五年工作經(jīng)驗的高手寫(xiě)的代碼發(fā)現,我其實(shí)可以開(kāi)啟定時(shí)器的更新中斷的,每輸出一個(gè)脈沖,必然是要進(jìn)入更新中斷的,只要在更新中斷完成計數即可。
還有高級定時(shí)器有一個(gè)寄存器是可以控制輸出脈沖數的,這個(gè)另說(shuō)。
因為這個(gè)問(wèn)題一直在腦海,所以在看一篇文章的時(shí)候,雖然它不是說(shuō)如何獲取脈沖數,但是也給了我一個(gè)靈感,那就是可以同步開(kāi)啟一個(gè)定時(shí)器的,知道輸出 PWM 頻率,只要在輸出 PWM 的同時(shí)開(kāi)始另一個(gè)定時(shí)器,就能在控制時(shí)間的情況下精確的控制輸出 PWM 數,雖然和之前的定時(shí) 1ms 計數類(lèi)似,但精度更高,畢竟對于人來(lái)說(shuō),1ms 的誤差精度雖然高,但是對于 1ms 輸出幾千個(gè)脈沖來(lái)說(shuō),誤差不是一般的大。
這樣一來(lái),馬上就開(kāi)始了程序的更新,所以花了大概三天的時(shí)間完成了程序的修改,并且因為已經(jīng)能對輸出的脈沖數進(jìn)行計數,對于之前要求的在到達紅外對管的下限之后再運行電機的功能也就能實(shí)現了。
但是實(shí)際運行之后,設置向下向上的脈沖數相同的情況下,它是不能回到原點(diǎn)的位置。因為這個(gè)程序是在第一個(gè)程序上寫(xiě)的,第一次寫(xiě)的時(shí)候需求不明,考慮的不夠完善,所以整個(gè)程序的結構比較混亂,對電機運行方向的判斷上可能會(huì )有誤差,所以在更新完成之后,就已經(jīng)打算有機會(huì )再更新一次,這一次更新將推翻之前的程序結構,要考慮的更完善才行,這是自我提高。
所以從家里回來(lái)之后,我就開(kāi)始考慮更新這個(gè)程序了,只是沒(méi)想到花了這么久時(shí)間才完成。我也沒(méi)想過(guò)在公司去完善這個(gè)程序,公司有公司的事,而且這個(gè)程序問(wèn)題的主因還是自己能力不夠,而且我需要更輕松的狀態(tài)去慢慢考慮清楚,并嘗試使用新方法去重新設計程序的結構。
有了前兩個(gè)程序的基礎,所以知道程序設計的時(shí)候該考慮哪些問(wèn)題。
變量設計考慮:首先就是反映電機狀態(tài)的變量必須保證緊隨實(shí)際的狀態(tài),并且改變這個(gè)狀態(tài)的位置只能是一個(gè)地方,而不能這個(gè)函數改一下,那個(gè)程序改一下,這樣肯定會(huì )導致整個(gè)程序的混亂。
還有就是變量的作用要明確且單一,不要一個(gè)變量給它附加多種用途,這樣也可能會(huì )程序混亂。
除此之外,還要考慮這個(gè)變量是否必須一直存在,還是說(shuō)多個(gè)變量其實(shí)歸納為一個(gè)變量。
一個(gè)例子就是,輸入用戶(hù)指令后,有多個(gè)參數變量,并且要指示這條指令是否完成,這條指令的當前輸出脈沖數等等。
其實(shí)對于一個(gè)執行器來(lái)說(shuō),只要將最基本的用戶(hù)指令保存即可,根本不需要附加其他的,即使有附加,也是在執行的過(guò)程存在,也就是說(shuō)這個(gè)命令組的其它附加信息其實(shí)只要共用一個(gè)變量即可,因為當前運行的只能是一條指令,只要在運行下一條指令時(shí)將上一條指令信息清除(可以清除一個(gè)標志,作為整條指令信息清除的標志,比如將下行指令標志改為無(wú)指令狀態(tài)即可)并重新初始化就可以繼續為下一條指令服務(wù)了。
時(shí)間同步:就是每次開(kāi)始處理數據的時(shí)候都統一在定時(shí)器更新中斷執行(前提是這個(gè)數據處理必須能在中斷時(shí)間內處理完成),這樣每處理一次,都是在定時(shí)器重新計數的情況下,而不會(huì )出現這里剛處理完數據,那里就中斷數據處理產(chǎn)生一個(gè)脈沖的情況,這樣必然可能出現脈沖計數不準的情況。
而且在進(jìn)行數據處理的時(shí)候,對關(guān)鍵數據的處理可以采用關(guān)閉全局中斷和停止定時(shí)器的方式來(lái)確保數據處理的完整性。
啟動(dòng)操作、結束操作:就是一個(gè)完整動(dòng)作的執行其實(shí)只有啟動(dòng)和停止而已,在將所有數據處理完之后就可以進(jìn)行啟動(dòng)操作,在該動(dòng)作完成之后只要處理身后事即可,這個(gè)身后事包括停止上一個(gè)動(dòng)作的所有影響,以防出現下一個(gè)動(dòng)作還未啟動(dòng)而上一個(gè)動(dòng)作繼續執行的情況。
當然還有一種情況就是在啟動(dòng)該動(dòng)作之后,有可能出現意外情況,而需要將該動(dòng)作停止,所以需要定時(shí)監視。但是定時(shí)監視有一個(gè)缺陷,就是你不能及時(shí)查看異常。所以如果硬件允許的話(huà),可以采用硬件中斷來(lái)監視。
走一步看一步:在硬件條件不允許的情況下可以采用走一步看一步的方法。就是每輸出一個(gè)脈沖(之后停止定時(shí)器),然后看有沒(méi)有到限制位置,如果沒(méi)到,就繼續走,如果到了,就停止該動(dòng)作。這樣看起來(lái)效率挺低的,可實(shí)際上,單片機的速度很快,對于我們人來(lái)說(shuō),我們根本感覺(jué)不到它是邊走邊看的,你看到的只有一個(gè)完整動(dòng)作執行的現象。
暫停動(dòng)作:之前提到如何暫停動(dòng)作,并且重新執行問(wèn)題,之前想的是從程序本身考慮(離開(kāi)代碼執行,然后重新返回該代碼位置重新執行,有點(diǎn)像操作系統切換任務(wù)的情況)。
其實(shí)我只要停止這個(gè)動(dòng)作的執行并且不破壞這個(gè)動(dòng)作的相關(guān)的變量即可,我根本不需要知道它執行到了哪個(gè) PC 地址。比如電機的執行靠 PWM 輸出,相關(guān)變量的變化是在中斷執行的,我只要停止了定時(shí)器,就一了百了了。既停止了 PWM 輸出(停止動(dòng)作),又不會(huì )讓它繼續執行相關(guān)變量的操作,這樣一旦重新啟動(dòng)定時(shí)器,變量繼續變化,PWM 繼續輸出,變量條件達到即可像沒(méi)事一樣繼續執行停止操作并完成身后事。
所有動(dòng)作暫停一下:這里所謂的動(dòng)作暫停不是外部現象的暫停,而是內部的。
并且這個(gè)動(dòng)作應該是內部控制情況下才可以暫停,否則如果是外部控制的情況下暫停內部運行,必然會(huì )因為響應不及時(shí)丟失重要東西。這里因為電機運行動(dòng)作全靠自身輸出 PWM 來(lái)控制的,所以暫停幾十毫秒在外界看來(lái)沒(méi)什么影響,也看不出來(lái)。
那為什么要暫停呢?比如你要在中斷串口輸出一個(gè)信息,如果采用查詢(xún)的方式輸出的話(huà),必然需要十幾毫秒才能輸出完成,而這個(gè)時(shí)間遠大于你的中斷時(shí)間,所以必然會(huì )錯過(guò)下一次中斷的執行,導致外部已經(jīng)輸出了一個(gè) PWM,而相關(guān)變量計數器沒(méi)有計數的情況。所以這個(gè)時(shí)候就可以通過(guò)停止定時(shí)器的方式來(lái)停止動(dòng)作的執行,這樣定時(shí)器的寄存器計數器就不會(huì )繼續累加了,也不會(huì )產(chǎn)生脈沖。當輸出完之后就可以繼續開(kāi)啟定時(shí)器。
那為什么不采用關(guān)閉全局中斷的方式呢?你要知道的是,你關(guān)閉全局中斷只是屏蔽了中斷,而不能阻止中斷的發(fā)生,只是說(shuō),中斷發(fā)生后,因為你的屏蔽,導致該中斷處理程序的延后執行而已。
之前說(shuō)過(guò)兩個(gè)電機不能獨立設置速度的問(wèn)題,主要就是因為兩個(gè)引腳共用同一個(gè)定時(shí)器,后來(lái)在偶然情況下想到其實(shí)引腳有一個(gè)復用功能的,有可能一個(gè)引腳是其他定時(shí)器的復用輸出引腳呢?或許是對于問(wèn)題的執著(zhù),我幸運的發(fā)現真的有一個(gè)引腳是高級定時(shí)器的一個(gè)復用 PWM 輸出引腳,這樣我就不用采用模擬的方式輸出 PWM 波了。
到此,當初所有的功能需求完成了。越努力越幸運或許真是如此!