1. 程式人生 > >《9.定時器、看門狗和RTC》

《9.定時器、看門狗和RTC》

《9.定時器、看門狗和RTC》

第一部分、章節目錄
1.9.1.什麼是定時器
1.9.2.S5PV210中的定時器
1.9.3.S5PV210的PWM定時器1
1.9.4.S5PV210的PWM定時器2
1.9.5.S5PV210的PWM定時器3
1.9.6.蜂鳴器和PWM定時器程式設計實踐1
1.9.7.蜂鳴器和PWM定時器程式設計實踐2
1.9.8.看門狗定時器
1.9.9.看門狗定時器的程式設計實踐
1.9.10.實時時鐘RTC
1.9.11.RTC程式設計實戰1
1.9.12.RTC程式設計實戰2

第二部分、章節介紹
1.9.1.什麼是定時器
本節講述定時器、計數器等的概念和基本工作原理,希望通過講解讓大家明白定時器到底是什麼,為什麼需要定時器。最後講解了定時器和看門狗、RTC、蜂鳴器等的關係。
1.9.2.S5PV210中的定時器
本節以資料手冊為綱領,講述S5PV210中的4類時間模組,主要目的是讓大家瞭解各個時間部件的區別和各自特點。
1.9.3.S5PV210的PWM定時器1
本節開始講解PWM定時器,主要講解一些外圍概念和S5PV210中PWM定時器的結構框圖,其中結構框圖是本節的核心重點。
1.9.4.S5PV210的PWM定時器2
本節繼續講解S5PV210的PWM定時器,主要講述預分頻器與分頻器、TCNTB和TCMPB暫存器及其工作原理、自動裝載與雙緩衝機制等。
1.9.5.S5PV210的PWM定時器3
本節繼續講解S5PV210的PWM定時器,主要講述PWM波形的生成原理、輸出電平翻轉器和死區生成器等相關概念和應用方法。
1.9.6.蜂鳴器和PWM定時器程式設計實踐1
本節進行PWM定時器的程式設計實踐。首先講解蜂鳴器的工作原理及其與PWM的關係,然後分析了原理圖、資料手冊、相關暫存器等,為下節寫程式準備好理論基礎。
1.9.7.蜂鳴器和PWM定時器程式設計實踐2
本節接上節繼續PWM定時器的程式設計實踐,帶領大家從零開始寫程式碼驅動timer2發出2KHz和10KHz等不同頻率的PWM波形驅動蜂鳴器發聲。
1.9.8.看門狗定時器
本節詳細講解看門狗定時器的原理、概念及實踐應用場景。並結合資料手冊分析了S5PV210內建的看門狗定時器的框圖、暫存器等。
1.9.9.看門狗定時器的程式設計實踐
本節進行看門狗定時器的程式設計實戰,從零開始寫程式碼操作看門狗定時器。以中斷方式和復位方式兩種工作方式演示了看門狗定時器的實踐用法。
1.9.10.實時時鐘RTC
本節首先講解RTC和timer的區別,然後分析了S5PV210的RTC模組的框圖、時鐘、暫存器,最後詳細講了BCD碼的編碼及意義,因為RTC中的時間全是用BCD碼編碼儲存的。
1.9.11.RTC程式設計實戰1
本節進行RTC的編碼實踐,在上節講解RTC暫存器的基礎上,程式設計實現RTC時間的設定、讀取顯示,及鬧鐘功能的演示,這些都是RTC最常用的功能。
1.9.12.RTC程式設計實戰2
本節進行RTC的編碼實踐,在上節講解RTC暫存器的基礎上,程式設計實現RTC時間的設定、讀取顯示,及鬧鐘功能的演示,這些都是RTC最常用的功能。

第三部分、隨堂記錄
1.9.1.什麼是定時器(timer)
1.9.1.1、定時器是SoC中常見外設
(1)定時器與計數器。計數器是用來計數的(每隔一個固定時間會計一個數);因為計數器的計數時間週期是固定的,因此到了一定時間只要用計數值×計數時間週期,就能得到一個時間段,這個時間段就是我們定的時間(這就是定時器了)。
(2)定時器/計數器作為SoC的外設,主要用來實現定時執行程式碼的功能。定時器相對於SoC來說,就好象鬧鐘相對於人來說意義一樣。

