题目要求:
一、 基本要求
二、 硬件框图
三、 功能描述
3.1 基本功能描述
3.2 设计说明
3.3 LED 指示灯工作模式
3.4 亮度等级控制
3.5 按键功能
底层函数内容:
1.初始化底层驱动专用文件
比如先用3个IO口控制74HC138译码器,控制Y4为低电平;当Y4为低电平时,或非门74HC02控制Y4C为高电平,使74HC573的OE端口有效,OE端口有效时,可使用P0口控制LED的亮灭。
可以去多了解74HC138译码器,74HC02或非门,74HC573八路输出透明锁存器的相关内容会更好理解
#include <Init.h>
//关闭外设
void System_Init()
{
P0 = 0xff;
P2 = P2 & 0x1f | 0x80;
P2 &= 0x1f;
P0 = 0x00;
P2 = P2 & 0x1f | 0xa0;
P2 &= 0x1f;
}
#include <STC15F2K60S2.H>
void System_Init();
2.Led底层驱动专用文件
与初始化底层驱动专用文件同理,需要了解对应的锁存器控制,可以在使用的芯片数据手册查看
#include <Led.h>
void Led_Disp(unsigned char addr,enable)
{
static unsigned char temp = 0x00;
static unsigned char temp_old = 0xff;
if(enable)
temp |= 0x01 << addr;
else
temp &= ~(0x01 << addr);
if(temp != temp_old)
{
P0 = ~temp;
P2 = P2 & 0x1f |0x80;
P2 &= 0x1f;
temp_old = temp;
}
}
#include <STC15F2K60S2.H>
void Led_Disp(unsigned char addr,enable);
3.按键底层驱动专用文件
(板子上的按键从按键4开始到按键19,可根据实际硬件修改)
#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;
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;
}
//头文件
#include <STC15F2K60S2.H>
unsigned char Key_Read();
4.数码管底层驱动专用文件
(这个板子使用的为共阳数码管,若使用的为共阴数码管要更换对应的段码表和位选表;与初始化底层驱动专用文件同理,需要了解对应的锁存器控制,可以在使用的芯片数据手册查看)
#include <Seg.h>
unsigned char Seg_Dula[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff};//数码管段码储存数组
unsigned char Seg_Wela[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};//数码管位码储存数组
void 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;
}
//头文件
#include <STC15F2K60S2.H>
void Seg_Disp(unsigned char wela,dula,point);
5.数模转换底层驱动专用头文件
/* # I2C代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include "iic.h"
#include <intrins.h>
#define DELAY_TIME 5
#define Photo_Res_Channel 0c41
#define Adj_Res_Channel 0x43
//总线引脚定义
sbit sda = P2^1; //数据线
sbit scl = P2^0; //时钟线
static void I2C_Delay(unsigned char n)
{
do
{
_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);
}
//通过I2C总线发送数据
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;
}
//从I2C总线上接收数据
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);
}
//函数名:ADC转换函数
//入口参数:要进行转换的通道控制位
//返回值:ADC转换的数值
//函数功能:对指定的通道进行ADC转换,函数返回转换的数值
unsigned char Adc_Read(unsigned char addr)
{
unsigned char temp;
I2CStart();//发送开启信号
I2CSendByte(0x90);//选择PCF8591芯片,确定写的模式
I2CWaitAck();//等待PCF8591反馈
I2CSendByte(addr);//确定要转换的通道(顺便,使能DA转换)
I2CWaitAck();//等待PCF8591反馈
I2CStart();//发送开启信号
I2CSendByte(0x91);//选择PCF8591芯片,确定读的模式
I2CWaitAck();//等待PCF8591反馈
temp = I2CReceiveByte();//接收数据
I2CSendAck(1);//选择不应答
I2CStop();//停止发送
return temp;
}
//函数名:写EEPROM函数
//入口参数:需要写入的字符串,写入的地址(务必为8的倍数),写入数量
//返回值:无
//函数功能:向EERPOM的某个地址写入字符串中特定数量的字符。
void EEPROM_Write(unsigned char*EEPROM_String,unsigned char addr,unsigned char num)
{
I2CStart();//发送开启信号
I2CSendByte(0xA0);//选择EEPROM芯片,确定写的模式
I2CWaitAck();//等待EEPROM反馈
I2CSendByte(addr);//写入要存储的数据地址
I2CWaitAck();//等待EEPROM反馈
while(num--)
{
I2CSendByte(*EEPROM_String++);
I2CWaitAck();//等待EEPROM反馈
I2C_Delay(200);
}
I2CStop();//停止发送
}
//函数名:读EEPROM函数
//入口参数:读到的数据需要存储的字符串,读取的地址(务必为8的倍数),读取的数量
//返回值:无
//函数功能:读取EERPOM的某个地址中的数据,并存放在字符串数组中。
void EEPROM_Read(unsigned char*EEPROM_String,unsigned char addr,unsigned char num)
{
I2CStart();//发送开启信号
I2CSendByte(0xA0);//选择EEPROM芯片,确定写的模式
I2CWaitAck();//等待EEPROM反馈
I2CSendByte(addr);//写入要存储的数据地址
I2CWaitAck();//等待EEPROM反馈
I2CStart();//发送开启信号
I2CSendByte(0xA1);//选择EEPROM芯片,确定读的模式
I2CWaitAck();//等待EEPROM反馈
while(num--)
{
*EEPROM_String++ = I2CReceiveByte();//将要写入的信息写入
if(num) I2CSendAck(0);//发送应答
else I2CSendAck(1);//不应答
}
I2CStop();//停止发送
}
//头文件
#include <STC15F2K60S2.H>
void I2CStart(void);
void I2CStop(void);
void I2CSendByte(unsigned char byt);
unsigned char I2CReceiveByte(void);
unsigned char I2CWaitAck(void);
void I2CSendAck(unsigned char ackbit);
unsigned char Adc_Read(unsigned char addr);
void EEPROM_Write(unsigned char*EEPROM_String,unsigned char addr,unsigned char num);
void EEPROM_Read(unsigned char*EEPROM_String,unsigned char addr,unsigned char num);
工程主函数内容:
1.头文件声明(把需要用到的头文件添加进来)
//头文件声明区
#include <STC15F2K60S2.H>//单片机寄存器专用头文件
#include "Init.h"//初始化底层驱动专用头文件
#include "Led.h"//Led底层驱动专用头文件
#include "Key.h"//按键底层驱动专用头文件
#include "Seg.h"//数码管底层驱动专用头文件
#include "iic.h"//单总线底层驱动专用头文件
2.变量声明(把需要用到的所有变量现在这里进行声明)
//变量声明区
unsigned char Key_Val,Key_Old,Key_Down,Key_Up;//按键专用变量
unsigned char Key_Slow_Down;//按键减速专用变量
unsigned char Sed_Pos;//数码管扫描专用变量
unsigned char Seg_Slow_Down;//数码管减速专用变量
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 ucLed[8] = {0,0,0,0,0,0,0,0};//Led显示数据存放数组
unsigned int Led_Time_Disp[4] = {400,400,400,400};//流转时间间隔数据存放数组
unsigned int Led_Time_Ctrol[4] = {400,400,400,400};//流转时间间隔数据控制数组
unsigned char Led_Time_Index;//流转时间间隔指针 --运行模式编号
unsigned char Seg_Index;//数码管索引值 0-熄灭 1-模式编号闪 2-流转间隔闪
unsigned int Timer_400ms;//400ms计时变量
bit Seg_Star_Flag;//数码管闪烁标志位
unsigned char EEPROM_Dat[4];//EEPROM专用数组
bit Seg_Disp_Mode;//数码管显示专用变量 0-参数界面 1-亮度等级界面
unsigned char Led_Mode;//系统流转模式变量
unsigned int Ms_Tick;//计时变量
bit System_Flag;//流转启动标志位 0-暂停 1-启动
unsigned char Led_Pos;//Led流转专用变量
unsigned char Led_Level;//Led亮度等级变量
unsigned char Led_Count;//Led计数变量
unsigned char i;//For循环专用变量
3.按键处理函数(在这里编写按键控制的函数)
//键盘处理函数
void Key_Proc()
{
if(Key_Slow_Down)return;
Key_Slow_Down = 1;//键盘减速程序
Key_Val = Key_Read();//实时读取键码值
Key_Down = Key_Val & (Key_Val ^ Key_Old);//捕捉按键下降沿
Key_Up = ~ Key_Val & (Key_Val ^ Key_Old);//捕捉按键上升沿
Key_Old = Key_Val;//辅助扫描变量
if(Seg_Index == 0) //处于非设置状态
{
if(Key_Old == 4)//长按S4
Seg_Disp_Mode = 1;//切换到亮度等级显示界面
else
Seg_Disp_Mode = 0;//切换回数据显示界面
}
switch(Key_Down)
{
case 6://设置按键
if(++Seg_Index == 3)
Seg_Index = 0;
if(Seg_Index == 0)//退出设置界面
{
Led_Time_Index = 0;//复位指针值
for(i=0;i<4;i++)
{
Led_Time_Ctrol[i] = Led_Time_Disp[i];//保存设置参数
EEPROM_Dat[i] = Led_Time_Ctrol[i] / 100;//EEPROM数据处理,前面给到初值为400,只需要写入4个数据,直接这样使用
}
EEPROM_Write(EEPROM_Dat,0,4);//将数据保存到EEPROM中
}
break;
case 7:
System_Flag ^= 1;//启动按键
break;
case 5://自加按键
if(Seg_Index == 1)//选中参数编号
{
if(++Led_Time_Index == 4)//四种模式循环切换
Led_Time_Index = 0;
}
else if(Seg_Index == 2)//选中流转间隔
{
Led_Time_Disp[Led_Time_Index] += 100;
if(Led_Time_Disp[Led_Time_Index] > 1200)//限制上限为1200
Led_Time_Disp[Led_Time_Index] = 1200;
}
break;
case 4://自减按键
if(Seg_Index == 1)//选中参数编号
{
if(--Led_Time_Index == 255)//四种模式循环切换
Led_Time_Index = 3;
}
else if(Seg_Index == 2)//选中流转间隔
{
Led_Time_Disp[Led_Time_Index] -= 100;
if(Led_Time_Disp[Led_Time_Index] < 400)//限制下限为400
Led_Time_Disp[Led_Time_Index] = 400;
}
break;
}
}
4.信息处理函数(需要使用到到的函数进行简单的预处理)
//信息处理函数
void Seg_Proc()
{
if(Seg_Slow_Down)return;
Seg_Slow_Down = 1;//数码管减速程序
Led_Level = Adc_Read(0x03) / 64;//实时获取亮度等级,分为了4个等级即(0-255)/ 64 = 4
if(Seg_Disp_Mode == 0)//处于设置界面
{
Seg_Buf[0] = Seg_Buf[2] = 11;//显示-
Seg_Buf[1] = Led_Time_Index+1;
Seg_Buf[4] = Led_Time_Disp[Led_Time_Index] / 1000 % 10;
Seg_Buf[5] = Led_Time_Disp[Led_Time_Index] / 100 % 10;
Seg_Buf[6] = Led_Time_Disp[Led_Time_Index] / 10 % 10;
Seg_Buf[7] = Led_Time_Disp[Led_Time_Index] % 10;
if(Seg_Index == 1)//运行编号闪烁
{
Seg_Buf[0] = Seg_Buf[2] = Seg_Star_Flag?10:11;//显示-
Seg_Buf[1] = Seg_Star_Flag?10:Led_Time_Index+1;
}
else if(Seg_Index == 2)//流转时间间隔闪烁
{
Seg_Buf[4] = Seg_Star_Flag?10:Led_Time_Disp[Led_Time_Index] / 1000 % 10;
Seg_Buf[5] = Seg_Star_Flag?10:Led_Time_Disp[Led_Time_Index] / 100 % 10;
Seg_Buf[6] = Seg_Star_Flag?10:Led_Time_Disp[Led_Time_Index] / 10 % 10;
Seg_Buf[7] = Seg_Star_Flag?10:Led_Time_Disp[Led_Time_Index] % 10;
}
if(Seg_Buf[4] == 0)Seg_Buf[4] = 10;//只有最高位需要考虑是否高位熄灭
}
else//处于亮度等级界面
{
for(i=0;i<6;i++)//熄灭前6个数码管
Seg_Buf[i] = 10;
Seg_Buf[6] = 11;
Seg_Buf[7] = Led_Level+1;
}
}
5.其他函数(其他编写的函数,在这里书写会比较方便理解)
//其他函数
void Led_Proc()
{
unsigned char i;
Beep(1);
if(System_Flag == 1)//系统处于启动状态
{
if(Ms_Tick == Led_Time_Ctrol[Led_Mode])//系统计时时间达到流转时间间隔
{
Ms_Tick = 0;//复位计时 便于下次进入
switch(Led_Mode)
{
case 0://模式1-从L1到L8
if(++Led_Pos == 8)
{
Led_Pos = 7;//模式2起始值
Led_Mode = 1;//切换到模式2
}
break;
case 1://模式2-从L8到L1
if(--Led_Pos == 255)
{
Led_Pos = 7;//模式3起始值
Led_Mode = 2;//切换到模式3
}
break;
case 2://模式3-07 16 25 34
Led_Pos += 9;
if(Led_Pos > 34)
{
Led_Pos = 34;//模式4起始值
Led_Mode = 3;//切换到模式4
}
break;
case 3://模式4-34 25 16 07
Led_Pos -= 9;
if(Led_Pos > 200)
{
Led_Pos = 0;//模式1起始值
Led_Mode = 0;//切换到模式1
}
break;
}
}
}
if(Led_Mode < 2)//系统处于前两种流转模式时
{
for(i=0;i<8;i++)
ucLed[i] = (i == Led_Pos);
}
else//系统处于后两种流转模式时
{
for(i=0;i<8;i++)
ucLed[i] =(i == (Led_Pos / 10) || i == (Led_Pos % 10));//点亮十位跟个位所对应的Led
}
// //两种情况可合并简化成一种 只需要屏蔽掉在前两种模式0X的时候 十位也会满足逻辑为真的情况
// for(i=0;i<8;i++)
// ucLed[i] = ((i == (Led_Pos / 10) & (Led_Mode / 2)) || (i == (Led_Pos % 10)));
}
6.定时器中断初始化函数
(这个可以使用STC的定时器计算那里生成c代码,后面要自己添加ET0,EA打开中断)这里使用定时器0计数,定时器1计时
//定时器中断初始化函数
void Timer0Init(void) //1毫秒@12.000MHz
{
//AUXR &= 0x7F; //定时器时钟12T模式,这个AUXR不能配置两次
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x05; //GATE = 0 计数模式 16位不自动重装//设
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
// ET0 = 1; //这两个不注释会在显示控制出现一个0,然后控制的按键不能正常使用
// EA = 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;
}
7.定时器1中断服务函数
(为了定时执行特定的任务,如此处设置了定时的时间触发了数码管和LED产生特定反应)//中断在测试时可以先注释掉,但是这里按键状态有延时,测试按键时可以解除注释
//定时器中断服务函数
void Timer1server ()interrupt 3
{
if(++Key_Slow_Down == 10)Key_Slow_Down = 0;//键盘减速专用
if(++Seg_Slow_Down == 10)Seg_Slow_Down = 0;//数码管减速专用
if(++Sed_Pos == 8)Sed_Pos = 0;//数码管显示专用
if(++Led_Count == 12)Led_Count = 0;//Led一个显示周期
if(Seg_Index != 0 || Seg_Disp_Mode == 1)
Seg_Disp(Sed_Pos,Seg_Buf[Sed_Pos],Seg_Point[Sed_Pos]);
else
Seg_Disp(Sed_Pos,10,0);
if(Led_Count <= ((Led_Level+1) * 3))//周期中均分四个亮度等级 0 4 8 12
Led_Disp(Sed_Pos,ucLed[Sed_Pos]);
else
Led_Disp(Sed_Pos,0);
if(++Timer_400ms == 400)//四百毫秒取反一次
{
Timer_400ms = 0;
Seg_Star_Flag ^= 1;
}
if(System_Flag == 1)//系统启动时开始计时
Ms_Tick++;
}
8.主函数Main(调用书写的函数实现所需的相应功能)
//Main
void main()
{ unsigned char i;//For循环专用变量
EEPROM_Read(EEPROM_Dat,0,4);//读取EEPROM数据
for(i=0;i<4;i++)
Led_Time_Ctrol[i]=Led_Time_Disp[i] = EEPROM_Dat[i] * 100;//数据处理
Sys_Init ();
Timer0Init();
Timer1Init();
while(1)
{
Led_Proc();
Seg_Proc();
Key_Proc();
}
}