【51单片机学习笔记】彩 {MOD}呼吸灯

2019-04-15 12:55发布

鼠标改造计划

               ——基于STC15F104E的彩 {MOD}呼吸灯

青岛科技大学 信息科学技术学院 集成162 Listen C

一.任务简介

近来本人的鼠标终于光荣下岗了,出于“主子”对“伙计”的“怜悯之情”,加上近来又恰好发现STC15F104E这么个神奇的单片机,居然只有8个脚,于是主决定恩赐它新的生命活力……好吧不瞎说了,只是单纯的想搞个事情而已。不过,从现实的角度来看,好像确实不错,用尽量小的体积做尽量多的事情吧。也是出于上次那个播放器被这芯片坑了一次不服气,打算换个项目“征服”它。好吧其实结局是又发现了新的问题,后文详述,这里就先留个伏笔,哈哈。

二.任务要求

(一)       改造对象

原希望改造的鼠标是下图这位“功臣”,因为其体积小外围透明,又光荣下岗,故获此“殊誉”。          但是,在后期改造的过程中,遇上点小麻烦……具体什么情况下文解释,这里为了文体整洁直接说明结果。结果就是,这个鼠标暂时搁置了,换了学长一个暂时不用的鼠标,下图:          关于这两个鼠标技术上的区别,经实验发现,当按下鼠标按键时,前者小黑(第一个鼠标,下同)产生的是高电平,后者小白(第二个鼠标,下同)产生的是低电平,具体为什么考虑这个问题下文讨论。

(二)       改造思路

首先,先让我们看下STC15F104E的原理图吧!          由其原理图外加查询数据手册,不难发现,除去VccGnd,还剩6IO,其中除了P3.1,都可以做外部中断使用。关于定时器中断,对不同批次的芯片存在争议,需按实际情况做决定,因为本次改造计划没有用到定时器中断,所以不去深究。          而关于外部中断,数据手册上说,P3.2INT0)和P3.3INT1)既可做上升沿触发,也可下降沿触发,而剩下的P3.4INT2)、P3.5INT3)、P3.6INT4)只能下降沿触发。          而关于彩灯,我选用了三灯芯共阳灯泡,下图所示: 这样,根据三基 {MOD}的原理,理论上我们可以组合出各种颜 {MOD}。请允许我盗个图。          由此,我们的目的也就明确了。 (1)      首先,我们需要3IO去组合灯效 (2)      其次,我们需要三个按键来触发中断(鼠标左键、右键、中键) (3)      然后,我们在中断未触发的情况下,让彩灯不断变换灯效 (4)      最后,产生中断时,灯效改变 当然,为了不那么死板,通过产生中断,我们可以改变灯效变换的频率,相当于是做了一个调节机制。 这样,细心的朋友一定会发现,哎,所有的IO刚好用完,三个下降沿也够用。这也就是为啥我要低电平的原因了。如果要高电平的话,只有2个中断可触发,所以是有限制的。不过不急,实际上我们可以用非门电路改变电平,比如三极管,反相器。反相器用74HC00的话有4路,多一路,但是要两个输入控制一个输出,详情请自行搜索下数据手册,也可用74HC04,六反相器,一输入对应一输出。当然本人手头只有印了74HC0474HC00芯片(不要问我为啥。。。贪便宜的我飘过),而且都是直插的,感觉特别大,所以只好先暂时搁浅下,有空再给小黑手术吧。反正,有小白呢。 顺便把原理图也附上吧!

(三)       改造效果简述

关于具体的灯效,上文有提,这里详述一下。 首先是未触发中断的情况。 这里,我采用了呼吸灯的原理,通过软件PWM,实现让灯效从A {MOD}缓缓变为B {MOD},B {MOD}缓缓变为C {MOD}……G {MOD}缓缓变为A {MOD},如此循环。关于颜 {MOD}的组合,根据上图三基 {MOD}原理,3种颜 {MOD}任意组合: 单 {MOD}:红,绿,蓝 双 {MOD}:黄,青,紫 三 {MOD}:百 总共7种颜 {MOD}。          当按下鼠标按键时,我让它对应显示单 {MOD},目前我的对应原则如下,大家可以随意调换:          左键:红          中键:绿          右键:蓝 当按下右键时再按下左键,我让它亮出自身颜 {MOD}的其他颜 {MOD}即蓝的对立黄,此时增加彩灯周期,同理反之减少彩灯周期。为了防止二货的我调乱了,当按下中键时按下任意其他键,我的操作是让周期复位。这样,一个简单的灯效思路就有了。

三.改造与制作

(一)       鼠标