1.9.1.2、定時器有什麼用
(1)定時器可以讓SoC在執行主程式的同時,可以(通過定時器)具有計時功能,到了一定時間(計時結束)後,定時器會產生中斷提醒CPU,CPU會去處理中斷並執行定時器中斷的ISR。從而去執行預先設定好的事件。
(2)定時器就好象是CPU的一個祕書一樣,這個祕書專門管幫CPU來計時,併到時間後提醒CPU要做某件事情。所以CPU有了定時器之後,只需預先把自己xx時間之後必須要做的事情繫結到定時器中斷ISR即可,到了時間之後定時器就會以中斷的方式提醒CPU來處理這個事情。

1.9.1.3、定時器的原理
(1)定時器計時其實是通過計數來實現的。定時器內部有一個計數器,這個計數器根據一個時鐘(這個時鐘源來自於ARM的APB匯流排,然後經過時鐘模組內部的分頻器來分頻得到)來工作。每隔一個時鐘週期,計數器就計數一次,定時器的時間就是計數器計數值×時鐘週期。
(2)定時器內部有1個暫存器TCNT,計時開始時我們會把一個總的計數值(譬如說300)放入TCNT暫存器中,然後每隔一個時鐘週期(假設為1ms)TCNT中的值會自動減1(硬體自動完成,不需要CPU軟體去幹預),直到TCNT中減為0的時候,TCNT就會觸發定時器中斷。
(3)定時時間是由2個東西共同決定的:一個是TCNT中的計數值,一個是時鐘週期。譬如上例中,定時週期就為300×1ms = 300ms。

1.9.1.4、定時器和看門狗、RTC、蜂鳴器的關係
(1)這幾個東西都是和時間有關的部件。
(2)看門狗其實就是一個定時器,只不過定時時間到了之後不只是中斷,還可以復位CPU
(3)RTC是實時時鐘,它和定時器的差別就好象鬧鐘(定時器)和鐘錶(RTC)的差別一樣。
(4)蜂鳴器是一個發聲裝置,在ARM裡面蜂鳴器是用定時器模組來驅動的。

1.9.2.S5PV210中的定時器
在S5PV210內部,一共有4類定時器件。這4類定時器件的功能、特徵是不同的。
1.9.2.1、PWM定時器
(1)這種是最常用的,平時所說的定時器一般指的是這個。像簡單微控制器(譬如51微控制器)中的定時器也是這類。
(2)為什麼叫PWM定時器,因為一般SoC中產生PWM訊號都是靠這個定時器模組的。
1.9.2.2、系統定時器
(1)系統(指的是作業系統)定時器,系統定時器也是用來產生固定時間間隔(TCNT×時鐘週期)訊號的,稱為systick,這個systick用來給作業系統提供tick訊號。
(2)產生systick作為作業系統的時間片(time slice)的。
(3)一般做作業系統移植的時候,這裡不會由我們自己來做,一般原廠提供的基礎移植部分就已經包含了,所以這裡我也從來沒有研究過。
1.9.2.3、看門狗定時器
(1)看門狗定時器本質上也是一個定時器,和上面2個沒有任何本質區別。
(2)看門狗定時器可以設定在時間到了的時候產生中斷,也可以選擇發出復位訊號復位CPU。
(3)看門狗定時器在實踐中應用很多,尤其是工業領域(環境複雜、干擾多)機器容易出問題,而且出問題後後果很嚴重,此時一般都會用看門狗來進行系統復位。本章節會用2節課來對210中的看門狗進行講解和程式設計。
1.9.2.4、實時時鐘RTC(real time clock)
(1)區分時間段和時間點。時間段是相對的,兩個時間點相減就會得到一個時間段;而時間點是絕對的,是絕無僅有的一個時間點。
(2)定時器關注的是時間段(而不是時間點),定時器計時從開啟定時器的那一刻開始,到定的時間段結束為止產生中斷;RTC中工作用的是時間點(xx年x月x日x時x分x秒星期x)。
(3)RTC和定時器的區別,就相當於是鐘錶和鬧鐘的區別。

