目录
一、功能需求分析
1.1基本功能描述
通过单片机控制 8个 LED 指示灯按照特定的顺序(工作模式)亮灭:指示灯的流转间隔可通过按键调整,亮度可由电位器 RB2 进行控制:各工作模式的流转间隔时间需在 E2PROM 中保存,并可在硬件重新上电后自动载入。
1.2 LED指示灯工作模式
(1)模式 1:按照 L1、 L2…L8 的顺序,从左到右单循环点亮。
(2)模式 2:按照 L8、 L7…L1 的顺序,从右到左单循环点亮。
(3)模式 3:
图1 模式3彩灯运行状态说明
(4)模式4:
图2 模式4彩灯运行状态说明
1.3 亮度等级控制
检测电位器 RB2 的输出电压,控制 8 个 LED 指示灯的亮度,要求在 0V-5V的可调区间内,实现 4 个均匀分布的 LED 指示灯亮度等级。
1.4 按键功能
(1)按键 S7 定义为“启动/停止”按键,按下后启动或停止 LED 的流转。
(2)按键 S6 定义为“设置”按键,按键按下后数码管进入“流转间隔”设置界面,如下图所示:
图3 设置显示界面
通过按键 S6 可切换选择“运行模式”和“流转间隔” 两个显示单元。
图4 模式切换
(3)按键 S5 定义为“加”按键,在设置界面下,按下该键,若当前选择的是运行模式,则运行模式编号加 1,若当前选择的是流转间隔,则流转间隔增加 100ms。
(4)按键 S4 定义为“减”按键,在设置界面下,按下该键,若当前选择的是运行模式,则运行模式编号减 1,若当前选择的是流转间隔,则流转间隔减少 100ms。
(5)按键功能说明:
(a)按键 S4、S5 的“加”、“减”功能只在“设置状态”下有效,数值的调整应注意边界属性。
(b)在非“设置状态”下,按下 S4 按键可显示指示灯当前的亮度等级, 4 个亮度等级从暗到亮,依次用数字 1、 2、 3、 4 表示; 松开S4 按键,数码管显示关闭, 亮度等级的显示格式如下图所示:
图5 亮度等级显示界面
二、彩灯控制器总体设计
2.1 硬件系统
(1)LED灯模块
图6 LED灯原理图
根据原理图,我们可以知道: Y4锁存器控制led灯,P0输入低 电平点亮,输入高电平熄灭。
(2)数码管模块
图7 数码管模块原理图
数码管(板载共集成8位共阳级数码管)数码管分段选和位选
段选:控制每一位数码管亮的二级管的个数,比如上面的a,b,c,d等等
比如:
0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff低电平点亮)
0 1 2 3 4 5 6 7 8 9 全灭
位选:控制8位数码管中那几位点亮,比如com1-com8(高电平点亮)
对于静态数码管,直接选段,选位再点亮就行。
对于动态数码管:利用人眼的视觉暂留,配合定时器中断,每2ms刷新一次,就能达到稳定的效果。
(3)按键模块
图8 按键模块原理图
这就是我们用到的按键模块,在这里有一个J5的排针,在开发板上面分别标有KBD和BTN,其中KBD就是key board,键盘的意思(矩阵键盘),BTN表示button,独立按键,当我们用跳线帽把J5的2,3(BTN)两个脚连接起来的时候,我们可以根据原理图看到,S7,S6,S5,S4两边分别连接单片机的I/O口和GND,假设我们现在按下S7按键,这个时候就P30就会直接和GND相连接,把P30拉低,这个时候我们可以通过检测单片机的P30是否为0来判断按键是否被按下。
(4)HC138模块
而输入信号Y4C.Y5C,Y6C,Y7C则由以下或非门(实际就相当于非门)输出得到,而Y4,Y5,Y6,Y7则由74138译码器得到。
图9 HC138模块原理图
2.2软件系统
软件环境: Keil uVision 4.10
程序说明: IIC总线驱动程序
该程序包含了总线启动条件、总线停止条件、应答位控制、等待应答、通过I2C总线发送数据、从I2C总线上接收数据等函数。此外,还包含了一些与ADC和EEPROM相关的函数,用于向PCF8591发送信号并读取数据,以及对AT24C02 EEPROM进行读写操作。这些函数的具体实现方式都是通过向总线上发送特定的数据和控制信号来实现的。
三、详细代码
3.1 main.c
#include<reg52.h>
#include<intrins.h>
#include<iic.h>
/*定时器中断用于控制LED灯的流转速度。
PCF8591模块用于读取INT3电压值,根据不同电压等级控制LED灯的亮度。
EEPROM用于存储流转间隔的设置值。
*/
sbit hc138_A=P2^5;
sbit hc138_B=P2^6;
sbit hc138_C=P2^7;
sbit S7 = P3^0;
sbit S6 = P3^1;
sbit S5 = P3^2;
sbit S4 = P3^3;
#define uchar unsigned char
#define uint unsigned int
/*****************LED灯******************/
uchar lights[4][8]={{0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f}, //模式1
{0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe}, //模式2
{0x7e,0xbd,0xdb,0xe7,0x7e,0xbd,0xdb,0xe7}, //模式3
{0xe7,0xdb,0xbd,0x7e,0xe7,0xdb,0xbd,0x7e}}; //模式4
uchar flag_moshi = 1;//LED灯模式;1-4
uchar led_stop = 0; //控制led的流转,0为亮,1为不亮,即停止流转
uchar i = 1; //LED灯闪烁周期1-8
uint jiange = 600; //流转间隔400-1200
uchar flag_pwm = 4; //亮度等级 1-4
/*****************数码管和按键******************/
uchar key_value = 0;
uchar shuzi[10]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; //0-9
uchar flag_S6 = 0; //S6状态选择位,0为熄灭,1位运行模式,2为流转间隔
uint t = 0; //每10ms t+1,范围0-jiange*2
uchar adc = 0; //读取到的INT3的数据0-255(分为四个等级)
uchar pwm = 0; //电压等级
uchar LED_123 = 1;
void init(); //初始化
void delay(unsigned int k); //延时函数
void HC138(unsigned int n); //配置138译码器
void led_work(); //流水灯的运行函数
void Display(); //数码管显示函数
void key_scan(); //按键扫描函数
void key_real(); //按键实现函数
void main()
{
init();
jiange = read_at24c02(0x02); //读0x02地址的数据
jiange = jiange * 10; //读到的数据范围为0-255
if(jiange < 400)
jiange = 400; //防止第一次读到的数据过小
init_pcf8591();
while(1)
{
adc = adc_pcf8591(); //读取INT3电压值,数值范围0-255,对应电压0-5v
if(adc < 64)
flag_pwm = 1;
else if(adc < 128)
flag_pwm = 2;
else if(adc < 192)
flag_pwm = 3;
else
flag_pwm = 4; //4个电压等级对应不同显示亮度
key_scan();
key_real();
Display();
if(pwm<=flag_pwm) //4个电压等级对应不同显示亮度
{
if(led_stop == 0) //S7按键控制启停
{
if(flag_S6 == 0) //设置状态下禁用
{
led_work();
}
else
{
HC138(4);P0=0xff;
}
}
}
else
{
HC138(4);P0=0xff;
}
}
}
/*****配置138译码器*****/
void HC138(unsigned int n)
{
switch(n)
{
case 4:
hc138_A=0 ; hc138_B=0 ; hc138_C=1; break;
case 5:
hc138_A=1 ; hc138_B=0 ; hc138_C=1; break;
case 6:
hc138_A=0 ; hc138_B=1 ; hc138_C=1; break;
case 7:
hc138_A=1 ; hc138_B=1 ; hc138_C=1; break;
}
}
/*****数码管显示函数*****/
void Display()
{
char x = 0x01;
int i = 0;
for(i = 1;i<=8 ;i++)
{
HC138(6);
P0 = x;
x = _crol_(x,1); //_crol_循环左移
HC138(7);
P0 = 0xff;delay(50); //数码管消隐放到这个地方好一点
if((flag_S6 == 1 ) || (flag_S6 == 2))
{
if (flag_S6 == 1)
{
switch (i)
{
case 1 :
if (LED_123 == 1)
{
P0 = 0xbf; break;
}
else
{
P0 = 0xff;
break;
}
case 2 :
if (LED_123 == 1)
{
P0 = shuzi[flag_moshi]; break;
}
else
{
P0 = 0xff;
break;
}
case 3 :
if (LED_123 == 1)
{
P0 = 0xbf; break;
}
else
{
P0 = 0xff;
break;
}
case 4 :P0 = 0xff; break;
case 5 :if(jiange >900)
P0 = shuzi[1];
else
P0 = 0xff;
break;
case 6 :P0 = shuzi[jiange%1000/100]; break;
case 7 :P0 = shuzi[0]; break;
case 8 :P0 = shuzi[0]; break;
}
}
else
{
switch (i)
{
case 1 :P0 = 0xbf; break;
case 2 :P0 = shuzi[flag_moshi]; break;
case 3 :P0 = 0xbf; break;
case 4 :P0 = 0xff; break;
case 5 :
if(jiange >900)
{
if (LED_123 == 1)
{
P0 = shuzi[1];
}
else
{
P0 = 0xff;
}
}
else
{
P0 = 0xff;
}
break;
case 6 :
if (LED_123 == 1)
{
P0 = shuzi[jiange%1000/100]; break;
}
else
{
P0 = 0xff;
break;
}
case 7 :
if (LED_123 == 1)
{
P0 = shuzi[0]; break;
}
else
{
P0 = 0xff;
break;
}
case 8 :
if (LED_123 == 1)
{
P0 = shuzi[0]; break;
}
else
{
P0 = 0xff;
break;
}
}
}
}
else
P0 = 0xff;
delay(700);
P0 = 0xff;
}
}
/*****长按S4显示*****/
void Display1()
{
char x = 0x01;
int i = 0;
for(i = 1;i<=8 ;i++)
{
HC138(6);
P0 = x;
x = _crol_(x,1);
HC138(7);
P0 = 0xff;
delay(50); //数码管消隐
switch(i)
{
case 7:P0 = 0xbf;break;
case 8:P0 = shuzi[flag_pwm];break;
default:P0 = 0xff;break;
}
delay(500);P0 = 0xff;
}
}
/*****按键扫描函数*****/
void key_scan()
{
if((S4 == 0) || (S5 == 0) || (S6 == 0) || (S7 == 0)) //其中一个按键被按下则为1
{
delay(100); //延时消抖
if(S4 == 0) key_value = 4;
if(S5 == 0) key_value = 5;
if(S6 == 0) key_value = 6;
if(S7 == 0) key_value = 7;
while((S5 == 0) || (S6 == 0) || (S7 == 0))//等待按键松开
{
Display();
}
while(S4 == 0)
{
if(flag_S6 == 0)
Display1(); //仅在非设置状态下显示
else
Display(); //设置状态下依然显示设置界面
}
}
}
/*****按键实现函数*****/
void key_real()
{
if(key_value == 7)
{
if(led_stop == 0)
led_stop = 1;
else if(led_stop == 1)
led_stop = 0;
}
else if(key_value == 6)
{
switch(flag_S6)
{
case 0:flag_S6 = 1;break;
case 1:flag_S6 = 2;break;
case 2:flag_S6 = 0;break;
}
}
else if(key_value == 5)
{
if(flag_S6 == 1)
{
if(flag_moshi < 4){
flag_moshi = flag_moshi +1;
i=1;
}
}
else if(flag_S6 == 2)
{
if(jiange < 1200)
jiange = jiange + 100;
write_at24c02(0x02,jiange/10);//jiange发生变化,eeprom数据更新
} //eeprom每个地址最大储存范围为0-255
}
else if(key_value == 4)
{
if(flag_S6 == 1)
{
if(flag_moshi > 1)
{
flag_moshi = flag_moshi -1;
i=1;
}
}
else if(flag_S6 == 2)
{
if(jiange >400 )
{
jiange = jiange - 100;
write_at24c02(0x02,jiange/10);//jiange发生变化,eeprom数据更新
} //eeprom每个地址最大储存范围为0-255
}
}
key_value = 0;
}
/*****单个灯的运行函数*****/
void led_work()
{
HC138(4);
P0 = lights[flag_moshi-1][i-1];
}
//定时多少us,就将10000改为多少
void TIM0_Init()
{
TMOD = 0x01;
TH0 = (65535 - 10000)/256; //设置定时器初值,高八位放在TH0中,
TL0 = (65535 - 10000)%256; //设置定时器初值,低八位放在TL0中
//定时时间10000us = 10ms
TR0 = 1; //启动T0
ET0 = 1; //开T0中断
EA = 1; //开总中断
}
/*********定时器工作方式1*********/
void tim0_work() interrupt 1
{
static unsigned char _count_ = 0;
uchar tmp = led_stop;
++_count_;
TH0 = (65535 - 10000)/256; //设置定时器初值,高八位放在TH0中,
TL0 = (65535 - 10000)%256; //设置定时器初值,低八位放在TL0中
if (_count_ >= 30)
{
LED_123 = 1;
if (_count_ >= 60)
{
_count_ = 0;
}
}
else
{
LED_123 = 0;
}
//LED灯的启停
if (led_stop==1)
{
flag_S6 = 0;
Display();
}
while(led_stop==1){
if(S7 == 0)
{
delay(100); //延时消抖
while(S7 == 0)
{
flag_S6 = tmp;
led_stop=0;
}
}
}
pwm++;
t++;
if(pwm == 5)
{
pwm = 1;
}
if(t == jiange/10) //每10ms +1
{
t = 0;
i = i+1;
if(i == 9)
i = 1;
}
}
//初始化
void init()
{
HC138(5);
P0=0x00; //蜂鸣器和继电器初始化、、全关
HC138(4);
P0= 0xff; //LED小灯初始化、、全关
TIM0_Init(); //中断初始化
}
/*****延时函数*****/
void delay(unsigned int k)
{
while(k--);
}
3.2 iic.h
#ifndef _IIC_H
#define _IIC_H
//函数声明
void IIC_Start(void);
void IIC_Stop(void);
void IIC_Ack(bit ackbit);
void IIC_SendByte(unsigned char byt);
bit IIC_WaitAck(void);
unsigned char IIC_RecByte(void);
void init_pcf8591(void);
unsigned char adc_pcf8591(void);
void write_at24c02(unsigned char add,unsigned char val);
unsigned char read_at24c02(unsigned char add);
#endif
3.3 iic.c
/*
程序说明: IIC总线驱动程序
软件环境: Keil uVision 4.10
硬件环境: CT107单片机综合实训平台 8051,12MHz
日 期: 2011-8-9
该程序为蓝桥杯单片机比赛官方所提供的参考代码
该程序为基于IIC总线的驱动程序,用于通过IIC总线发送和接收数据。
其中,包含了总线启动条件、总线停止条件、应答位控制、等待应答、
通过I2C总线发送数据、从I2C总线上接收数据等函数。
此外,还包含了一些与ADC和EEPROM相关的函数,用于向PCF8591发送信号并读取数据,
以及对AT24C02 EEPROM进行读写操作。这些函数的具体实现方式都是通过向总线上发送特定的数据和控制信号来实现的。
*/
#include "reg52.h"
#include "intrins.h"
#define somenop {_nop_();_nop_();_nop_();_nop_();_nop_();}
#define SlaveAddrW 0xA0
#define SlaveAddrR 0xA1
//总线引脚定义
sbit SDA = P2^1; /* 数据线 */
sbit SCL = P2^0; /* 时钟线 */
//总线启动条件
void IIC_Start(void)
{
SDA = 1;
SCL = 1;
somenop;
SDA = 0;
somenop;
SCL = 0;
}
//总线停止条件
void IIC_Stop(void)
{
SDA = 0;
SCL = 1;
somenop;
SDA = 1;
}
//应答位控制
void IIC_Ack(bit ackbit)
{
if(ackbit)
{
SDA = 0;
}
else
{
SDA = 1;
}
somenop;
SCL = 1;
somenop;
SCL = 0;
SDA = 1;
somenop;
}
//等待应答
bit IIC_WaitAck(void)
{
SDA = 1;
somenop;
SCL = 1;
somenop;
if(SDA)
{
SCL = 0;
IIC_Stop();
return 0;
}
else
{
SCL = 0;
return 1;
}
}
//通过I2C总线发送数据
void IIC_SendByte(unsigned char byt)
{
unsigned char i;
for(i=0;i<8;i++)
{
if(byt&0x80)
{
SDA = 1;
}
else
{
SDA = 0;
}
somenop;
SCL = 1;
byt <<= 1;
somenop;
SCL = 0;
}
}
//从I2C总线上接收数据
unsigned char IIC_RecByte(void)
{
unsigned char da;
unsigned char i;
for(i=0;i<8;i++)
{
SCL = 1;
somenop;
da <<= 1;
if(SDA)
da |= 0x01;
SCL = 0;
somenop;
}
return da;
}
/***** ADC相关 *****/
//向PCF8591发送信号,准备读取0x03的数据
void init_pcf8591(void)
{
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(0x43); //PCF8591的通道三,接滑动变阻器Rb2,若是光敏电阻则为0x41
IIC_WaitAck();
IIC_Stop();
}
//读取数据
unsigned char adc_pcf8591(void)
{
unsigned char temp;
IIC_Start();
IIC_SendByte(0x91);
IIC_WaitAck();
temp = IIC_RecByte();
IIC_Ack(0);
IIC_Stop();
return temp;
}
/*****EEPROM相关*****/
//写函数,要先发送写的地址
//add为写入的地址,范围0x00-0xff,val为写入的数值
void write_at24c02(unsigned char add,unsigned char val)
{
IIC_Start();
IIC_SendByte(0xa0);
//地址位高4位为固定1010,后三位接地为000,即1010000H
//写操作为0,所以为10100000,即0xa0
IIC_WaitAck();
IIC_SendByte(add);
IIC_WaitAck();
IIC_SendByte(val);
IIC_WaitAck();
IIC_Stop();
}
//读函数,要先发送读的地址
//add为读的地址,范围0x00-0xff,
unsigned char read_at24c02(unsigned char add)
{
unsigned char x;
IIC_Start();
IIC_SendByte(0xa0);
//地址位高4位为固定1010,后三位接地为000,即1010000H
//写操作为0,所以为10100000,即0xa0
IIC_WaitAck();
IIC_SendByte(add);
IIC_WaitAck();
IIC_Start();
IIC_SendByte(0xa1);
//地址位高4位为固定1010,后三位接地为000,即1010000H
//读操作为1,所以为10100000,即0xa0
IIC_WaitAck();
x = IIC_RecByte();
IIC_Ack(0);
IIC_Stop();
return x;
}