首先将鼠标“大卸八块”,探索内部结构,顺便测试是哪种情况(小黑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 #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中颜 {MOD}点亮与否的值分别存在了一个unsigned char数据中的低3位,二进制中的0表示低电平,而这个LED是共阳的,所以是灌电流模式,这样就产生了一个电势差,故点亮LED。而置1时,两端都为高电平,电势差为0,故灭掉LED。这样,再根据三基 {MOD}原理,对着颜 {MOD}取值,就取出了7种标准颜 {MOD}的值。          那么问题来了,三灯芯的7 {MOD}灯只能显示7种颜 {MOD}吗?我认为,其实不是的。别忘了,我们只是取了标准 {MOD},如果说,混合颜 {MOD}的“亮度”不同,即颜 {MOD}的深度不同,就可以混合出其他颜 {MOD}了。如果要这么实现的话,我个人认为有两种方案。一是PWM分别调节三 {MOD},使得等效为灯的亮度不同。二我们可以采用DAC,直接给模拟量,调节更加方便。这里我们只要等效循环变换,不需要精确取 {MOD},故不做深入处理了。   delay.c 普通的延时函数,不过多解释直接上代码   /************************************* 延时函数库 By LZK 频率:12MHz *************************************/ #include "USER_Config.h" /* 延时x*9us */ void delay_8us(uint x) { uint i,j; for(i=0;i
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); //默认蓝 {MOD}的对立 {MOD}为红+绿=黄 Open_LED(BLUE); //松开复位颜 {MOD} 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); //默认绿 {MOD}的对立为蓝+红=紫 CYCLE = INITCYCLE; } Open_LED(GREEN); //松开颜 {MOD}复位 } 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); //默认红 {MOD}的对立为蓝+绿=青 Open_LED(RED); //松开颜 {MOD}复位 if(CYCLE > 200) CYCLE -= STEPCYCLE; } } INT_CLKO |= 0x70; }
其中要注意的是,我们的思路是这样的。首先,先触发中断,触发中断后,先暂时关掉中断,注意3个中断标志位不同,注释中是实际位,儿为了避免混乱,我全部都置0了,然后亮相应等效提醒进入中断了。然后继续判断,上文提到的非中键的话判断相反键是否按下,这里用了按键去抖,松开有效,随即对应增加或减少PWM周期。中键的判断只需要按下其他任意键并进行复位,故不需要去抖。 当完成这些工作时,我们的操作是松开最初按下的按键,此时我们需要重新打开中断。   LED.c          我们的核心部分到了,话不多说先上代码。 /*-------------------------------------------------------------------- 彩灯控制文件 By Listen C 2017/6/26 Open_LED函数说明: 为方便调节灯 {MOD},本人将灯 {MOD}选择存在了一个unsigned char变量中 通过位运算 低1位为蓝 {MOD}开关 低2位为绿 {MOD}开关 低3位为红 {MOD}开关 具体三 {MOD}混合效果参见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,这里可能就很明白了,不知道的话可以自行查一下,网络的介绍比我这清楚多了。总之就是,在一个固定的周期内,高电平占一定时间,低电平占剩下的时间,这样等效地调节了灯的亮度。然后,如果低电平慢慢增加,那么就等效认为亮度在增加。我们只考虑一种颜 {MOD}的情况,那么它在缓慢变亮,反过来就缓慢变暗,如此就是呼吸灯的原理。注意全亮之后给一段时间固定,这样更加逼真。而我们的任务是,亮完这个颜 {MOD},在它灭掉的时候去亮下一种颜 {MOD},故将点灯部分改为了两种灯效。关于这两个灯效,在主函数中来计算,因为涉及到要让这个函数更加通用嘛。如果不去变换灯效,也一样可以调用它,大不了yCLOSE_ALL   main.c          既然核心部分都做好了,剩下的调用就好了。 /*--------------------------------------------------------------- 基于STC15F104E的鼠标改造方案 目的:通过外部中断控制三灯芯小灯 通过PWM信号来变换灯效 按下鼠标左键时按下鼠标右键,PWM周期增加 按下鼠标右键时按下鼠标左键,PWM周期减少 按下鼠标中键时按下任意键,PWM周期复位 灯效参见it.c的注释 为方便调节灯 {MOD},本人为灯 {MOD}选择函数做了优化,详见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 {MOD}循环闪烁既可。  

五.反思与总结

说句良心话,这个作品的Bug还是很大的。我遇到最大的问题是,当按键频率过高时,中断会莫名其妙挂掉了。虽然出了问题应该先考虑自身程序问题,但是我觉得我的中断没有进入这种状态的步骤,况且我在主函数中while里随时开启外部中断都无济于事,于是就上网查了下,果然有网友遇到了相同的问题。有人的解答是初期的该芯片存在Bug,但是后期的修复了。好吧我就暂且认为这锅芯片背吧! 其实,表面上,任务以改造鼠标做依托进行的,但这个灯效的小玩意可以做其他的装饰,比如USB彩灯。恰好又一位挚友过生日,于是亲手又焊了一个给他,礼轻情义重嘛~。不过说句良心话,做USB彩灯的效果,是优于放进鼠标里的。下图为送他的彩灯。

最后,附上共享链接: 链接:http://pan.baidu.com/s/1gf5n0vL 密码:yfpz
STC15F104E数据手册: 链接:http://pan.baidu.com/s/1qYuLNas 密码:a3au