1.9.3.S5PV210的PWM定時器1
1.9.1.1、為什麼叫PWM定時器
(1)叫定時器說明它本質上的原理是定時器
(2)叫PWM定時器,是因為這個定時器天然是用來產生PWM波形的。
1.9.3.2、PWM定時器介紹
(1)S5PV210有5個PWM定時器。其中0、1、2、3各自對應一個外部GPIO,可以通過這些對應的GPIO產生PWM波形訊號並輸出;timer4沒有對應的外部GPIO(因此不是為了生成PWM波形而是為了產生內部定時器中斷而生的)
(2)S5PV210的5個PWM定時器的時鐘源為PCLK_PSYS,timer0和timer1共同使用一個預分頻器、timer2、3、4共同使用一個預分頻器;每個timer有一個專用的獨立的分頻器;預分頻器和分頻器構成了2級分頻系統,將PCLK_PSYS兩級分頻後生成的時鐘供給timer模組作為時鐘週期。
(3)剩下的介紹內容我就不逐句帶讀,大家自己讀,讀的時候結合後面的內容和翻譯。

1.9.3.3、S5PV210的PWM定時器框圖簡介
(1)關鍵點:時鐘源、預分頻器、分頻器、TCMPB&TCNTB、dead zone

1.9.4.S5PV210的PWM定時器2
1.9.4.1、預分頻器與分頻器
(1)兩級分頻是串聯(級聯)的,所以兩級分頻的分頻數是相乘的。
(2)兩級分頻的分頻係數分別在TCFG0和TCFG1兩個暫存器中設定。
(3)預分頻器有2個,prescaler0為timer0&timer1共用;prescaler1為timer2、3、4共用;兩個prescaler都是8個bit位,因此prescaler value範圍為0~255;所以預分頻器的分頻值範圍為1~256(注意實際分頻值為prescaler value + 1)。
(4)分頻器實質上是一個MUX開關,多選一開關決定了走哪個分頻係數路線。可以選擇的有1/1,1/2,1/4,1/8,1/16等。
(5)計算一下,兩級分頻下來,分頻最小為1/1(也可能是1/2),最大分頻為1/256×16(1/4096).
(6)在PCLK_PSYS為66MHz的情況下(預設時鐘設定就是66MHz的),此時兩級分頻後的時鐘週期範圍為0.03us到62.061us;再結合TCNTB的值的設定(範圍為1~2的32次方),可知能定出來的時間最長為266548.27s(摺合74小時多,遠遠夠用了)。

1.9.4.2、TCNT&TCMP、TCNTB&TCMPB、TCNTO
(1)TCNT和TCNTB是相對應的,TCNTB是有地址的暫存器,供程式設計師操作;TCNT在內部和TCNTB相對應,它沒有暫存器地址,程式設計師不能程式設計訪問這個暫存器。
(2)TCNT暫存器功能就是用來減1的,它是內部的不能讀寫;我們向TCNT中寫要通過TCNTB往進寫;讀取TCNT暫存器中的值要通過讀取相對應的TCNTO暫存器。
(3)工作流程就是:我們事先算好TCNT暫存器中開始減的那個數(譬如300),然後將之寫入TCNTB暫存器中,在啟動timer前,將TCNTB中的值刷到TCNT暫存器中(有一位暫存器專門用來操作刷資料過去的),刷過去後就可以啟動定時器開始計時;在計時過程中如果想知道TCNT暫存器中的值減到多少了,可以讀取相應的TCNTO暫存器來得知。
(4)定時功能只需要TCNT、TCNTB兩個即可;TCNTO暫存器用來做一些捕獲計時;TCMPB用來生成PWM波形。

1.9.4.3、自動過載和雙緩衝(auto-reload and double buffering)
(1)定時器工作的時候,一次定時算一個工作迴圈。定時器預設是單個迴圈工作的,也就是說定時一次,計時一次,到期中斷一次就完了。下次如果還要再定時中斷,需要另外設定。
(2)但是現實中用定時器來做的時候往往是迴圈的,最簡單最笨的方法就是寫程式碼反覆重置定時器暫存器的值(在每次中斷處理的isr中再次給TCNTB中賦值,再次刷到TCNT中再次啟動定時器),早期的微控制器定時器就是這樣的;但是現在的高階SoC中的定時器已經預設內建了這種迴圈定時工作模式,就叫自動裝載(auto-reload)機制。
(3)自動裝載機制就是當定時器初始化好開始計時後再不用管了,他一個週期到了後會自己從TCNTB中再次裝載值到TCNT中,再次啟動定時器開始下個迴圈。

