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

题目要求:

一、 基本要求

1.1
使用
CT107D
单片机竞赛板,完成“彩灯控制器”功能的程序设计与调

试;

1.2
设计与调试过程中,可参考组委会提供的“资源数据包”;

1.3 Keil
工程文件以准考证号命名,完成设计后,提交完整、可编译的
Keil

工程文件到服务器。

二、 硬件框图

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

三、 功能描述

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
的顺序,从右到左单循环点亮。

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

3.4 亮度等级控制

检测电位器
RB2
的输出电压,控制
8

LED
指示灯的亮度,要求在
0V-5V

的可调区间内,实现
4
个均匀分布的
LED
指示灯亮度等级。

3.5 按键功能

1
)按键
S7
定义为“启动
/
停止”按键,按下后启动或停止
LED
的流转。

2
)按键
S6
定义为“设置”按键,按键按下后数码管进入“流转间隔”

设置界面,如下图所示:

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

通过按键
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();

        

    }

}

 

本文来自网络,不代表协通编程立场,如若转载,请注明出处:https://net2asp.com/e07e744eda.html