第十五屆省賽真題代碼題解析
- 前言
- 賽題代碼思路筆記
- 競賽板配置
- 建立模板
- 明確基本要求
- 顯示功能部分
- 頻率界面
- 正常顯示
- 高位熄滅
- 參數界面
- 基礎寫法:兩個界面分開來寫
- 優化寫法:兩個界面合一起寫
- 時間界面
- 回顯界面
- 校準
- 校準過程
- 校準錯誤顯示
- DAC輸出部分
- 按鍵功能部分
- S4:“界面”按鍵
- S5:“選擇”按鍵
- 優化寫法
- S8、S9:“加”按鍵 、“減”按鍵
- LED指示燈功能部分
- 調試時發現的問題
- 最終代碼
- User文件
- main.c
- Driver文件
- init.c
- Key.c
- Led.c
- Seg.c
- iic.c
- ds1302.c
- 結語
前言
本文是對藍橋杯第十五屆省賽真題的代碼題做的解題筆記。
我在看完西風老師的解析視頻后,自己重新寫了一遍,做了點整理也加入了一點個人的思考,因此在細節上(比方說:變量名…)會有一點出入。
思路參考西風第二十三講內容(資料鏈接已經在下方給出)
https://www.bilibili.com/video/BV1TR4y1k7iz?p=23&vd_source=e2191f89c557f5ac44bb6c7aa3967c7c
關于藍橋杯第十五屆省賽真題可以在官網查看(資料鏈接已經在下方給出)
https://www.lanqiao.cn/courses/2786/learning/?id=2870978&compatibility=false
賽題代碼思路筆記
競賽板配置
建立模板
根據賽題中的硬件框圖確定本賽題的框架
如圖,在Driver文件夾里建立LED、數碼管、按鍵、iic模塊(DAC輸出)、DS1302,在User文件夾中建立主函數模塊(大模板參考西風老師的2024版大模板)。
明確基本要求
注意1.的內容:
- P34引腳和矩陣按鍵有沖突,寫按鍵模塊(Key.h)時注意屏蔽掉P34=0的情況
- 板子的P34口和SIGNAL的跳帽要接上
注意2.的內容:
- 按鍵延遲時間要控制在100ms以內
/*按鍵10ms的減速*/if(Slow_Down % 10 == 0){Key_Flag = 0;}
注意第三點的內容:
- 數碼管延遲時間要控制在100ms以內
/*數碼管90ms的減速*/if(++Slow_Down == 90){Seg_Flag = Slow_Down = 0;}
顯示功能部分
- 顯示部分一共4個界面,定義一個變量界面顯示模式變量Seg_Disp_Mode各個界面。
后續通過對 Seg_Disp_Mode (界面顯示模式變量)的判斷切換不同的界面。
unsigned char Seg_Disp_Mode; //界面顯示模式變量 0-頻率界面 1-參數界面 2-時間界面 3-回顯界面
switch(Seg_Disp_Mode){case 0://頻率界面break;case 1://參數界面break;case 2://時間界面break;case 3://回顯界面break;}
- 在顯示功能部分中,需要顯示的字符有:0-9、熄滅、A、F、H、P、-,在seg.c文件段選數組中添加對應的十六進制表示。
code unsigned char Seg_dula[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff,0x88,0x8e,0x89,0x8c,0xBF};//0-9,10-熄滅,11-A,12-F,13-H,14-P,15--
頻率界面
頻率界面由三部分組成:
- 第一部分為編號(Seg_Buf[0]),在該界面下全程顯示字母F。
- 第二部分熄滅部分(Seg_Buf[1] - Seg_Buf[2]),在該界面下全程熄滅。
- 第三部分為頻率顯示部分(Seg_Buf[3] - Seg_Buf[7]),用NE555實時計算頻率。
正常顯示
在數碼管顯示數組(Seg_Buf[])對應的數碼管中輸入要顯示的字符在seg.c文件段選數組(Seg_dula[])對應的十六進制表示的位置。
Seg_Buf[0] = 12;//FSeg_Buf[1] = Seg_Buf[2] = 10;Seg_Buf[3] = Freq / 10000 % 10;Seg_Buf[4] = Freq / 1000 % 10;Seg_Buf[5] = Freq / 100 % 10;Seg_Buf[6] = Freq / 10 % 10;Seg_Buf[7] = Freq % 10;
高位熄滅
用while循環從高位開始檢測,當高位是0時,將該位置的數碼管置為熄滅狀態。
注意:
顯示最小為0,即數碼管的最后一位(Seg_Buf[7])不論是否為0都要保證正常顯示,所以在while循環中不對最后一位檢測。
unsigned char i=3;/*高位為0時熄滅*/while(Seg_Buf[i] == 0){Seg_Buf[i] = 10;if(++i == 7) break;}break;
參數界面
參數界面中又有兩個子界面,定義參數界面模式變量Seg_DM1_Mode對兩個子界面標記。
bit Seg_DM1_Mode = 1; //參數界面模式變量 0-超限參數界面 1-校準值參數界面
定義參數數組Seg_Set_Arr,存儲兩個界面下的參數值。
介于校準值界面的參數值有負數的情況,可以將參數數組(Seg_Set_Arr)直接定義為有符號的數組,后續只要對校準值變量的正負進行判斷就可以確定校準值的符號位要不要點亮。
int Seg_Set_Arr[2] = {2000,0}; //參數數組 [0]-超限參數 [1]-校準值變量
基礎寫法:兩個界面分開來寫
- if 判斷當前子界面:Seg_DM1_Mode(參數界面模式變量)為0,處于超限參數界面;反之,處于校準值參數界面。
- 在超限參數界面(邏輯和頻率界面的正常顯示部分相同,不再贅述)
- 在校準值參數界面時:
用if檢測校準值,分為校準值參數不為0和校準值參數為0兩種情況。
當校準值參數不為0時:用一組 if 判斷校準值的正負性,先確定符號位,再對數值進行顯示。
注意:校準值是有符號的數據,要取出各位數可以對他先取它的絕對值(math.h文件中的abs函數)
當校準值參數為0時:顯示一位0。
#include "math.h" //abs函數
if(Seg_DM1_Mode == 0)//超限參數界面{Seg_Buf[4] = Seg_Set_Arr[0] /1000 % 10;Seg_Buf[5] = Seg_Set_Arr[0] /100 % 10;Seg_Buf[6] = Seg_Set_Arr[0] /10 % 10;Seg_Buf[7] = Seg_Set_Arr[0] % 10;}else//校準值參數界面{if(Seg_Set_Arr[1] != 0){/*確定符號位*/if(Seg_Set_Arr[1] < 0)Seg_Buf[4] = 15;//-else Seg_Buf[4] = 10;//熄滅Seg_Buf[5] = abs(Seg_Set_Arr[1]) /100 % 10;Seg_Buf[6] = abs(Seg_Set_Arr[1]) /10 % 10;Seg_Buf[7] = abs(Seg_Set_Arr[1]) % 10;}else{Seg_Buf[5] = Seg_Buf[6] = 10;Seg_Buf[7] = 0;} }
優化寫法:兩個界面合一起寫
(這個部分是我對本套題解析視頻做的筆記,西風老師這個寫法的思路真的蠻好的)
先看到具體代碼:
Seg_Buf[0] = 14;//PSeg_Buf[1] = (unsigned char)Seg_DM1_Mode+1;Seg_Buf[2] = Seg_Buf[3] = 10;Seg_Buf[4] = Seg_DM1_Mode?(10 + 5 * (Seg_Set_Arr[Seg_DM1_Mode] < 0)):abs(Seg_Set_Arr[Seg_DM1_Mode]) /1000 % 10;Seg_Buf[5] = abs(Seg_Set_Arr[Seg_DM1_Mode]) /100 % 10;Seg_Buf[6] = abs(Seg_Set_Arr[Seg_DM1_Mode]) /10 % 10;Seg_Buf[7] = abs(Seg_Set_Arr[Seg_DM1_Mode]) % 10;/*校準值為0時,只顯示一位0*/if(Seg_Set_Arr[Seg_DM1_Mode] == 0)Seg_Buf[5] = Seg_Buf[6] = 10;
優化寫法重點在于:
- Seg_DM1_Mode(參數界面模式變量)和顯示相關變量的代數關系
- 條件運算符
簡單回顧一下條件運算符:
<表達式1>?<表達式2>:<表達式3>
當表達式1為真,返回表達式2的值;如果為假,則返回表達式3的值。
基礎的顯示不再贅述,在此對 Seg_Buf[1] 和 Seg_Buf[4] 做簡單的分析:
- Seg_Buf[1]:
由題目所給信息可知,Seg_Buf[1]在1和2之間變化,對應的是兩個子界面。
在此前我們已經給這兩個界面定義好了標志變量Seg_DM1_Mode(參數界面模式變量),它在0和1之間變化。
不難得出,Seg_Buf[1]和Seg_DM1_Mode之間的代數關系,即Seg_Buf[1]的值比Seg_DM1_Mode大1。
注意: Seg_DM1_Mode是bit型數據,不能到達2,所以讓Seg_DM1_Mode直接+1不能達到效果。那么需要解決的問題是增大它數值的可變區間,可以用強制類型轉換。 - Seg_Buf[4]:
Seg_Buf[4]對應了兩種顯示狀態,即超限參數界面下的高位和校準值參數界面下的符號位。
用條件運算符對Seg_DM1_Mode(參數界面模式變量)進行判斷,可以劃分出兩種狀態,即當Seg_DM1_Mode為真(Seg_DM1_Mode為1)時,是校準值參數界面;當Seg_DM1_Mode為假(Seg_DM1_Mode為0)時,是超限參數界面。
那么,只要找出Seg_Buf[4]在兩種顯示狀態下的數值在段選數組(Seg.h文件下的Seg_dula[])中的位置就可以完成顯示。
當Seg_Buf[4]在超限參數界面下的高位狀態時(Seg_DM1_Mode為0):只要獲取超限參數(Seg_Set_Arr[0])的最高位就行。
當Seg_Buf[4]在校準值參數界面下的符號位狀態時(Seg_DM1_Mode為1):Seg_Buf[4]在Seg_Set_Arr[1](校準值參數)為正,Seg_Buf[4]不點亮;Seg_Set_Arr[1](校準值參數)為負,Seg_Buf[4]才點亮,顯示符號位。所以還要找熄滅對應的位置和負號對應的位置之間的關系。
(這里我覺得比較妙的一點是用條件是否為真(Seg_Set_Arr[Seg_DM1_Mode] < 0)進行了熄滅和負號點亮之間的切換。)
時間界面
定義**時間參數數組ucRtc[]**用來存儲時分秒的數據。
unsigned char ucRtc[3] = {13,03,05}; //時間參數數組 [0]-時 [1]-分 [2]-秒
調用ds1302里寫入的函數Set_Rtc(),將初始顯示狀態的數據在上電時寫入。
Set_Rtc(ucRtc);
每次進入信息處理函數時讀取(Read_Rtc())時鐘信息,更新時間參數數組ucRtc[] 的數據,為后續顯示做準備。
Read_Rtc(ucRtc);
(顯示可以一位數碼管一位數碼管寫,和頻率界面的正常顯示相似,就不再贅述了。)
用 for循環 將數碼管顯示的時分秒數據寫入。
這里要找出時分秒在數碼管的位置之間的代數關系,時分秒的高位分別占0 3 6,低位占1 4 7,可知高位滿足 y = 3x ,低位滿足 y = 3x+1。
在一次循環里實現一組高低位的取值。
for(j = 0;j < 3;j++){Seg_Buf[j * 3] = ucRtc[j] / 10;Seg_Buf[j * 3 + 1] = ucRtc[j] %10;}Seg_Buf[2] = Seg_Buf[5] = 15;//-
回顯界面
回顯界面也有兩個子界面,聲明回顯界面模式變量bit Seg_DM3_Mode標記兩個子界面。
頻率回顯界面要顯示最大頻率值,時間回顯界面要顯示最大頻率發生時間,分別聲明最大頻率值變量Max_Freq和**最大頻率發生時間數組Max_Freq_Time[3]**存儲這兩個值。
bit Seg_DM3_Mode; //回顯界面模式變量 0-頻率回顯界面 1-時間回顯界面
unsigned int Max_Freq; //最大頻率值變量
unsigned int Max_Freq_Time[3]; //最大頻率發生時間數組
每次進入信息處理函數后獲取最大頻率及其發生時間,存到對應的變量里,以供后續調用。
/*獲取最大頻率及其發生時間*/if(Max_Freq < Freq){Max_Freq = Freq;for(j = 0;j < 3;j ++)Max_Freq_Time[j] = ucRtc[j];}
頻率回顯界面要注意高位熄滅
時間回顯界面用循環獲取時間的話,和時間界面相似,要找出各位之間的代數關系。
Seg_Buf[0] = 13;//Hif(Seg_DM3_Mode == 0)//頻率回顯界面{Seg_Buf[1] = 12;//FSeg_Buf[2] = 10;Seg_Buf[3] = Max_Freq / 10000 % 10;Seg_Buf[4] = Max_Freq / 1000 % 10;Seg_Buf[5] = Max_Freq / 100 % 10;Seg_Buf[6] = Max_Freq / 10 % 10;Seg_Buf[7] = Max_Freq % 10;/*高位為0時熄滅*/while(Seg_Buf[i] == 0){Seg_Buf[i] = 10;if(++i == 7) break;}}else//時間回顯界面{Seg_Buf[1] = 11;//Afor(j = 0;j < 3;j ++){Seg_Buf[2+2*j] = Max_Freq_Time[j] / 10;Seg_Buf[2+2*j+1] = Max_Freq_Time[j] % 10;}}
校準
校準過程
顯示的頻率都是實時頻率加上校準值后的結果,那么只要在計算完頻率之后加上校準值即可。
Freq += Seg_Set_Arr[1]; //加上校準值
校準錯誤顯示
校準錯誤顯示在頻率顯示界面(Seg_Disp_Mode為0)下實現。
可以將顯示分為兩種情況:
- 頻率正常
- 頻率錯誤
用 if 對校準后的頻率進行判斷,當頻率大于0時,正常讀取正常顯示;當頻率小于0時,只顯示首位的編號和末兩位的LL。
if(Freq >= 0){Seg_Buf[3] = Freq / 10000 % 10;Seg_Buf[4] = Freq / 1000 % 10;Seg_Buf[5] = Freq / 100 % 10;Seg_Buf[6] = Freq / 10 % 10;Seg_Buf[7] = Freq % 10;/*高位為0時熄滅*/while(Seg_Buf[i] == 0){Seg_Buf[i] = 10;if(++i == 7) break;}}else{Seg_Buf[3] = Seg_Buf[4] = Seg_Buf[5] = 10;//熄滅Seg_Buf[6] = Seg_Buf[7] = 16;//LL}
注意: Freq要用 long int型。
考慮兩個點 :
- 頻率的正負。因為加上校驗值后頻率可能為負數,如果用無符號型無論如何都得不到負數,所以要用有符號型的數據。
- 范圍。int型的取值范圍為 -32768 到 32767 ,用來存儲單純的頻率是夠用的,但是加上校驗值后就可能產生數據溢出的問題,使得顯示數據不準,所以要擴大取值范圍。
DAC輸出部分
聲明輸出電壓變量V_OutPut,存儲要輸出的電壓大小。
float V_OutPut; //輸出電壓變量
整體的輸出可以把分成四段:
- 頻率錯誤(Frqe<0)時,DAC輸出0V。
- 頻率大于等于0且小于500Hz時,DAC輸出1V
- 頻率大于超限參數時,DAC輸出5V
- 頻率在500Hz和超過限制頻率之間時,DAC輸出要求出該斜線的表達式。
易得數學表達式:
V = ( ( 5 - 1 ) / ( 超限參數 - 500 ) ) * (F-500) + 1 - 頻率錯誤時,DAC輸出0V。
注意: V_OutPut(輸出電壓變量)寫入的 0-5 的值是需要輸出的模擬輸出值,而在調用DAC輸出函數(Da_Write)時寫入的參數是 0 - 255 范圍內的數字輸入值。如果直接用V_OutPut的值寫入參數,那么實際輸出的電壓值將會很小,所以在此要將 0-5 范圍的值線性映射成 0-255 范圍的量值,不難得出這兩個范圍的倍數關系是51,因此只要對小的值(輸出電壓變量V_OutPut)乘上51即可完成映射。
/*DAC相關*/if(Freq < 0) V_OutPut = 0;else if(Freq >= 0 && Freq <= 500) V_OutPut = 1;else if(Freq >= Seg_Set_Arr[0]) V_OutPut = 5;else V_OutPut = 1 + (float)(5.0 - 1.0)/(Seg_Set_Arr[0] - 500) * (Freq - 500);Da_Write(V_OutPut * 51);
按鍵功能部分
/*按鍵處理函數*/
void Key_Proc()
{if(Key_Flag) return;Key_Flag = 1;Key_Val = Key_Read();Key_Down = Key_Val & (Key_Val ^ Key_Old);Key_Old = Key_Val;switch(Key_Down){case 4://“界面”按鍵break;case 5://“選擇”按鍵break;case 8://“加”按鍵break;case 9://“減”按鍵break;}
}
S4:“界面”按鍵
界面切換要考慮模式變量的取值情況。
按鍵按下一次模式切換一次,即 Seg_Disp_Mode(界面顯示模式變量)+1。Seg_Disp_Mode在 0-3 之間變化,對應4個模式,最大值不能為3,所以當 Seg_Disp_Mode 加到4時,要將它重新賦0,以此形成一個循環的切換。
if(++Seg_Disp_Mode == 4) Seg_Disp_Mode = 0;
S5:“選擇”按鍵
分成兩種情況對各自界面下的模式變量操作。
因為這兩個界面下的模式變量都是bit型,可以不用像“界面”按鍵那里一樣進行加法操作,可以用異或。
這里用異或,其實就相當于取反。因為bit型數據本身就只有0和1兩個數值,0異或后為1,1異或后為0。
if(Seg_Disp_Mode == 1)Seg_DM1_Mode ^= 1;else if(Seg_Disp_Mode == 3)Seg_DM3_Mode ^= 1;
注意:
- 頻率界面切換到參數界面和時間界面切換到回顯界面時,有默認顯示的子界面,所以在每次在完成這兩種切換時,都要保證當前顯示處于默認子界面,但是在上面的操作中并不能實現。
- 默認子界面的顯示是在按下“界面”按鍵時生效的(Key_Down為4),即要對“界面”按鍵按下后將要顯示的界面進行判斷,決定是否要重置子界面的模式變量。
if(++Seg_Disp_Mode == 1) Seg_DM1_Mode = 0; //默認顯示超限參數子界面if(Seg_Disp_Mode == 3) Seg_DM3_Mode = 0; //默認顯示頻率回顯子界面if(Seg_Disp_Mode == 4) Seg_Disp_Mode = 0; //退回到最初界面
優化寫法
上面是對按鍵按下后的界面進行了判斷后才執行重置。但是,其實不進行判斷也是可以的。因為子界面模式變量的值是多少對于另外兩個界面的顯示沒有任何影響,所以可以省去判斷的那一步,在每次按下按鍵就重置。
if(++Seg_Disp_Mode == 4) Seg_DM1_Mode = Seg_DM3_Mode = Seg_Disp_Mode = 0;
S8、S9:“加”按鍵 、“減”按鍵
“加”按鍵和“減”按鍵邏輯類似,此處只對“加”按鍵做簡單的解析。
在進行加法運算時,要考慮單次加的單位值大小和參數的上限值。
此處是對同界面下的兩個子界面的參數進行的加法,所以可以將兩個子界面的單次加的單位值和參數上限值分組后分別存在兩個數組中(單次加減大小數組Step_Arr[2]、上限值數組Max_Arr[2]),前一個數是超限參數對應的值,后一個數是校準參數對應的值。
//按鍵功能相關變量
int code Step_Arr[2] = {1000,100}; //單次加減大小數組 [0]-超限參數單次增加量/減小量 [1]-校準參數單次增加量/減小量
int code Max_Arr[2] = {9000,900}; //上限值數組 [0]-超限參數上限值 [1]-校準參數上限值
int code Min_Arr[2] = {1000,-900}; //下限值數組 [0]-超限參數下限值 [1]-校準參數下限值
通過參數界面模式變量(Seg_DM1_Mode) 根據當前界面的不同獲取對應的值,進行加法處理。
每次加完都要對當前數值進行判斷,避免超出限制。
(減法運算邏輯類似,不贅述)
case 8://“加”按鍵if(Seg_Disp_Mode == 1 ){Seg_Set_Arr[Seg_DM1_Mode] += Step_Arr[Seg_DM1_Mode];if(Seg_Set_Arr[Seg_DM1_Mode] > Max_Arr[Seg_DM1_Mode])Seg_Set_Arr[Seg_DM1_Mode] = Max_Arr[Seg_DM1_Mode];}break;case 9://“減”按鍵if(Seg_Disp_Mode == 1 ){Seg_Set_Arr[Seg_DM1_Mode] -= Step_Arr[Seg_DM1_Mode];if(Seg_Set_Arr[Seg_DM1_Mode] < Min_Arr[Seg_DM1_Mode])Seg_Set_Arr[Seg_DM1_Mode] = Min_Arr[Seg_DM1_Mode];}break;
LED指示燈功能部分
這塊的重點是兩個地方:
- 0.2s間隔的閃爍。
- 亮、滅狀態的切換。
聲明閃爍間隔計時變量Time_200ms,后續用于0.2間隔閃爍的計時。
聲明閃爍標志位Led_Flag,用于標記亮滅狀態,并且通過它的改變實現亮滅的切換。
//LED指示燈功能相關變量
unsigned char Time_200ms; //閃爍間隔計時變量
bit Led_Flag; //閃爍標志位
在定時中斷服務函數中完成。
每計時滿200ms重新開始計時,并且改變一次亮滅狀態。
/*LED閃爍200ms計時*/if(++Time_200ms == 200){Time_200ms = 0;Led_Flag ^= 1;}
(在實現點亮時,可以用最基礎的 if 的方式一步一步判斷,這個邏輯很簡單,暫時先不寫了。下面這個是西風老師的邏輯,我認為比最基礎版寫法更好,因為更能體現你對C語言運算符運用,對此我做個簡要的分析一下)
題目涉及的LED只有L1和L2,所以只要對Led控制數組(ucLed[])前兩位進行操作。
- L1在頻率界面下生效(Seg_Disp_Mode為0),用邏輯非運算符( !) 對 Seg_Disp_Mode 的邏輯值取反。即Seg_Disp_Mode為 0時(當前處于頻率界面),那邏輯值就是假,! 運算之后的結果就是 1;Seg_Disp_Mode若不為 0(當前不處于頻率界面),那邏輯值就是真,! 運算之后的結果就是 0。
再和閃爍標志位(Led_Flag)進行邏輯與(有0出0)的運算,那么當Seg_Disp_Mode為 0時(當前處于頻率界面),L1的點亮狀態全由 Led_Flag(閃爍標志位)決定。 - L2在頻率大于超限參數 或者 頻率為負數時生效,因為兩種情況中任意滿足一種它就生效,所以可以對這兩個情況進行邏輯或運算(其中一個操作數的邏輯值為真,整個表達式的結果就為真)。
當處于頻率大于超限參數時,在這可以直接對頻率判斷 也 可以對DAC輸出電壓變量(V_OutPut)進行判斷。對 V_OutPut 判斷(回顧DAC輸出部分),當 V_OutPut 為 5 時表示當前處于頻率大于超限參數的狀態,要實現L2閃爍。那么只要對V_OutPut == 5
進行真假邏輯的判斷就可以用1和0來表示 頻率大于超限參數(L2閃爍) 和 頻率不大于超限參數(L2不閃爍) 兩種情況。此時就和L1的思路相似,將V_OutPut == 5
邏輯真假的輸出值和閃爍標志位(Led_Flag)進行邏輯與(有0出0)的運算,那么當邏輯為真(頻率大于超限參數(L2閃爍))時,L2 的點亮狀態全由 Led_Flag(閃爍標志位)決定。
當處于頻率為負數時,直接對Freq < 0
進行邏輯判斷,那么當頻率為負數是輸出為1,此時L1點亮。
/**LED相關**/ucLed[0] = Led_Flag & (!Seg_Disp_Mode);ucLed[1] = (Led_Flag & (V_OutPut == 5)) || (Freq < 0);
調試時發現的問題
最終代碼
User文件
main.c
/*頭文件聲明區*/
#include <STC15F2K60S2.H>
#include "Init.h"
#include "Seg.h"
#include "Key.h"
#include "Led.h"
#include "iic.h"
#include "ds1302.h"
#include "math.h"/*變量聲明區*/
unsigned char Key_Down,Key_Val,Key_Up,Key_Old;
unsigned char Seg_Buf[8] = {10,10,10,10,10,10,10,10};
unsigned char Seg_Point[8] = {0,0,0,0,0,0,0,0};
unsigned char Seg_Pos;
unsigned char ucLed[8] = {0,0,0,0,0,0,0,0};
unsigned int Slow_Down;
bit Seg_Flag,Key_Flag;
unsigned int Time_1s;
long int Freq;//界面相關變量
unsigned char Seg_Disp_Mode; //界面顯示模式變量 0-頻率界面 1-參數界面 2-時間界面 3-回顯界面
bit Seg_DM1_Mode; //參數界面模式變量 0-超限參數界面 1-校準值參數界面
int Seg_Set_Arr[2] = {2000,0}; //參數數組 [0]-超限參數 [1]-校準值變量
unsigned char ucRtc[3] = {13,03,05}; //時間參數數組 [0]-時 [1]-分 [2]-秒
bit Seg_DM3_Mode; //回顯界面模式變量 0-頻率回顯界面 1-時間回顯界面
unsigned int Max_Freq; //最大頻率值變量
unsigned int Max_Freq_Time[3]; //最大頻率發生時間數組//DAC相關變量
float V_OutPut; //輸出電壓變量//按鍵功能相關變量
int code Step_Arr[2] = {1000,100}; //單次加減大小數組 [0]-超限參數單次增加量/減小量 [1]-校準參數單次增加量/減小量
int code Max_Arr[2] = {9000,900}; //上限值數組 [0]-超限參數上限值 [1]-校準參數上限值
int code Min_Arr[2] = {1000,-900}; //下限值數組 [0]-超限參數下限值 [1]-校準參數下限值//LED指示燈功能相關變量
unsigned char Time_200ms; //閃爍間隔計時變量
bit Led_Flag; //閃爍標志位/*按鍵處理函數*/
void Key_Proc()
{if(Key_Flag) return;Key_Flag = 1;Key_Val = Key_Read();Key_Down = Key_Val & (Key_Val ^ Key_Old);Key_Old = Key_Val;switch(Key_Down){case 4://“界面”按鍵
// /*分開寫*/
// if(++Seg_Disp_Mode == 1) Seg_DM1_Mode = 0; //默認顯示超限參數子界面
// if(Seg_Disp_Mode == 3) Seg_DM3_Mode = 0; //默認顯示頻率回顯子界面
// if(Seg_Disp_Mode == 4) Seg_Disp_Mode = 0; //退回到最初界面/*合在一起寫*/if(++Seg_Disp_Mode == 4) Seg_DM1_Mode = Seg_DM3_Mode = Seg_Disp_Mode = 0;break;case 5://“選擇”按鍵if(Seg_Disp_Mode == 1)Seg_DM1_Mode ^= 1;else if(Seg_Disp_Mode == 3)Seg_DM3_Mode ^= 1;break;case 8://“加”按鍵if(Seg_Disp_Mode == 1 ){Seg_Set_Arr[Seg_DM1_Mode] += Step_Arr[Seg_DM1_Mode];if(Seg_Set_Arr[Seg_DM1_Mode] >= Max_Arr[Seg_DM1_Mode])Seg_Set_Arr[Seg_DM1_Mode] = Max_Arr[Seg_DM1_Mode];}break;case 9://“減”按鍵if(Seg_Disp_Mode == 1 ){Seg_Set_Arr[Seg_DM1_Mode] -= Step_Arr[Seg_DM1_Mode];if(Seg_Set_Arr[Seg_DM1_Mode] < Min_Arr[Seg_DM1_Mode])Seg_Set_Arr[Seg_DM1_Mode] = Min_Arr[Seg_DM1_Mode];}break;}
}/*信息處理函數*/
void Seg_Proc()
{unsigned char i = 3;unsigned char j = 0;if(Seg_Flag) return;Seg_Flag = 1;/*更新時鐘*/Read_Rtc(ucRtc);/*獲取最大頻率及其發生時間*/if(Max_Freq < Freq){Max_Freq = Freq;for(j = 0;j < 3;j ++)Max_Freq_Time[j] = ucRtc[j];}switch(Seg_Disp_Mode){case 0://頻率界面Seg_Buf[0] = 12;//FSeg_Buf[1] = Seg_Buf[2] = 10;if(Freq >= 0){Seg_Buf[3] = Freq / 10000 % 10;Seg_Buf[4] = Freq / 1000 % 10;Seg_Buf[5] = Freq / 100 % 10;Seg_Buf[6] = Freq / 10 % 10;Seg_Buf[7] = Freq % 10;/*高位為0時熄滅*/while(Seg_Buf[i] == 0){Seg_Buf[i] = 10;if(++i == 7) break;}}else{Seg_Buf[3] = Seg_Buf[4] = Seg_Buf[5] = 10;//熄滅Seg_Buf[6] = Seg_Buf[7] = 16;//LL} break;case 1://參數界面Seg_Buf[0] = 14;//PSeg_Buf[1] = (unsigned char)Seg_DM1_Mode+1;Seg_Buf[2] = Seg_Buf[3] = 10;// if(Seg_DM1_Mode == 0)//超限參數界面
// {
// Seg_Buf[4] = Seg_Set_Arr[0] /1000 % 10;
// Seg_Buf[5] = Seg_Set_Arr[0] /100 % 10;
// Seg_Buf[6] = Seg_Set_Arr[0] /10 % 10;
// Seg_Buf[7] = Seg_Set_Arr[0] % 10;
// }
// else//校準值參數界面
// {
// if(Seg_Set_Arr[1] != 0)
// {
// /*確定符號位*/
// if(Seg_Set_Arr[1] < 0)
// Seg_Buf[4] = 15;//-
// else
// Seg_Buf[4] = 10;//熄滅
//
//
// Seg_Buf[5] = abs(Seg_Set_Arr[1]) /100 % 10;
// Seg_Buf[6] = abs(Seg_Set_Arr[1]) /10 % 10;
// Seg_Buf[7] = abs(Seg_Set_Arr[1]) % 10;
// }
// else
// {
// Seg_Buf[5] = Seg_Buf[6] = 10;
// Seg_Buf[7] = 0;
// }
// }Seg_Buf[4] = Seg_DM1_Mode?(10 + 5 * (Seg_Set_Arr[Seg_DM1_Mode] < 0)):abs(Seg_Set_Arr[Seg_DM1_Mode]) /1000 % 10;Seg_Buf[5] = abs(Seg_Set_Arr[Seg_DM1_Mode]) /100 % 10;Seg_Buf[6] = abs(Seg_Set_Arr[Seg_DM1_Mode]) /10 % 10;Seg_Buf[7] = abs(Seg_Set_Arr[Seg_DM1_Mode]) % 10;/*校準值為0時,只顯示一位0*/if(Seg_Set_Arr[Seg_DM1_Mode] == 0)Seg_Buf[5] = Seg_Buf[6] = 10;break;case 2://時間界面for(j = 0;j < 3;j++){Seg_Buf[j * 3] = ucRtc[j] / 10;Seg_Buf[j * 3 + 1] = ucRtc[j] %10;}Seg_Buf[2] = Seg_Buf[5] = 15;//-break;case 3://回顯界面Seg_Buf[0] = 13;//Hif(Seg_DM3_Mode == 0)//頻率回顯界面{Seg_Buf[1] = 12;//FSeg_Buf[2] = 10;Seg_Buf[3] = Max_Freq / 10000 % 10;Seg_Buf[4] = Max_Freq / 1000 % 10;Seg_Buf[5] = Max_Freq / 100 % 10;Seg_Buf[6] = Max_Freq / 10 % 10;Seg_Buf[7] = Max_Freq % 10;/*高位為0時熄滅*/while(Seg_Buf[i] == 0){Seg_Buf[i] = 10;if(++i == 7) break;}}else//時間回顯界面{Seg_Buf[1] = 11;//Afor(j = 0;j < 3;j ++){Seg_Buf[2+2*j] = Max_Freq_Time[j] / 10;Seg_Buf[2+2*j+1] = Max_Freq_Time[j] % 10;}}break;}
}/*其他顯示函數*/
void Led_Proc()
{/*DAC相關*/if(Freq < 0) V_OutPut = 0;else if(Freq >= 0 && Freq <= 500) V_OutPut = 1;else if(Freq >= Seg_Set_Arr[0]) V_OutPut = 5;else V_OutPut = 1 + (float)(5.0 - 1.0)/(Seg_Set_Arr[0] - 500) * (Freq - 500);Da_Write(V_OutPut * 51);/**LED相關**/ucLed[0] = Led_Flag & (!Seg_Disp_Mode);ucLed[1] = (Led_Flag & (V_OutPut == 5)) || (Freq < 0);
}/*定時器0初始化函數*/
void Timer0Init(void) //1毫秒@12.000MHz
{AUXR &= 0x7F; //定時器時鐘12T模式TMOD &= 0xF0; //設置定時器模式TMOD |= 0x05; TL0 = 0; //設置定時初值TH0 = 0; //設置定時初值TF0 = 0; //清除TF0標志TR0 = 1; //定時器0開始計時
}/*定時器1初始化函數*/
void Timer1Init(void) //1毫秒@12.000MHz
{AUXR &= 0xBF; //定時器時鐘12T模式TMOD &= 0x0F; //設置定時器模式TL1 = 0x18; //設置定時初值TH1 = 0xFC; //設置定時初值TF1 = 0; //清除TF1標志TR1 = 1; //定時器1開始計時ET1 = 1;EA = 1;
}/*定時器1中斷服務函數*/
void Timer1_Isr(void) interrupt 3
{/*數碼管90ms的減速*/if(++Slow_Down == 90){Seg_Flag = Slow_Down = 0;}/*按鍵10ms的減速*/if(Slow_Down % 10 == 0){Key_Flag = 0;}/*1s的計數*/if(++Time_1s == 1000){Time_1s = 0;Freq = TH0 << 8 | TL0; //計算頻率Freq += Seg_Set_Arr[1]; //加上校準值TH0 = 0;TL0 = 0;}/*信息處理*/Seg_Disp(Slow_Down % 8, Seg_Buf[Slow_Down % 8],Seg_Point[Slow_Down % 8]);Led_Disp(Slow_Down % 8,ucLed[Slow_Down % 8]);/*LED閃爍200ms計時*/if(++Time_200ms == 200){Time_200ms = 0;Led_Flag ^= 1;}
}/*主函數*/
void main()
{Sys_Init();Timer0Init();Timer1Init();Set_Rtc(ucRtc);while(1){Key_Proc();Seg_Proc();Led_Proc();}
}
Driver文件
init.c
#include "Init.h"void Sys_Init()
{P0 = 0xff;P2 = P2 & 0x1f | 0x80;P2 &= 0x1f;P0 = 0x00;P2 = P2 & 0x1f | 0xa0;P2 &= 0x1f;
}
Key.c
#include "Key.h"unsigned char Key_Read()
{unsigned char temp=0;P44 = 0; P42 = 1; P35 = 1; P34 = 1;if(P33 == 0) temp = 4;if(P32 == 0) temp = 5;if(P31 == 0) temp = 6;if(P30 == 0) temp = 7;P44 = 1; P42 = 0; P35 = 1; P34 = 1;if(P33 == 0) temp = 8;if(P32 == 0) temp = 9;if(P31 == 0) temp = 10;if(P30 == 0) temp = 11;P44 = 1; P42 = 1; P35 = 0; P34 = 1;if(P33 == 0) temp = 12;if(P32 == 0) temp = 13;if(P31 == 0) temp = 14;if(P30 == 0) temp = 15;/*P34引腳和NE555沖突*/
// P44 = 1; P42 = 1; P35 = 1; P34 = 0;
// if(P33 == 0) temp = 16;
// if(P32 == 0) temp = 17;
// if(P31 == 0) temp = 18;
// if(P30 == 0) temp = 19;return temp;
}
Led.c
#include "Led.h"void Led_Disp(unsigned char addr,enable)
{unsigned char temp = 0x00;unsigned char temp_old = 0xff;if(enable)temp |= 0x01 << addr;elsetemp &= ~(0x01 << addr);if(temp != temp_old){P0 = ~temp;P2 = P2 & 0x1f | 0x80;P2 &= 0x1f;temp_old = temp;}
}
Seg.c
#include "Seg.h"code unsigned char Seg_wela[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
code unsigned char Seg_dula[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff,0x88,0x8e,0x89,0x8c,0xBF,0xC7};//0-9,10-熄滅,11-A,12-F,13-H,14-P,15--,16-Lvoid Seg_Disp(unsigned char wela,dula,point)
{P0 = 0xff;P2 = P2 & 0x1f | 0xe0;P2 &= 0x1f;P0 = Seg_wela[wela];P2 = P2 & 0x1f | 0xc0;P2 &= 0x1f;P0 = Seg_dula[dula];if(point)P0 &= 0x7f;P2 = P2 & 0x1f | 0xe0;P2 &= 0x1f;
}
iic.c
#include "iic.h"
#include "intrins.h"
#define DELAY_TIME 10sbit scl = P2 ^ 0;
sbit sda = P2 ^ 1;//
static void I2C_Delay(unsigned char n)
{do{_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_(); }while(n--);
}//
void I2CStart(void)
{sda = 1;scl = 1;I2C_Delay(DELAY_TIME);sda = 0;I2C_Delay(DELAY_TIME);scl = 0;
}//
void I2CStop(void)
{sda = 0;scl = 1;I2C_Delay(DELAY_TIME);sda = 1;I2C_Delay(DELAY_TIME);
}//
void I2CSendByte(unsigned char byt)
{unsigned char i;for(i=0; i<8; i++){scl = 0;I2C_Delay(DELAY_TIME);if(byt & 0x80){sda = 1;}else{sda = 0;}I2C_Delay(DELAY_TIME);scl = 1;byt <<= 1;I2C_Delay(DELAY_TIME);}scl = 0;
}//
unsigned char I2CReceiveByte(void)
{unsigned char da;unsigned char i;for(i=0;i<8;i++){ scl = 1;I2C_Delay(DELAY_TIME);da <<= 1;if(sda) da |= 0x01;scl = 0;I2C_Delay(DELAY_TIME);}return da;
}//
unsigned char I2CWaitAck(void)
{unsigned char ackbit;scl = 1;I2C_Delay(DELAY_TIME);ackbit = sda; scl = 0;I2C_Delay(DELAY_TIME);return ackbit;
}//
void I2CSendAck(unsigned char ackbit)
{scl = 0;sda = ackbit; I2C_Delay(DELAY_TIME);scl = 1;I2C_Delay(DELAY_TIME);scl = 0; sda = 1;I2C_Delay(DELAY_TIME);
}void Da_Write(unsigned char dat)
{I2CStart();I2CSendByte(0x90);I2CWaitAck();I2CSendByte(0x41);I2CWaitAck();I2CSendByte(dat);I2CWaitAck();I2CStop();
}
ds1302.c
#include "ds1302.h"
#include "intrins.h"sbit SCK = P1 ^ 7;
sbit SDA = P2 ^ 3;
sbit RST = P1 ^ 3;
//
void Write_Ds1302(unsigned char temp)
{unsigned char i;for (i=0;i<8;i++) { SCK = 0;SDA = temp&0x01;temp>>=1; SCK=1;}
} //
void Write_Ds1302_Byte( unsigned char address,unsigned char dat )
{RST=0; _nop_();SCK=0; _nop_();RST=1; _nop_(); Write_Ds1302(address); Write_Ds1302(dat); RST=0;
}//
unsigned char Read_Ds1302_Byte ( unsigned char address )
{unsigned char i,temp=0x00;RST=0; _nop_();SCK=0; _nop_();RST=1; _nop_();Write_Ds1302(address);for (i=0;i<8;i++) { SCK=0;temp>>=1; if(SDA)temp|=0x80; SCK=1;} RST=0; _nop_();SCK=0; _nop_();SCK=1; _nop_();SDA=0; _nop_();SDA=1; _nop_();return (temp);
}void Set_Rtc(unsigned char *ucRtc)
{unsigned char i;Write_Ds1302_Byte(0x8e,0x00);for(i = 0;i < 3; i++)Write_Ds1302_Byte(0x84 - 2 * i, ucRtc[i] / 10 % 10 << 4 | ucRtc[i] % 10);Write_Ds1302_Byte(0x8e,0x80);
}void Read_Rtc(unsigned char *ucRtc)
{unsigned char i;unsigned temp;for(i = 0; i < 3; i++){temp = Read_Ds1302_Byte(0x85 - 2 * i);ucRtc[i] = temp /16 * 10 + temp % 16;}
}
結語
這套題整理也花了不少時間,但在整理的同時也讓我對這套題的解法和思路有了更清晰的認知,收獲還是蠻大的。
感謝你能看到這里,希望這篇筆記不僅對我有所幫助,也能對你能有所啟發。
那么,文章里面有問題的話,也感謝大家指出。