1.9.5.S5PV210的PWM定時器3
1.9.5.1、什麼是PWM?
(1)PWM(pulse wide modulation 脈寬調製)
(2)PWM波形是一個週期性波形,週期為T,在每個週期內波形是完全相同的。每個週期內由一個高電平和一個低電平組成。
(3)PWM波形有2個重要引數:一個是週期T,另一個是佔空比duty(佔空比就是一個週期內高電平的時間除以週期時間的商)。
(4)對於一個PWM波形,知道了週期T和佔空比duty,就可以算出這個波形的所有細節。譬如高電平時間為Tduty,低電平時間為T(1-duty)。
(5)PWM波形有很多用處,譬如通訊上用PWM來進行脈寬調製對基波進行載波調製;在發光二極體LED照明領域可以用PWM波形來調製電流進行調光;用來驅動蜂鳴器等裝置。

1.9.5.2、PWM波形的生成原理
(1)PWM波形其實就是用時間來控制電平高低,所以用定時器來實現PWM波形是天經地義的。
(2)早期的簡單微控制器裡(譬如51微控制器)是沒有專用的PWM定時器的,那時候我們需要自己結合GPIO和定時器模組來手工生產PWM波形(流程是這樣:先將GPIO引腳電平拉高、同時啟動定時器定Tduty時間,時間到了在isr中將電平拉低,然後定時T(1-duty)後再次啟動定時器,然後時間到了後在isr中將電平拉高,然後再定時T*duty時間再次啟動定時器····如此迴圈即可得到週期為T,佔空比為duty的PWM波形)。
(3)後來因為定時器經常和PWM產生糾結一起,所以設計SoC的時候就直接把定時器和一個GPIO引腳內部繫結起來了,然後在定時器內部給我們設定了PWM產生的機制,可以更方便的利用定時器產生PWM波形。此時我們利用PWM定時器來產生PWM波形再不用中斷了。綁定了之後壞處就是GPIO引腳是固定的、死板的、不能隨便換的;好處是不用進入中斷isr中,直接可以生成PWM。
(4)在S5PV210中,PWM波形產生有2個暫存器很關鍵,一個是TCNTB、一個是TCMPB。其中,TCNTB決定了PWM波形的週期,TCMPB決定了PWM波形的佔空比。
(5)最終生成的PWM波形的週期是:TCNTB×時鐘週期(PCLK_PSYS經過兩極分頻後得到的時鐘週期)。注意這個週期是PWM中高電平+低電平的總時間,不是其中之一。
(6)最終生成的PWM波形的佔空比是:TCMPB/TCNTB

1.9.5.3、輸出電平翻轉器
(1)PWM定時器可以規定:當TCNT>TCMPB時為高電平,當TCNT<TCMPB時為低電平。也可以規定:當TCNT>TCMPB時為低電平,當TCNT<TCMPB時為高電平。在這兩種規定下,計算時TCMP暫存器的值會變化。
(2)基於上面講的,當duty從30%變到70%時,我們TCMPB暫存器中的值就要改(譬如TCNTB中是300時,TCMPB就要從210變化到90)。這樣的改變可以滿足需要,但是計算有點麻煩。於是乎210的PWM定時器幫我們提供了一個友好的工具叫做電平翻轉器。
(3)電平翻轉器在電路上的實質就是一個電平取反的部件,在程式設計上反映為一個暫存器位。寫0就關閉輸出電平反轉,寫1就開啟輸出電平反轉。開啟後和開啟前輸出電平剛好高低反轉。(輸出電平一反轉30%的duty就變成70%了)
(4)實戰中到底是TCNT和TCMPB誰大誰小時高電平還是低電平,一般不用理論分析,只要寫個程式碼然後用示波器實際看一下出來的波形就知道了;如果反了就直接開啟電平翻轉器即可。

