好久沒來電源網發帖,學習電子技術的過程中積累了一點心得體會,總想直抒胸臆,可身邊沒幾個同行,只好跑到電源網寫點酸文,貽笑與大方之家。
既然是技術心得類的帖子,肯定存在理解不透徹的地方,肯定有自己沒有關注到的知識點,還望大家海涵,及時斧正,謝謝。
先在此挖個坑,慢慢填吧,可能會虎頭蛇尾,但肯定會不定期更新。
好久沒來電源網發帖,學習電子技術的過程中積累了一點心得體會,總想直抒胸臆,可身邊沒幾個同行,只好跑到電源網寫點酸文,貽笑與大方之家。
既然是技術心得類的帖子,肯定存在理解不透徹的地方,肯定有自己沒有關注到的知識點,還望大家海涵,及時斧正,謝謝。
先在此挖個坑,慢慢填吧,可能會虎頭蛇尾,但肯定會不定期更新。
說心里話,很早就想寫一點有關C語言的學習文章,但是怎么寫,才能讓大家相對愉快地學習,我也想了很久。為此我也翻了很多程序語言書籍,自認為積累了一點學習經驗,分享給大家,下面轉入正題。
講解程序語言,教科書大多從瑣碎的語法結構開始,這樣的講解方式直接把多數的學生拒之門外。回想我當年學習C語言的經歷,只能說苦不堪言。起初C語言的書籍是我的枕邊讀物,一般晚上入睡前翻一兩頁,相當有助于睡眠。
我們能不能站得更高一點,嘗試著采用先整體后局部的方法講講C語言呢?換句話說,我們能否先將程序語言的框架搭起來,然后再根據功能需求用各種語法一步一步地完善框架,最終使其成為完整的程序。
通俗點說,就是先建立骨架,再摸索著往骨架上填充血肉,接著嘗試關聯各個功能部件,最后大功告成。當然過程肯定不會一帆風順,必然存在諸多的問題,通過不斷地調試,最終問題都會解決,功能也會實現。
其實程序的運行不就和眼前的滾滾長江水一樣嗎?流動的長江水必然有其源頭以及盡頭。長江水在流經不同地貌時,會產生很多的支流,不同的支流又會分化出諸多小支流。支流中有些繞過一段河道后又重新匯入長江,有些則成了一潭死水。
如果從江水的源頭與盡頭看,我們可以粗略地認為長江是以直線的運動軌跡川流不息。看看長江的各個支流,我們可以說這是長江的流動分支。再瞧瞧江水繞過的各個灣灣碼頭,這不是在循環往復嗎?
再看看程序的運行,不也是一樣嗎。整體上看,程序從上往下依次執行,再執行過程中根據不同條件分化出諸多的節點,有些節點執行一次即可,有些節點需要執行多次,有些節點則直接跳過。
至此,程序的框架終于清晰了,再結合書本專業知識,我們總算弄通了程序是怎么運行的。
程序是由順序結構,分支結構以及循環結構組成的。如果對匯編語言有所了解或者熟悉C語言goto關鍵字的用法,我們甚至可以說程序語言只有一種結構,那就是跳轉結構。再深入一點,若理解了指針的概念,同時懂得一點計算機的組成原理,我們滿可以說,程序的本質就是通過合理地規劃內存,最大限度發揮計算機內部各功能模塊的性能。
上述放棄學習單片機的工程師離單片機入門僅僅差一小步,那就是思考,準確地說是帶著問題去思考。
學習單片機的心態很重要,我是一個學習單片機失敗了很多次的LOSER(我對電子技術沒有多少興趣,堅持至今是因為沒有更好的謀生手段罷了,我比常人多了一點耐心,恒心),在此總結歸納一下我認為正確的或者說是恰當的學習單片機的方法及心態。
第一點是忘,忘掉過往已經掌握的開關電源的系統知識(電源網的大多數受眾是電源工程師),對于射頻工程師,PLC工程師,上位機軟件工程類似。為什么有此一說呢,這是我嘗試著學習單片機失敗了多次后悟出來的。
我們一旦有門技術傍身后,總會習慣性地靠這門擅長的手藝謀生,慢慢就形成了依賴,專業術語稱作思維舒適區。我們思想中總會有一個誤區,認為過往掌握的技能有助于我們學習后面的新知識。不自覺的我們就把以前的思維模式帶入到新的知識領域中。這就是最大的問題。
單片機與開關電源,變頻器,射頻電子,PLC,上位機軟件編程等都是相對獨立的技能,不要指望學會了一門手藝后,就能觸類旁通其他的相關技能。這是我們開始學習單片機必須要有的思想覺悟。
不信,你可以問問身邊精通單片機,或者PLC,乃至JAVA編程的同事懂不懂開關電源的設計,大概率是不懂的。會抄個反激電源的單片機工程師不算,他那點淺薄的對開關電源的認識不夠看。
能夠將開關電源和單片機區分開來學習,你已經成功了一大步。單片機和開關電源是兩個不同的領域,雖然熟悉開關電源有助于我們理解單片機中涉及的硬件知識,但這遠遠不夠。
學習單片機需要具有的第二個心態是不急,有耐心。市面上有一些教材號稱一個禮拜掌握單片機,這樣的教程針對的受眾通常是有一定基礎的,且資質相對較好的朋友。如果沒有一定的知識儲備,個人建議還是按部就班的學習。
知識是逐步緩慢積累的,快速上手的教程最大的副作用可能是營造了一種焦慮的環境,讓很多初學者再學過好幾遍仍找不著頭緒時,開始了自我否定,最終放棄。在此我送給那些一直徘徊在單片機入門階段的朋友一句話:“堅持就是在快要放棄時再忍耐忍耐。”
我個人定義單片機技術為一門謀生的手藝,甚至可以說是安身立命的手段,那肯定不太容易掌握,否則滿大街的人都會玩,何談養家糊口一說。
周圍也有很多電子工程師精通單片機技術,那說明這門技術的學習掌握也沒有難于上青天。只要努力,理順思路,儲備了足夠的知識,找到適合自己的學習方法,肯定可以精通單片機技術。
學習單片機技術需要具備的第三個心態是恒心,或者說是韌性。想學好單片機必然會遇到各種各樣稀奇古怪的問題,這時心態一定要足夠堅韌,不能但凡遇到一點困難,一時半會解決不了就想著放棄。
在某個問題上卡住了,如果以當前的知識儲備確實解決不了,那就放一放,繼續往前走,千萬不能自設囚籠,鉆牛角尖把自己困在那里,這一點很重要。我自己屬于典型的一根筋,遇到問題總想著立馬解決,但身邊又缺少能夠及時給自己答疑解惑的朋友。在苦思冥想過一番仍不可得時,我就失去了繼續往前探索學習的興趣。等下一次再提起興趣時,又得從頭開始,周而復始,永遠困在單片機入門學習的道路上。
在學習的過程中明知會遇到各種問題而依舊選擇努力前行不失為勇者的表現,在遇到超出自己能力范圍的問題而選擇適時地放一放,可謂是智者的選擇。
C語言中的內存被分割為四個區域,分別對應著代碼區(code),數據區(data),棧區(stack),堆區(heap)。不同區域的功能特性也不盡相同。首先講講代碼區(code).
單片機上電復位后,編譯生成的HEX代碼經由機器內部固化的微指***令加載進內存,實質上代碼被引導至內存中的代碼區,即code區。該段內存的特性之一是不可以修改,專業術語為只讀。其次該區域內容是可共享的(即某段可執行代碼能夠訪問它)。
C語言代碼區可共享的目的是針對頻繁調用的只讀數據,使用此法可有效提高程序的訪問效率。代碼區設置為只讀的意義在于防止程序運行過程中的意外而修改了某些指***令或數據,導致不可預料的錯誤發生。
51單片機代碼區如何配置可共享數據呢?使用51單片機特有的C語言關鍵字code,即可實現。C51中該類變量常用于存放一些復雜函數的固定值數據,如各類真值表,三角函數值,反三角函數值,對數值,指數值等。
為什么要這樣做呢?很多的8位機,16位機內部并沒有浮點運算器,遇到復雜一些的運算基本都不能勝任,而用戶僅僅關注最后的結果。我們在程序中預先給出運算結果,當涉及某一功能函數計算時,直接將代碼區中對應的運算結果調用顯示即可。此種方法既高效,又節省了大量機器資源。這種思想在單片機技術中被稱作查表執行。
51單片機中code關鍵字修飾的數據,其特性為只讀。從單片機內部資源的角度看,此類數據存儲至程序空間ROM中(C語言中則為代碼區)。這樣可以大大節省單片機的RAM使用量,畢竟我們的單片機RAM空間比較小,而程序空間相對大的多。
數據區又可以細分為data區,bss區及常量區。其中data區里主要存放的是已經初始化的全局變量、靜態變量。bss區主要存放未初始化的全局變量、靜態變量。未初始化的數據在程序執行前會被編譯器自動初始化為0或NULL。顧名思義常量區存放的是一些常量,如const關鍵字修飾的全局變量以及字符串常量等。
數據區中的數據相較于代碼區中的數據,其不僅可讀而且可寫。可讀表明該區域里的數據可被函數調用,可寫表明該區域中的數據能夠被改變。該區域數據的特點是只要內存不掉電,數據就能一直存活。單片機中的各功能寄存器也有此類似特性。只要配置好寄存器相關數據后,寄存器中的數據就能一直存在,除非掉電。有很多場合的應用需要保證寄存器中的數據不丟失,為防止外界電源斷電,單片機系統會接入備用電池。如實時時鐘,日歷,低功耗模式等。
不同類型的變量在內存中的存活時間各不相同,這一特性專業術語稱作變量的生命周期。其次可讀數據變量在內存中能被哪些函數調用涉及到變量的調用范圍,專業術語叫變量的作用域。
類人猿之所以能夠進化為人類,我的理解是它們具備不斷試錯不斷學習的能力。在進化的道路中每一次試錯都伴隨著一次成長過程。在緩慢的成長過程中,其心志得到不斷的進化。當積累到一定程度,量變引起質變,類人猿終于成長為今天的你我。將人類的進化過程映射到計算機技術上,雖有些扯,但多少有些相通性。
某種意義上說,循環結構是特殊的選擇結構。程序語言根據條件判斷依次往下執行時,我們說這是典型的選擇結構。當程序語言根據條件判斷執行若干動作后,又回至條件判斷的起點,重新進入條件判斷的流程,依此往復執行…… 我們可以說這就是循環結構。
從上述角度解讀,我們可以認為循環結構是特殊的選擇結構。當然,如果循環結構的判斷執行條件僅只執行一次,我們同樣可以說選擇結構是特殊的循環結構。換句話說,選擇結構和循環結構之間可以相互轉換。
不管是代碼區還是數據區中的數據自始至終都駐扎在內存中,而內存規劃的初衷是為了高效利用內存,及時回收釋放多余的內存,使有限的內存有足夠的空間支撐程序的全過程運行,顯然僅僅靠代碼區及數據區遠遠不夠,遂引入堆和棧的概念。
堆區(heap),該區域可看作是一個存儲數據的大容器,其容量遠遠大于后面要講解的棧區,該區域中的數據不需要像棧區那樣遵循先進后出的規則。
堆區主要用于動態內存分配。堆區在內存中位于數據區中的BSS區和棧區之間。該區域一般由程序員手動分配和釋放,若程序員不釋放,程序結束時由操作系統回收(該話題涉及到操作系統內存回收的話題,此處一筆帶過,有機會分析嵌入式linux系統時講解)。
C語言中堆區的大小可使用malloc函數進行分配,堆區的釋放采用free函數實現。程序中malloc等內存分配函數的使用次數一定要和free函數相等,并一一配對使用。否則隨著程序的運行會出現內存泄漏的問題。
棧區(stack),用于存儲所有的局部變量,包括用戶定義的auto型局部變量,函數的實參,返回值,嵌套函數的調用(遞歸函數)等。棧區和其他三個區域最大的不同點在于其采用的是先進后出的內存結構。局部變量在該區域由編譯器在程序運行過程中實時加載和釋放。所以局部變量的生存周期很短,需要時即申請,不用即銷毀釋放。
auto型局部變量經編譯器自動放入棧中后,其生存周期及作用域只限于定義的功能函數內有效,當一個auto型局部變量超出其作用域時,自動從棧中彈出,用完后再由操作系統自動釋放。
auto型局部變量的生存周期及作用域的特點牽出一個很有意思的話題,即函數實參的調用是否影響對應變量的數值,答案是不影響(后續C語言語法章節會詳細講解)。
另外一個注意事項是不要返回局部變量的地址,因為棧區開辟的地址空間由編譯器自動釋放,當auto型局部變量失效后,原先對應的地址空間也隨之釋放,返回的局部變量地址是一個隨機數,而不是之前對應的局部變量的地址。
上述堆區和棧區的概念是針對C語言而言,對于單片機我們也經常提到堆棧的概念,但C語言的堆區棧區和單片機的堆棧還是有區別的。單片機的堆棧實質是C語言中棧的描述。通常單片機的內存資源相當有限,所以一般不分配堆區,而且很少用到標準庫的malloc類函數,當然上升到操作系統層面就是另一說了。
單片機的堆棧也屬于內存的一部分,所以它肯定有臨時存儲的功能,那單片機的堆棧主要用于哪些存儲場合呢?如果說代碼區和數據區中的數據是用于單片機自始至終的全程數據存儲,那堆棧就是用于單片機運行過程中的中間數據處理。
通俗的說,代碼區和數據區中存儲的數據只要單片機不掉電,其中的數據就不會被釋放。而堆棧中的數據是根據程序需要實時產生,實時釋放,我們通常提到的內存回收與釋放實質是針對堆棧中數據的回收釋放操作。