前言
第十二屆省賽涉及知識點:NE555頻率數據讀取,NE555頻率轉換周期,PCF8591同時測量光敏電阻和電位器的電壓、按鍵長短按判斷。本試題涉及模塊較少,題目不難,基本上準備充分的都能完整的實現每一個功能,并且板子上都能實現,一個惡心的地方就是通過PCF8591只采集一條通道的電壓值是沒有問題的,但是同時采集兩條通道的時候,會出現問題,在另一篇文章已經給出了解決方法:
PCF8591一次測量多個通道導致數值不準確解決方法
附件:藍橋杯單片機組第十二屆省賽第二批次
一、閱讀題目,了解性能需求
可以得出以下信息:
- 板子上需要將P34與SIGNAL通過跳線帽短接讀取NE555產生的頻率。
NE555部分已經詳細地講過如何實現了,可以點擊下方傳送門查閱:
傳送門:NE555模塊
- 數碼管顯示的頻率、周期和電壓值都是實際值,題目中說到的采集頻率和電壓只有在LED燈有用。
二、底層函數搭建
1.初始化
Init.h
#ifndef __Init_H__
#define __Init_H__void Init();#endif
Init.c
#include <STC15F2K60S2.H>
void Init()
{P0 = 0xff;P2 = P2 & 0x1f | 0x80;P2 &= 0x1f;P0 = 0x00;P2 = P2 & 0x1f | 0xa0;P2 &= 0x1f;
}
2.NE555和獨立按鍵
由于NE555是通過P34引腳測量的,所以需要修改獨立按鍵的底層代碼。(屏蔽P34)
Key.h
#include <STC15F2K60S2.H>
#ifndef __Key_H__
#define __Key_H__unsigned char KeyDisp();#endif
Key.c
#include <STC15F2K60S2.H>unsigned char KeyDisp()
{unsigned char temp = 0;P44 = 0;P42 = 1;P35 = 1;if(P30 == 0)temp = 7;if(P31 == 0)temp = 6;if(P32 == 0)temp = 5;if(P33 == 0)temp = 4; return temp;
}
定時器部分
void Timer0_Init(void) //0毫秒@12.000MHz
{TMOD &= 0xF0; //設置定時器模式TMOD |= 0x05;TL0 = 0; //設置定時初始值TH0 = 0; //設置定時初始值TF0 = 0; //清除TF0標志TR0 = 1; //定時器0開始計時
}void Timer1_Init(void) //1毫秒@12.000MHz
{AUXR &= 0xBF; //定時器時鐘12T模式TMOD &= 0x0F; //設置定時器模式TL1 = 0x18; //設置定時初始值TH1 = 0xFC; //設置定時初始值TF1 = 0; //清除TF1標志TR1 = 1; //定時器1開始計時ET1 = 1; //使能定時器1中斷EA = 1;
}void Timer1_Isr(void) interrupt 3
{systick++;if(++SegPos == 8)SegPos = 0;SegDisp(SegPos, SegBuf[SegPos], SegPoint[SegPos]);if(++Time_1s == 1000){Time_1s = 0;f = (TH0 << 8) | TL0;TH0 = TL0 = 0;}
}
2.數碼管部分
數碼管底層代碼引入
Seg.h
#ifndef __Seg_H__
#define __Seg_H__void SegDisp(unsigned char wela, unsigned char dula, unsigned char point);#endif
Seg.c
#include <STC15F2K60S2.H>code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0xff, //空
0xbf, //-
0x8e, //F
0xc1, //U
0xc8 //n
};void SegDisp(unsigned char wela, unsigned char dula, unsigned char point)
{P0 = 0xff;P2 = P2 & 0x1f | 0xe0;P2 &= 0x1f;P0 = (0x01 << wela);P2 = P2 & 0x1f | 0xc0;P2 &= 0x1f;P0 = Seg_Table[dula];if(point)P0 &= 0x7f;P2 = P2 & 0x1f | 0xe0;P2 &= 0x1f;
}
3.Led部分
Led.h
#ifndef __Led_H__
#define __Led_H__void LedDisp(unsigned char *ucLed);#endif
Led.c
#include <STC15F2K60S2.H>void LedDisp(unsigned char *ucLed)
{unsigned char i, temp = 0x00;static unsigned char temp_old = 0xff;for(i = 0; i < 8; i++)temp |= (ucLed[i] << i);if(temp != temp_old){P0 = ~temp;P2 = P2 & 0x1f | 0x80;P2 &= 0x1f;temp_old = temp;}
}
4.PCF8591部分
注:本篇文章中解決多通道讀取采用的是連續讀取兩次電壓值,舍棄第一個電壓值的方法。
pcf8591.h
#ifndef __pcf8591_H__
#define __pcf8591_H__unsigned char AD_Read(unsigned char add);#endif
pcf8591.c
#include <STC15F2K60S2.H>
#include <intrins.h>/* # I2C代碼片段說明1. 本文件夾中提供的驅動代碼供參賽選手完成程序設計參考。2. 參賽選手可以自行編寫相關代碼或以該代碼為基礎,根據所選單片機類型、運行速度和試題中對單片機時鐘頻率的要求,進行代碼調試和修改。
*/
#define DELAY_TIME 5
sbit 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);
}unsigned char AD_Read(unsigned char add)
{unsigned char temp;I2CStart();I2CSendByte(0x90);I2CWaitAck();I2CSendByte(add);I2CWaitAck();I2CStart();I2CSendByte(0x91);I2CWaitAck();temp = I2CReceiveByte(); I2CSendAck(1);I2CStop();// 再次讀取I2CStart();I2CSendByte(0x90);I2CWaitAck();I2CSendByte(add);I2CWaitAck();I2CStart();I2CSendByte(0x91);I2CWaitAck();temp = I2CReceiveByte();I2CSendAck(1);I2CStop();return temp;
}
5.main.c
#include <STC15F2K60S2.H>
#include "Init.h"
#include "LED.h"
#include "Key.h"
#include "Seg.h"
#include "pcf8591.h"/* 變量聲明區 */
unsigned char Key_Slow; //按鍵減速變量 10ms
unsigned char Key_Val, Key_Down, Key_Up, Key_Old; //按鍵檢測四件套
unsigned int Seg_Slow; //數碼管減速變量 500ms
unsigned char Seg_Buf[] = {10,10,10,10,10,10,10,10,10,10};//數碼管緩存數組
unsigned char Seg_Pos;//數碼管緩存數組專用索引
unsigned char Seg_Point[8] = {0,0,0,0,0,0,0,0};//數碼管小數點使能數組
unsigned char ucLed[8] = {0,0,0,0,0,0,0,0};//LED顯示數據存放數組
unsigned int Time_1s, f;/* 按鍵處理函數 */
void Key_Proc()
{if(Key_Slow) return;Key_Slow = 1; //按鍵減速Key_Val = Key();Key_Down = Key_Val & ~Key_Old; Key_Up = ~Key_Val & Key_Old;Key_Old = Key_Val;}/* 信息處理函數 */
void Seg_Proc()
{if(Seg_Slow) return;Seg_Slow = 1; //數碼管減速}/* 其他顯示函數 */
void Led_Proc()
{}/* 定時器0只用于計數 */
void Timer0_Init(void) //1毫秒@12.000MHz
{TMOD &= 0xF0; //設置定時器模式TMOD |= 0x05;TL0 = 0; //設置定時初始值TH0 = 0; //設置定時初始值TF0 = 0; //清除TF0標志TR0 = 1; //定時器0開始計時
}/* 定時器1用于計時 */
void Timer1_Init(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_Server() interrupt 3
{/* NE555 */if(++Time_1s == 1000){Time_1s = 0;f = (TH0 << 8) | TL0;TH0 = TL0 = 0;}
}void main()
{Init();Timer0_Init();Timer1_Init();while(1){Key_Proc(); Seg_Proc();Led_Proc();}
}
三、數碼管部分
老樣子,定義SegMode變量來控制三個頁面,SegMode值為0時為頻率顯示頁面,為1時為周期設置界面,為2時為電壓顯示界面
在數碼管Seg.c底層函數的段碼表已經包含F、N、U和-的段碼表了
1.頻率顯示頁面
NE555測量頻率的上限值是五位數,題目要求七位顯示頻率數據,要求高位為0熄滅,可以直接默認前兩位數碼管熄滅,再對后五個數碼管進行高位熄滅,高位熄滅的實現邏輯如下:
unsigned char i = 0;
while(SegBuf[i] == 0)//循環條件:SegBuf[i]不為0時退出
{SegBuf[i] = 10;if(++i == 7)break;
}
數碼管實現如下:
void SegProc()
{unsigned char i;switch(SegMode){case 0:SegPoint[5] = 0;SegBuf[0] = 12;SegBuf[1] = 10;SegBuf[2] = 10;SegBuf[3] = f / 10000 % 10;SegBuf[4] = f / 1000 % 10;SegBuf[5] = f / 100 % 10;SegBuf[6] = f / 10 % 10;SegBuf[7] = f % 10;i = 3;while(!SegBuf[i]){SegBuf[i] = 10;if(++i == 7)break;}break;}
}
2.周期顯示頁面
從題目可以得到,顯示的周期是頻率的倒數,也就是T= 1 f \frac{1}{f} f1?,單位為us,而1s = 100 0000us,所以轉換周期時要省上10 6 ^6 6。代碼實現如下:
case 1:T = 1000000 / f;SegBuf[0] = 14;SegBuf[1] = T / 1000000 % 10;SegBuf[2] = T / 100000 % 10;SegBuf[3] = T / 10000 % 10;SegBuf[4] = T / 1000 % 10;SegBuf[5] = T / 100 % 10;SegBuf[6] = T / 10 % 10;SegBuf[7] = T % 10;i = 1;while(!SegBuf[i]){SegBuf[i] = 10;if(++i == 7)break;}
break;
3.電壓顯示頁面
電壓讀取
這邊給出兩種方法
方法一:定義float型變量
idata float RD1_100x, RB2_100x;
idata bit ChannelMode;void ADCProc()
{RD1_100x = AD_Read(0x01) / 51.0; RB2_100x = AD_Read(0x03) / 51.0;
}void SegProc()
{case 2:SegBuf[0] = 13;SegBuf[1] = 11;SegBuf[2] = !ChannelMode ? 1 : 3;SegBuf[3] = 10;SegBuf[4] = 10;SegBuf[5] = !ChannelMode ? RD1_100x % 10 : RB2_100x % 10;SegPoint[5] = 1;SegBuf[6] = !ChannelMode ? RD1_100x * 10 % 10: RB2_100x * 10 % 10;SegBuf[7] = !ChannelMode ? RD1_100x * 100 % 10 : RB2_100x * 100 % 10;break;
}
方法二:定義unsigned int型變量接受讀取的電壓值放大100倍后的值
idata u16 RD1_100x, RB2_100x;
void ADCProc()
{RD1_100x = AD_Read(0x01) * 100 / 51; RB2_100x = AD_Read(0x03) * 100 / 51;
}
void SegProc()
{case 2:SegBuf[0] = 13;SegBuf[1] = 11;SegBuf[2] = !ChannelMode ? 1 : 3;SegBuf[3] = 10;SegBuf[4] = 10;SegBuf[5] = !ChannelMode ? RD1_100x / 100 : RB2_100x / 100;SegPoint[5] = 1;SegBuf[6] = !ChannelMode ? RD1_100x / 10 % 10 : RB2_100x / 10 % 10;SegBuf[7] = !ChannelMode ? RD1_100x % 10 : RB2_100x % 10;break;
}
4.數碼管完整代碼:
void SegProc()
{unsigned char i;if(Seg_Slow) return;Seg_Slow = 1; //數碼管減速switch(SegMode){case 0:SegPoint[5] = 0;SegBuf[0] = 12;SegBuf[1] = 10;SegBuf[2] = 10;SegBuf[3] = f / 10000 % 10;SegBuf[4] = f / 1000 % 10;SegBuf[5] = f / 100 % 10;SegBuf[6] = f / 10 % 10;SegBuf[7] = f % 10;i = 3;while(!SegBuf[i]){SegBuf[i] = 10;if(++i == 7)break;}break;case 1:T = 1000000 / f;SegBuf[0] = 14;SegBuf[1] = T / 1000000 % 10;SegBuf[2] = T / 100000 % 10;SegBuf[3] = T / 10000 % 10;SegBuf[4] = T / 1000 % 10;SegBuf[5] = T / 100 % 10;SegBuf[6] = T / 10 % 10;SegBuf[7] = T % 10;i = 1;while(!SegBuf[i]){SegBuf[i] = 10;if(++i == 7)break;}break;case 2:SegBuf[0] = 13;SegBuf[1] = 11;SegBuf[2] = !ChannelMode ? 1 : 3;SegBuf[3] = 10;SegBuf[4] = 10;SegBuf[5] = !ChannelMode ? RD1_100x / 100 : RB2_100x / 100;SegPoint[5] = 1;SegBuf[6] = !ChannelMode ? RD1_100x / 10 % 10 : RB2_100x / 10 % 10;SegBuf[7] = !ChannelMode ? RD1_100x % 10 : RB2_100x % 10;break;}
}
四、按鍵部分
S4和S5的實現很簡單,直接給出代碼
S6的功能是任意界面下按下S6后,保存電位器的電壓數據到電位器緩存變量中。
idata u16 RD1_100x, RB2_100x;
idata u16 RB2_100x_keep, f_keep;void KeyProc()
{KeyVal = KeyDisp();KeyDown = KeyVal & ~KeyOld;KeyUp = ~KeyVal & KeyOld;KeyOld = KeyVal;switch(KeyDown){case 4:if(++SegMode == 3){SegMode = 0;ChannelMode = 0;}break;case 5:ChannelMode ^= 1;break;case 6:RB2_100x_keep = RB2_100x;break;}
}
S7的功能是短按保存頻率,長按打開/關閉Led
這個也是很常考的點了,也很簡單
先定義一個Time_1000ms的unsigned int型變量放入定時器1中定時,當超過1000ms時置為1000(防止長按太久數據溢出),然后在設置一個按下S7的變量idata bit型變量CountFlag,當S7按下,CountFlag置1,定時器開始計時,松開S7,CountFlag置為0,計數值清零
注意:NE555和長按S7都是以定時1s為判斷,因此定義變量時不要重復定義!
void KeyProc()
{KeyVal = KeyDisp();KeyDown = KeyVal & ~KeyOld;KeyUp = ~KeyVal & KeyOld;KeyOld = KeyVal;if(KeyDown == 7)CountFlag = 1;if(KeyUp == 7){CountFlag = 0;if(Time_1000ms >= 1001){LedFlag = !LedFlag;}elsef_keep = f;}
}void Timer1_Isr(void) interrupt 3
{systick++;if(++SegPos == 8)SegPos = 0;SegDisp(SegPos, SegBuf[SegPos], SegPoint[SegPos]);if(++Time_1s == 1000){Time_1s = 0;f = (TH0 << 8) | TL0;TH0 = TL0 = 0;}if(CountFlag){if(++Time_1000ms >= 1001)Time_1000ms = 1001;}elseTime_1000ms = 0;
}
按鍵完整代碼
void KeyProc()
{KeyVal = KeyDisp();KeyDown = KeyVal & ~KeyOld;KeyUp = ~KeyVal & KeyOld;KeyOld = KeyVal;if(KeyDown == 7)CountFlag = 1;if(KeyUp == 7){CountFlag = 0;if(Time_1000ms >= 1001){LedFlag = !LedFlag;}elsef_keep = f;}switch(KeyDown){case 4:if(++SegMode == 3){SegMode = 0;ChannelMode = 0;}break;case 5:ChannelMode ^= 1;break;case 6:RB2_100x_keep = RB2_100x;break;}
}
五、Led部分
Led的實現完全沒有難度
直接給出代碼
void LedProc()
{if(LedFlag == 0){ucLed[0] = (RB2_100x > RB2_100x_keep);ucLed[1] = (f > f_keep);ucLed[2] = (SegMode == 0);ucLed[3] = (SegMode == 1);ucLed[4] = (SegMode == 2);}else{ucLed[0] = 0;ucLed[1] = 0;ucLed[2] = 0;ucLed[3] = 0;ucLed[4] = 0;}LedDisp(ucLed);
}
六、完整代碼
#include <STC15F2K60S2.H>
#include "Init.h"
#include "LED.h"
#include "Key.h"
#include "Seg.h"
#include "pcf8591.h"/* 變量聲明區 */
typedef unsigned char u8;
typedef unsigned int u16;idata u8 KeyVal, KeyDown, KeyUp, KeyOld;
idata u8 SegPos;
idata u8 SegMode;idata u16 f, Time_1s;
idata u16 T;
idata u16 RD1_100x, RB2_100x;
idata u16 RB2_100x_keep, f_keep;
idata u16 Time_1000ms;pdata u8 SegBuf[8] = {10,10,10,10,10,10,10,10};
pdata u8 SegPoint[8] = {0,0,0,0,0,0,0,0};
pdata u8 ucLed[8] = {0,0,0,0,0,0,0,0};idata bit ChannelMode;
idata bit CountFlag;
idata bit LedFlag;/* 按鍵處理函數 */
void Key_Proc()
{if(Key_Slow) return;Key_Slow = 1; //按鍵減速KeyVal = KeyDisp();KeyDown = KeyVal & ~KeyOld;KeyUp = ~KeyVal & KeyOld;KeyOld = KeyVal;if(KeyDown == 7)CountFlag = 1;if(KeyUp == 7){CountFlag = 0;if(Time_1000ms >= 1001){LedFlag = !LedFlag;}elsef_keep = f;}switch(KeyDown){case 4:if(++SegMode == 3){SegMode = 0;ChannelMode = 0;}break;case 5:ChannelMode ^= 1;break;case 6:RB2_100x_keep = RB2_100x;break;}
}/* 信息處理函數 */
void Seg_Proc()
{unsigned char i;if(Seg_Slow) return;Seg_Slow = 1; //數碼管減速switch(SegMode){case 0:SegPoint[5] = 0;SegBuf[0] = 12;SegBuf[1] = 10;SegBuf[2] = 10;SegBuf[3] = f / 10000 % 10;SegBuf[4] = f / 1000 % 10;SegBuf[5] = f / 100 % 10;SegBuf[6] = f / 10 % 10;SegBuf[7] = f % 10;i = 3;while(!SegBuf[i]){SegBuf[i] = 10;if(++i == 7)break;}break;case 1:T = 1000000 / f;SegBuf[0] = 14;SegBuf[1] = T / 1000000 % 10;SegBuf[2] = T / 100000 % 10;SegBuf[3] = T / 10000 % 10;SegBuf[4] = T / 1000 % 10;SegBuf[5] = T / 100 % 10;SegBuf[6] = T / 10 % 10;SegBuf[7] = T % 10;i = 1;while(!SegBuf[i]){SegBuf[i] = 10;if(++i == 7)break;}break;case 2:SegBuf[0] = 13;SegBuf[1] = 11;SegBuf[2] = !ChannelMode ? 1 : 3;SegBuf[3] = 10;SegBuf[4] = 10;SegBuf[5] = !ChannelMode ? RD1_100x / 100 : RB2_100x / 100;SegPoint[5] = 1;SegBuf[6] = !ChannelMode ? RD1_100x / 10 % 10 : RB2_100x / 10 % 10;SegBuf[7] = !ChannelMode ? RD1_100x % 10 : RB2_100x % 10;break;}
}/* 其他顯示函數 */
void Led_Proc()
{if(LedFlag == 0){ucLed[0] = (RB2_100x > RB2_100x_keep);ucLed[1] = (f > f_keep);ucLed[2] = (SegMode == 0);ucLed[3] = (SegMode == 1);ucLed[4] = (SegMode == 2);}else{ucLed[0] = 0;ucLed[1] = 0;ucLed[2] = 0;ucLed[3] = 0;ucLed[4] = 0;}LedDisp(ucLed);
}/* 定時器0只用于計數 */
void Timer0_Init(void) //1毫秒@12.000MHz
{TMOD &= 0xF0; //設置定時器模式TMOD |= 0x05;TL0 = 0; //設置定時初始值TH0 = 0; //設置定時初始值TF0 = 0; //清除TF0標志TR0 = 1; //定時器0開始計時
}/* 定時器1用于計時 */
void Timer1_Init(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_Server() interrupt 3
{/* NE555 */if(++Time_1s == 1000){Time_1s = 0;f = (TH0 << 8) | TL0;TH0 = TL0 = 0;}if(CountFlag){if(++Time_1000ms >= 1001)Time_1000ms = 1001;}elseTime_1000ms = 0;
}void main()
{Init();Timer0_Init();Timer1_Init();while(1){Key_Proc(); Seg_Proc();Led_Proc();}
}
本篇文章中的代碼已經通過4T測試
其余模塊代碼請自行添加到工程中即可運行,本篇文章僅提供一種實現思路,如有模塊代碼無法實現或者與題目要求相違,請移步評論區指出或私信我,看到會及時回復。
每周會更新兩篇模擬賽、省賽或國賽的文章,敬請期待。