1.9.5.4、死區生成器
(1)PWM有一個應用就是用在功率電路中用來對交流電壓進行整流。整流時2路整流分別在正電平和負電平時導通工作,不能同時導通(同時導通會直接短路,瞬間的同時導通都會導致電路燒燬)。大功率的開關電源、逆變器等裝置廣泛使用了整流技術。特別是逆變器,用SoC的GPIO輸出的PWM波形來分別驅動2路整流的IGBT。
(2)PWM波形用來做整理時要求不能同時高或低,因為會短路。但是實際電路是不理想的,不可能同時上升/下降沿,所以比較安全的做法是留死區。
(3)死區這東西離不了也多不了。死區少了容易短路,死區多了控制精度低了不利於產品效能的提升。
(4)S5PV210給大家提供了自帶的死區生成器,只要開啟死區生成器,生產出來的PWM波形就自帶了死區控制功能,使用者不用再自己去操心死區問題。
(5)大部分人工作是用不到這個的,直接關掉死區生成器即可。

1.9.6.蜂鳴器和PWM定時器程式設計實踐1
1.9.6.1、蜂鳴器的工作原理
(1)蜂鳴器裡面有2個金屬片,離的很緊但沒挨著;沒電的時候兩個片在彈簧本身張力作用下分開彼此平行;有電的時候兩邊分別充電,在異性電荷的吸力作用下兩個片挨著;
(2)我們只要以快速的頻率給蜂鳴器的正負極:供電、斷電。進行這樣的迴圈,蜂鳴器的兩個彈簧片就會挨著分開挨著分開···形成敲擊,發出聲音。
(3)因為人的耳朵能聽見的聲音訊率有限制(20Hz-20000Hz),我們做實驗時一般給個2KHz的頻率,大部分人都能聽到(聽不到的就就近醫院處理)。
(4)頻率高低會影響聲音的音訊,一般是音訊越低聲音聽起來越低沉、音訊越高聽起來越尖銳。
(5)根據以上的分析,可以看出,只要用PWM波形的電壓訊號來驅動蜂鳴器,把PWM波形的週期T設定為要發出的聲音訊號的1/頻率即可;PWM的佔空比只要確保能驅動蜂鳴器即可(驅動能力問題,一般引腳驅動能力都不夠,所以蜂鳴器會額外用三極體來放大流來供電)。

1.9.6.2、原理圖和硬體資訊
(1)查閱原理圖可知,開發板底板上的蜂鳴器通過GPD0_2(XpwmTOUT2)引腳連線在SoC上。
(2)GPD0_2引腳通過限流電阻接在三極體基極上,引腳有電蜂鳴器就會有電(三極體導通);引腳沒電蜂鳴器就會沒電(三極體關閉)。這些都是硬體問題,軟體工程師不用管,軟體工程師只要寫程式控制GPD0_2引腳的電平產生PWM波形即可。
(3)GPD0CON(0xE02000A0),要把bit8~bit11設定為0b0010(功能選擇為TOUT_2,就是把這個引腳設定為PWM輸出功能)
(4)從GPD0_2引腳可以反推出使用的是timer2這個PWM定時器。

1.9.6.3、PWM定時器的主要暫存器詳解
(1)相關的暫存器有TCFG0、TCFG1、CON、TCNTB2、TCMPB2、TCNTO2

1.9.7.蜂鳴器和PWM定時器程式設計實踐2
基於uart_stdio專案原始碼來新增PWM定時器驅動蜂鳴器實驗
注意:PWM定時器來產生PWM波形時是不需要中斷干預的。

1.9.8.看門狗定時器
1.9.8.1、什麼是看門狗、有什麼用
(1)看門狗定時器和普通的定時器並無本質區別。定時器可以設定一個時間,在這個時間完成之前定時器不斷計時,時間到的時候定時器會復位CPU(重啟系統)。
(2)系統正常工作的時候當然不希望被重啟,但是系統受到干擾、極端環境等可能會產生異常工作或者不工作,這種狀態可能會造成不良影響(至少是不工作),此時解決方案就是重啟系統。
(3)普通裝置重啟不是問題,但是有些裝置人工重啟存在困難。這時候我們希望系統能夠自己檢驗自己是否已經跑飛,並且在意識到自己跑飛的時候,可以很快的(幾個ms或者更短)自我重啟。這個功能就要靠看門狗定時器來實現。
(4)典型應用的情景是:我們在應用程式中開啟看門狗裝置,初始化好給它一個時間,然後應用程式使用一個執行緒來喂狗,這個執行緒的執行時間安全短於看門狗的復位時間。當系統(或者應用程式)異常後,喂狗執行緒自然就不工作了,然後到時候看門狗就會復位。
(5)補充:實戰中有時候為了絕對的可靠,我們並不會用SoC中自帶的看門狗,而是使用專門的外接的看門狗晶片來實現看門狗。

