蓝桥杯第九届省赛题-----彩灯控制系统笔记

题目要求:

一、 基本要求

1.1 使用 CT107D 单片机竞赛板,完成“彩灯控制器”功能的程序设计与调
试;
1.2 设计与调试过程中,可参考组委会提供的“资源数据包”;
1.3 Keil 工程文件以准考证号命名,完成设计后,提交完整、可编译的 Keil
工程文件到服务器。

二、 硬件框图

蓝桥杯第九届省赛题-----彩灯控制系统笔记_第1张图片

三、 功能描述

3.1 基本功能描述

通过单片机控制 8 LED 指示灯按照特定的顺序(工作模式)亮灭;指
示灯的流转间隔可通过按键调整,亮度可由电位器 RB2 进行控制;各工
作模式的流转间隔时间需在 E2PROM 中保存,并可在硬件重新上电后,
自动载入。

3.2 设计说明

1 )关闭蜂鸣器、继电器等与本试题程序设计无关的外设资源;
2 )设备上电后默认数码管、 LED 指示灯均为熄灭状态;
单片机
LED 指示灯
按键
模拟输入
数码管显示
E2PROM 存储器 3 )流转间隔可调整范围为 400ms-1200ms
4 )设备固定按照模式 1 、模式 2 、模式 3 、模式 4 的次序循环往复运行。

3.3 LED 指示灯工作模式

1 )模式 1 :按照 L1 L2…L8 的顺序,从左到右单循环点亮。
2 )模式 2 :按照 L8 L7…L1 的顺序,从右到左单循环点亮。
蓝桥杯第九届省赛题-----彩灯控制系统笔记_第2张图片

3.4 亮度等级控制

检测电位器 RB2 的输出电压,控制 8 LED 指示灯的亮度,要求在 0V-5V
的可调区间内,实现 4 个均匀分布的 LED 指示灯亮度等级。

3.5 按键功能

1 )按键 S7 定义为“启动 / 停止”按键,按下后启动或停止 LED 的流转。
2 )按键 S6 定义为“设置”按键,按键按下后数码管进入“流转间隔”
设置界面,如下图所示:
蓝桥杯第九届省赛题-----彩灯控制系统笔记_第3张图片
通过按键 S6 可切换选择“运行模式”和“流转间隔”两个显示单元,
当前被选择的显示单元以 0.8 秒为间隔亮灭
3 )按键 S5 定义为“加”按键,在设置界面下,按下该键,若当前选择
的是运行模式,则运行模式编号加 1 ,若当前选择的是流转间隔,则
流转间隔增加 100ms
4 )按键 S4 定义为“减”按键,在设置界面下,按下该键,若当前选择
的是运行模式,则运行模式编号减 1 ,若当前选择的是流转间隔,则
流转间隔减少 100ms
5 )按键功能说明:
a )按键 S4 S5 的“加”、“减”功能只在“设置状态”下有效,数
值的调整应注意边界属性。
b )在非“设置状态”下,按下 S4 按键可显示指示灯当前的亮度等
级, 4 个亮度等级从暗到亮,依次用数字 1 2 3 4 表示;松开
S4 按键,数码管显示关闭。
亮度等级的显示格式如下图所示:

底层函数内容:


1.初始化底层驱动专用文件

比如先用3个IO口控制74HC138译码器,控制Y4为低电平;当Y4为低电平时,或非门74HC02控制Y4C为高电平,使74HC573的OE端口有效,OE端口有效时,可使用P0口控制LED的亮灭。
可以去多了解74HC138译码器,74HC02或非门,74HC573八路输出透明锁存器的相关内容会更好理解
#include

//关闭外设
void System_Init()
{
    P0 = 0xff;
    P2 = P2 & 0x1f | 0x80;
    P2 &= 0x1f;
    P0 = 0x00;
    P2 = P2 & 0x1f | 0xa0;
    P2 &= 0x1f;
}

#include
void System_Init();

2.Led底层驱动专用文件

与初始化底层驱动专用文件同理,需要了解对应的锁存器控制,可以在使用的芯片数据手册查看
#include

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
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

unsigned char Key_Read();

4.数码管底层驱动专用文件

(这个板子使用的为共阳数码管,若使用的为共阴数码管要更换对应的段码表和位选表;与初始化底层驱动专用文件同理,需要了解对应的锁存器控制,可以在使用的芯片数据手册查看)
#include

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

void Seg_Disp(unsigned char wela,dula,point);

5.数模转换底层驱动专用头文件


/*    #   I2C代码片段说明
    1.     本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
    2.     参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
        中对单片机时钟频率的要求,进行代码调试和修改。
*/

#include "iic.h"
#include

#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

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 //单片机寄存器专用头文件
#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();
        
    }
}
 

你可能感兴趣的:(蓝桥杯笔记,蓝桥杯,笔记,职场和发展,单片机)