之前大致寫過一篇狀態機比較粗糙的博文,寫了:狀態機的方式比較適合裸機編程,但是不夠深入,這里在深入一點,起因是美敦力medtronic公開了PB560呼吸機完整設計資料,里面用到了ST10F276的芯片,就是用的狀態機的思想,所以狀態機的思想肯定是比較好的,不然大公司不會使用這個框架來做呼吸機啊,這可是醫療產品,質量必須過關的。整個產品都要經過醫療認證的,無論是電氣、軟件、結構等等。
因此中小型裸機項目,使用狀態機來編程應該是毫無壓力的。但是要用好,還是要好好規划一下。
狀態機的使用離不開定時器,通常是使用定時中斷來觸發一個時間來切換狀態機的狀態的,比如按鍵,10ms就去處理一下按鍵處理子程序,理想情況下,10ms基本上能夠輪詢到這個按鍵子程序,但是主程序通常不止一個處理子函數啊,還有其他的程序如下面所示,那請問,10ms到了,主程序能否執行完其他的函數呢,即能否在10ms左右再次進入按鍵子程序?不一定,要看1、單片機的主頻,2、看外部設備的處理速度,3、單片機和外部芯片的通信速度,4、單片機其他函數的數學、邏輯計算量。
1、單片機主頻越高,那么相應的數學計算,邏輯運算都會提升不少。
2、與外部芯片的通信速度,這個稍稍復雜,涉及到硬件的布線,是否允許高速通信,涉及到抗干擾,高頻電路等;還有涉及到通信協議,SPI(MHZ)的速度比IIC(KHZ)快上一個數量級,而且spi還有qspi,dspi等高速通信的方式,這個速度就更加快了,當然單片機的總線速度也要比較高才行,不支持高速,那么只能低速了。
3、外部芯片的處理速度,有的芯片如果處理的很慢,比如ds18b20,開啟轉換到轉換結束,最慢700多ms,那么就干脆把兩個狀態使用狀態機來處理,不用死等,基本上耗費的時間就是單片機對於單總線的數據訪問的時間了。看datesheet,一般都是us級別的,幾ms的時間肯定是通信完成的。但是盡量在2ms以內處理完畢。而有的芯片,處理速度比較快,10ms以內,那么直接等待處理完畢即可,不用再設計一個狀態機了,比如ad1259模數轉換芯片。但是ad7793這個芯片,是∑-Δ行模數轉換芯片,那么轉換時間是120ms,有點長了,可以設計一個狀態機,spi通信時間基本上us級別,可以忽略。
那么再來看看按鍵處理程序能否10ms左右來處理了,分析后,有點懸,那么解決方案有兩種:是把按鍵處理的時間延長一倍:20ms輪詢一次;要么使用rtos的方式來處理,但是ds18b20的時序比較嚴格,有一個時間必須延時幾us之內,那么和其他的延時就有所區別(其他的延時都是至少延時幾us,這種延時適合rtos系統,多延時幾us無關緊要,即便被中斷了幾us也可以接受)。
如果保留10ms的處理間隔時間,那么實際上,按鍵處理間隔時間是長於10ms,比如2ms的時候處理輪詢了按鍵程序,定時器在12ms重新置位了按鍵標志位,但是main函數還沒處理完其他子程序,導致按鍵程序可能在15ms的時候處理了。如果在長一點,23ms的時候再來處理了,那么干脆把按鍵的時間間隔延長到20ms算了。
其實,while(1)的大循環,本身時序性就不夠准確的,沒有rtos的實時性高,想想,很多商業的rtos都是硬實時rtos,實時性當然比裸機要好了。裸機本身在的一定程度上就是軟實時的,對於按鍵來說,10-20ms其實對人來說,差別不大的,真正使用起來不會影響用戶體驗的,多按一段時間和少按一段時間,對於呼吸機這個產品來說都是可以接受的。
這邊文章是吃飯的時候無意中想到,想深入剖析一下,把微觀的部分放大,來看看單片機到底是怎么執行狀態機的,如有寫的不好,還望指點。
int main(int argc, char const *argv[])
{
while(1)
{
ds18b20_discope();
key_discope();
adc_discope();
}
return 0;
}
void ds18b20_discope(void)
{
switch (ds18b20的狀態機的全局變量)
{
case 發送命令:
發送轉換命令
賦值到等待裝態
break;
case 等待裝態:
判斷是否有超時,
如果有超時,則:讀取,計數器清零,並回到發送命令狀態
否則,do nothing
break;
default:
break;
}
}