1.9.8.2、S5PV210看門狗定時器的結構框圖
(1)PCLK_PSYS經過兩級分頻後生成WDT(watchdog timer)的時鐘週期,然後把要定的時間寫到WTDAT暫存器中,刷到WTCNT暫存器中去減1,減到0時(定時時間到)產生復位訊號或中斷訊號。
(2)典型應用中是配置為產生復位訊號,我們應該在WTCNT暫存器減到0之前給WTDAT暫存器中重新寫值以喂狗。
1.9.8.3、看門狗定時器的主要暫存器
WTCON WTDAT WTCNT WTCLRINT

1.9.9.看門狗定時器的程式設計實踐
1.9.9.1、產生中斷訊號
1.9.9.2、產生復位訊號

1.9.10.實時時鐘RTC
1.9.10.1、何為實時時鐘
(1)real time clock,真實時間,就是所謂的xx年x月x日x時x分x秒星期x
(2)RTC是SoC中一個內部外設,RTC有自己獨立的晶振提供RTC時鐘源(32.768KHz),內部有一些暫存器用來記錄時間(年月日時分秒星期)。一般情況下為了在系統關機時時間仍然在走,還會給RTC提供一個電池供電。
1.9.10.2、S5PV210實時時鐘的結構框圖
(1)時間暫存器7個
(2)鬧鐘發生器

1.9.10.3、鬧鐘發生器
(1)可以定鬧鐘時間,到時間會產生RTC alarm interrupt,通知系統鬧鐘定時到了。
(2)鬧鐘定時是定的時間點,而timer定時是定的時間段。

1.9.10.4、S5PV210實時時鐘的主要暫存器
(1)INTP 中斷掛起暫存器
(2)RTCCON RTC控制暫存器
(3)RTCALM ALMxxx 鬧鐘功能有關的暫存器
(4)BCDxxx 時間暫存器

1.9.10.5、BCD碼
(1)RTC中所有的時間(年月日時分秒星期,包括鬧鐘)都是用BCD碼編碼的。
(2)BCD碼本質上是對數字的一種編碼。用來解決這種問題:由56得到0x56(或者反過來)。也就是說我們希望十進位制的56可以被編碼成56(這裡的56不是十進位制56,而是兩個數字5和6).
(3)BCD碼的作用在於可以將十進位制數拆成組成這個十進位制數的各個數字的編碼,變成編碼後就沒有位數的限制了。譬如我有一個很大的數123456789123456789,如果這個數純粹當數字肯定超出了int的範圍,計算機無法直接處理。要想讓計算機處理這個數,計算機首先得能表達這個數,表達的方式就是先把這個數轉成對應的BCD碼(123456789123456789)
(4)BCD碼在計算機中可以用十六進位制的形式來表示。也就是說十進位制的56轉成BCD碼後是56,在計算機中用0x56來表達(暫時儲存與運算)。
(5)需要寫2個函式,一個是bcd轉十進位制,一個是十進位制轉bcd。當我們要設定時間時(譬如要設定為23分),我們需要將這個23轉成0x23然後再賦值給相應的暫存器BCDMIN;當我們從暫存器BCDMIN中讀取一個時間時(譬如讀取到的是0x59),需要將之當作BCD碼轉成十進位制再去顯示(0x59當作BCD碼就是59,轉成十進位制就是59,所以顯示就是59分)。

1.9.11.RTC程式設計實戰1
1.9.11.1、設定時間與讀取顯示時間
(1)為了安全,預設情況下RTC讀寫是禁止的,此時讀寫RTC的時間是不允許的;當我們要更改RTC時間時,應該先開啟RTC的讀寫開關,然後再進行讀寫操作,操作完了後立即關閉讀寫開關。
(2)讀寫RTC暫存器時,一定要注意BCD碼和十進位制之間的轉換。
(3)年的問題。S5PV210中做了個設定,BCDYEAR暫存器存的並不是完整的年數(譬如今年2015年),而是基於2000年的偏移量來儲存的,譬如今年2015年實際存的就是15(2015-2000).還有些RTC晶片是以1970年(貌似)為基點來記錄的。
1.9.11.2、鬧鐘實驗

1.9.12.RTC程式設計實戰2