鼠标改造计划
——基于STC15F104E的彩色呼吸灯
青岛科技大学 信息科学技术学院 集成162 Listen C
一.任务简介
近来本人的鼠标终于光荣下岗了,出于“主子”对“伙计”的“怜悯之情”,加上近来又恰好发现STC15F104E这么个神奇的单片机,居然只有8个脚,于是主决定恩赐它新的生命活力……好吧不瞎说了,只是单纯的想搞个事情而已。不过,从现实的角度来看,好像确实不错,用尽量小的体积做尽量多的事情吧。也是出于上次那个播放器被这芯片坑了一次不服气,打算换个项目“征服”它。好吧其实结局是又发现了新的问题,后文详述,这里就先留个伏笔,哈哈。
二.任务要求
(一) 改造对象
原希望改造的鼠标是下图这位“功臣”,因为其体积小外围透明,又光荣下岗,故获此“殊誉”。
但是,在后期改造的过程中,遇上点小麻烦……具体什么情况下文解释,这里为了文体整洁直接说明结果。结果就是,这个鼠标暂时搁置了,换了学长一个暂时不用的鼠标,下图:
关于这两个鼠标技术上的区别,经实验发现,当按下鼠标按键时,前者小黑(第一个鼠标,下同)产生的是高电平,后者小白(第二个鼠标,下同)产生的是低电平,具体为什么考虑这个问题下文讨论。
(二) 改造思路
首先,先让我们看下STC15F104E的原理图吧!
由其原理图外加查询数据手册,不难发现,除去Vcc和Gnd,还剩6个IO,其中除了P3.1,都可以做外部中断使用。关于定时器中断,对不同批次的芯片存在争议,需按实际情况做决定,因为本次改造计划没有用到定时器中断,所以不去深究。
而关于外部中断,数据手册上说,P3.2(INT0)和P3.3(INT1)既可做上升沿触发,也可下降沿触发,而剩下的P3.4(INT2)、P3.5(INT3)、P3.6(INT4)只能下降沿触发。
而关于彩灯,我选用了三灯芯共阳灯泡,下图所示:
这样,根据三基色的原理,理论上我们可以组合出各种颜色。请允许我盗个图。
由此,我们的目的也就明确了。
(1) 首先,我们需要3个IO去组合灯效
(2) 其次,我们需要三个按键来触发中断(鼠标左键、右键、中键)
(3) 然后,我们在中断未触发的情况下,让彩灯不断变换灯效
(4) 最后,产生中断时,灯效改变
当然,为了不那么死板,通过产生中断,我们可以改变灯效变换的频率,相当于是做了一个调节机制。
这样,细心的朋友一定会发现,哎,所有的IO刚好用完,三个下降沿也够用。这也就是为啥我要低电平的原因了。如果要高电平的话,只有2个中断可触发,所以是有限制的。不过不急,实际上我们可以用非门电路改变电平,比如三极管,反相器。反相器用74HC00的话有4路,多一路,但是要两个输入控制一个输出,详情请自行搜索下数据手册,也可用74HC04,六反相器,一输入对应一输出。当然本人手头只有印了74HC04的74HC00芯片(不要问我为啥。。。贪便宜的我飘过),而且都是直插的,感觉特别大,所以只好先暂时搁浅下,有空再给小黑手术吧。反正,有小白呢。
顺便把原理图也附上吧!
(三) 改造效果简述
关于具体的灯效,上文有提,这里详述一下。
首先是未触发中断的情况。
这里,我采用了呼吸灯的原理,通过软件PWM,实现让灯效从A色缓缓变为B色,B色缓缓变为C色……G色缓缓变为A色,如此循环。关于颜色的组合,根据上图三基色原理,3种颜色任意组合:
单色:红,绿,蓝
双色:黄,青,紫
三色:百
总共7种颜色。
当按下鼠标按键时,我让它对应显示单色,目前我的对应原则如下,大家可以随意调换:
左键:红
中键:绿
右键:蓝
当按下右键时再按下左键,我让它亮出自身颜色的其他颜色即蓝的对立黄,此时增加彩灯周期,同理反之减少彩灯周期。为了防止二货的我调乱了,当按下中键时按下任意其他键,我的操作是让周期复位。这样,一个简单的灯效思路就有了。
三.改造与制作
(一) 鼠标
首先将鼠标“大卸八块”,探索内部结构,顺便测试是哪种情况(小黑or小白)
(二)焊接电路
根据原理图准备材料
彩灯 x1
STC15F104E x1
220Ω电阻 x3
单片机底座 x1
洞洞板 x0.5(整个太大了就掰开了)
导线若干
然后,根据原理图将它们焊好
最后,将它与鼠标焊在一起。装好就OK啦。
四.程序设计
最后就是我们的重头戏——程序了。老规矩,先让我们看看文件结构。
头文件:
STC15F104E.h 芯片配置
USER_Config.h 用户配置
interrupt.h 中断配置
delay.h 延时配置
LED.h 彩灯配置
程序文件
main.c 主函数文件
it.c 中断服务文件
delay.c 延时函数文件
LED.c 彩灯控制文件
下面分别介绍下各部分的实现
USER_Config.h
/******************************************
USER HEARD 2017/6/25
*******************************************/
#ifndef _USERCONFIG_H_
#define _USERCONFIG_H_
/* 定义数据类型 */
#define uchar unsigned char
#define uint unsigned int
#define NOP() _nop_()
/* 灯效 */
/*
Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0
- - - - - 红 绿 蓝
低电平0为亮,高电平1为暗
*/
#define WHITE 0x00 //白 ----_-000
#define YELLOW 0x01 //黄 ----_-001
#define PURPLE 0x02 //紫 ----_-010
#define RED 0x03 //红 ----_-011
#define CYAN 0x04 //青 ----_-100
#define GREEN 0x05 //绿 ----_-101
#define BLUE 0x06 //蓝 ----_-110
#define CLOSE_ALL 0x07 //无 ----_-111
/* PWM周期调节 */
#define INITCYCLE 1500 //初值
#define STEPCYCLE 100 //步进值
/* 包含头文件 */
#include "STC15F104E.h"
#include <intrins.h>
#include "interrupt.h"
#include "delay.h"
#include "LED.h"
/* 彩灯 */
sbit LED_Blue = P3^3;
sbit LED_Green = P3^2;
sbit LED_Red = P3^1;
/* 按键 */
sbit Key_Left = P3^4;
sbit Key_Mid = P3^5;
sbit Key_Right = P3^0;
#endif
这里包含了用户所有需要的东西,包括定义数据类型、引用头文件、宏定义、定义引脚功能等。这里仅介绍主要部分
先说说PWM调节周期吧。对于PWM调节,我仅仅做了周期调节处理。这里我定义了一个频率初始值和一个调节步进值,便于更改。具体这两个值的作用,在LED.c中详细解释。
对于七种灯效值的由来,大概可以这么理解。首先我将3中颜色点亮与否的值分别存在了一个unsigned char数据中的低3位,二进制中的0表示低电平,而这个LED是共阳的,所以是灌电流模式,这样就产生了一个电势差,故点亮LED。而置1时,两端都为高电平,电势差为0,故灭掉LED。这样,再根据三基色原理,对着颜色取值,就取出了7种标准颜色的值。
那么问题来了,三灯芯的7色灯只能显示7种颜色吗?我认为,其实不是的。别忘了,我们只是取了标准色,如果说,混合颜色的“亮度”不同,即颜色的深度不同,就可以混合出其他颜色了。如果要这么实现的话,我个人认为有两种方案。一是PWM分别调节三色,使得等效为灯的亮度不同。二我们可以采用DAC,直接给模拟量,调节更加方便。这里我们只要等效循环变换,不需要精确取色,故不做深入处理了。
delay.c
普通的延时函数,不过多解释直接上代码
/*************************************
延时函数库 By LZK
频率:12MHz
*************************************/
#include "USER_Config.h"
/* 延时x*9us */
void delay_8us(uint x)
{
uint i,j;
for(i=0;i<x;i++)
for(j=0;j<1;j++);
}
/* 延时xms */
void delay_ms(uint x)
{
uint i,j;
for(i=0;i<x;i++)
for(j=0;j<120;j++);
}
/* 延时 */
void delay(uint x)
{
while(x--);
}
it.c
中断服务。这里我们用的是三个外部中断,下降沿触发。关于怎么使用这三个中断,这里就需要查询我们的数据手册了。数据手册我一起扔进了压缩包里大家自取好了。
/*---------------------------------------------------
中断服务文件
By Listen C 2017/6/25
参考自官方数据手册
----------------------------------------------------*/
#include "USER_Config.h"
extern uint CYCLE;
/* 中断初始化 */
void IT_Config()
{
/* 开启外部中断 */
Key_Left = 1;
Key_Mid = 1;
Key_Right = 1;
INT_CLKO |= 0x70;// 0111_0000
EA = 1;
}
/* 外部中断2,3,4 对应 10,11,16 */
void Int2_Left() interrupt 10
{
INT_CLKO = 0x8F; //1110_1111
Open_LED(BLUE);
while(!Key_Left)
{
if(!Key_Right) //如果按下鼠标右键
{
delay_ms(10); //去抖
while(!Key_Right)
Open_LED(YELLOW); //默认蓝色的对立色为红+绿=黄
Open_LED(BLUE); //松开复位颜色
if(CYCLE < 65400)
CYCLE += STEPCYCLE;
}
}
INT_CLKO |= 0x70;
}
void Int3_Mid() interrupt 11
{
INT_CLKO = 0x8F; //1101_1111
Open_LED(GREEN);
while(!Key_Mid)
{
if((!Key_Left)||(!Key_Right)) //在按住鼠标中键时判断是否按下鼠标左键或鼠标右键
{ //是的话,周期复位
Open_LED(PURPLE); //默认绿色的对立为蓝+红=紫
CYCLE = INITCYCLE;
}
Open_LED(GREEN); //松开颜色复位
}
INT_CLKO |= 0x70;
}
void Int2_Right() interrupt 16
{
INT_CLKO = 0x8F; //1011_1111
Open_LED(RED);
while(!Key_Right)
{
if(!Key_Left) //如果按下鼠标左键
{
delay_ms(10); //去抖
while(!Key_Left)
Open_LED(CYAN); //默认红色的对立为蓝+绿=青
Open_LED(RED); //松开颜色复位
if(CYCLE > 200)
CYCLE -= STEPCYCLE;
}
}
INT_CLKO |= 0x70;
}
其中要注意的是,我们的思路是这样的。首先,先触发中断,触发中断后,先暂时关掉中断,注意3个中断标志位不同,注释中是实际位,儿为了避免混乱,我全部都置0了,然后亮相应等效提醒进入中断了。然后继续判断,上文提到的非中键的话判断相反键是否按下,这里用了按键去抖,松开有效,随即对应增加或减少PWM周期。中键的判断只需要按下其他任意键并进行复位,故不需要去抖。
当完成这些工作时,我们的操作是松开最初按下的按键,此时我们需要重新打开中断。
LED.c
我们的核心部分到了,话不多说先上代码。
/*--------------------------------------------------------------------
彩灯控制文件
By Listen C 2017/6/26
Open_LED函数说明:
为方便调节灯色,本人将灯色选择存在了一个unsigned char变量中
通过位运算
低1位为蓝色开关
低2位为绿色开关
低3位为红色开关
具体三色混合效果参见USER_Config.h
-------------------------------------------------------------------*/
#include "USER_Config.h"
uint PWM_LOW = 0;
uint CYCLE = INITCYCLE;
/* 点灯函数 */
void Open_LED(uchar x)
{
LED_Blue = x&0x01;
LED_Green = (x&0x02)>>1;
LED_Red = (x&0x04)>>2;
}
/* 彩灯效果 */
void PWM_LED(uchar x,uchar y)
{
for(PWM_LOW = 1;PWM_LOW < CYCLE;PWM_LOW++)
{
Open_LED(x);
delay(PWM_LOW);
Open_LED(y);
delay(CYCLE-PWM_LOW);
}
delay(CYCLE);
}
首先先让我们详细探索下点灯函数。稍有基础的朋友大概一看就明白了,这里用的是位运算,取某位的值。即,先对对应位进行1位与,其他0位与,以只保留对应位,然后通过右移操作移到最低位,给引脚赋值。
说句题外话,用位运算存储数据真的非常好用,曾经我比赛时对4方位的红外障碍进行了位运算的存储,将四个方位的状态存在一个数据里,就是跟我们这个任务恰好相反的过程,直接优势就是,在判断过程中变的异常容易,只需要判断数据的值就能知道哪里遇到障碍了,除去了冗杂的if判断,程序上反正是舒服多了,也因此那部分做的效果还是不错的。
关于PWM调节的问题,就是我们灯效的控制了。如果知道PWM,这里可能就很明白了,不知道的话可以自行查一下,网络的介绍比我这清楚多了。总之就是,在一个固定的周期内,高电平占一定时间,低电平占剩下的时间,这样等效地调节了灯的亮度。然后,如果低电平慢慢增加,那么就等效认为亮度在增加。我们只考虑一种颜色的情况,那么它在缓慢变亮,反过来就缓慢变暗,如此就是呼吸灯的原理。注意全亮之后给一段时间固定,这样更加逼真。而我们的任务是,亮完这个颜色,在它灭掉的时候去亮下一种颜色,故将点灯部分改为了两种灯效。关于这两个灯效,在主函数中来计算,因为涉及到要让这个函数更加通用嘛。如果不去变换灯效,也一样可以调用它,大不了y取CLOSE_ALL。
main.c
既然核心部分都做好了,剩下的调用就好了。
/*---------------------------------------------------------------
基于STC15F104E的鼠标改造方案
目的:通过外部中断控制三灯芯小灯
通过PWM信号来变换灯效
按下鼠标左键时按下鼠标右键,PWM周期增加
按下鼠标右键时按下鼠标左键,PWM周期减少
按下鼠标中键时按下任意键,PWM周期复位
灯效参见it.c的注释
为方便调节灯色,本人为灯色选择函数做了优化,详见LED.c
By Listen C 2017/6/25
-----------------------------------------------------------------*/
#include "STC15F104E.h"
#include "USER_Config.h"
uchar i = 0;
void main()
{
uchar x,y;
Open_LED(CLOSE_ALL);
IT_Config();
while(1)
{
for(i=0;i<7;i++)
{
x=(i+1)%7;
y=i;
PWM_LED(x,y);
}
}
}
注意先要中断初始化,然后7色循环闪烁既可。
五.反思与总结
说句良心话,这个作品的Bug还是很大的。我遇到最大的问题是,当按键频率过高时,中断会莫名其妙挂掉了。虽然出了问题应该先考虑自身程序问题,但是我觉得我的中断没有进入这种状态的步骤,况且我在主函数中while里随时开启外部中断都无济于事,于是就上网查了下,果然有网友遇到了相同的问题。有人的解答是初期的该芯片存在Bug,但是后期的修复了。好吧我就暂且认为这锅芯片背吧!
其实,表面上,任务以改造鼠标做依托进行的,但这个灯效的小玩意可以做其他的装饰,比如USB彩灯。恰好又一位挚友过生日,于是亲手又焊了一个给他,礼轻情义重嘛~。不过说句良心话,做USB彩灯的效果,是优于放进鼠标里的。下图为送他的彩灯。
最后,附上共享链接:
链接:http://pan.baidu.com/s/1gf5n0vL 密码:yfpz
STC15F104E数据手册:
链接:http://pan.baidu.com/s/1qYuLNas 密码:a3au